feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)
llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、 builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、 编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。 产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt) 留在 AutoGo,宿主接入方式见 MIGRATION.md。
This commit is contained in:
commit
d5dac5a6fc
140 files changed
+26497
No files matched your search
@@ -0,0 +1,99 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.gopher.ink/mirrors2/AutoZero/llm"
|
||||
"git.gopher.ink/mirrors2/AutoZero/tools"
|
||||
)
|
||||
|
||||
// TestMaxTurnsWrapUp:模型一直请求工具、始终不收敛时,到步数上限**不再抛错**,
|
||||
// 而是禁掉工具再要一轮收尾说明。
|
||||
//
|
||||
// 回归:此前到顶直接 return reached max turns,用户只拿到一句错误——不知道做到
|
||||
// 哪、还剩什么,也看不到任何可以行动的结论。现在上限从「中断」降级成「体面收尾」。
|
||||
func TestMaxTurnsWrapUp(t *testing.T) {
|
||||
var (
|
||||
wrapCalls int
|
||||
wrapToolCount = -1
|
||||
wrapSystem string
|
||||
wrapDisabled bool
|
||||
normalCalls int
|
||||
)
|
||||
var prov *scriptedProvider
|
||||
prov = &scriptedProvider{fn: func(call int, system string, msgs []llm.Message, tl []llm.ToolDef) (llm.Message, error) {
|
||||
if !prov.lastOpts().DisableTools { // 正常轮:永远请求工具,永不收敛
|
||||
normalCalls++
|
||||
return llm.Message{Role: llm.RoleAssistant, ToolCalls: []llm.ToolCall{
|
||||
{ID: fmt.Sprintf("t%d", call), Name: "noop", Input: "{}"},
|
||||
}}, nil
|
||||
}
|
||||
// 收尾轮:工具定义保留(缓存前缀不变),但 tool_choice=none 禁止调用
|
||||
wrapCalls++
|
||||
wrapToolCount = len(tl)
|
||||
wrapSystem = system
|
||||
wrapDisabled = prov.lastOpts().DisableTools
|
||||
return llm.Message{Role: llm.RoleAssistant, Content: "已完成 A;剩余 B。"}, nil
|
||||
}}
|
||||
|
||||
noop := &tools.Tool{
|
||||
Name: "noop", Description: "no-op",
|
||||
Run: func(context.Context, string) (tools.Result, error) { return tools.TextResult("ok"), nil },
|
||||
}
|
||||
a, sess := newTestAgent(t, noop)
|
||||
a.Provider = prov
|
||||
a.MaxTurns = 3
|
||||
|
||||
if err := a.Run(context.Background(), "do it"); err != nil {
|
||||
t.Fatalf("到上限应体面收尾而不是报错: %v", err)
|
||||
}
|
||||
if normalCalls != 3 {
|
||||
t.Errorf("正常轮次数 = %d, want 3(正好用完预算)", normalCalls)
|
||||
}
|
||||
if wrapCalls != 1 {
|
||||
t.Errorf("收尾轮应恰好一次,实际 %d", wrapCalls)
|
||||
}
|
||||
if wrapToolCount <= 0 {
|
||||
t.Errorf("收尾轮应保留工具定义(只禁用调用),实际 %d 个", wrapToolCount)
|
||||
}
|
||||
if !wrapDisabled {
|
||||
t.Error("收尾轮没有禁用工具调用(DisableTools)")
|
||||
}
|
||||
if !strings.Contains(wrapSystem, "步数已用尽") {
|
||||
t.Errorf("收尾轮 system 未带收尾指令: %q", wrapSystem)
|
||||
}
|
||||
|
||||
last := sess.Messages[len(sess.Messages)-1]
|
||||
if last.Role != llm.RoleAssistant {
|
||||
t.Fatalf("最后一条应是 assistant,实际 %s", last.Role)
|
||||
}
|
||||
if !strings.Contains(last.Content, "已达本轮步数上限(3)") {
|
||||
t.Errorf("收尾正文缺少上限标记: %q", last.Content)
|
||||
}
|
||||
if !strings.Contains(last.Content, "已完成 A") {
|
||||
t.Errorf("收尾正文缺少模型给出的结论: %q", last.Content)
|
||||
}
|
||||
if len(last.ToolCalls) != 0 {
|
||||
t.Errorf("收尾消息不该带 tool_use(会破坏 tool_use/tool_result 配对): %+v", last.ToolCalls)
|
||||
}
|
||||
// 上限用完但工作没丢:前面几轮的 tool_use/tool_result 都在会话里,
|
||||
// 用户回一句「继续」就能带着完整上下文接着跑。
|
||||
if !strings.Contains(sess.Messages[len(sess.Messages)-2].Content, "ok") {
|
||||
t.Errorf("倒数第二条应是最后一个工具结果: %+v", sess.Messages[len(sess.Messages)-2])
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyDefaultsMaxTurns:未配置时回落默认值。
|
||||
func TestApplyDefaultsMaxTurns(t *testing.T) {
|
||||
a, _ := newTestAgent(t)
|
||||
a.applyDefaults()
|
||||
if a.MaxTurns != DefaultMaxTurns {
|
||||
t.Errorf("MaxTurns = %d, want %d", a.MaxTurns, DefaultMaxTurns)
|
||||
}
|
||||
if DefaultMaxTurns < 40 {
|
||||
t.Errorf("默认上限 %d 不该低于旧硬编码值 40", DefaultMaxTurns)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user