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。
88 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|