返回 DeepSeek-Reasonix
compact_partition_test.go
根目录 / internal / agent / compact_partition_test.go
1 package agent
2
3 import (
4 "strings"
5 "testing"
6
7 "reasonix/internal/provider"
8 )
9
10 // partitionCoversRegion is the invariant that makes a lost user turn impossible:
11 // every provider-visible message in the region lands in exactly one group.
12 func partitionCoversRegion(t *testing.T, a *Agent, region []provider.Message) (kept, fold []provider.Message) {
13 t.Helper()
14 kept, fold, retention := a.partitionFoldForProjection(region)
15 userTurns := 0
16 for _, m := range region {
17 if m.Role == provider.RoleUser && !m.LocalOnly && !isCompactionSummary(m) {
18 userTurns++
19 }
20 }
21 if got := retention.Kept + retention.Dropped; got != userTurns {
22 t.Errorf("retention accounts for %d user turns, region has %d", got, userTurns)
23 }
24 seen := map[string]int{}
25 for _, group := range [][]provider.Message{kept, fold} {
26 for _, m := range group {
27 seen[m.Content]++
28 }
29 }
30 for _, m := range region {
31 if m.LocalOnly {
32 if seen[m.Content] != 0 {
33 t.Errorf("display-only message %q reached the projection", m.Content)
34 }
35 continue
36 }
37 switch seen[m.Content] {
38 case 1:
39 case 0:
40 t.Errorf("message %q is in no group — it would vanish from the projection", m.Content)
41 default:
42 t.Errorf("message %q is in %d groups — it would be duplicated", m.Content, seen[m.Content])
43 }
44 }
45 return kept, fold
46 }
47
48 func TestPartitionFoldsSmallUserTurns(t *testing.T) {
49 a := &Agent{}
50 region := []provider.Message{
51 {Role: provider.RoleUser, Content: "small turn 0"},
52 {Role: provider.RoleUser, Content: "small turn 1"},
53 {Role: provider.RoleUser, Content: "small turn 2"},
54 {Role: provider.RoleAssistant, Content: "work"},
55 }
56 kept, fold := partitionCoversRegion(t, a, region)
57 if len(kept) != 0 || len(fold) != 4 {
58 t.Fatalf("kept=%d fold=%d, want 0/4", len(kept), len(fold))
59 }
60 if got := renderTranscript(fold); !strings.Contains(got, "small turn") {
61 t.Fatalf("old user turns must reach the summarizer: %s", got)
62 }
63 }
64
65 func TestPartitionFoldsLargeUserTurns(t *testing.T) {
66 a := &Agent{}
67 oversize := provider.Message{
68 Role: provider.RoleUser,
69 Content: strings.Repeat("y", 12_000),
70 }
71 region := []provider.Message{
72 {Role: provider.RoleUser, Content: "small and kept"},
73 oversize,
74 }
75 kept, fold := partitionCoversRegion(t, a, region)
76 if len(kept) != 0 || len(fold) != 2 {
77 t.Fatalf("kept=%d fold=%d, want both turns folded", len(kept), len(fold))
78 }
79 if !strings.Contains(renderTranscript(fold), "small and kept") {
80 t.Fatal("the small turn should be summarized with the oversize one")
81 }
82 }
83
84 func TestPartitionMergesUserTurnsAcrossPriorDigest(t *testing.T) {
85 a := &Agent{}
86 region := []provider.Message{
87 {Role: provider.RoleUser, Content: "constraint from before the digest"},
88 {Role: provider.RoleUser, Content: summaryTagOpen + "\nprior digest\n" + summaryTagClose},
89 {Role: provider.RoleAssistant, Content: "work"},
90 }
91 kept, fold := partitionCoversRegion(t, a, region)
92 if len(kept) != 0 || !strings.Contains(renderTranscript(fold), "constraint from before") {
93 t.Fatalf("pre-digest user turn must enter the merged summary; fold=%v", renderTranscript(fold))
94 }
95 if !strings.Contains(renderTranscript(fold), "prior digest") {
96 t.Fatal("the digest itself must still fold into the next one")
97 }
98 }
99
100 func TestPartitionFoldsPriorDigests(t *testing.T) {
101 // Any digest in the region is merged into the next one, so the model
102 // never sees a chain of digests.
103 a := &Agent{}
104 region := []provider.Message{
105 {Role: provider.RoleUser, Content: summaryTagOpen + "\nolder digest\n" + summaryTagClose},
106 {Role: provider.RoleUser, Content: summaryTagOpen + "\nnewer digest\n" + summaryTagClose},
107 {Role: provider.RoleAssistant, Content: "work"},
108 }
109 kept, fold := partitionCoversRegion(t, a, region)
110 if len(kept) != 0 || len(fold) != 3 {
111 t.Fatalf("kept=%d fold=%d, want every digest folded", len(kept), len(fold))
112 }
113 }
114
115 func TestPartitionIgnoresDeprecatedKeepPolicy(t *testing.T) {
116 a := &Agent{keepPolicy: KeepErrors}
117 region := []provider.Message{
118 {Role: provider.RoleAssistant, Content: "unrelated prose"},
119 {Role: provider.RoleAssistant, Content: "call", ToolCalls: []provider.ToolCall{{ID: "t1", Name: "bash"}}},
120 {Role: provider.RoleTool, ToolCallID: "t1", Name: "bash", Content: "error: boom"},
121 }
122 kept, fold := partitionCoversRegion(t, a, region)
123 if len(kept) != 0 || len(fold) != 3 {
124 t.Fatalf("kept=%d fold=%d, want every old message folded", len(kept), len(fold))
125 }
126 if got := renderTranscript(fold); !strings.Contains(got, "error: boom") || !strings.Contains(got, "call") {
127 t.Fatalf("the failing call and its result must enter the summary together: %s", got)
128 }
129 }
130
130 lines GO