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。
100 lines
3.5 KiB
Go
100 lines
3.5 KiB
Go
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)
|
||
}
|
||
}
|