返回 DeepSeek-Reasonix
review_step_budget_test.go
根目录 / internal / agent / review_step_budget_test.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8 "testing"
9
10 "reasonix/internal/agent/testutil"
11 "reasonix/internal/event"
12 "reasonix/internal/provider"
13 "reasonix/internal/tool"
14 )
15
16 // A review child under an uncapped parent takes as many rounds as the review
17 // needs; a multi-file review that reads more than a handful of files must not
18 // stop at a budget the user has no setting for.
19 func TestReviewSubagentUnderUncappedParentFinishesPastEightRounds(t *testing.T) {
20 const rounds = 12
21 turns := make([]testutil.Turn, 0, rounds+1)
22 for i := range rounds {
23 turns = append(turns, testutil.Turn{ToolCalls: []provider.ToolCall{{
24 ID: fmt.Sprintf("read-%d", i), Name: "echo", Arguments: fmt.Sprintf(`{"text":"file %d"}`, i),
25 }}})
26 }
27 turns = append(turns, testutil.Turn{Text: "verdict: approve"})
28 parent := tool.NewRegistry()
29 parent.Add(echoTool{})
30 task := NewTaskTool(testutil.NewMock("sub", turns...), nil, parent, 0, 0, 0, 0, 0, 0, 0, 0.0, "", "sys", nil, 0, "", "", nil).
31 WithTranscripts(mustSubagentStore(t), t.TempDir(), "base", "high").
32 WithProfileLookup(func(name string) (ProfileDefinition, bool) {
33 return ProfileDefinition{Name: name, Body: "You review changes."}, name == "review"
34 })
35
36 out, err := task.Execute(withCallContext(context.Background(), "c", event.Discard, nil, false),
37 json.RawMessage(`{"prompt":"review the change","profile":"review"}`))
38 if err != nil {
39 t.Fatalf("review task: %v", err)
40 }
41 if strings.Contains(out, "paused after") || !strings.Contains(out, "verdict: approve") {
42 t.Fatalf("review child must finish its review, got:\n%s", out)
43 }
44 }
45
46 func TestReviewBudgetKeepsInheritedStepsAndBoundsOutput(t *testing.T) {
47 for _, tc := range []struct {
48 parent, requested, want int
49 }{
50 {parent: 0, requested: 0, want: 0},
51 {parent: 40, requested: 0, want: 20},
52 {parent: 0, requested: 24, want: 24},
53 } {
54 task := &TaskTool{maxSteps: tc.parent}
55 spec := ProfileExecSpec{Worker: WorkerSpec{Profile: "review"}, Sched: SchedulerPolicy{MaxSteps: tc.requested}}
56 ctx, steps := task.childMaxStepsForSpec(context.Background(), &spec)
57 if steps != tc.want {
58 t.Fatalf("parent=%d requested=%d: steps = %d, want %d", tc.parent, tc.requested, steps, tc.want)
59 }
60 if childOutputBudgetFrom(ctx) != defaultReviewOutputTokens {
61 t.Fatalf("review output budget = %d", childOutputBudgetFrom(ctx))
62 }
63 }
64 }
65
65 lines GO