返回 DeepSeek-Reasonix
deepseek_vision_test.go
根目录 / internal / config / deepseek_vision_test.go
1 package config
2
3 import (
4 "testing"
5
6 "reasonix/internal/provider/openai"
7 )
8
9 func TestEffectiveVisionEnablesPinnedOfficialDeepSeekVisionSKU(t *testing.T) {
10 sku := &ProviderEntry{
11 Name: "deepseek",
12 Kind: "openai",
13 BaseURL: "https://api.deepseek.com",
14 Model: openai.OfficialDeepSeekVisionModel,
15 }
16 if !CanConfigureVision(sku) {
17 t.Fatal("official DeepSeek Settings must expose per-model image-input checkboxes")
18 }
19 if !EffectiveVision(sku) || !ExplicitModelVision(sku) {
20 t.Fatal("selecting the pinned official DeepSeek vision SKU must enable image input")
21 }
22 }
23
24 func TestEffectiveVisionHonorsOfficialDeepSeekVisionModels(t *testing.T) {
25 sku := &ProviderEntry{
26 Name: "deepseek",
27 Kind: "openai",
28 BaseURL: "https://api.deepseek.com",
29 Model: openai.OfficialDeepSeekVisionModel,
30 VisionModels: []string{openai.OfficialDeepSeekVisionModel},
31 }
32 if !EffectiveVision(sku) || !ExplicitModelVision(sku) {
33 t.Fatal("checking image input on the official vision SKU must enable image input")
34 }
35
36 sku.VisionModels = []string{}
37 if EffectiveVision(sku) || ExplicitModelVision(sku) {
38 t.Fatal("unchecking image input must disable image input on the official vision SKU")
39 }
40
41 pro := &ProviderEntry{
42 Name: "deepseek",
43 Kind: "openai",
44 BaseURL: "https://api.deepseek.com",
45 Model: "deepseek-v4-pro",
46 VisionModels: []string{"deepseek-v4-pro", openai.OfficialDeepSeekVisionModel},
47 }
48 if EffectiveVision(pro) || ExplicitModelVision(pro) {
49 t.Fatal("checking image input on a text-only model must not enable official DeepSeek image payloads")
50 }
51
52 // V4.1 Flash is natively multimodal, so a curated list that predates it must
53 // not veto it — while an explicitly emptied list still turns images off.
54 flash := &ProviderEntry{
55 Name: "deepseek",
56 Kind: "openai",
57 BaseURL: "https://api.deepseek.com",
58 Model: "deepseek-flash",
59 VisionModels: []string{openai.OfficialDeepSeekVisionModel},
60 }
61 if !EffectiveVision(flash) {
62 t.Fatal("a stale curated vision list vetoed a natively multimodal model")
63 }
64 flash.VisionModels = []string{}
65 if EffectiveVision(flash) {
66 t.Fatal("an explicitly emptied vision list must disable image input")
67 }
68 }
69
70 func TestNormalizeOfficialDeepSeekModelsBackfillsVisionSKUOnStockCatalog(t *testing.T) {
71 c := &Config{Providers: []ProviderEntry{{
72 Name: "deepseek",
73 Kind: "anthropic",
74 BaseURL: "https://api.deepseek.com/anthropic",
75 Models: []string{"deepseek-v4-flash", "deepseek-v4-pro"},
76 Default: "deepseek-v4-flash",
77 APIKeyEnv: "DEEPSEEK_API_KEY",
78 Prices: DeepSeekV4PricesForCurrency("USD"),
79 }}}
80 normalizeOfficialDeepSeekModels(c)
81 p, ok := c.Provider("deepseek")
82 if !ok {
83 t.Fatal("deepseek provider missing")
84 }
85 if !p.HasModel(openai.OfficialDeepSeekVisionModel) {
86 t.Fatalf("stock official catalog = %v, want pinned vision SKU", p.ModelList())
87 }
88 if p.Default != "deepseek-v4-flash" {
89 t.Fatalf("default = %q, want flash after vision backfill", p.Default)
90 }
91 if !p.HasVisionModel(openai.OfficialDeepSeekVisionModel) {
92 t.Fatalf("vision_models = %v, want pinned vision SKU", p.VisionModels)
93 }
94 flash := p.Prices["deepseek-v4-flash"]
95 got := p.Prices[openai.OfficialDeepSeekVisionModel]
96 if flash == nil || got == nil || got.CacheHit != flash.CacheHit || got.Input != flash.Input || got.Output != flash.Output || got.Currency != flash.Currency {
97 t.Fatalf("vision SKU price = %+v, want Flash table %+v", got, flash)
98 }
99 }
100
101 func TestNormalizeOfficialDeepSeekModelsBackfillsVisionOnFlashProvider(t *testing.T) {
102 c := &Config{Providers: []ProviderEntry{{
103 Name: "deepseek-flash",
104 Kind: "openai",
105 BaseURL: "https://api.deepseek.com",
106 Models: []string{"deepseek-v4-flash"},
107 Default: "deepseek-v4-flash",
108 APIKeyEnv: "DEEPSEEK_API_KEY",
109 }}}
110 normalizeOfficialDeepSeekModels(c)
111 p, ok := c.Provider("deepseek-flash")
112 if !ok {
113 t.Fatal("deepseek-flash provider missing")
114 }
115 if !p.HasModel("deepseek-v4-flash") || !p.HasModel(openai.OfficialDeepSeekVisionModel) || p.HasModel("deepseek-v4-pro") {
116 t.Fatalf("deepseek-flash models = %v, want flash + vision SKU", p.ModelList())
117 }
118 }
119
120 func TestNormalizeOfficialDeepSeekModelsSkipsVisionOnProProvider(t *testing.T) {
121 c := &Config{Providers: []ProviderEntry{{
122 Name: "deepseek-pro",
123 Kind: "openai",
124 BaseURL: "https://api.deepseek.com",
125 Models: []string{"deepseek-v4-pro"},
126 Default: "deepseek-v4-pro",
127 APIKeyEnv: "DEEPSEEK_API_KEY",
128 }}}
129 normalizeOfficialDeepSeekModels(c)
130 p, ok := c.Provider("deepseek-pro")
131 if !ok {
132 t.Fatal("deepseek-pro provider missing")
133 }
134 if p.HasModel(openai.OfficialDeepSeekVisionModel) {
135 t.Fatalf("deepseek-pro models = %v, want pro only", p.ModelList())
136 }
137 }
138
139 func TestNormalizeOfficialDeepSeekModelsPreservesExplicitEmptyVisionModels(t *testing.T) {
140 c := &Config{Providers: []ProviderEntry{{
141 Name: "deepseek",
142 Kind: "openai",
143 BaseURL: "https://api.deepseek.com",
144 Models: []string{"deepseek-v4-flash", "deepseek-v4-pro"},
145 Default: "deepseek-v4-flash",
146 VisionModels: []string{},
147 APIKeyEnv: "DEEPSEEK_API_KEY",
148 }}}
149 normalizeOfficialDeepSeekModels(c)
150 p, ok := c.Provider("deepseek")
151 if !ok {
152 t.Fatal("deepseek provider missing")
153 }
154 if p.HasModel(openai.OfficialDeepSeekVisionModel) {
155 t.Fatal("explicit empty vision_models must not re-add the vision SKU to a Flash/Pro catalog")
156 }
157 if p.VisionModels == nil || len(p.VisionModels) != 0 {
158 t.Fatalf("vision_models = %#v, want preserved explicit empty list", p.VisionModels)
159 }
160 }
161
162 func TestDeepSeekV4PricesIncludeVisionSKU(t *testing.T) {
163 for _, currency := range []string{"CNY", "USD"} {
164 prices := DeepSeekV4PricesForCurrency(currency)
165 flash := prices["deepseek-v4-flash"]
166 got := prices[openai.OfficialDeepSeekVisionModel]
167 if flash == nil || got == nil || got.CacheHit != flash.CacheHit || got.Input != flash.Input || got.Output != flash.Output || got.Currency != flash.Currency {
168 t.Fatalf("%s vision SKU price = %+v, want Flash table %+v", currency, got, flash)
169 }
170 }
171 }
172
173 func TestDeepSeekOfficialPresetsRouteVisionToMultimodalSKUs(t *testing.T) {
174 for _, id := range []string{"deepseek-anthropic", "deepseek-responses"} {
175 preset, ok := CuratedProviderPreset(id)
176 if !ok || len(preset.Entries) != 1 {
177 t.Fatalf("%s preset = %+v found=%v", id, preset, ok)
178 }
179 entry := preset.Entries[0]
180 if !entry.HasModel(openai.OfficialDeepSeekVisionModel) || entry.Default != "deepseek-v4-flash" || entry.Vision {
181 t.Fatalf("%s entry = %+v", id, entry)
182 }
183 var cfg Config
184 if err := cfg.UpsertProvider(entry); err != nil {
185 t.Fatalf("UpsertProvider(%s): %v", id, err)
186 }
187 flash, _ := cfg.ResolveModel(entry.Name + "/deepseek-v4-flash")
188 pro, _ := cfg.ResolveModel(entry.Name + "/deepseek-v4-pro")
189 vision, ok := cfg.ResolveModel(entry.Name + "/" + openai.OfficialDeepSeekVisionModel)
190 if flash == nil || pro == nil || !ok {
191 t.Fatalf("%s models did not resolve", id)
192 }
193 if !EffectiveVision(flash) || EffectiveVision(pro) || !EffectiveVision(vision) {
194 t.Fatalf("%s vision routing = flash:%t pro:%t vision:%t", id, EffectiveVision(flash), EffectiveVision(pro), EffectiveVision(vision))
195 }
196 }
197 }
198
198 lines GO