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) } }