feat(loop): agent loop(8 轮 + checkpoint + 流式 + 记忆注入/提取 + trace + 工具超时回喂)
This commit is contained in:
1 parent
01a4e29f84
commit
71697ca4f8
12 files changed
+1292
No files matched your search
@@ -0,0 +1,83 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// injectMemory retrieves the persona's relevant long-term memories and injects
|
||||
// them into the first-turn context as a leading system message, so the model can
|
||||
// ground its answer in recalled context (design §6 / §2). It returns the input
|
||||
// messages unchanged when no retriever/persona is configured, retrieval fails, or
|
||||
// nothing relevant is found.
|
||||
func (l *Loop) injectMemory(ctx context.Context, messages []model.Message) []model.Message {
|
||||
if l.retriever == nil || l.personaID == "" {
|
||||
return messages
|
||||
}
|
||||
block, err := l.retriever.Inject(ctx, l.personaID, queryFromMessages(messages), 0)
|
||||
if err != nil {
|
||||
l.log.Warn("memory inject", "error", err)
|
||||
return messages
|
||||
}
|
||||
if block == "" {
|
||||
return messages
|
||||
}
|
||||
out := make([]model.Message, 0, len(messages)+1)
|
||||
out = append(out, model.TextMessage(model.RoleSystem, block))
|
||||
out = append(out, messages...)
|
||||
return out
|
||||
}
|
||||
|
||||
// extractMemory runs the extraction hook (design §6) over the current
|
||||
// conversation and persists any derived items. Errors are logged, never fatal, so
|
||||
// a bad extraction cannot abort the run.
|
||||
func (l *Loop) extractMemory(ctx context.Context, messages []model.Message, step int) {
|
||||
if l.extractor == nil || l.memory == nil {
|
||||
return
|
||||
}
|
||||
items, err := l.extractor.Extract(ctx, messages)
|
||||
if err != nil {
|
||||
l.log.Warn("memory extract", "step", step, "error", err)
|
||||
return
|
||||
}
|
||||
for _, it := range items {
|
||||
if it.PersonaID == "" && l.personaID != "" {
|
||||
it.PersonaID = l.personaID
|
||||
}
|
||||
if _, err := l.memory.Add(ctx, it); err != nil {
|
||||
l.log.Warn("memory store", "step", step, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// queryFromMessages derives a retrieval query from the conversation: the last
|
||||
// user turn's text, falling back to the last message's text, then "".
|
||||
func queryFromMessages(messages []model.Message) string {
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
if messages[i].Role == model.RoleUser {
|
||||
return messageText(messages[i])
|
||||
}
|
||||
}
|
||||
if n := len(messages); n > 0 {
|
||||
return messageText(messages[n-1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// messageText returns the concatenated plain-text payload of a message's text
|
||||
// parts, which is the shared view used to build a retrieval query.
|
||||
func messageText(m model.Message) string {
|
||||
var b strings.Builder
|
||||
for _, part := range m.Content {
|
||||
if part.Type != model.ContentTypeText {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(part.Text)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
Reference in new issue
Block a user