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,87 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user