返回 DeepSeek-Reasonix
compact_manual_history_test.go
根目录 / internal / agent / compact_manual_history_test.go
1 package agent
2
3 import (
4 "errors"
5 "fmt"
6 "reflect"
7 "strings"
8 "testing"
9
10 "reasonix/internal/event"
11 "reasonix/internal/provider"
12 "reasonix/internal/sessioncontext"
13 )
14
15 func TestManualCompactFindsHistoryBehindFixedContext(t *testing.T) {
16 for _, window := range []int{0, 128_000} {
17 name := "configured-window"
18 if window == 0 {
19 name = "automatic-compaction-disabled"
20 }
21 t.Run(name, func(t *testing.T) {
22 prov := &fakeProvider{reply: "- Completed the requested work."}
23 sess := NewSession(strings.Repeat("stable system instructions ", 500))
24 contextMessage := HostGeneratedUserMessage(sessioncontext.Build(sessioncontext.Sections{Workspace: strings.Repeat("workspace instructions ", 500)}).Content)
25 sess.Add(contextMessage)
26 sess.Add(provider.Message{Role: provider.RoleUser, Content: "Implement the requested change."})
27 sess.Add(provider.Message{Role: provider.RoleAssistant, Content: strings.Repeat("Completed implementation and verification. ", 50)})
28 a := New(prov, nil, sess, Options{ContextWindow: window}, event.Discard)
29 canonical := append([]provider.Message(nil), sess.Messages...)
30 if err := a.PrepareContext(t.Context()); err != nil {
31 t.Fatal(err)
32 }
33 if len(prov.got) != 0 {
34 t.Fatal("automatic maintenance compacted below its threshold")
35 }
36 if err := a.CompactNow(t.Context(), "retain decisions"); err != nil {
37 t.Fatal(err)
38 }
39 if len(prov.got) == 0 || a.currentProjectionVersion() == 0 {
40 t.Fatal("manual compaction reported no history for a completed conversation")
41 }
42 if !reflect.DeepEqual(canonical, sess.Messages) {
43 t.Fatal("manual compaction changed the canonical transcript")
44 }
45 visible := a.modelVisibleMessages()
46 if visible[0].Content != canonical[0].Content {
47 t.Fatal("system prefix changed")
48 }
49 foundContext := false
50 for _, msg := range visible {
51 foundContext = foundContext || msg.Content == contextMessage.Content
52 }
53 if !foundContext {
54 t.Fatal("workspace context was lost")
55 }
56 })
57 }
58 }
59
60 func TestManualCompactWithoutWindowRecoversOverflow(t *testing.T) {
61 for _, numbered := range []bool{false, true} {
62 t.Run(fmt.Sprintf("numbered=%v", numbered), func(t *testing.T) {
63 prov := &denseTokenizerProvider{window: 20_000, unnumberedReplay: !numbered}
64 turns := 4 // Replay-only rejection; the transcript representation fits.
65 if numbered {
66 turns = 16 // Learn the physical window and split the oversized history.
67 }
68 a := New(prov, nil, longASCIISession(turns), Options{}, event.Discard)
69 if err := a.CompactNow(t.Context(), "retain decisions"); err != nil {
70 t.Fatal(err)
71 }
72 if prov.overflows == 0 || len(prov.requests) < 2 || a.currentProjectionVersion() == 0 || !hasCompactionSummary(a.modelVisibleMessages()) {
73 t.Fatalf("overflow recovery did not install a summary: calls=%d overflows=%d", len(prov.requests), prov.overflows)
74 }
75 if numbered && a.ContextUsedTokens() >= a.hardInputCeiling() {
76 t.Fatal("manual rescue left the context above the learned ceiling")
77 }
78 calls := len(prov.requests)
79 for range 30 {
80 a.sess.conversation.Add(provider.Message{Role: provider.RoleUser, Content: strings.Repeat("continue ", 1000)})
81 }
82 if err := a.PrepareContext(t.Context()); err != nil {
83 t.Fatal(err)
84 }
85 if a.ContextWindow() != 0 || len(prov.requests) != calls {
86 t.Fatal("manual recovery re-enabled automatic compaction")
87 }
88 })
89 }
90 }
91
92 func TestManualCompactWithoutWindowReportsSummaryFailure(t *testing.T) {
93 want := errors.New("summary service unavailable")
94 prov := &fakeProvider{streamErr: want}
95 a := New(prov, nil, longASCIISession(4), Options{}, event.Discard)
96 if err := a.CompactNow(t.Context(), ""); !errors.Is(err, want) {
97 t.Fatalf("error = %v, want original summary error", err)
98 }
99 if a.currentProjectionVersion() != 0 {
100 t.Fatal("unknown ceiling caused lossy truncation after a summary failure")
101 }
102 }
103
104 func TestManualCompactWithoutWindowKeepsEmptyAndActiveHistory(t *testing.T) {
105 for _, active := range []bool{false, true} {
106 prov := &fakeProvider{reply: "- Summary."}
107 sess := NewSession("system")
108 a := New(prov, nil, sess, Options{}, event.Discard)
109 if active {
110 sess.Add(provider.Message{Role: provider.RoleUser, Content: "Keep the active request.", CreatedAt: 42})
111 sess.Add(provider.Message{Role: provider.RoleAssistant, Content: strings.Repeat("work in progress ", 100)})
112 a.activeTurnCreatedAt.Store(42)
113 }
114 if err := a.CompactNow(t.Context(), ""); err != nil {
115 t.Fatal(err)
116 }
117 if len(prov.got) != 0 || a.currentProjectionVersion() != 0 {
118 t.Fatalf("active=%v: compacted empty or active history", active)
119 }
120 }
121 }
122
122 lines GO