Files
AutoZero/agent/maxturns_test.go
T
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

100 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}