返回 DeepSeek-Reasonix
reasoning_replay_test.go
根目录 / internal / control / reasoning_replay_test.go
1 package control
2
3 import (
4 "context"
5 "testing"
6
7 "reasonix/internal/agent"
8 "reasonix/internal/event"
9 "reasonix/internal/provider"
10 "reasonix/internal/tool"
11 )
12
13 type strictReplayControlProvider struct{}
14
15 func (strictReplayControlProvider) Name() string { return "deepseek-anthropic" }
16 func (strictReplayControlProvider) Stream(context.Context, provider.Request) (<-chan provider.Chunk, error) {
17 panic("unexpected Stream call")
18 }
19 func (strictReplayControlProvider) RequiresAssistantReasoningReplay(m provider.Message) bool {
20 return len(m.ToolCalls) > 0 || len(m.ServerSearch) > 0
21 }
22
23 func TestInterruptedRecoveryExcludesStructurallyCompleteButUnreplayableToolTurn(t *testing.T) {
24 sess := agent.NewSession("system")
25 user := provider.Message{Role: provider.RoleUser, Content: "update file"}
26 sess.Add(user)
27 sess.Add(provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{
28 ID: "c1", Name: "write_file", Arguments: `{"path":"a.txt","content":"ok"}`,
29 }}})
30 sess.Add(provider.Message{Role: provider.RoleTool, ToolCallID: "c1", Name: "write_file", Content: "wrote a.txt"})
31 exec := agent.New(strictReplayControlProvider{}, tool.NewRegistry(), sess, agent.Options{}, event.Discard)
32 c := newOwnedTestController(t, Options{Executor: exec})
33
34 c.stripCancelledVisibleTurnMessagesAfterWithFallback(1, user)
35
36 model := provider.ModelMessages(sess.Snapshot())
37 for _, m := range model {
38 if len(m.ToolCalls) > 0 || m.Role == provider.RoleTool {
39 t.Fatalf("unreplayable completed pair remained provider-visible: %+v", model)
40 }
41 }
42 msgs := sess.Snapshot()
43 last := msgs[len(msgs)-1]
44 if !last.LocalOnly || last.InterruptedTurn == nil || !last.InterruptedTurn.Pending {
45 t.Fatalf("missing local recovery handoff: %+v", msgs)
46 }
47 }
48
49 func TestInterruptedPartialBatchKeepsCompletedAndUnknownSeparate(t *testing.T) {
50 sess := agent.NewSession("system")
51 user := provider.Message{Role: provider.RoleUser, Content: "update files"}
52 sess.Add(user)
53 sess.Add(provider.Message{Role: provider.RoleAssistant, ReasoningContent: "original", ReasoningSignature: "original-proof", ToolCalls: []provider.ToolCall{
54 {ID: "done", Name: "write_file", Arguments: `{"path":"a.txt"}`},
55 {ID: "uncertain", Name: "write_file", Arguments: `{"path":"b.txt"}`},
56 {ID: "pending", Name: "read_file", Arguments: `{"path":"c.txt"}`},
57 }})
58 sess.Add(provider.Message{Role: provider.RoleTool, ToolCallID: "done", Name: "write_file", Content: "wrote a.txt", ToolRunState: provider.ToolRunCompleted})
59 sess.Add(provider.Message{Role: provider.RoleTool, ToolCallID: "pending", Name: "read_file", Content: "cancelled: context cancelled before execution", ToolRunState: provider.ToolRunNotStarted})
60 exec := agent.New(strictReplayControlProvider{}, tool.NewRegistry(), sess, agent.Options{}, event.Discard)
61 c := newOwnedTestController(t, Options{Executor: exec})
62 c.stripCancelledVisibleTurnMessagesAfterWithFallback(1, user)
63 msgs := sess.Snapshot()
64 r := msgs[len(msgs)-1].InterruptedTurn
65 if r == nil || len(r.CompletedTools) != 1 || r.CompletedTools[0].ID != "done" || len(r.UnknownTools) != 1 || r.UnknownTools[0].ID != "uncertain" || len(r.NotStartedTools) != 1 {
66 t.Fatalf("recovery=%+v", r)
67 }
68 if msgs[2].ReasoningSignature != "original-proof" || msgs[2].ToolCalls[0].Arguments == "" {
69 t.Fatalf("canonical evidence lost: %+v", msgs[2])
70 }
71 }
72
72 lines GO