Files
Sakurasan d5dac5a6fc 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。
2026-10-26 00:54:46 +08:00

88 lines
2.7 KiB
Go

package agent
import (
"context"
"io"
"path/filepath"
"testing"
"git.gopher.ink/mirrors2/AutoZero/llm"
"git.gopher.ink/mirrors2/AutoZero/session"
"git.gopher.ink/mirrors2/AutoZero/tools"
)
// TestRunRepairsDanglingToolUse:上一次运行死在工具执行中途(Ctrl-C、进程被杀、
// 机器重启),会话文件里留下一条没有 tool_result 配对的 tool_use。
//
// 续跑必须在写入本回合的 user 消息**之前**补齐:Anthropic 要求 tool_result 紧挨着
// 含 tool_use 的那条 assistant,中间一旦插进 user 消息就再也修不回来——届时这条
// 会话每次请求都 400,用户看到的是"这个会话之后问什么都不回"。
func TestRunRepairsDanglingToolUse(t *testing.T) {
t.Setenv("HOME", t.TempDir())
path := filepath.Join(t.TempDir(), "dangling.jsonl")
s := &session.Session{Path: path}
if err := s.Append(llm.Message{Role: llm.RoleUser, Content: "跑一下测试"}); err != nil {
t.Fatal(err)
}
half := llm.Message{Role: llm.RoleAssistant, Content: "执行中",
ToolCalls: []llm.ToolCall{{ID: "call_1", Name: "bash", Input: `{"command":"go test ./..."}`}}}
if err := s.Append(half); err != nil {
t.Fatal(err)
}
// 重新 Load:模拟进程重启后从盘上恢复。
reloaded, err := session.Load(path)
if err != nil {
t.Fatal(err)
}
a := &Agent{
Provider: &stubProvider{reply: "已恢复"},
Session: reloaded,
Tools: tools.NewRegistry(),
Opt: Options{Stdout: io.Discard, Stderr: io.Discard},
}
if err := a.Run(context.Background(), "继续"); err != nil {
t.Fatalf("Run: %v", err)
}
idxTool, idxUser, dup := -1, -1, 0
for i, m := range reloaded.Messages {
if m.Role == llm.RoleTool && m.ToolCallID == "call_1" {
idxTool, dup = i, dup+1
}
if m.Role == llm.RoleUser && m.Content == "继续" {
idxUser = i
}
}
if idxTool < 0 {
t.Fatal("悬挂的 tool_use 没被补齐")
}
if dup != 1 {
t.Errorf("同一次调用出现了 %d 条 tool_result,应为 1", dup)
}
if idxUser < 0 {
t.Fatal("本回合的 user 消息没落库")
}
if idxTool > idxUser {
t.Errorf("合成的 tool_result 必须排在新 user 消息之前:tool=%d user=%d", idxTool, idxUser)
}
// 幂等:已配对的会话再跑一次,不该冒出第二条合成结果。
b := &Agent{Provider: &stubProvider{reply: "ok"}, Session: reloaded,
Tools: tools.NewRegistry(), Opt: Options{Stdout: io.Discard, Stderr: io.Discard}}
if err := b.Run(context.Background(), "再说一句"); err != nil {
t.Fatalf("Run #2: %v", err)
}
got := 0
for _, m := range reloaded.Messages {
if m.Role == llm.RoleTool && m.ToolCallID == "call_1" {
got++
}
}
if got != 1 {
t.Errorf("重复补齐:tool_result = %d 条,want 1", got)
}
}