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。
99 lines
3.1 KiB
Go
99 lines
3.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"git.gopher.ink/mirrors2/AutoZero/llm"
|
|
"git.gopher.ink/mirrors2/AutoZero/tools"
|
|
)
|
|
|
|
// TestSteerDeliveredAtStepBoundary:插话必须在工具结果回填之后、下一次模型调用
|
|
// 之前落成普通 user 消息;模型下一步就能看到它。
|
|
func TestSteerDeliveredAtStepBoundary(t *testing.T) {
|
|
calls := 0
|
|
prov := &scriptedProvider{fn: func(call int, _ string, _ []llm.Message, _ []llm.ToolDef) (llm.Message, error) {
|
|
calls++
|
|
if call == 1 {
|
|
return llm.Message{Role: llm.RoleAssistant, ToolCalls: []llm.ToolCall{
|
|
{ID: "t1", Name: "noop", Input: "{}"},
|
|
}}, nil
|
|
}
|
|
return llm.Message{Role: llm.RoleAssistant, Content: "done"}, nil
|
|
}}
|
|
noop := &tools.Tool{Name: "noop", Run: func(context.Context, string) (tools.Result, error) {
|
|
return tools.TextResult("ok"), nil
|
|
}}
|
|
a, sess := newTestAgent(t, noop)
|
|
a.Provider = prov
|
|
|
|
pending := []SteerMessage{{Text: "also fix the typo"}}
|
|
a.Opt.Steer = func() []SteerMessage {
|
|
out := pending
|
|
pending = nil
|
|
return out
|
|
}
|
|
var steered []SteerMessage
|
|
a.Opt.OnSteer = func(m SteerMessage) { steered = append(steered, m) }
|
|
|
|
if err := a.Run(context.Background(), "do it"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls != 2 {
|
|
t.Fatalf("model calls = %d, want 2", calls)
|
|
}
|
|
if len(steered) != 1 || steered[0].Text != "also fix the typo" {
|
|
t.Fatalf("OnSteer = %+v", steered)
|
|
}
|
|
steerAt, toolAt := -1, -1
|
|
for i, m := range sess.Messages {
|
|
if m.Role == llm.RoleUser && m.Content == "also fix the typo" {
|
|
steerAt = i
|
|
}
|
|
if m.Role == llm.RoleTool {
|
|
toolAt = i
|
|
}
|
|
}
|
|
if steerAt < 0 || toolAt < 0 || steerAt < toolAt {
|
|
t.Fatalf("steer must land after the tool result (tool=%d steer=%d): %+v", toolAt, steerAt, sess.Messages)
|
|
}
|
|
}
|
|
|
|
// TestSteerContinuesAnOtherwiseCompletedTurn:模型已经说完了,但宿主还有插话——
|
|
// 不能直接收尾,要把插话落盘再跑一步。这保证"回合结束才发"的旧排队语义也能
|
|
// 在同一回合内被消费,而不是等下一次用户手动发送。
|
|
func TestSteerContinuesAnOtherwiseCompletedTurn(t *testing.T) {
|
|
calls := 0
|
|
prov := &scriptedProvider{fn: func(call int, _ string, _ []llm.Message, _ []llm.ToolDef) (llm.Message, error) {
|
|
calls++
|
|
if call == 1 {
|
|
return llm.Message{Role: llm.RoleAssistant, Content: "first done"}, nil
|
|
}
|
|
return llm.Message{Role: llm.RoleAssistant, Content: "second done"}, nil
|
|
}}
|
|
a, sess := newTestAgent(t)
|
|
a.Provider = prov
|
|
|
|
pending := []SteerMessage{{Text: "one more thing"}}
|
|
a.Opt.Steer = func() []SteerMessage {
|
|
out := pending
|
|
pending = nil
|
|
return out
|
|
}
|
|
|
|
if err := a.Run(context.Background(), "start"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls != 2 {
|
|
t.Fatalf("model calls = %d, want 2 (the steer must not be dropped)", calls)
|
|
}
|
|
if len(sess.Messages) < 4 {
|
|
t.Fatalf("messages = %+v", sess.Messages)
|
|
}
|
|
last := sess.Messages[len(sess.Messages)-1]
|
|
prev := sess.Messages[len(sess.Messages)-2]
|
|
if prev.Role != llm.RoleUser || prev.Content != "one more thing" || last.Content != "second done" {
|
|
t.Fatalf("steer was not answered in the same turn: %+v", sess.Messages)
|
|
}
|
|
}
|