@ai-sdk/deepseek 3.0.31 → 3.0.34
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 +41 -0
- package/README.md +1 -1
- package/dist/index.d.ts +57 -19
- package/dist/index.js +626 -124
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +54 -4
- package/dist/internal/index.js +520 -109
- package/dist/internal/index.js.map +1 -1
- package/docs/30-deepseek.mdx +306 -23
- package/package.json +6 -6
- package/src/chat/convert-to-deepseek-chat-messages.ts +166 -19
- package/src/chat/convert-to-deepseek-usage.ts +1 -1
- package/src/chat/deepseek-chat-api-types.ts +50 -5
- package/src/chat/deepseek-chat-language-model-options.ts +102 -11
- package/src/chat/deepseek-chat-language-model.ts +293 -20
- package/src/chat/deepseek-file-part-options.ts +24 -0
- package/src/chat/deepseek-prepare-tools.ts +29 -3
- package/src/deepseek-provider.ts +2 -0
- package/src/files/deepseek-files-api.ts +9 -5
- package/src/files/deepseek-files.ts +129 -4
- package/src/index.ts +3 -0
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
createEventSourceResponseHandler,
|
|
15
15
|
createJsonErrorResponseHandler,
|
|
16
16
|
createJsonResponseHandler,
|
|
17
|
+
createProviderStreamError,
|
|
17
18
|
generateId,
|
|
18
19
|
isCustomReasoning,
|
|
19
20
|
mapReasoningToProviderEffort,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
deepseekChatChunkSchema,
|
|
35
36
|
deepseekChatResponseSchema,
|
|
36
37
|
deepSeekErrorSchema,
|
|
38
|
+
type DeepSeekChatLogprob,
|
|
37
39
|
type DeepSeekChatTokenUsage,
|
|
38
40
|
} from './deepseek-chat-api-types';
|
|
39
41
|
import {
|
|
@@ -49,10 +51,133 @@ export type DeepSeekChatConfig = {
|
|
|
49
51
|
headers?: () => Record<string, string | undefined>;
|
|
50
52
|
url: (options: { modelId: string; path: string }) => string;
|
|
51
53
|
fetch?: FetchFunction;
|
|
54
|
+
supportsAssistantPrefixCompletion?: boolean;
|
|
55
|
+
supportsStrictToolCalls?: boolean;
|
|
56
|
+
supportsPenaltySampling?: boolean;
|
|
52
57
|
supportsThinking?: boolean;
|
|
53
58
|
supportsStructuredOutputs?: boolean;
|
|
54
59
|
};
|
|
55
60
|
|
|
61
|
+
function createDeepSeekStreamError(
|
|
62
|
+
error: {
|
|
63
|
+
message: string;
|
|
64
|
+
type?: string | null;
|
|
65
|
+
code?: string | number | null;
|
|
66
|
+
},
|
|
67
|
+
data: unknown,
|
|
68
|
+
) {
|
|
69
|
+
const metadata = getDeepSeekStreamErrorMetadata(error);
|
|
70
|
+
|
|
71
|
+
return createProviderStreamError({
|
|
72
|
+
message: error.message,
|
|
73
|
+
type: error.type ?? undefined,
|
|
74
|
+
code: error.code ?? undefined,
|
|
75
|
+
...metadata,
|
|
76
|
+
data,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getDeepSeekStreamErrorMetadata(error: {
|
|
81
|
+
type?: string | null;
|
|
82
|
+
code?: string | number | null;
|
|
83
|
+
}): {
|
|
84
|
+
statusCode?: number;
|
|
85
|
+
isRetryable?: boolean;
|
|
86
|
+
} {
|
|
87
|
+
if (
|
|
88
|
+
error.code === 'insufficient_quota' ||
|
|
89
|
+
error.type === 'insufficient_quota'
|
|
90
|
+
) {
|
|
91
|
+
return { statusCode: 429, isRetryable: false };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const explicitStatusCode = getHttpStatusCode(error.code);
|
|
95
|
+
if (explicitStatusCode != null) {
|
|
96
|
+
return {
|
|
97
|
+
statusCode: explicitStatusCode,
|
|
98
|
+
isRetryable: isRetryableStatusCode(explicitStatusCode),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const discriminator of [error.code, error.type]) {
|
|
103
|
+
switch (discriminator) {
|
|
104
|
+
case 'rate_limit_exceeded':
|
|
105
|
+
case 'rate_limit_error':
|
|
106
|
+
return { statusCode: 429, isRetryable: true };
|
|
107
|
+
case 'server_error':
|
|
108
|
+
case 'api_error':
|
|
109
|
+
case 'internal_server_error':
|
|
110
|
+
return { statusCode: 500, isRetryable: true };
|
|
111
|
+
case 'overloaded_error':
|
|
112
|
+
case 'service_unavailable':
|
|
113
|
+
return { statusCode: 503, isRetryable: true };
|
|
114
|
+
case 'timeout':
|
|
115
|
+
case 'timeout_error':
|
|
116
|
+
return { statusCode: 504, isRetryable: true };
|
|
117
|
+
case 'authentication_error':
|
|
118
|
+
case 'invalid_api_key':
|
|
119
|
+
return { statusCode: 401, isRetryable: false };
|
|
120
|
+
case 'permission_error':
|
|
121
|
+
return { statusCode: 403, isRetryable: false };
|
|
122
|
+
case 'not_found_error':
|
|
123
|
+
case 'model_not_found':
|
|
124
|
+
return { statusCode: 404, isRetryable: false };
|
|
125
|
+
case 'bad_request':
|
|
126
|
+
case 'context_length_exceeded':
|
|
127
|
+
case 'invalid_request_error':
|
|
128
|
+
return { statusCode: 400, isRetryable: false };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getHttpStatusCode(value: unknown): number | undefined {
|
|
136
|
+
const statusCode =
|
|
137
|
+
typeof value === 'string' && /^\d{3}$/.test(value) ? Number(value) : value;
|
|
138
|
+
|
|
139
|
+
return typeof statusCode === 'number' &&
|
|
140
|
+
Number.isInteger(statusCode) &&
|
|
141
|
+
statusCode >= 400 &&
|
|
142
|
+
statusCode <= 599
|
|
143
|
+
? statusCode
|
|
144
|
+
: undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isRetryableStatusCode(statusCode: number): boolean {
|
|
148
|
+
return (
|
|
149
|
+
statusCode === 408 ||
|
|
150
|
+
statusCode === 409 ||
|
|
151
|
+
statusCode === 429 ||
|
|
152
|
+
statusCode >= 500
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function mapDeepSeekProviderReasoningEffort({
|
|
157
|
+
reasoningEffort,
|
|
158
|
+
warnings,
|
|
159
|
+
}: {
|
|
160
|
+
reasoningEffort: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
161
|
+
warnings: SharedV4Warning[];
|
|
162
|
+
}): 'low' | 'high' | 'max' {
|
|
163
|
+
const mapped =
|
|
164
|
+
reasoningEffort === 'medium'
|
|
165
|
+
? 'high'
|
|
166
|
+
: reasoningEffort === 'xhigh'
|
|
167
|
+
? 'max'
|
|
168
|
+
: reasoningEffort;
|
|
169
|
+
|
|
170
|
+
if (mapped !== reasoningEffort) {
|
|
171
|
+
warnings.push({
|
|
172
|
+
type: 'compatibility',
|
|
173
|
+
feature: 'reasoningEffort',
|
|
174
|
+
details: `reasoningEffort "${reasoningEffort}" is not a canonical DeepSeek value. mapped to "${mapped}".`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return mapped;
|
|
179
|
+
}
|
|
180
|
+
|
|
56
181
|
export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
57
182
|
readonly specificationVersion = 'v4';
|
|
58
183
|
|
|
@@ -123,11 +248,16 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
123
248
|
|
|
124
249
|
const supportsStructuredOutputs =
|
|
125
250
|
this.config.supportsStructuredOutputs === true;
|
|
251
|
+
const supportsPenaltySampling =
|
|
252
|
+
this.config.supportsPenaltySampling === true;
|
|
126
253
|
|
|
127
|
-
const { messages, warnings } = convertToDeepSeekChatMessages({
|
|
254
|
+
const { messages, warnings } = await convertToDeepSeekChatMessages({
|
|
128
255
|
prompt,
|
|
129
256
|
responseFormat,
|
|
130
257
|
modelId: this.modelId,
|
|
258
|
+
providerOptionsName: this.providerOptionsName,
|
|
259
|
+
supportsAssistantPrefixCompletion:
|
|
260
|
+
this.config.supportsAssistantPrefixCompletion,
|
|
131
261
|
supportsStructuredOutputs,
|
|
132
262
|
});
|
|
133
263
|
const allWarnings: SharedV4Warning[] = [...warnings];
|
|
@@ -140,6 +270,24 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
140
270
|
allWarnings.push({ type: 'unsupported', feature: 'seed' });
|
|
141
271
|
}
|
|
142
272
|
|
|
273
|
+
if (!supportsPenaltySampling && frequencyPenalty != null) {
|
|
274
|
+
allWarnings.push({
|
|
275
|
+
type: 'deprecated',
|
|
276
|
+
setting: 'frequencyPenalty',
|
|
277
|
+
message:
|
|
278
|
+
'frequencyPenalty is deprecated by DeepSeek and has been omitted. Remove frequencyPenalty from the request.',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!supportsPenaltySampling && presencePenalty != null) {
|
|
283
|
+
allWarnings.push({
|
|
284
|
+
type: 'deprecated',
|
|
285
|
+
setting: 'presencePenalty',
|
|
286
|
+
message:
|
|
287
|
+
'presencePenalty is deprecated by DeepSeek and has been omitted. Remove presencePenalty from the request.',
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
143
291
|
const {
|
|
144
292
|
tools: deepseekTools,
|
|
145
293
|
toolChoice: deepseekToolChoices,
|
|
@@ -147,41 +295,88 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
147
295
|
} = prepareTools({
|
|
148
296
|
tools,
|
|
149
297
|
toolChoice,
|
|
298
|
+
supportsStrictToolCalls: this.config.supportsStrictToolCalls,
|
|
150
299
|
});
|
|
151
300
|
|
|
301
|
+
const thinkingType = deepseekOptions.thinking?.type;
|
|
302
|
+
if (thinkingType === 'adaptive') {
|
|
303
|
+
allWarnings.push({
|
|
304
|
+
type: 'compatibility',
|
|
305
|
+
feature: 'thinking.type',
|
|
306
|
+
details:
|
|
307
|
+
'thinking.type "adaptive" is not a canonical DeepSeek value. mapped to "enabled".',
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
152
311
|
const thinking =
|
|
153
312
|
this.config.supportsThinking === false
|
|
154
313
|
? undefined
|
|
155
|
-
:
|
|
156
|
-
? { type:
|
|
314
|
+
: thinkingType != null
|
|
315
|
+
? { type: thinkingType === 'adaptive' ? 'enabled' : thinkingType }
|
|
157
316
|
: isCustomReasoning(reasoning)
|
|
158
317
|
? { type: reasoning === 'none' ? 'disabled' : 'enabled' }
|
|
159
318
|
: undefined;
|
|
160
319
|
|
|
320
|
+
const isThinkingEnabled =
|
|
321
|
+
this.config.supportsThinking !== false &&
|
|
322
|
+
thinking?.type !== 'disabled' &&
|
|
323
|
+
(thinking != null ||
|
|
324
|
+
this.modelId === 'deepseek-reasoner' ||
|
|
325
|
+
this.modelId.includes('deepseek-v4'));
|
|
326
|
+
|
|
327
|
+
if (isThinkingEnabled && temperature != null) {
|
|
328
|
+
allWarnings.push({
|
|
329
|
+
type: 'unsupported',
|
|
330
|
+
feature: 'temperature',
|
|
331
|
+
details:
|
|
332
|
+
"temperature has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use temperature.",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (isThinkingEnabled && topP != null) {
|
|
337
|
+
allWarnings.push({
|
|
338
|
+
type: 'unsupported',
|
|
339
|
+
feature: 'topP',
|
|
340
|
+
details:
|
|
341
|
+
"topP has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use topP.",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
161
345
|
const reasoningEffort =
|
|
162
|
-
deepseekOptions.reasoningEffort
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
reasoning,
|
|
166
|
-
effortMap: {
|
|
167
|
-
minimal: 'low',
|
|
168
|
-
low: 'low',
|
|
169
|
-
medium: 'medium',
|
|
170
|
-
high: 'high',
|
|
171
|
-
xhigh: 'max',
|
|
172
|
-
},
|
|
346
|
+
deepseekOptions.reasoningEffort != null
|
|
347
|
+
? mapDeepSeekProviderReasoningEffort({
|
|
348
|
+
reasoningEffort: deepseekOptions.reasoningEffort,
|
|
173
349
|
warnings: allWarnings,
|
|
174
350
|
})
|
|
175
|
-
:
|
|
351
|
+
: isCustomReasoning(reasoning) && reasoning !== 'none'
|
|
352
|
+
? mapReasoningToProviderEffort({
|
|
353
|
+
reasoning,
|
|
354
|
+
effortMap: {
|
|
355
|
+
minimal: 'low',
|
|
356
|
+
low: 'low',
|
|
357
|
+
medium: 'high',
|
|
358
|
+
high: 'high',
|
|
359
|
+
xhigh: 'max',
|
|
360
|
+
},
|
|
361
|
+
warnings: allWarnings,
|
|
362
|
+
})
|
|
363
|
+
: undefined;
|
|
176
364
|
|
|
177
365
|
return {
|
|
178
366
|
args: {
|
|
179
367
|
model: this.modelId,
|
|
368
|
+
...((deepseekOptions.logprobs === true ||
|
|
369
|
+
deepseekOptions.topLogprobs != null) && { logprobs: true }),
|
|
370
|
+
...(deepseekOptions.topLogprobs != null && {
|
|
371
|
+
top_logprobs: deepseekOptions.topLogprobs,
|
|
372
|
+
}),
|
|
180
373
|
max_tokens: maxOutputTokens,
|
|
181
|
-
temperature,
|
|
182
|
-
top_p: topP,
|
|
183
|
-
frequency_penalty:
|
|
184
|
-
|
|
374
|
+
temperature: isThinkingEnabled ? undefined : temperature,
|
|
375
|
+
top_p: isThinkingEnabled ? undefined : topP,
|
|
376
|
+
frequency_penalty: supportsPenaltySampling
|
|
377
|
+
? frequencyPenalty
|
|
378
|
+
: undefined,
|
|
379
|
+
presence_penalty: supportsPenaltySampling ? presencePenalty : undefined,
|
|
185
380
|
response_format:
|
|
186
381
|
responseFormat?.type === 'json'
|
|
187
382
|
? supportsStructuredOutputs && responseFormat.schema != null
|
|
@@ -201,6 +396,9 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
201
396
|
tools: deepseekTools,
|
|
202
397
|
tool_choice: deepseekToolChoices,
|
|
203
398
|
thinking,
|
|
399
|
+
...(deepseekOptions.userId != null && {
|
|
400
|
+
user_id: deepseekOptions.userId,
|
|
401
|
+
}),
|
|
204
402
|
...(thinking?.type !== 'disabled' &&
|
|
205
403
|
reasoningEffort != null && {
|
|
206
404
|
reasoning_effort: reasoningEffort,
|
|
@@ -275,6 +473,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
275
473
|
[this.providerOptionsName]: {
|
|
276
474
|
promptCacheHitTokens: responseBody.usage?.prompt_cache_hit_tokens,
|
|
277
475
|
promptCacheMissTokens: responseBody.usage?.prompt_cache_miss_tokens,
|
|
476
|
+
...(responseBody.object != null && {
|
|
477
|
+
responseObject: responseBody.object,
|
|
478
|
+
}),
|
|
479
|
+
...(choice.index != null && { choiceIndex: choice.index }),
|
|
480
|
+
...(choice.message.role != null && {
|
|
481
|
+
messageRole: choice.message.role,
|
|
482
|
+
}),
|
|
483
|
+
...(choice.message.tool_calls != null && {
|
|
484
|
+
toolCallTypes: choice.message.tool_calls
|
|
485
|
+
.map(toolCall => toolCall.type)
|
|
486
|
+
.filter(type => type != null),
|
|
487
|
+
}),
|
|
488
|
+
...(choice.logprobs != null && { logprobs: choice.logprobs }),
|
|
489
|
+
...(responseBody.system_fingerprint != null && {
|
|
490
|
+
systemFingerprint: responseBody.system_fingerprint,
|
|
491
|
+
}),
|
|
278
492
|
},
|
|
279
493
|
},
|
|
280
494
|
request: { body: args },
|
|
@@ -320,10 +534,17 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
320
534
|
raw: undefined,
|
|
321
535
|
};
|
|
322
536
|
let usage: DeepSeekChatTokenUsage | undefined = undefined;
|
|
537
|
+
let systemFingerprint: string | undefined = undefined;
|
|
323
538
|
let isFirstChunk = true;
|
|
324
539
|
const providerOptionsName = this.providerOptionsName;
|
|
325
540
|
let isActiveReasoning = false;
|
|
326
541
|
let isActiveText = false;
|
|
542
|
+
let responseObject: 'chat.completion.chunk' | undefined;
|
|
543
|
+
let choiceIndex: number | undefined;
|
|
544
|
+
let messageRole: 'assistant' | undefined;
|
|
545
|
+
const toolCallTypes = new Map<number, 'function'>();
|
|
546
|
+
const contentLogprobs: DeepSeekChatLogprob[] = [];
|
|
547
|
+
const reasoningLogprobs: DeepSeekChatLogprob[] = [];
|
|
327
548
|
|
|
328
549
|
return {
|
|
329
550
|
stream: response.pipeThrough(
|
|
@@ -355,7 +576,10 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
355
576
|
// handle error chunks:
|
|
356
577
|
if ('error' in value) {
|
|
357
578
|
finishReason = { unified: 'error', raw: undefined };
|
|
358
|
-
controller.enqueue({
|
|
579
|
+
controller.enqueue({
|
|
580
|
+
type: 'error',
|
|
581
|
+
error: createDeepSeekStreamError(value.error, value),
|
|
582
|
+
});
|
|
359
583
|
return;
|
|
360
584
|
}
|
|
361
585
|
|
|
@@ -372,8 +596,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
372
596
|
usage = value.usage;
|
|
373
597
|
}
|
|
374
598
|
|
|
599
|
+
if (value.object != null) {
|
|
600
|
+
responseObject = value.object;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// The fingerprint is repeated on stream chunks; keep the latest
|
|
604
|
+
// non-null value in case it changes during the response.
|
|
605
|
+
if (value.system_fingerprint != null) {
|
|
606
|
+
systemFingerprint = value.system_fingerprint;
|
|
607
|
+
}
|
|
608
|
+
|
|
375
609
|
const choice = value.choices[0];
|
|
376
610
|
|
|
611
|
+
if (choice?.index != null) {
|
|
612
|
+
choiceIndex = choice.index;
|
|
613
|
+
}
|
|
614
|
+
|
|
377
615
|
if (choice?.finish_reason != null) {
|
|
378
616
|
finishReason = {
|
|
379
617
|
unified: mapDeepSeekFinishReason(choice.finish_reason),
|
|
@@ -381,12 +619,24 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
381
619
|
};
|
|
382
620
|
}
|
|
383
621
|
|
|
622
|
+
if (choice?.logprobs?.content != null) {
|
|
623
|
+
contentLogprobs.push(...choice.logprobs.content);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (choice?.logprobs?.reasoning_content != null) {
|
|
627
|
+
reasoningLogprobs.push(...choice.logprobs.reasoning_content);
|
|
628
|
+
}
|
|
629
|
+
|
|
384
630
|
if (choice?.delta == null) {
|
|
385
631
|
return;
|
|
386
632
|
}
|
|
387
633
|
|
|
388
634
|
const delta = choice.delta;
|
|
389
635
|
|
|
636
|
+
if (delta.role != null) {
|
|
637
|
+
messageRole = delta.role;
|
|
638
|
+
}
|
|
639
|
+
|
|
390
640
|
// enqueue reasoning before text deltas:
|
|
391
641
|
const reasoningContent = delta.reasoning_content;
|
|
392
642
|
if (reasoningContent) {
|
|
@@ -438,6 +688,9 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
438
688
|
}
|
|
439
689
|
|
|
440
690
|
for (const toolCallDelta of delta.tool_calls) {
|
|
691
|
+
if (toolCallDelta.type != null) {
|
|
692
|
+
toolCallTypes.set(toolCallDelta.index, toolCallDelta.type);
|
|
693
|
+
}
|
|
441
694
|
toolCallTracker.processDelta(toolCallDelta);
|
|
442
695
|
}
|
|
443
696
|
}
|
|
@@ -464,6 +717,26 @@ export class DeepSeekChatLanguageModel implements LanguageModelV4 {
|
|
|
464
717
|
usage?.prompt_cache_hit_tokens ?? undefined,
|
|
465
718
|
promptCacheMissTokens:
|
|
466
719
|
usage?.prompt_cache_miss_tokens ?? undefined,
|
|
720
|
+
...(responseObject != null && { responseObject }),
|
|
721
|
+
...(choiceIndex != null && { choiceIndex }),
|
|
722
|
+
...(messageRole != null && { messageRole }),
|
|
723
|
+
...(toolCallTypes.size > 0 && {
|
|
724
|
+
toolCallTypes: [...toolCallTypes.entries()]
|
|
725
|
+
.sort(([left], [right]) => left - right)
|
|
726
|
+
.map(([, type]) => type),
|
|
727
|
+
}),
|
|
728
|
+
...((contentLogprobs.length > 0 ||
|
|
729
|
+
reasoningLogprobs.length > 0) && {
|
|
730
|
+
logprobs: {
|
|
731
|
+
...(contentLogprobs.length > 0 && {
|
|
732
|
+
content: contentLogprobs,
|
|
733
|
+
}),
|
|
734
|
+
...(reasoningLogprobs.length > 0 && {
|
|
735
|
+
reasoning_content: reasoningLogprobs,
|
|
736
|
+
}),
|
|
737
|
+
},
|
|
738
|
+
}),
|
|
739
|
+
...(systemFingerprint != null && { systemFingerprint }),
|
|
467
740
|
},
|
|
468
741
|
},
|
|
469
742
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
export const deepseekFilePartProviderOptions = z.object({
|
|
4
|
+
/**
|
|
5
|
+
* Controls how DeepSeek processes an image sent as an `image_url` part.
|
|
6
|
+
*
|
|
7
|
+
* @see https://api-docs.deepseek.com/api/create-chat-completion/
|
|
8
|
+
*/
|
|
9
|
+
imageDetail: z.enum(['low', 'high', 'original', 'auto']).optional(),
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sends inline image data as a DeepSeek `file` part using `file_data`
|
|
13
|
+
* instead of an `image_url` data URL. When set, the file part's filename
|
|
14
|
+
* is preserved.
|
|
15
|
+
*
|
|
16
|
+
* This option only applies to inline image data. It cannot be combined
|
|
17
|
+
* with `imageDetail`.
|
|
18
|
+
*/
|
|
19
|
+
fileData: z.literal(true).optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type DeepSeekFilePartProviderOptions = z.infer<
|
|
23
|
+
typeof deepseekFilePartProviderOptions
|
|
24
|
+
>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
UnsupportedFunctionalityError,
|
|
3
|
+
type LanguageModelV4CallOptions,
|
|
4
|
+
type SharedV4Warning,
|
|
4
5
|
} from '@ai-sdk/provider';
|
|
5
6
|
import type {
|
|
6
7
|
DeepSeekFunctionTool,
|
|
@@ -10,9 +11,11 @@ import type {
|
|
|
10
11
|
export function prepareTools({
|
|
11
12
|
tools,
|
|
12
13
|
toolChoice,
|
|
14
|
+
supportsStrictToolCalls,
|
|
13
15
|
}: {
|
|
14
16
|
tools: LanguageModelV4CallOptions['tools'];
|
|
15
17
|
toolChoice?: LanguageModelV4CallOptions['toolChoice'];
|
|
18
|
+
supportsStrictToolCalls?: boolean;
|
|
16
19
|
}): {
|
|
17
20
|
tools: undefined | Array<DeepSeekFunctionTool>;
|
|
18
21
|
toolChoice: DeepSeekToolChoice;
|
|
@@ -27,6 +30,29 @@ export function prepareTools({
|
|
|
27
30
|
return { tools: undefined, toolChoice: undefined, toolWarnings };
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
const functionTools = tools.filter(tool => tool.type === 'function');
|
|
34
|
+
const hasStrictTool = functionTools.some(tool => tool.strict === true);
|
|
35
|
+
|
|
36
|
+
if (hasStrictTool && supportsStrictToolCalls === false) {
|
|
37
|
+
throw new UnsupportedFunctionalityError({
|
|
38
|
+
functionality: 'DeepSeek strict tool calls',
|
|
39
|
+
message:
|
|
40
|
+
'DeepSeek strict tool calls require a beta base URL ending in `/beta`.',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
hasStrictTool &&
|
|
46
|
+
supportsStrictToolCalls === true &&
|
|
47
|
+
functionTools.some(tool => tool.strict !== true)
|
|
48
|
+
) {
|
|
49
|
+
throw new UnsupportedFunctionalityError({
|
|
50
|
+
functionality: 'mixed DeepSeek strict and non-strict tool calls',
|
|
51
|
+
message:
|
|
52
|
+
'DeepSeek strict mode requires every function tool in the request to set `strict: true`.',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
30
56
|
const deepseekTools: Array<DeepSeekFunctionTool> = [];
|
|
31
57
|
|
|
32
58
|
for (const tool of tools) {
|
package/src/deepseek-provider.ts
CHANGED
|
@@ -5,12 +5,16 @@ export const deepSeekFilesResponseSchema = lazySchema(() =>
|
|
|
5
5
|
zodSchema(
|
|
6
6
|
z.object({
|
|
7
7
|
id: z.string(),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// These fields are required by DeepSeek's OpenAPI schema, but they are
|
|
9
|
+
// not needed to construct the provider reference. Keep them nullish so
|
|
10
|
+
// uploads remain resilient to incomplete responses while validating any
|
|
11
|
+
// returned values precisely enough to avoid misleading metadata.
|
|
12
|
+
object: z.literal('file').nullish(),
|
|
13
|
+
bytes: z.number().int().nonnegative().nullish(),
|
|
14
|
+
created_at: z.number().int().nonnegative().nullish(),
|
|
11
15
|
filename: z.string().nullish(),
|
|
12
|
-
purpose: z.
|
|
13
|
-
expires_at: z.number().nullish(),
|
|
16
|
+
purpose: z.literal('user_data').nullish(),
|
|
17
|
+
expires_at: z.number().int().nonnegative().nullish(),
|
|
14
18
|
}),
|
|
15
19
|
),
|
|
16
20
|
);
|