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,81 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/agent"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// buildRequest assembles a model.Request from the agent's public surface,
|
||||
// mirroring agent.buildRequest so the streaming path behaves identically to
|
||||
// RunStep (leading instruction message, tool schemas, model routing).
|
||||
func (l *Loop) buildRequest(messages []model.Message) model.Request {
|
||||
req := model.Request{
|
||||
Model: l.agent.Model(),
|
||||
Messages: l.assembleMessages(messages),
|
||||
Temperature: l.temperature(),
|
||||
MaxTokens: l.agent.MaxTokens(),
|
||||
Purpose: l.agent.Purpose(),
|
||||
Tools: l.toolSchemas(),
|
||||
}
|
||||
if l.agent.Router() != nil {
|
||||
if m, _ := l.agent.Router().RouteRequest(req); m != "" {
|
||||
req.Model = m
|
||||
}
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
// assembleMessages prepends the agent's instructions as a leading system message
|
||||
// when they are non-empty, otherwise returns the input slice unchanged.
|
||||
func (l *Loop) assembleMessages(messages []model.Message) []model.Message {
|
||||
instr := l.agent.Instructions()
|
||||
if instr == "" {
|
||||
return messages
|
||||
}
|
||||
out := make([]model.Message, 0, len(messages)+1)
|
||||
out = append(out, model.TextMessage(model.RoleSystem, instr))
|
||||
out = append(out, messages...)
|
||||
return out
|
||||
}
|
||||
|
||||
// temperature returns a pointer to the configured temperature, mapping zero to
|
||||
// nil so the provider default applies (matching the agent package).
|
||||
func (l *Loop) temperature() *float64 {
|
||||
t := l.agent.Temperature()
|
||||
if t == 0 {
|
||||
return nil
|
||||
}
|
||||
return &t
|
||||
}
|
||||
|
||||
// toolSchemas advertises the registered tool schemas to the model, or nil when no
|
||||
// dispatcher is configured.
|
||||
func (l *Loop) toolSchemas() []model.ToolSpec {
|
||||
d := l.dispatcher()
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
return d.ToolSchemas()
|
||||
}
|
||||
|
||||
// decodeToolCalls decodes tool calls from a model.Response using the same two
|
||||
// conventions as the agent package: the normalized RoleTool placeholder, then a
|
||||
// JSON envelope of the form {"tool_calls":[...]} in Content. It reports ok=true
|
||||
// (with a non-empty slice) when a call is decoded.
|
||||
func decodeToolCalls(resp model.Response) ([]agent.ToolCall, bool) {
|
||||
if resp.Message.Role == model.RoleAssistant && resp.Message.ToolCallName != "" {
|
||||
return []agent.ToolCall{{Name: resp.Message.ToolCallName, Args: map[string]any{}}}, true
|
||||
}
|
||||
var payload struct {
|
||||
ToolCalls []agent.ToolCall `json:"tool_calls"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(resp.Content), &payload); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if len(payload.ToolCalls) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return payload.ToolCalls, true
|
||||
}
|
||||
Reference in new issue
Block a user