返回 DeepSeek-Reasonix
output_budget_test.go
根目录 / internal / provider / openai / output_budget_test.go
1 package openai
2
3 import (
4 "testing"
5
6 "reasonix/internal/provider"
7 )
8
9 func TestSharedWindowOutputBudgetCapability(t *testing.T) {
10 deepseek := &client{deepseek: true, maxOutputTokens: 32 * 1024}
11 if !deepseek.SharesContextWindow() || deepseek.OutputBudget() != 32*1024 {
12 t.Fatalf("DeepSeek capability = shared:%v budget:%d", deepseek.SharesContextWindow(), deepseek.OutputBudget())
13 }
14 openai := &client{maxOutputTokens: 32 * 1024}
15 if openai.SharesContextWindow() {
16 t.Fatal("ordinary OpenAI mode must keep its independent output ceiling")
17 }
18 }
19
20 func TestOfficialDeepSeekPolicyOmitsWhenSafe(t *testing.T) {
21 p, err := New(provider.Config{Name: "ds", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-pro"})
22 if err != nil {
23 t.Fatal(err)
24 }
25 got := p.(provider.ContextBudgetPolicyProvider).ContextBudgetPolicy()
26 if got.WindowMode != provider.ContextWindowShared || got.AutoOutputTokens != provider.DeepSeekMaxOutputTokens || got.LimitMode != provider.OutputLimitOmitWhenSafe {
27 t.Fatalf("official DeepSeek policy = %+v", got)
28 }
29 req := p.(*client).buildRequest(provider.Request{})
30 if req.MaxTokens != 0 {
31 t.Fatalf("safe official DeepSeek should omit max_tokens, got %d", req.MaxTokens)
32 }
33 clipped := p.(*client).buildRequest(provider.Request{MaxTokens: 229_502})
34 if clipped.MaxTokens != 229_502 {
35 t.Fatalf("clipped official DeepSeek max_tokens = %d", clipped.MaxTokens)
36 }
37 }
38
39 func TestOpenCodeGoChatPolicySendsGenericMaxTokens(t *testing.T) {
40 p, err := New(provider.Config{Name: "og", BaseURL: "https://opencode.ai/zen/go/v1", Model: "kimi-k3"})
41 if err != nil {
42 t.Fatal(err)
43 }
44 got := p.(provider.ContextBudgetPolicyProvider).ContextBudgetPolicy()
45 if got.WindowMode != provider.ContextWindowShared || got.MaxOutputTokens != 131_072 || got.LimitMode != provider.OutputLimitAlways {
46 t.Fatalf("OpenCode Go kimi-k3 policy = %+v", got)
47 }
48 req := p.(*client).buildRequest(provider.Request{MaxTokens: 131_072})
49 if req.MaxTokens != 131_072 || req.MaxCompletionTokens != 0 {
50 t.Fatalf("OpenCode Go Kimi must keep generic max_tokens: %+v", req)
51 }
52 }
53
54 func TestOfficialKimiK3KeepsMaxCompletionTokens(t *testing.T) {
55 p, err := New(provider.Config{
56 Name: "kimi", BaseURL: "https://api.moonshot.cn/v1", Model: "kimi-k3",
57 Extra: map[string]any{"reasoning_protocol": "kimi-k3", "max_output_tokens": 131_072},
58 })
59 if err != nil {
60 t.Fatal(err)
61 }
62 got := p.(provider.ContextBudgetPolicyProvider).ContextBudgetPolicy()
63 if got.WindowMode != provider.ContextWindowShared || got.AutoOutputTokens != 131_072 {
64 t.Fatalf("official Kimi K3 policy = %+v", got)
65 }
66 req := p.(*client).buildRequest(provider.Request{MaxTokens: 131_072})
67 if req.MaxTokens != 0 || req.MaxCompletionTokens != 131_072 {
68 t.Fatalf("official Kimi K3 wire = max_tokens %d max_completion_tokens %d", req.MaxTokens, req.MaxCompletionTokens)
69 }
70 }
71
72 func TestSharedWindowInputPolicyCountsReplayedReasoning(t *testing.T) {
73 if !(&client{deepseek: true}).SharedWindowInputPolicy().ReplaysOrdinaryReasoning {
74 t.Fatal("DeepSeek replays reasoning_content on every assistant turn that carries it; admission must count it")
75 }
76 if (&client{}).SharedWindowInputPolicy().ReplaysOrdinaryReasoning {
77 t.Fatal("ordinary OpenAI mode strips history reasoning and must not count it")
78 }
79 }
80
80 lines GO