@ai-sdk/deepseek 2.0.7 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,524 @@
1
+ import { LanguageModelV3Prompt } from '@ai-sdk/provider';
2
+ import { convertReadableStreamToArray } from '@ai-sdk/provider-utils/test';
3
+ import { createTestServer } from '@ai-sdk/test-server/with-vitest';
4
+ import fs from 'node:fs';
5
+ import { beforeEach, describe, expect, it } from 'vitest';
6
+ import { createDeepSeek } from '../deepseek-provider';
7
+ import { DeepSeekChatOptions } from './deepseek-chat-options';
8
+
9
+ const TEST_PROMPT: LanguageModelV3Prompt = [
10
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
11
+ ];
12
+
13
+ const provider = createDeepSeek({
14
+ apiKey: 'test-api-key',
15
+ });
16
+
17
+ const server = createTestServer({
18
+ 'https://api.deepseek.com/chat/completions': {},
19
+ });
20
+
21
+ describe('DeepSeekChatLanguageModel', () => {
22
+ describe('doGenerate', () => {
23
+ function prepareJsonFixtureResponse(filename: string) {
24
+ server.urls['https://api.deepseek.com/chat/completions'].response = {
25
+ type: 'json-value',
26
+ body: JSON.parse(
27
+ fs.readFileSync(`src/chat/__fixtures__/${filename}.json`, 'utf8'),
28
+ ),
29
+ };
30
+ return;
31
+ }
32
+
33
+ describe('text', () => {
34
+ beforeEach(() => {
35
+ prepareJsonFixtureResponse('deepseek-text');
36
+ });
37
+
38
+ it('should send correct request body', async () => {
39
+ await provider.chat('deepseek-chat').doGenerate({
40
+ prompt: [
41
+ { role: 'system', content: 'You are a helpful assistant.' },
42
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
43
+ ],
44
+ temperature: 0.5,
45
+ topP: 0.3,
46
+ });
47
+
48
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
49
+ {
50
+ "messages": [
51
+ {
52
+ "content": "You are a helpful assistant.",
53
+ "role": "system",
54
+ },
55
+ {
56
+ "content": "Hello",
57
+ "role": "user",
58
+ },
59
+ ],
60
+ "model": "deepseek-chat",
61
+ "temperature": 0.5,
62
+ "top_p": 0.3,
63
+ }
64
+ `);
65
+ });
66
+
67
+ it('should extract text content', async () => {
68
+ const result = await provider.chat('deepseek-chat').doGenerate({
69
+ prompt: TEST_PROMPT,
70
+ });
71
+
72
+ expect(result).toMatchSnapshot();
73
+ });
74
+ });
75
+
76
+ describe('reasoning', () => {
77
+ beforeEach(() => {
78
+ prepareJsonFixtureResponse('deepseek-reasoning');
79
+ });
80
+
81
+ it('should send correct request body', async () => {
82
+ await provider.chat('deepseek-reasoner').doGenerate({
83
+ prompt: [
84
+ {
85
+ role: 'user',
86
+ content: [
87
+ {
88
+ type: 'text',
89
+ text: 'How many "r"s are in the word "strawberry"?',
90
+ },
91
+ ],
92
+ },
93
+ ],
94
+ providerOptions: {
95
+ deepseek: {
96
+ thinking: { type: 'enabled' },
97
+ } satisfies DeepSeekChatOptions,
98
+ },
99
+ });
100
+
101
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
102
+ {
103
+ "messages": [
104
+ {
105
+ "content": "How many "r"s are in the word "strawberry"?",
106
+ "role": "user",
107
+ },
108
+ ],
109
+ "model": "deepseek-reasoner",
110
+ "thinking": {
111
+ "type": "enabled",
112
+ },
113
+ }
114
+ `);
115
+ });
116
+
117
+ it('should extract text content', async () => {
118
+ const result = await provider.chat('deepseek-chat').doGenerate({
119
+ prompt: TEST_PROMPT,
120
+ });
121
+
122
+ expect(result).toMatchSnapshot();
123
+ });
124
+ });
125
+
126
+ describe('tool call', () => {
127
+ beforeEach(() => {
128
+ prepareJsonFixtureResponse('deepseek-tool-call');
129
+ });
130
+
131
+ it('should send correct request body', async () => {
132
+ await provider.chat('deepseek-reasoner').doGenerate({
133
+ prompt: TEST_PROMPT,
134
+ tools: [
135
+ {
136
+ type: 'function',
137
+ name: 'weather',
138
+ inputSchema: {
139
+ type: 'object',
140
+ properties: { location: { type: 'string' } },
141
+ required: ['location'],
142
+ additionalProperties: false,
143
+ $schema: 'http://json-schema.org/draft-07/schema#',
144
+ },
145
+ },
146
+ ],
147
+ providerOptions: {
148
+ deepseek: {
149
+ thinking: { type: 'enabled' },
150
+ } satisfies DeepSeekChatOptions,
151
+ },
152
+ });
153
+
154
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
155
+ {
156
+ "messages": [
157
+ {
158
+ "content": "Hello",
159
+ "role": "user",
160
+ },
161
+ ],
162
+ "model": "deepseek-reasoner",
163
+ "thinking": {
164
+ "type": "enabled",
165
+ },
166
+ "tools": [
167
+ {
168
+ "function": {
169
+ "name": "weather",
170
+ "parameters": {
171
+ "$schema": "http://json-schema.org/draft-07/schema#",
172
+ "additionalProperties": false,
173
+ "properties": {
174
+ "location": {
175
+ "type": "string",
176
+ },
177
+ },
178
+ "required": [
179
+ "location",
180
+ ],
181
+ "type": "object",
182
+ },
183
+ },
184
+ "type": "function",
185
+ },
186
+ ],
187
+ }
188
+ `);
189
+ });
190
+
191
+ describe('json response format', () => {
192
+ beforeEach(() => {
193
+ prepareJsonFixtureResponse('deepseek-json');
194
+ });
195
+
196
+ it('should send correct request body without schema', async () => {
197
+ await provider.chat('deepseek-reasoner').doGenerate({
198
+ prompt: TEST_PROMPT,
199
+ responseFormat: { type: 'json' },
200
+ tools: [
201
+ {
202
+ type: 'function',
203
+ name: 'weather',
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: { location: { type: 'string' } },
207
+ required: ['location'],
208
+ additionalProperties: false,
209
+ $schema: 'http://json-schema.org/draft-07/schema#',
210
+ },
211
+ },
212
+ ],
213
+ providerOptions: {
214
+ deepseek: {
215
+ thinking: { type: 'enabled' },
216
+ } satisfies DeepSeekChatOptions,
217
+ },
218
+ });
219
+
220
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
221
+ {
222
+ "messages": [
223
+ {
224
+ "content": "Return JSON.",
225
+ "role": "system",
226
+ },
227
+ {
228
+ "content": "Hello",
229
+ "role": "user",
230
+ },
231
+ ],
232
+ "model": "deepseek-reasoner",
233
+ "response_format": {
234
+ "type": "json_object",
235
+ },
236
+ "thinking": {
237
+ "type": "enabled",
238
+ },
239
+ "tools": [
240
+ {
241
+ "function": {
242
+ "name": "weather",
243
+ "parameters": {
244
+ "$schema": "http://json-schema.org/draft-07/schema#",
245
+ "additionalProperties": false,
246
+ "properties": {
247
+ "location": {
248
+ "type": "string",
249
+ },
250
+ },
251
+ "required": [
252
+ "location",
253
+ ],
254
+ "type": "object",
255
+ },
256
+ },
257
+ "type": "function",
258
+ },
259
+ ],
260
+ }
261
+ `);
262
+ });
263
+
264
+ it('should send correct request body with schema', async () => {
265
+ await provider.chat('deepseek-reasoner').doGenerate({
266
+ prompt: TEST_PROMPT,
267
+ responseFormat: {
268
+ type: 'json',
269
+ schema: {
270
+ type: 'object',
271
+ properties: {
272
+ elements: {
273
+ type: 'array',
274
+ items: {
275
+ type: 'object',
276
+ properties: {
277
+ location: { type: 'string' },
278
+ temperature: { type: 'number' },
279
+ condition: { type: 'string' },
280
+ },
281
+ required: ['location', 'temperature', 'condition'],
282
+ additionalProperties: false,
283
+ },
284
+ },
285
+ },
286
+ required: ['elements'],
287
+ additionalProperties: false,
288
+ $schema: 'http://json-schema.org/draft-07/schema#',
289
+ },
290
+ },
291
+ tools: [
292
+ {
293
+ type: 'function',
294
+ name: 'weather',
295
+ inputSchema: {
296
+ type: 'object',
297
+ properties: { location: { type: 'string' } },
298
+ required: ['location'],
299
+ additionalProperties: false,
300
+ $schema: 'http://json-schema.org/draft-07/schema#',
301
+ },
302
+ },
303
+ ],
304
+ providerOptions: {
305
+ deepseek: {
306
+ thinking: { type: 'enabled' },
307
+ } satisfies DeepSeekChatOptions,
308
+ },
309
+ });
310
+
311
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
312
+ {
313
+ "messages": [
314
+ {
315
+ "content": "Return JSON that conforms to the following schema: {"type":"object","properties":{"elements":{"type":"array","items":{"type":"object","properties":{"location":{"type":"string"},"temperature":{"type":"number"},"condition":{"type":"string"}},"required":["location","temperature","condition"],"additionalProperties":false}}},"required":["elements"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}",
316
+ "role": "system",
317
+ },
318
+ {
319
+ "content": "Hello",
320
+ "role": "user",
321
+ },
322
+ ],
323
+ "model": "deepseek-reasoner",
324
+ "response_format": {
325
+ "type": "json_object",
326
+ },
327
+ "thinking": {
328
+ "type": "enabled",
329
+ },
330
+ "tools": [
331
+ {
332
+ "function": {
333
+ "name": "weather",
334
+ "parameters": {
335
+ "$schema": "http://json-schema.org/draft-07/schema#",
336
+ "additionalProperties": false,
337
+ "properties": {
338
+ "location": {
339
+ "type": "string",
340
+ },
341
+ },
342
+ "required": [
343
+ "location",
344
+ ],
345
+ "type": "object",
346
+ },
347
+ },
348
+ "type": "function",
349
+ },
350
+ ],
351
+ }
352
+ `);
353
+ });
354
+
355
+ it('should extract text content', async () => {
356
+ const result = await provider.chat('deepseek-reasoner').doGenerate({
357
+ prompt: TEST_PROMPT,
358
+ responseFormat: { type: 'json' },
359
+ tools: [
360
+ {
361
+ type: 'function',
362
+ name: 'weather',
363
+ inputSchema: {
364
+ type: 'object',
365
+ properties: { location: { type: 'string' } },
366
+ required: ['location'],
367
+ additionalProperties: false,
368
+ $schema: 'http://json-schema.org/draft-07/schema#',
369
+ },
370
+ },
371
+ ],
372
+ providerOptions: {
373
+ deepseek: {
374
+ thinking: { type: 'enabled' },
375
+ } satisfies DeepSeekChatOptions,
376
+ },
377
+ });
378
+
379
+ expect(result).toMatchSnapshot();
380
+ });
381
+ });
382
+
383
+ it('should extract tool call content', async () => {
384
+ const result = await provider.chat('deepseek-reasoner').doGenerate({
385
+ prompt: TEST_PROMPT,
386
+ tools: [
387
+ {
388
+ type: 'function',
389
+ name: 'weather',
390
+ inputSchema: {
391
+ type: 'object',
392
+ properties: { location: { type: 'string' } },
393
+ required: ['location'],
394
+ additionalProperties: false,
395
+ $schema: 'http://json-schema.org/draft-07/schema#',
396
+ },
397
+ },
398
+ ],
399
+ providerOptions: {
400
+ deepseek: {
401
+ thinking: { type: 'enabled' },
402
+ } satisfies DeepSeekChatOptions,
403
+ },
404
+ });
405
+
406
+ expect(result).toMatchSnapshot();
407
+ });
408
+ });
409
+ });
410
+
411
+ describe('doStream', () => {
412
+ function prepareChunksFixtureResponse(filename: string) {
413
+ const chunks = fs
414
+ .readFileSync(`src/chat/__fixtures__/${filename}.chunks.txt`, 'utf8')
415
+ .split('\n')
416
+ .map(line => `data: ${line}\n\n`);
417
+ chunks.push('data: [DONE]\n\n');
418
+
419
+ server.urls['https://api.deepseek.com/chat/completions'].response = {
420
+ type: 'stream-chunks',
421
+ chunks,
422
+ };
423
+ }
424
+
425
+ describe('text', () => {
426
+ beforeEach(() => {
427
+ prepareChunksFixtureResponse('deepseek-text');
428
+ });
429
+
430
+ it('should send model id, settings, and input', async () => {
431
+ await provider.chat('deepseek-chat').doStream({
432
+ prompt: [
433
+ { role: 'system', content: 'You are a helpful assistant.' },
434
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
435
+ ],
436
+ temperature: 0.5,
437
+ topP: 0.3,
438
+ });
439
+
440
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
441
+ {
442
+ "messages": [
443
+ {
444
+ "content": "You are a helpful assistant.",
445
+ "role": "system",
446
+ },
447
+ {
448
+ "content": "Hello",
449
+ "role": "user",
450
+ },
451
+ ],
452
+ "model": "deepseek-chat",
453
+ "stream": true,
454
+ "stream_options": {
455
+ "include_usage": true,
456
+ },
457
+ "temperature": 0.5,
458
+ "top_p": 0.3,
459
+ }
460
+ `);
461
+ });
462
+
463
+ it('should stream text', async () => {
464
+ const result = await provider.chat('deepseek-chat').doStream({
465
+ prompt: TEST_PROMPT,
466
+ });
467
+
468
+ expect(
469
+ await convertReadableStreamToArray(result.stream),
470
+ ).toMatchSnapshot();
471
+ });
472
+ });
473
+
474
+ describe('reasoning', () => {
475
+ beforeEach(() => {
476
+ prepareChunksFixtureResponse('deepseek-reasoning');
477
+ });
478
+
479
+ it('should stream reasoning', async () => {
480
+ const result = await provider.chat('deepseek-reasoning').doStream({
481
+ prompt: TEST_PROMPT,
482
+ });
483
+
484
+ expect(
485
+ await convertReadableStreamToArray(result.stream),
486
+ ).toMatchSnapshot();
487
+ });
488
+ });
489
+
490
+ describe('tool call', () => {
491
+ beforeEach(() => {
492
+ prepareChunksFixtureResponse('deepseek-tool-call');
493
+ });
494
+
495
+ it('should stream tool call', async () => {
496
+ const result = await provider.chat('deepseek-reasoner').doStream({
497
+ prompt: TEST_PROMPT,
498
+ tools: [
499
+ {
500
+ type: 'function',
501
+ name: 'weather',
502
+ inputSchema: {
503
+ type: 'object',
504
+ properties: { location: { type: 'string' } },
505
+ required: ['location'],
506
+ additionalProperties: false,
507
+ $schema: 'http://json-schema.org/draft-07/schema#',
508
+ },
509
+ },
510
+ ],
511
+ providerOptions: {
512
+ deepseek: {
513
+ thinking: { type: 'enabled' },
514
+ } satisfies DeepSeekChatOptions,
515
+ },
516
+ });
517
+
518
+ expect(
519
+ await convertReadableStreamToArray(result.stream),
520
+ ).toMatchSnapshot();
521
+ });
522
+ });
523
+ });
524
+ });