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) } }