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:
Sakurasan committed 2026-10-26 00:54:46 +08:00
commit d5dac5a6fc
140 files changed
+26497

No files matched your search

+99
View File
@@ -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)
}
}