返回 DeepSeek-Reasonix
reasoning_replay_test.go
根目录 / internal / provider / responses / reasoning_replay_test.go
1 package responses
2
3 import (
4 "testing"
5
6 "reasonix/internal/provider"
7 )
8
9 func TestEmptyReasoningFallbackIsScopedToThinkingStatelessVendors(t *testing.T) {
10 deepseek := New(Config{Name: "deepseek", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-flash"})
11 if !provider.AllowsEmptyReasoningFallback(deepseek) {
12 t.Fatal("DeepSeek Responses provider must accept tool turns whose output omitted reasoning")
13 }
14 mimo := New(Config{Name: "mimo", BaseURL: "https://api.xiaomimimo.com/v1", Model: "mimo-v2.5-pro"})
15 if !provider.AllowsEmptyReasoningFallback(mimo) {
16 t.Fatal("MiMo Responses provider must accept its optional tool-call reasoning")
17 }
18 disabled := New(Config{Name: "deepseek", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-pro", Effort: "none"})
19 if provider.RequiresToolCallReasoning(disabled) || provider.WarnOnMissingToolCallReasoning(disabled) {
20 t.Fatal("reasoning-disabled Responses provider must not require or diagnose missing reasoning")
21 }
22 unknown := New(Config{Name: "other", BaseURL: "https://example.com", Model: "m"})
23 if provider.AllowsEmptyReasoningFallback(unknown) {
24 t.Fatal("unknown Responses endpoint must not inherit a vendor-specific empty fallback")
25 }
26 }
27
28 func TestStatelessRequestReplaysToolPairWithoutFabricatingReasoning(t *testing.T) {
29 client := New(Config{Name: "deepseek", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-flash"}).(*client)
30 body, _, _ := client.buildRequestBody(provider.Request{Messages: []provider.Message{
31 {Role: provider.RoleUser, Content: "run"},
32 {Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: "call_1", Name: "echo", Arguments: `{"text":"hi"}`}}},
33 {Role: provider.RoleTool, ToolCallID: "call_1", Name: "echo", Content: "hi"},
34 }})
35
36 items := body["input"].([]map[string]any)
37 if len(items) != 3 || items[1]["type"] != "function_call" || items[2]["type"] != "function_call_output" {
38 t.Fatalf("input = %#v, want user/call/output", items)
39 }
40 for _, item := range items {
41 if item["type"] == "reasoning" {
42 t.Fatalf("missing provider reasoning was fabricated: %#v", item)
43 }
44 }
45 }
46
47 func TestExplicitGatewayReplayContractWithoutModelGuess(t *testing.T) {
48 message := provider.Message{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{{ID: "c", Name: "echo", Arguments: `{}`}}}
49 history := []provider.Message{{Role: provider.RoleUser, Content: "go"}, message, {Role: provider.RoleTool, Name: "echo", ToolCallID: "c", Content: "done"}}
50 for _, explicit := range []bool{false, true} {
51 cfg := Config{BaseURL: "https://gateway.example/v1", Model: "deepseek-v4-flash", Mode: "stateless", Effort: "high"}
52 if explicit {
53 cfg.Extra = map[string]any{"reasoning_protocol": "deepseek"}
54 }
55 p := New(cfg)
56 if provider.RequiresToolCallReasoning(p) != explicit {
57 t.Fatal("explicit contract not honored or model name inferred strictness")
58 }
59 if explicit {
60 if !provider.CanReplayAssistantMessage(p, message) {
61 t.Fatal("healthy missing-item fallback changed")
62 }
63 projected, changed := provider.ProjectReasoningStrippedMessages(p, history)
64 if !changed || len(projected) != 1 {
65 t.Fatalf("rejected missing proof cannot recover: %+v", projected)
66 }
67 }
68 }
69 }
70
70 lines GO