1+ import Testing
2+ import Foundation
3+ @testable import OpenFoundationModelsOpenAI
4+ import OpenFoundationModels
5+
6+ /// Tests specifically for DeepSeek API compatibility
7+ @Suite ( " DeepSeek Compatibility Tests " )
8+ struct DeepSeekCompatibilityTests {
9+
10+ // MARK: - Model Identification Tests
11+
12+ @Test ( " DeepSeek model identification works correctly " )
13+ func testDeepSeekModelIdentification( ) {
14+ // Test various DeepSeek model names
15+ let deepSeekModels = [ " deepseek-chat " , " deepseek-coder " , " deepseek-v3 " , " deepseek-r1 " ]
16+
17+ for modelName in deepSeekModels {
18+ let model = OpenAIModel ( modelName)
19+ #expect( model. modelType == . deepseek, " Model \( modelName) should be identified as DeepSeek " )
20+ #expect( model. apiName == modelName, " API name should match model name " )
21+ }
22+
23+ // Test that non-DeepSeek models are not identified as DeepSeek
24+ let nonDeepSeekModels = [ " gpt-4o " , " gpt-4-turbo " , " o1 " , " claude-3 " ]
25+
26+ for modelName in nonDeepSeekModels {
27+ let model = OpenAIModel ( modelName)
28+ #expect( model. modelType != . deepseek, " Model \( modelName) should not be identified as DeepSeek " )
29+ }
30+ }
31+
32+ @Test ( " DeepSeek models have correct capabilities " )
33+ func testDeepSeekModelCapabilities( ) {
34+ let deepSeekModel = OpenAIModel ( " deepseek-chat " )
35+
36+ // DeepSeek should support text generation, function calling, and streaming
37+ #expect( deepSeekModel. supportsVision == false , " DeepSeek should not support vision " )
38+ #expect( deepSeekModel. supportsFunctionCalling == true , " DeepSeek should support function calling " )
39+ #expect( deepSeekModel. supportsStreaming == true , " DeepSeek should support streaming " )
40+ #expect( deepSeekModel. isReasoningModel == false , " DeepSeek should not be a reasoning model " )
41+
42+ // Check capabilities set
43+ #expect( deepSeekModel. capabilities. contains ( . textGeneration) , " DeepSeek should support text generation " )
44+ #expect( deepSeekModel. capabilities. contains ( . functionCalling) , " DeepSeek should support function calling " )
45+ #expect( deepSeekModel. capabilities. contains ( . streaming) , " DeepSeek should support streaming " )
46+ #expect( deepSeekModel. capabilities. contains ( . toolAccess) , " DeepSeek should support tool access " )
47+ #expect( !deepSeekModel. capabilities. contains ( . vision) , " DeepSeek should not support vision " )
48+ #expect( !deepSeekModel. capabilities. contains ( . reasoning) , " DeepSeek should not support reasoning " )
49+ }
50+
51+ @Test ( " DeepSeek models have correct context window and tokens " )
52+ func testDeepSeekModelLimits( ) {
53+ let deepSeekModel = OpenAIModel ( " deepseek-chat " )
54+
55+ #expect( deepSeekModel. contextWindow == 32768 , " DeepSeek should have 32K context window " )
56+ #expect( deepSeekModel. maxOutputTokens == 8192 , " DeepSeek should have 8K max output tokens " )
57+ }
58+
59+ // MARK: - Response Format Compatibility Tests
60+
61+ @Test ( " DeepSeek models use JSON response format instead of JSON Schema " )
62+ func testDeepSeekResponseFormatCompatibility( ) throws {
63+ // Create a simple schema for testing
64+ let schema = GenerationSchema (
65+ type: String . self,
66+ description: " Test response " ,
67+ properties: [ ]
68+ )
69+
70+ // Test with GPT model (should use json_schema)
71+ let gptModel = OpenAIModel ( " gpt-4o " )
72+ let gptResponseFormat = convertSchemaToResponseFormat ( schema, for: gptModel)
73+
74+ // Test with DeepSeek model (should use json)
75+ let deepSeekModel = OpenAIModel ( " deepseek-chat " )
76+ let deepSeekResponseFormat = convertSchemaToResponseFormat ( schema, for: deepSeekModel)
77+
78+ // GPT should use jsonSchema format
79+ switch gptResponseFormat {
80+ case . jsonSchema:
81+ // Expected for GPT models
82+ break
83+ default :
84+ #expect( Bool ( false ) , " GPT model should use jsonSchema format " )
85+ }
86+
87+ // DeepSeek should use json format
88+ switch deepSeekResponseFormat {
89+ case . json:
90+ // Expected for DeepSeek models
91+ break
92+ case . jsonSchema:
93+ #expect( Bool ( false ) , " DeepSeek model should not use jsonSchema format " )
94+ case . text:
95+ #expect( Bool ( false ) , " DeepSeek model should not use text format " )
96+ }
97+ }
98+
99+ @Test ( " TranscriptConverter respects model type for response format extraction " )
100+ func testTranscriptConverterDeepSeekResponseFormat( ) throws {
101+ // Create a transcript with a prompt that has a response format
102+ let schema = GenerationSchema (
103+ type: String . self,
104+ description: " Response schema " ,
105+ properties: [ ]
106+ )
107+
108+ let responseFormat = Transcript . ResponseFormat ( schema: schema)
109+
110+ let transcript = Transcript (
111+ entries: [
112+ . prompt(
113+ Transcript . Prompt (
114+ segments: [ . text( Transcript . TextSegment ( content: " Generate a response " ) ) ] ,
115+ responseFormat: responseFormat
116+ )
117+ )
118+ ]
119+ )
120+
121+ // Test with GPT model
122+ let gptModel = OpenAIModel ( " gpt-4o " )
123+ let gptExtractedFormat = TranscriptConverter . extractResponseFormatWithSchema ( from: transcript, for: gptModel)
124+
125+ // Test with DeepSeek model
126+ let deepSeekModel = OpenAIModel ( " deepseek-chat " )
127+ let deepSeekExtractedFormat = TranscriptConverter . extractResponseFormatWithSchema ( from: transcript, for: deepSeekModel)
128+
129+ // GPT should extract jsonSchema format
130+ switch gptExtractedFormat {
131+ case . jsonSchema:
132+ // Expected
133+ break
134+ default :
135+ #expect( Bool ( false ) , " GPT model should extract jsonSchema format " )
136+ }
137+
138+ // DeepSeek should extract json format
139+ switch deepSeekExtractedFormat {
140+ case . json:
141+ // Expected
142+ break
143+ case . jsonSchema:
144+ #expect( Bool ( false ) , " DeepSeek model should extract json format, not jsonSchema " )
145+ default :
146+ #expect( Bool ( false ) , " DeepSeek model should extract json format " )
147+ }
148+ }
149+
150+ // MARK: - Request Builder Compatibility Tests
151+
152+ @Test ( " DeepSeek models use correct request builder " )
153+ func testDeepSeekRequestBuilder( ) {
154+ let deepSeekModel = OpenAIModel ( " deepseek-chat " )
155+
156+ // DeepSeek should use GPTRequestBuilder, not ReasoningRequestBuilder
157+ switch deepSeekModel. modelType {
158+ case . deepseek:
159+ // This should work without throwing
160+ let builder = GPTRequestBuilder ( )
161+ #expect( type ( of: builder) == GPTRequestBuilder . self)
162+ default :
163+ #expect( Bool ( false ) , " DeepSeek model should have deepseek type " )
164+ }
165+ }
166+
167+ @Test ( " DeepSeek models use correct response handler " )
168+ func testDeepSeekResponseHandler( ) {
169+ let deepSeekModel = OpenAIModel ( " deepseek-chat " )
170+
171+ // DeepSeek should use GPTResponseHandler, not ReasoningResponseHandler
172+ switch deepSeekModel. modelType {
173+ case . deepseek:
174+ // This should work without throwing
175+ let handler = GPTResponseHandler ( )
176+ #expect( type ( of: handler) == GPTResponseHandler . self)
177+ default :
178+ #expect( Bool ( false ) , " DeepSeek model should have deepseek type " )
179+ }
180+ }
181+ }
182+
183+ // MARK: - Helper Functions for Testing
184+
185+ /// Convert GenerationSchema to ResponseFormat for a specific model (test helper)
186+ private func convertSchemaToResponseFormat( _ schema: GenerationSchema , for model: OpenAIModel ) -> ResponseFormat {
187+ // This replicates the logic from OpenAILanguageModel.convertSchemaToResponseFormat
188+ // For models that don't support json_schema (like DeepSeek), fallback to json mode
189+ if model. modelType == . deepseek {
190+ return . json
191+ }
192+
193+ // For other models, try to create jsonSchema format
194+ do {
195+ let encoder = JSONEncoder ( )
196+ let schemaData = try encoder. encode ( schema)
197+
198+ // Convert to JSON dictionary
199+ if let schemaJson = try JSONSerialization . jsonObject ( with: schemaData) as? [ String : Any ] {
200+ // Transform to OpenAI's expected JSON Schema format
201+ let transformedSchema = transformToOpenAIJSONSchema ( schemaJson)
202+ return . jsonSchema( transformedSchema)
203+ }
204+ } catch {
205+ // Ignore encoding errors in test
206+ }
207+
208+ // Fallback to JSON mode
209+ return . json
210+ }
211+
212+ /// Transform GenerationSchema JSON to OpenAI's JSON Schema format (test helper)
213+ private func transformToOpenAIJSONSchema( _ json: [ String : Any ] ) -> [ String : Any ] {
214+ var schema : [ String : Any ] = [ : ]
215+
216+ // Extract type (default to "object")
217+ schema [ " type " ] = json [ " type " ] as? String ?? " object "
218+
219+ // Extract and transform properties
220+ if let properties = json [ " properties " ] as? [ String : [ String : Any ] ] {
221+ var transformedProperties : [ String : [ String : Any ] ] = [ : ]
222+
223+ for (key, propJson) in properties {
224+ var prop : [ String : Any ] = [ : ]
225+ prop [ " type " ] = propJson [ " type " ] as? String ?? " string "
226+
227+ if let description = propJson [ " description " ] as? String {
228+ prop [ " description " ] = description
229+ }
230+
231+ if let enumValues = propJson [ " enum " ] as? [ String ] {
232+ prop [ " enum " ] = enumValues
233+ }
234+
235+ if prop [ " type " ] as? String == " array " ,
236+ let items = propJson [ " items " ] as? [ String : Any ] {
237+ prop [ " items " ] = items
238+ }
239+
240+ transformedProperties [ key] = prop
241+ }
242+
243+ schema [ " properties " ] = transformedProperties
244+ }
245+
246+ // Extract required fields
247+ if let required = json [ " required " ] as? [ String ] {
248+ schema [ " required " ] = required
249+ }
250+
251+ // Add description if present
252+ if let description = json [ " description " ] as? String {
253+ schema [ " description " ] = description
254+ }
255+
256+ return schema
257+ }
0 commit comments