@ai-sdk/deepseek 2.0.58 → 2.0.60
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.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -1
- package/dist/index.d.mts +57 -19
- package/dist/index.d.ts +57 -19
- package/dist/index.js +465 -124
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +467 -121
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.d.mts +54 -4
- package/dist/internal/index.d.ts +54 -4
- package/dist/internal/index.js +463 -118
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +465 -117
- package/dist/internal/index.mjs.map +1 -1
- package/docs/30-deepseek.mdx +294 -23
- package/package.json +2 -2
- package/src/chat/convert-to-deepseek-chat-messages.ts +169 -22
- package/src/chat/convert-to-deepseek-usage.ts +1 -1
- package/src/chat/deepseek-chat-api-types.ts +47 -2
- package/src/chat/deepseek-chat-language-model.ts +192 -12
- package/src/chat/deepseek-chat-options.ts +102 -11
- package/src/chat/deepseek-file-part-options.ts +24 -0
- package/src/chat/deepseek-prepare-tools.ts +29 -3
- package/src/deepseek-provider.ts +4 -3
- package/src/index.ts +3 -0
package/docs/30-deepseek.mdx
CHANGED
|
@@ -75,7 +75,7 @@ import { deepseek } from '@ai-sdk/deepseek';
|
|
|
75
75
|
import { generateText } from 'ai';
|
|
76
76
|
|
|
77
77
|
const { text } = await generateText({
|
|
78
|
-
model: deepseek('deepseek-
|
|
78
|
+
model: deepseek('deepseek-v4-flash'),
|
|
79
79
|
prompt: 'Write a vegetarian lasagna recipe for 4 people.',
|
|
80
80
|
});
|
|
81
81
|
```
|
|
@@ -83,39 +83,73 @@ const { text } = await generateText({
|
|
|
83
83
|
You can also use the `.chat()` or `.languageModel()` factory methods:
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
|
-
const model = deepseek.chat('deepseek-
|
|
86
|
+
const model = deepseek.chat('deepseek-v4-flash');
|
|
87
87
|
// or
|
|
88
|
-
const model = deepseek.languageModel('deepseek-
|
|
88
|
+
const model = deepseek.languageModel('deepseek-v4-flash');
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
DeepSeek language models can be used in the `streamText` function
|
|
92
92
|
(see [AI SDK Core](/docs/ai-sdk-core)).
|
|
93
93
|
|
|
94
|
+
DeepSeek retired the `deepseek-chat` and `deepseek-reasoner` aliases on July 24,
|
|
95
|
+
2026. Use `deepseek-v4-flash` or `deepseek-v4-pro` for the current API. Custom
|
|
96
|
+
and legacy model IDs remain accepted as strings for compatibility with custom
|
|
97
|
+
endpoints.
|
|
98
|
+
|
|
94
99
|
The following optional provider options are available for DeepSeek models:
|
|
95
100
|
|
|
101
|
+
- `logprobs` _boolean_
|
|
102
|
+
|
|
103
|
+
Optional. Returns log probabilities for generated content and reasoning
|
|
104
|
+
tokens in `providerMetadata.deepseek.logprobs`.
|
|
105
|
+
|
|
106
|
+
- `topLogprobs` _number_
|
|
107
|
+
|
|
108
|
+
Optional. Returns the specified number of most likely tokens at each token
|
|
109
|
+
position. Accepts values from `0` through `20` and automatically enables
|
|
110
|
+
`logprobs`.
|
|
111
|
+
|
|
112
|
+
- `userId` _string_
|
|
113
|
+
|
|
114
|
+
Optional. An opaque end-user identifier that DeepSeek uses for content-safety
|
|
115
|
+
tracing, KV-cache isolation, and scheduling isolation. The value must match
|
|
116
|
+
`^[a-zA-Z0-9_-]+$` and contain at most 512 characters. Do not include names,
|
|
117
|
+
email addresses, or other private user information.
|
|
118
|
+
|
|
96
119
|
- `thinking` _object_
|
|
97
120
|
|
|
98
|
-
Optional. Controls thinking mode (chain-of-thought reasoning)
|
|
99
|
-
- `type`: `'
|
|
121
|
+
Optional. Controls thinking mode (chain-of-thought reasoning) for DeepSeek V4 models.
|
|
122
|
+
- `type`: `'enabled' | 'disabled'` - Enable or disable thinking mode. See [DeepSeek's thinking mode docs](https://api-docs.deepseek.com/guides/thinking_mode).
|
|
123
|
+
|
|
124
|
+
- `reasoningEffort` _'low' | 'high' | 'max'_
|
|
125
|
+
|
|
126
|
+
Optional. Controls thinking strength for DeepSeek V4 reasoning models.
|
|
127
|
+
`medium` is sent as `high`, and `xhigh` is sent as `max`. A compatibility
|
|
128
|
+
warning is returned whenever the requested value is mapped.
|
|
100
129
|
|
|
101
|
-
|
|
130
|
+
For backwards compatibility, legacy provider options supplied at runtime are
|
|
131
|
+
also mapped to documented values: `thinking.type: 'adaptive'` becomes
|
|
132
|
+
`'enabled'`, `reasoningEffort: 'medium'` becomes `'high'`, and
|
|
133
|
+
`reasoningEffort: 'xhigh'` becomes `'max'`. Each mapping returns a compatibility
|
|
134
|
+
warning so callers can migrate to a canonical value.
|
|
102
135
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
136
|
+
DeepSeek has deprecated the top-level `frequencyPenalty` and `presencePenalty`
|
|
137
|
+
settings. The provider omits these settings and returns a deprecation warning
|
|
138
|
+
when they are used. `temperature` and `topP` have no effect while thinking is
|
|
139
|
+
enabled, including the default thinking mode for DeepSeek V4 models, so the
|
|
140
|
+
provider omits them with an unsupported warning. Explicitly set
|
|
141
|
+
`thinking.type` to `'disabled'` to use `temperature` and `topP`.
|
|
109
142
|
|
|
110
|
-
```ts highlight="7-
|
|
143
|
+
```ts highlight="7-13"
|
|
111
144
|
import { deepseek, type DeepSeekLanguageModelOptions } from '@ai-sdk/deepseek';
|
|
112
145
|
import { generateText } from 'ai';
|
|
113
146
|
|
|
114
147
|
const { text, reasoning } = await generateText({
|
|
115
|
-
model: deepseek('deepseek-
|
|
148
|
+
model: deepseek('deepseek-v4-flash'),
|
|
116
149
|
prompt: 'How many "r"s are in the word "strawberry"?',
|
|
117
150
|
providerOptions: {
|
|
118
151
|
deepseek: {
|
|
152
|
+
userId: 'tenant_123-user',
|
|
119
153
|
thinking: { type: 'enabled' },
|
|
120
154
|
reasoningEffort: 'high',
|
|
121
155
|
} satisfies DeepSeekLanguageModelOptions,
|
|
@@ -123,16 +157,106 @@ const { text, reasoning } = await generateText({
|
|
|
123
157
|
});
|
|
124
158
|
```
|
|
125
159
|
|
|
160
|
+
### Message Names
|
|
161
|
+
|
|
162
|
+
DeepSeek supports optional participant names on system, user, and assistant
|
|
163
|
+
messages. Set `providerOptions.deepseek.name` on each message that should
|
|
164
|
+
include a name:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import {
|
|
168
|
+
deepSeek,
|
|
169
|
+
type DeepSeekMessageProviderOptions,
|
|
170
|
+
} from '@ai-sdk/deepseek';
|
|
171
|
+
import { generateText } from 'ai';
|
|
172
|
+
|
|
173
|
+
const { text } = await generateText({
|
|
174
|
+
model: deepSeek('deepseek-chat'),
|
|
175
|
+
instructions: {
|
|
176
|
+
role: 'system',
|
|
177
|
+
content: 'Help the customer plan a short trip.',
|
|
178
|
+
providerOptions: {
|
|
179
|
+
deepseek: {
|
|
180
|
+
name: 'travel_planner',
|
|
181
|
+
} satisfies DeepSeekMessageProviderOptions,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
messages: [
|
|
185
|
+
{
|
|
186
|
+
role: 'user',
|
|
187
|
+
content: 'I want to visit Lisbon for a weekend.',
|
|
188
|
+
providerOptions: {
|
|
189
|
+
deepseek: {
|
|
190
|
+
name: 'customer',
|
|
191
|
+
} satisfies DeepSeekMessageProviderOptions,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
role: 'assistant',
|
|
196
|
+
content: 'What kinds of activities do you enjoy?',
|
|
197
|
+
providerOptions: {
|
|
198
|
+
deepseek: {
|
|
199
|
+
name: 'travel_planner',
|
|
200
|
+
} satisfies DeepSeekMessageProviderOptions,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
role: 'user',
|
|
205
|
+
content: 'Food, architecture, and walking.',
|
|
206
|
+
providerOptions: {
|
|
207
|
+
deepseek: {
|
|
208
|
+
name: 'customer',
|
|
209
|
+
} satisfies DeepSeekMessageProviderOptions,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The same message option works with `streamText`:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import {
|
|
220
|
+
deepSeek,
|
|
221
|
+
type DeepSeekMessageProviderOptions,
|
|
222
|
+
} from '@ai-sdk/deepseek';
|
|
223
|
+
import { streamText } from 'ai';
|
|
224
|
+
|
|
225
|
+
const result = streamText({
|
|
226
|
+
model: deepSeek('deepseek-chat'),
|
|
227
|
+
messages: [
|
|
228
|
+
{
|
|
229
|
+
role: 'user',
|
|
230
|
+
content: 'Suggest a name for my neighborhood book club.',
|
|
231
|
+
providerOptions: {
|
|
232
|
+
deepseek: {
|
|
233
|
+
name: 'organizer',
|
|
234
|
+
} satisfies DeepSeekMessageProviderOptions,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
for await (const textPart of result.textStream) {
|
|
241
|
+
process.stdout.write(textPart);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Names are omitted when the option is not set. The `name` value must be a
|
|
246
|
+
string. DeepSeek does not support names on tool messages, so the provider
|
|
247
|
+
ignores that placement and returns an unsupported-feature warning. Avoid
|
|
248
|
+
including unnecessary personal or identifying information in message names.
|
|
249
|
+
|
|
126
250
|
### Reasoning
|
|
127
251
|
|
|
128
|
-
DeepSeek
|
|
252
|
+
DeepSeek V4 models support reasoning. The reasoning is exposed through streaming:
|
|
129
253
|
|
|
130
254
|
```ts
|
|
131
255
|
import { deepseek } from '@ai-sdk/deepseek';
|
|
132
256
|
import { streamText } from 'ai';
|
|
133
257
|
|
|
134
258
|
const result = streamText({
|
|
135
|
-
model: deepseek('deepseek-
|
|
259
|
+
model: deepseek('deepseek-v4-pro'),
|
|
136
260
|
prompt: 'How many "r"s are in the word "strawberry"?',
|
|
137
261
|
});
|
|
138
262
|
|
|
@@ -150,39 +274,186 @@ for await (const part of result.fullStream) {
|
|
|
150
274
|
See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details
|
|
151
275
|
on how to integrate reasoning into your chatbot.
|
|
152
276
|
|
|
153
|
-
###
|
|
277
|
+
### Chat Prefix Completion
|
|
154
278
|
|
|
155
|
-
DeepSeek
|
|
279
|
+
DeepSeek's beta [chat prefix completion](https://api-docs.deepseek.com/guides/chat_prefix_completion/)
|
|
280
|
+
continues the content of a final assistant message. Create a provider with a
|
|
281
|
+
beta base URL and set `prefix: true` in that assistant message's provider
|
|
282
|
+
options:
|
|
283
|
+
|
|
284
|
+
```ts highlight="8,20-26"
|
|
285
|
+
import {
|
|
286
|
+
createDeepSeek,
|
|
287
|
+
type DeepSeekAssistantMessageProviderOptions,
|
|
288
|
+
} from '@ai-sdk/deepseek';
|
|
289
|
+
import { generateText } from 'ai';
|
|
290
|
+
|
|
291
|
+
const deepSeek = createDeepSeek({
|
|
292
|
+
baseURL: 'https://api.deepseek.com/beta',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const { text } = await generateText({
|
|
296
|
+
model: deepSeek('deepseek-v4-flash'),
|
|
297
|
+
messages: [
|
|
298
|
+
{
|
|
299
|
+
role: 'user',
|
|
300
|
+
content: 'Write a short sentence about the color of the sky.',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
role: 'assistant',
|
|
304
|
+
content: 'The sky is',
|
|
305
|
+
providerOptions: {
|
|
306
|
+
deepseek: {
|
|
307
|
+
prefix: true,
|
|
308
|
+
} satisfies DeepSeekAssistantMessageProviderOptions,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The prefixed message must be an assistant message and the final message in the
|
|
316
|
+
prompt. The configured `baseURL` must end in `/beta`, including when using a
|
|
317
|
+
proxy. Invalid placement or a non-beta base URL causes the request to fail
|
|
318
|
+
before it is sent.
|
|
319
|
+
|
|
320
|
+
<Note>
|
|
321
|
+
Chat prefix completion is a DeepSeek beta feature and its behavior may change.
|
|
322
|
+
</Note>
|
|
323
|
+
|
|
324
|
+
### Strict Tool Calls
|
|
325
|
+
|
|
326
|
+
DeepSeek's strict tool-call mode is a beta feature. Create the provider with a
|
|
327
|
+
beta base URL and set `strict: true` on every function tool in the request:
|
|
328
|
+
|
|
329
|
+
```ts highlight="5,14"
|
|
330
|
+
import { createDeepSeek } from '@ai-sdk/deepseek';
|
|
331
|
+
import { generateText, tool } from 'ai';
|
|
332
|
+
import { z } from 'zod';
|
|
333
|
+
|
|
334
|
+
const deepSeek = createDeepSeek({
|
|
335
|
+
baseURL: 'https://api.deepseek.com/beta',
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const result = await generateText({
|
|
339
|
+
model: deepSeek('deepseek-chat'),
|
|
340
|
+
prompt: 'What is the weather in San Francisco?',
|
|
341
|
+
tools: {
|
|
342
|
+
weather: tool({
|
|
343
|
+
description: 'Get the weather for a location.',
|
|
344
|
+
inputSchema: z.object({ location: z.string() }),
|
|
345
|
+
strict: true,
|
|
346
|
+
execute: async ({ location }) => ({ location, temperature: 18 }),
|
|
347
|
+
}),
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Strict tools fail locally when the base URL does not end in `/beta`. When any
|
|
353
|
+
function tool is strict, every function tool in the same request must set
|
|
354
|
+
`strict: true`.
|
|
355
|
+
|
|
356
|
+
### Provider Metadata
|
|
357
|
+
|
|
358
|
+
DeepSeek exposes the response system fingerprint and context cache usage through
|
|
359
|
+
the `providerMetadata` property:
|
|
156
360
|
|
|
157
361
|
```ts
|
|
158
362
|
import { deepseek } from '@ai-sdk/deepseek';
|
|
159
363
|
import { generateText } from 'ai';
|
|
160
364
|
|
|
161
365
|
const result = await generateText({
|
|
162
|
-
model: deepseek('deepseek-
|
|
366
|
+
model: deepseek('deepseek-v4-flash'),
|
|
163
367
|
prompt: 'Your prompt here',
|
|
164
368
|
});
|
|
165
369
|
|
|
166
370
|
console.log(result.providerMetadata);
|
|
167
|
-
// Example output:
|
|
371
|
+
// Example output:
|
|
372
|
+
// {
|
|
373
|
+
// deepseek: {
|
|
374
|
+
// systemFingerprint: 'fp_eaab8d114b_prod0820_fp8_kvcache',
|
|
375
|
+
// promptCacheHitTokens: 1856,
|
|
376
|
+
// promptCacheMissTokens: 5,
|
|
377
|
+
// },
|
|
378
|
+
// }
|
|
168
379
|
```
|
|
169
380
|
|
|
170
|
-
The
|
|
381
|
+
The metadata includes:
|
|
171
382
|
|
|
383
|
+
- `systemFingerprint`: The backend configuration fingerprint for the response
|
|
172
384
|
- `promptCacheHitTokens`: Number of input tokens that were cached
|
|
173
385
|
- `promptCacheMissTokens`: Number of input tokens that were not cached
|
|
174
386
|
|
|
387
|
+
For streamed responses, the latest non-null fingerprint from the response
|
|
388
|
+
chunks is returned.
|
|
389
|
+
|
|
175
390
|
<Note>
|
|
176
391
|
For more details about DeepSeek's caching system, see the [DeepSeek caching
|
|
177
392
|
documentation](https://api-docs.deepseek.com/guides/kv_cache#checking-cache-hit-status).
|
|
178
393
|
</Note>
|
|
179
394
|
|
|
395
|
+
### Chat Response Metadata
|
|
396
|
+
|
|
397
|
+
DeepSeek preserves provider-specific response fields in
|
|
398
|
+
`providerMetadata.deepseek` for generated and streamed responses:
|
|
399
|
+
|
|
400
|
+
- `responseObject`: `chat.completion` or `chat.completion.chunk`
|
|
401
|
+
- `choiceIndex`: the selected response choice index
|
|
402
|
+
- `messageRole`: the response message role, when supplied
|
|
403
|
+
- `toolCallTypes`: the tool-call type for each returned call
|
|
404
|
+
|
|
405
|
+
These fields remain in provider metadata because they are specific to
|
|
406
|
+
DeepSeek's Chat Completions response rather than shared AI SDK result fields.
|
|
407
|
+
|
|
408
|
+
### Vision File Parts
|
|
409
|
+
|
|
410
|
+
For inline images or image URLs, use file-part provider options to select the
|
|
411
|
+
image processing detail. DeepSeek supports `low`, `high`, `original`, and
|
|
412
|
+
`auto`:
|
|
413
|
+
|
|
414
|
+
```ts highlight="2,13-17"
|
|
415
|
+
import {
|
|
416
|
+
deepseek,
|
|
417
|
+
type DeepSeekFilePartProviderOptions,
|
|
418
|
+
} from '@ai-sdk/deepseek';
|
|
419
|
+
import { generateText } from 'ai';
|
|
420
|
+
|
|
421
|
+
const { text } = await generateText({
|
|
422
|
+
model: deepseek('deepseek-v4-flash-vision-exp'),
|
|
423
|
+
messages: [
|
|
424
|
+
{
|
|
425
|
+
role: 'user',
|
|
426
|
+
content: [
|
|
427
|
+
{ type: 'text', text: 'Describe this image.' },
|
|
428
|
+
{
|
|
429
|
+
type: 'file',
|
|
430
|
+
data: new URL('https://example.com/image.webp'),
|
|
431
|
+
mediaType: 'image/webp',
|
|
432
|
+
providerOptions: {
|
|
433
|
+
deepseek: {
|
|
434
|
+
imageDetail: 'low',
|
|
435
|
+
} satisfies DeepSeekFilePartProviderOptions,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Set `fileData: true` on an inline image file part to use DeepSeek's
|
|
445
|
+
`file_data` content-part representation. This preserves the file part's
|
|
446
|
+
`filename`. `fileData` cannot be used with image URLs or `imageDetail`.
|
|
447
|
+
|
|
448
|
+
DeepSeek accepts JPEG, PNG, GIF, and WebP image inputs. HTTP image URLs can be
|
|
449
|
+
at most 8,192 characters.
|
|
450
|
+
|
|
180
451
|
## Model Capabilities
|
|
181
452
|
|
|
182
453
|
| Model | Text Generation | Object Generation | Image Input | Tool Usage | Tool Streaming |
|
|
183
454
|
| ------------------------------ | ------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
|
|
184
|
-
| `deepseek-
|
|
185
|
-
| `deepseek-
|
|
455
|
+
| `deepseek-v4-flash` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
456
|
+
| `deepseek-v4-pro` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
186
457
|
| `deepseek-v4-flash-vision-exp` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
187
458
|
|
|
188
459
|
<Note>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/deepseek",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.60",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@ai-sdk/provider": "3.0.15",
|
|
40
|
-
"@ai-sdk/provider-utils": "4.0.
|
|
40
|
+
"@ai-sdk/provider-utils": "4.0.49"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "20.17.24",
|
|
@@ -1,28 +1,44 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
InvalidPromptError,
|
|
3
|
+
UnsupportedFunctionalityError,
|
|
4
|
+
type LanguageModelV3CallOptions,
|
|
5
|
+
type LanguageModelV3Prompt,
|
|
6
|
+
type SharedV3Warning,
|
|
5
7
|
} from '@ai-sdk/provider';
|
|
6
|
-
import { convertToBase64 } from '@ai-sdk/provider-utils';
|
|
8
|
+
import { convertToBase64, parseProviderOptions } from '@ai-sdk/provider-utils';
|
|
7
9
|
import type {
|
|
8
10
|
DeepSeekChatPrompt,
|
|
9
11
|
DeepSeekContentPart,
|
|
10
12
|
} from './deepseek-chat-api-types';
|
|
13
|
+
import { deepseekAssistantMessageProviderOptions } from './deepseek-chat-options';
|
|
14
|
+
import { deepseekFilePartProviderOptions } from './deepseek-file-part-options';
|
|
15
|
+
|
|
16
|
+
const supportedImageMediaTypes = new Set([
|
|
17
|
+
'image/gif',
|
|
18
|
+
'image/jpeg',
|
|
19
|
+
'image/jpg',
|
|
20
|
+
'image/png',
|
|
21
|
+
'image/webp',
|
|
22
|
+
]);
|
|
11
23
|
|
|
12
|
-
export function convertToDeepSeekChatMessages({
|
|
24
|
+
export async function convertToDeepSeekChatMessages({
|
|
13
25
|
prompt,
|
|
14
26
|
responseFormat,
|
|
15
27
|
modelId,
|
|
28
|
+
providerOptionsName = 'deepseek',
|
|
29
|
+
supportsAssistantPrefixCompletion = false,
|
|
16
30
|
supportsStructuredOutputs = false,
|
|
17
31
|
}: {
|
|
18
32
|
prompt: LanguageModelV3Prompt;
|
|
19
33
|
responseFormat: LanguageModelV3CallOptions['responseFormat'];
|
|
20
34
|
modelId: string;
|
|
35
|
+
providerOptionsName?: string;
|
|
36
|
+
supportsAssistantPrefixCompletion?: boolean;
|
|
21
37
|
supportsStructuredOutputs?: boolean;
|
|
22
|
-
}): {
|
|
38
|
+
}): Promise<{
|
|
23
39
|
messages: DeepSeekChatPrompt;
|
|
24
40
|
warnings: Array<SharedV3Warning>;
|
|
25
|
-
} {
|
|
41
|
+
}> {
|
|
26
42
|
const isDeepSeekV4 = modelId.includes('deepseek-v4');
|
|
27
43
|
const messages: DeepSeekChatPrompt = [];
|
|
28
44
|
const warnings: Array<SharedV3Warning> = [];
|
|
@@ -59,12 +75,34 @@ export function convertToDeepSeekChatMessages({
|
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
let index = -1;
|
|
62
|
-
for (const { role, content } of prompt) {
|
|
78
|
+
for (const { role, content, providerOptions } of prompt) {
|
|
63
79
|
index++;
|
|
64
80
|
|
|
81
|
+
// The assistant schema extends the common message schema, so one parse
|
|
82
|
+
// validates names for every role and the assistant-only prefix option.
|
|
83
|
+
const deepseekMessageOptions = await parseProviderOptions({
|
|
84
|
+
provider: providerOptionsName,
|
|
85
|
+
providerOptions,
|
|
86
|
+
schema: deepseekAssistantMessageProviderOptions,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (deepseekMessageOptions?.prefix === true && role !== 'assistant') {
|
|
90
|
+
throw new InvalidPromptError({
|
|
91
|
+
prompt,
|
|
92
|
+
message:
|
|
93
|
+
'DeepSeek assistant prefix completion requires `prefix: true` on an assistant message.',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
65
97
|
switch (role) {
|
|
66
98
|
case 'system': {
|
|
67
|
-
messages.push({
|
|
99
|
+
messages.push({
|
|
100
|
+
role: 'system',
|
|
101
|
+
content,
|
|
102
|
+
...(deepseekMessageOptions?.name != null && {
|
|
103
|
+
name: deepseekMessageOptions.name,
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
68
106
|
break;
|
|
69
107
|
}
|
|
70
108
|
|
|
@@ -88,7 +126,13 @@ export function convertToDeepSeekChatMessages({
|
|
|
88
126
|
}
|
|
89
127
|
}
|
|
90
128
|
|
|
91
|
-
messages.push({
|
|
129
|
+
messages.push({
|
|
130
|
+
role: 'user',
|
|
131
|
+
content: userContent,
|
|
132
|
+
...(deepseekMessageOptions?.name != null && {
|
|
133
|
+
name: deepseekMessageOptions.name,
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
92
136
|
break;
|
|
93
137
|
}
|
|
94
138
|
|
|
@@ -100,20 +144,86 @@ export function convertToDeepSeekChatMessages({
|
|
|
100
144
|
part.type === 'file' &&
|
|
101
145
|
(part.mediaType === 'image' || part.mediaType.startsWith('image/'))
|
|
102
146
|
) {
|
|
103
|
-
const
|
|
147
|
+
const filePartOptions = await parseProviderOptions({
|
|
148
|
+
provider: providerOptionsName,
|
|
149
|
+
providerOptions: part.providerOptions,
|
|
150
|
+
schema: deepseekFilePartProviderOptions,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const resolvedMediaType =
|
|
104
154
|
part.mediaType === 'image' || part.mediaType === 'image/*'
|
|
105
155
|
? 'image/jpeg'
|
|
106
156
|
: part.mediaType;
|
|
107
157
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
158
|
+
if (!supportedImageMediaTypes.has(resolvedMediaType)) {
|
|
159
|
+
throw new UnsupportedFunctionalityError({
|
|
160
|
+
functionality: `DeepSeek image media type ${resolvedMediaType}`,
|
|
161
|
+
message:
|
|
162
|
+
'DeepSeek supports JPEG, PNG, GIF, and WebP image inputs.',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (part.data instanceof URL) {
|
|
167
|
+
const url = part.data.toString();
|
|
168
|
+
|
|
169
|
+
if (url.length > 8192) {
|
|
170
|
+
throw new InvalidPromptError({
|
|
171
|
+
prompt,
|
|
172
|
+
message:
|
|
173
|
+
'DeepSeek image URLs must not exceed 8192 characters.',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (filePartOptions?.fileData === true) {
|
|
178
|
+
throw new InvalidPromptError({
|
|
179
|
+
prompt,
|
|
180
|
+
message:
|
|
181
|
+
'DeepSeek `fileData` image parts require inline data, not a URL.',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
userContent.push({
|
|
186
|
+
type: 'image_url',
|
|
187
|
+
image_url: {
|
|
188
|
+
url,
|
|
189
|
+
...(filePartOptions?.imageDetail != null && {
|
|
190
|
+
detail: filePartOptions.imageDetail,
|
|
191
|
+
}),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
const dataUrl = `data:${
|
|
196
|
+
resolvedMediaType === 'image/jpg'
|
|
197
|
+
? 'image/jpeg'
|
|
198
|
+
: resolvedMediaType
|
|
199
|
+
};base64,${convertToBase64(part.data)}`;
|
|
200
|
+
|
|
201
|
+
if (filePartOptions?.fileData === true) {
|
|
202
|
+
if (filePartOptions.imageDetail != null) {
|
|
203
|
+
throw new InvalidPromptError({
|
|
204
|
+
prompt,
|
|
205
|
+
message:
|
|
206
|
+
'DeepSeek `imageDetail` cannot be combined with `fileData`.',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
userContent.push({
|
|
211
|
+
type: 'file',
|
|
212
|
+
file_data: dataUrl,
|
|
213
|
+
...(part.filename != null && { filename: part.filename }),
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
userContent.push({
|
|
217
|
+
type: 'image_url',
|
|
218
|
+
image_url: {
|
|
219
|
+
url: dataUrl,
|
|
220
|
+
...(filePartOptions?.imageDetail != null && {
|
|
221
|
+
detail: filePartOptions.imageDetail,
|
|
222
|
+
}),
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
117
227
|
} else {
|
|
118
228
|
warnings.push({
|
|
119
229
|
type: 'unsupported',
|
|
@@ -122,11 +232,35 @@ export function convertToDeepSeekChatMessages({
|
|
|
122
232
|
}
|
|
123
233
|
}
|
|
124
234
|
|
|
125
|
-
messages.push({
|
|
235
|
+
messages.push({
|
|
236
|
+
role: 'user',
|
|
237
|
+
content: userContent,
|
|
238
|
+
...(deepseekMessageOptions?.name != null && {
|
|
239
|
+
name: deepseekMessageOptions.name,
|
|
240
|
+
}),
|
|
241
|
+
});
|
|
126
242
|
|
|
127
243
|
break;
|
|
128
244
|
}
|
|
129
245
|
case 'assistant': {
|
|
246
|
+
if (deepseekMessageOptions?.prefix === true) {
|
|
247
|
+
if (index !== prompt.length - 1) {
|
|
248
|
+
throw new InvalidPromptError({
|
|
249
|
+
prompt,
|
|
250
|
+
message:
|
|
251
|
+
'DeepSeek assistant prefix completion requires the prefixed assistant message to be the final message.',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!supportsAssistantPrefixCompletion) {
|
|
256
|
+
throw new UnsupportedFunctionalityError({
|
|
257
|
+
functionality: 'DeepSeek assistant prefix completion',
|
|
258
|
+
message:
|
|
259
|
+
'DeepSeek assistant prefix completion requires a beta base URL ending in `/beta`.',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
130
264
|
let text = '';
|
|
131
265
|
let reasoning: string | undefined;
|
|
132
266
|
|
|
@@ -174,6 +308,12 @@ export function convertToDeepSeekChatMessages({
|
|
|
174
308
|
messages.push({
|
|
175
309
|
role: 'assistant',
|
|
176
310
|
content: text,
|
|
311
|
+
...(deepseekMessageOptions?.name != null && {
|
|
312
|
+
name: deepseekMessageOptions.name,
|
|
313
|
+
}),
|
|
314
|
+
...(deepseekMessageOptions?.prefix === true && {
|
|
315
|
+
prefix: true,
|
|
316
|
+
}),
|
|
177
317
|
reasoning_content: reasoning ?? (isDeepSeekV4 ? '' : undefined),
|
|
178
318
|
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
179
319
|
});
|
|
@@ -182,6 +322,13 @@ export function convertToDeepSeekChatMessages({
|
|
|
182
322
|
}
|
|
183
323
|
|
|
184
324
|
case 'tool': {
|
|
325
|
+
if (deepseekMessageOptions?.name != null) {
|
|
326
|
+
warnings.push({
|
|
327
|
+
type: 'unsupported',
|
|
328
|
+
feature: 'message name on tool messages',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
185
332
|
for (const toolResponse of content) {
|
|
186
333
|
if (toolResponse.type === 'tool-approval-response') {
|
|
187
334
|
continue;
|