返回 DeepSeek-Reasonix
reasoning_contract_test.go
根目录 / internal / provider / reasoning_contract_test.go
1 package provider
2
3 import (
4 "encoding/json"
5 "reflect"
6 "testing"
7 )
8
9 type contractProvider struct {
10 replayProjectionProvider
11 caps ReasoningReplayCapabilities
12 }
13
14 func (p contractProvider) ReasoningReplayCapabilities() ReasoningReplayCapabilities { return p.caps }
15
16 func TestReasoningContractsDistinguishMissingEmptyAndUnsafe(t *testing.T) {
17 call := []ToolCall{{ID: "c", Name: "write_file"}}
18 for _, tc := range []struct {
19 name string
20 caps ReasoningReplayCapabilities
21 fallback bool
22 msg Message
23 want bool
24 }{
25 {"signed-empty", ReasoningReplayCapabilities{RequireSignature: true}, false, Message{ReasoningSignature: "proof", ReasoningState: ReasoningEmpty}, true},
26 {"native-missing-signature", ReasoningReplayCapabilities{RequireSignature: true}, false, Message{ReasoningContent: "thought"}, false},
27 {"deepseek-anthropic-empty", ReasoningReplayCapabilities{Format: "anthropic-thinking"}, false, Message{ReasoningState: ReasoningEmpty}, false},
28 {"chat-empty", ReasoningReplayCapabilities{Format: "chat-completions"}, true, Message{ReasoningState: ReasoningEmpty}, true},
29 {"chat-truncated", ReasoningReplayCapabilities{Format: "chat-completions"}, true, Message{ReasoningContent: "cut", ReasoningState: ReasoningTruncated}, false},
30 {"responses-incomplete", ReasoningReplayCapabilities{Format: "responses-items"}, true, Message{ReasoningState: ReasoningIncomplete}, false},
31 {"legacy-reasoning", ReasoningReplayCapabilities{}, false, Message{ReasoningContent: "complete"}, true},
32 {"unknown-missing", ReasoningReplayCapabilities{}, false, Message{}, false},
33 } {
34 t.Run(tc.name, func(t *testing.T) {
35 p := contractProvider{replayProjectionProvider{allowEmpty: tc.fallback}, tc.caps}
36 tc.msg.Role = RoleAssistant
37 tc.msg.ToolCalls = call
38 if got := CanReplayAssistantMessage(p, tc.msg); got != tc.want {
39 t.Fatalf("replay=%v want=%v", got, tc.want)
40 }
41 })
42 }
43 }
44
45 func TestReplayMetadataLegacyAndProjection(t *testing.T) {
46 var old Message
47 if err := json.Unmarshal([]byte(`{"role":"assistant","content":"old"}`), &old); err != nil {
48 t.Fatal(err)
49 }
50 if old.ReasoningState != "" || len(old.ThinkingBlocks) > 0 {
51 t.Fatal("legacy defaults changed")
52 }
53 original := Message{Role: RoleAssistant, ReasoningState: ReasoningComplete, ThinkingBlocks: []ThinkingBlock{{Type: "thinking", Signature: "one"}, {Type: "redacted_thinking", Data: "opaque"}}}
54 raw, _ := json.Marshal(original)
55 var reloaded Message
56 if err := json.Unmarshal(raw, &reloaded); err != nil || !reflect.DeepEqual(original, reloaded) {
57 t.Fatalf("roundtrip: %s %v", raw, err)
58 }
59 p := contractProvider{caps: ReasoningReplayCapabilities{RequireSignature: true}}
60 original.ToolCalls = []ToolCall{{ID: "c", Name: "read_file"}}
61 msgs := []Message{original}
62 if got, changed := ProjectReplaySafeMessages(p, msgs); changed || &got[0] != &msgs[0] {
63 t.Fatal("healthy signed blocks changed")
64 }
65 tool := Message{Role: RoleTool, Content: "done", ToolRunState: ToolRunCompleted}
66 if ModelMessages([]Message{tool})[0].ToolRunState != "" || ProjectionMessages([]Message{tool})[0].ToolRunState != ToolRunCompleted {
67 t.Fatal("execution state projection wrong")
68 }
69 }
70
71 func TestLegacyInterruptedResultIsUnknown(t *testing.T) {
72 if ToolResultRunState(Message{Content: interruptedToolResult}) != ToolRunUnknown {
73 t.Fatal("synthetic missing result must not count completed")
74 }
75 }
76
76 lines GO