返回 DeepSeek-Reasonix
compaction_persistence_test.go
根目录 / internal / control / compaction_persistence_test.go
1 package control
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "path/filepath"
8 "reflect"
9 "strings"
10 "testing"
11
12 "reasonix/internal/agent"
13 "reasonix/internal/agent/testutil"
14 "reasonix/internal/event"
15 "reasonix/internal/provider"
16 "reasonix/internal/session"
17 "reasonix/internal/tool"
18 )
19
20 func compactionPersistenceHistory(tools bool) *agent.Session {
21 s := agent.NewSession("system")
22 if tools {
23 s.Add(provider.Message{Role: provider.RoleUser, Content: "inspect the files"})
24 for i := range 3 {
25 id := fmt.Sprintf("call-%d", i)
26 s.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: id, Name: "read_file", Arguments: `{}`}}})
27 s.Add(provider.Message{Role: provider.RoleTool, ToolCallID: id, Name: "read_file", Content: strings.Repeat("x", 16_000)})
28 }
29 } else {
30 for i := range 24 {
31 s.Add(provider.Message{Role: provider.RoleUser, Content: fmt.Sprintf("task %d", i)})
32 s.Add(provider.Message{Role: provider.RoleAssistant, Content: strings.Repeat("x", 2_000)})
33 }
34 }
35 s.Add(provider.Message{Role: provider.RoleUser, Content: "continue"})
36 s.Add(provider.Message{Role: provider.RoleAssistant, Content: "ready"})
37 return s
38 }
39
40 func TestContextMaintenanceProjectionSurvivesSessionSwitch(t *testing.T) {
41 for _, mode := range []string{"summary", "prune", "truncate", "auto-summary", "auto-prune", "auto-truncate"} {
42 t.Run(mode, func(t *testing.T) {
43 service, err := session.NewService("desktop", session.NewFilesystemPersistence(filepath.Join(t.TempDir(), "sessions-v5")))
44 if err != nil {
45 t.Fatal(err)
46 }
47 t.Cleanup(func() { _ = service.CloseAll(context.Background()) })
48 runtime, err := service.Create(t.Context(), session.CreateOptions{SessionID: "maintenance"})
49 if err != nil {
50 t.Fatal(err)
51 }
52 providerMock := testutil.NewMock("maintenance", testutil.Turn{Text: "durable summary"}, testutil.Turn{Text: "final answer"}, testutil.Turn{Text: "final answer"})
53 if strings.Contains(mode, "truncate") {
54 providerMock = testutil.NewMock("maintenance", testutil.Turn{StreamError: errors.New("summary unavailable")}, testutil.Turn{Text: "final answer"})
55 }
56 exec := agent.New(providerMock, tool.NewRegistry(), compactionPersistenceHistory(strings.Contains(mode, "prune")), agent.Options{ContextWindow: 10_000, CompactRatio: .8}, event.Discard)
57 controller := newOwnedTestController(t, Options{Runner: exec, Executor: exec, Sink: event.Discard, SessionService: service, SessionRuntime: runtime, ExclusiveSession: true})
58
59 canonicalBefore := runtime.Session().ExecutionSnapshot().Projection.Messages
60 if strings.HasPrefix(mode, "auto-") {
61 err = controller.RunTurn(t.Context(), "continue the task")
62 } else if mode == "prune" {
63 err = exec.PrepareContext(t.Context())
64 } else {
65 err = controller.Compact(t.Context(), "")
66 }
67 if err != nil {
68 t.Fatal(err)
69 }
70
71 wantModel := provider.ModelMessages(exec.ModelHistorySnapshot())
72 snapshot := runtime.Session().ExecutionSnapshot()
73 if !reflect.DeepEqual(snapshot.Projection.ModelMessages, wantModel) {
74 t.Fatal("durable model projection differs from the installed projection")
75 }
76 if !strings.HasPrefix(mode, "auto-") && !reflect.DeepEqual(snapshot.Projection.Messages, canonicalBefore) {
77 t.Fatal("context maintenance rewrote canonical history")
78 }
79
80 other, err := service.Create(t.Context(), session.CreateOptions{SessionID: "other"})
81 if err != nil {
82 t.Fatal(err)
83 }
84 if _, err := controller.OpenSession(t.Context(), other.Ref()); err != nil {
85 t.Fatal(err)
86 }
87 if _, err := controller.OpenSession(t.Context(), runtime.Ref()); err != nil {
88 t.Fatal(err)
89 }
90 if got := provider.ModelMessages(exec.ModelHistorySnapshot()); !reflect.DeepEqual(got, wantModel) {
91 t.Fatal("model projection changed after switching away and reopening")
92 }
93 })
94 }
95 }
96
96 lines GO