返回 DeepSeek-Reasonix
context_maintenance_issue7935_test.go
根目录 / internal / agent / context_maintenance_issue7935_test.go
1 package agent
2
3 import (
4 "context"
5 "fmt"
6 "strings"
7 "testing"
8
9 "reasonix/internal/event"
10 "reasonix/internal/provider"
11 "reasonix/internal/tool"
12 )
13
14 // Issue #7935 used to install prune projections every time many tool results
15 // crossed a snip threshold. Automatic maintenance is now summary-only and
16 // generation-scoped: below compact_ratio nothing happens; at the threshold
17 // exactly one summary lands; appends before the next threshold do not re-pay.
18 func TestIssue7935Maintains206ToolResultsOnceAndOnlyProcessesNewTail(t *testing.T) {
19 const staleResults = 40
20 big := strings.Repeat("line\n", 200)
21 messages := []provider.Message{
22 {Role: provider.RoleSystem, Content: "system"},
23 {Role: provider.RoleUser, Content: "first task"},
24 }
25 for i := range staleResults {
26 id := fmt.Sprintf("old-%d", i)
27 messages = append(messages,
28 provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: id, Name: "read_file", Arguments: "{}"}}},
29 provider.Message{Role: provider.RoleTool, ToolCallID: id, Name: "read_file", Content: big},
30 )
31 }
32 messages = append(messages,
33 provider.Message{Role: provider.RoleUser, Content: "recent question"},
34 provider.Message{Role: provider.RoleAssistant, Content: "recent answer"},
35 )
36 sess := &Session{Messages: messages}
37 sink := &recordSink{}
38 a := New(&fakeProvider{reply: "structured history digest"}, tool.NewRegistry(), sess, Options{
39 ContextWindow: 8_000,
40 CompactRatio: 0.80,
41 RecentKeep: 2,
42 ArchiveDir: t.TempDir(),
43 }, sink)
44
45 // Below the fold trigger nothing may be rewritten.
46 prepareForObservedUsage(a, context.Background(), &provider.Usage{PromptTokens: 5_000})
47 if got := a.currentProjectionVersion(); got != 0 {
48 t.Fatalf("history was rewritten below the fold trigger: projection version %d", got)
49 }
50
51 // Crossing the sole trigger installs one summary checkpoint, never prune.
52 prepareForObservedUsage(a, context.Background(), &provider.Usage{PromptTokens: 7_000})
53 firstVersion := a.currentProjectionVersion()
54 if firstVersion != 1 {
55 t.Fatalf("first maintenance version = %d, want 1", firstVersion)
56 }
57 if got := countToolResultsWithPrefix(a.modelVisibleMessages(), prunedMarker); got != 0 {
58 t.Fatalf("automatic prune markers installed = %d, want 0", got)
59 }
60 status := a.ContextMaintenanceSnapshot()
61 if status.LastReceipt == nil || status.LastReceipt.Action != "summary" || status.LastReceipt.Status != "applied" {
62 t.Fatalf("maintenance snapshot receipt = %+v", status.LastReceipt)
63 }
64 for i, msg := range sess.Snapshot() {
65 if msg.Role == provider.RoleTool && msg.Content != big {
66 t.Fatalf("canonical tool result %d was rewritten", i)
67 }
68 }
69
70 // Appends that stay under the next threshold must not re-summarize.
71 sess.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: "new", Name: "read_file", Arguments: "{}"}}})
72 sess.Add(provider.Message{Role: provider.RoleTool, ToolCallID: "new", Name: "read_file", Content: "small"})
73 sess.Add(provider.Message{Role: provider.RoleUser, Content: "continue"})
74 sess.Add(provider.Message{Role: provider.RoleAssistant, Content: "ok"})
75 prepareForObservedUsage(a, context.Background(), &provider.Usage{PromptTokens: 1_500})
76 if got := a.currentProjectionVersion(); got != firstVersion {
77 t.Fatalf("below-threshold append advanced version to %d, want %d", got, firstVersion)
78 }
79
80 var applied int
81 for _, got := range sink.kinds(event.ContextMaintenanceEvent) {
82 if got.Maintenance != nil && got.Maintenance.Status == "applied" && got.Maintenance.Action == "summary" {
83 applied++
84 }
85 }
86 if applied != 1 {
87 t.Fatalf("summary applied events = %d, want exactly 1", applied)
88 }
89 }
90
91 func countToolResultsWithPrefix(messages []provider.Message, prefix string) int {
92 var count int
93 for _, msg := range messages {
94 if msg.Role == provider.RoleTool && strings.HasPrefix(msg.Content, prefix) {
95 count++
96 }
97 }
98 return count
99 }
100
100 lines GO