返回 DeepSeek-Reasonix
effort_test.go
根目录 / internal / provider / openai / effort_test.go
1 package openai
2
3 import (
4 "encoding/json"
5 "strings"
6 "testing"
7
8 "reasonix/internal/provider"
9 )
10
11 func newClient(t *testing.T, baseURL, effort string) *client {
12 t.Helper()
13 extra := map[string]any{}
14 if effort != "" {
15 extra["effort"] = effort
16 }
17 p, err := New(provider.Config{Name: "p", BaseURL: baseURL, Model: "m", APIKey: "k", Extra: extra})
18 if err != nil {
19 t.Fatalf("New(%q, effort=%q): %v", baseURL, effort, err)
20 }
21 return p.(*client)
22 }
23
24 func TestEffortNormalization(t *testing.T) {
25 const mimo = "https://api.xiaomimimo.com/v1"
26 const deepseek = "https://api.deepseek.com/v1"
27
28 tests := []struct {
29 base, effort, want string
30 }{
31 {mimo, "max", "high"}, // DeepSeek-ism clamped to the OpenAI ceiling — MiMo 400s on "max"
32 {mimo, "high", "high"},
33 {mimo, "medium", "medium"},
34 {mimo, "low", "low"},
35 {mimo, "MAX", "high"}, // case-insensitive
36 {mimo, "auto", ""}, // UI/config auto means omit provider-specific effort
37 {mimo, "", ""}, // unset stays omitted
38 {deepseek, "max", "max"},
39 {deepseek, "high", "high"},
40 {deepseek, "auto", "high"},
41 {deepseek, "", "high"}, // DeepSeek default depth
42 }
43 for _, tc := range tests {
44 if tc.base == mimo && strings.EqualFold(tc.effort, "max") {
45 _, err := New(provider.Config{BaseURL: tc.base, Model: "m", Extra: map[string]any{"effort": tc.effort}})
46 if err == nil {
47 t.Fatal("max must not clamp to high")
48 }
49 continue
50 }
51 if got := newClient(t, tc.base, tc.effort).effort; got != tc.want {
52 t.Errorf("base=%s effort=%q: got %q, want %q", tc.base, tc.effort, got, tc.want)
53 }
54 }
55 }
56
57 func TestExplicitSupportedEffortsPreservesKimiK3Max(t *testing.T) {
58 p, err := New(provider.Config{
59 Name: "opencode-go",
60 BaseURL: "https://opencode.ai/zen/go/v1",
61 Model: "kimi-k3",
62 APIKey: "k",
63 Extra: map[string]any{
64 "effort": "max",
65 "supported_efforts": []string{"high", "max"},
66 },
67 })
68 if err != nil {
69 t.Fatalf("New: %v", err)
70 }
71 if got := p.(*client).buildRequest(provider.Request{}).ReasoningEffort; got != "max" {
72 t.Fatalf("reasoning_effort = %q, want explicitly supported max", got)
73 }
74 }
75
76 func TestExplicitSupportedEffortsPreserveCustomGenericEffort(t *testing.T) {
77 const effort = "ultra"
78 p, err := New(provider.Config{
79 Name: "custom-openai",
80 BaseURL: "https://gateway.example.com/v1",
81 Model: "custom-reasoning-model",
82 APIKey: "k",
83 Extra: map[string]any{
84 "effort": effort,
85 "supported_efforts": []string{"high", effort},
86 },
87 })
88 if err != nil {
89 t.Fatalf("New custom effort: %v", err)
90 }
91 req := p.(*client).buildRequest(provider.Request{})
92 if req.ReasoningEffort != effort {
93 t.Fatalf("reasoning_effort = %q, want %q", req.ReasoningEffort, effort)
94 }
95 body, err := json.Marshal(req)
96 if err != nil {
97 t.Fatalf("marshal request: %v", err)
98 }
99 if !strings.Contains(string(body), `"reasoning_effort":"ultra"`) {
100 t.Fatalf("custom effort was not preserved on the wire: %s", body)
101 }
102 }
103
104 func TestExplicitSupportedEffortsPreserveCustomDeepSeekEfforts(t *testing.T) {
105 for _, effort := range []string{"medium", "none"} {
106 t.Run(effort, func(t *testing.T) {
107 p, err := New(provider.Config{
108 Name: "custom-deepseek",
109 BaseURL: "https://gateway.example.com/v1",
110 Model: "deepseek-v4-flash",
111 APIKey: "k",
112 Extra: map[string]any{
113 "effort": effort,
114 "reasoning_protocol": "deepseek",
115 "supported_efforts": []string{"low", "medium", "high", "none"},
116 },
117 })
118 if err != nil {
119 t.Fatalf("New custom DeepSeek effort: %v", err)
120 }
121 req := p.(*client).buildRequest(provider.Request{})
122 if req.ReasoningEffort != effort {
123 t.Fatalf("reasoning_effort = %q, want %q", req.ReasoningEffort, effort)
124 }
125 if req.Thinking == nil || req.Thinking.Type != "enabled" {
126 t.Fatalf("thinking = %#v, want enabled", req.Thinking)
127 }
128 body, err := json.Marshal(req)
129 if err != nil {
130 t.Fatalf("marshal request: %v", err)
131 }
132 if !strings.Contains(string(body), `"reasoning_effort":"`+effort+`"`) {
133 t.Fatalf("custom DeepSeek effort was not preserved on the wire: %s", body)
134 }
135 })
136 }
137 }
138
139 func TestExplicitSupportedEffortsRejectUndeclaredEffort(t *testing.T) {
140 tests := []struct {
141 name string
142 extra map[string]any
143 }{
144 {
145 name: "generic",
146 extra: map[string]any{
147 "effort": "ultra",
148 "supported_efforts": []string{"high"},
149 },
150 },
151 {
152 name: "generic-max",
153 extra: map[string]any{
154 "effort": "max",
155 "supported_efforts": []string{"high"},
156 },
157 },
158 {
159 name: "deepseek",
160 extra: map[string]any{
161 "effort": "max",
162 "reasoning_protocol": "deepseek",
163 "supported_efforts": []string{"medium", "none"},
164 },
165 },
166 }
167 for _, tc := range tests {
168 t.Run(tc.name, func(t *testing.T) {
169 _, err := New(provider.Config{
170 Name: "custom-" + tc.name,
171 BaseURL: "https://gateway.example.com/v1",
172 Model: "custom-reasoning-model",
173 APIKey: "k",
174 Extra: tc.extra,
175 })
176 if err == nil || !strings.Contains(err.Error(), "UNSUPPORTED_REASONING_EFFORT") {
177 t.Fatalf("New error = %v, want supported_efforts rejection", err)
178 }
179 })
180 }
181 }
182
183 func TestImplicitOnlySupportedEffortsKeepBuiltInValidation(t *testing.T) {
184 _, err := New(provider.Config{
185 Name: "generic",
186 BaseURL: "https://gateway.example.com/v1",
187 Model: "reasoning-model",
188 APIKey: "k",
189 Extra: map[string]any{
190 "effort": "max",
191 "supported_efforts": []string{"", " auto ", " "},
192 },
193 })
194 if err == nil {
195 t.Fatal("an empty declaration must not enable or clamp max")
196 }
197 }
198
199 func TestEffortInvalidRejected(t *testing.T) {
200 _, err := New(provider.Config{
201 Name: "p", BaseURL: "https://api.xiaomimimo.com/v1", Model: "m", APIKey: "k",
202 Extra: map[string]any{"effort": "turbo"},
203 })
204 if err == nil || !strings.Contains(err.Error(), "UNSUPPORTED_REASONING_EFFORT") {
205 t.Fatalf("expected a low/medium/high validation error, got: %v", err)
206 }
207 }
208
209 func TestReasoningProtocolOverridesEndpointHeuristic(t *testing.T) {
210 p, err := New(provider.Config{
211 Name: "deepseek-proxy",
212 BaseURL: "https://proxy.example.com/v1",
213 Model: "deepseek-v4-flash",
214 APIKey: "k",
215 Extra: map[string]any{"reasoning_protocol": "deepseek"},
216 })
217 if err != nil {
218 t.Fatalf("New deepseek protocol: %v", err)
219 }
220 c := p.(*client)
221 if !c.deepseek || c.effort != "high" {
222 t.Fatalf("deepseek=%v effort=%q, want true/high", c.deepseek, c.effort)
223 }
224
225 p, err = New(provider.Config{
226 Name: "deepseek-direct",
227 BaseURL: "https://api.deepseek.com/v1",
228 Model: "deepseek-v4-flash",
229 APIKey: "k",
230 Extra: map[string]any{
231 "reasoning_protocol": "none",
232 "effort": "max",
233 "supported_efforts": []string{"low"},
234 },
235 })
236 if err != nil {
237 t.Fatalf("New none protocol: %v", err)
238 }
239 c = p.(*client)
240 if c.deepseek || c.effort != "" {
241 t.Fatalf("deepseek=%v effort=%q, want false/empty", c.deepseek, c.effort)
242 }
243 }
244
245 func TestLongCatThinkingUsesThinkingField(t *testing.T) {
246 p, err := New(provider.Config{
247 Name: "longcat",
248 BaseURL: "https://api.longcat.chat/openai/v1",
249 Model: "LongCat-2.0",
250 APIKey: "k",
251 Extra: map[string]any{"effort": "disabled", "thinking": "enabled"},
252 })
253 if err != nil {
254 t.Fatalf("New longcat: %v", err)
255 }
256 c := p.(*client)
257 if !c.longcat || c.effort != "disabled" {
258 t.Fatalf("longcat=%v effort=%q, want true/disabled", c.longcat, c.effort)
259 }
260 req := c.buildRequest(provider.Request{
261 Messages: []provider.Message{{Role: provider.RoleUser, Content: "hi"}},
262 })
263 b, err := json.Marshal(req)
264 if err != nil {
265 t.Fatalf("marshal request: %v", err)
266 }
267 var body map[string]any
268 if err := json.Unmarshal(b, &body); err != nil {
269 t.Fatalf("decode request: %v", err)
270 }
271 thinking, _ := body["thinking"].(map[string]any)
272 if thinking["type"] != "disabled" {
273 t.Fatalf("thinking = %#v, want disabled", body["thinking"])
274 }
275 if _, ok := body["reasoning_effort"]; ok {
276 t.Fatalf("LongCat request must omit reasoning_effort: %s", b)
277 }
278 }
279
279 lines GO