@ai-sdk/deepseek 2.0.58 → 2.0.59
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 +20 -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
|
@@ -12,16 +12,19 @@ export type DeepSeekMessage =
|
|
|
12
12
|
export interface DeepSeekSystemMessage {
|
|
13
13
|
role: 'system';
|
|
14
14
|
content: string;
|
|
15
|
+
name?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export interface DeepSeekUserMessage {
|
|
18
19
|
role: 'user';
|
|
19
20
|
content: string | Array<DeepSeekContentPart>;
|
|
21
|
+
name?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export type DeepSeekContentPart =
|
|
23
25
|
| DeepSeekContentPartText
|
|
24
|
-
| DeepSeekContentPartImage
|
|
26
|
+
| DeepSeekContentPartImage
|
|
27
|
+
| DeepSeekContentPartFile;
|
|
25
28
|
|
|
26
29
|
export interface DeepSeekContentPartText {
|
|
27
30
|
type: 'text';
|
|
@@ -30,12 +33,22 @@ export interface DeepSeekContentPartText {
|
|
|
30
33
|
|
|
31
34
|
export interface DeepSeekContentPartImage {
|
|
32
35
|
type: 'image_url';
|
|
33
|
-
image_url: {
|
|
36
|
+
image_url: {
|
|
37
|
+
url: string;
|
|
38
|
+
detail?: 'low' | 'high' | 'original' | 'auto';
|
|
39
|
+
};
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
export interface DeepSeekContentPartFile {
|
|
43
|
+
type: 'file';
|
|
44
|
+
file_data: string;
|
|
45
|
+
filename?: string;
|
|
46
|
+
}
|
|
36
47
|
export interface DeepSeekAssistantMessage {
|
|
37
48
|
role: 'assistant';
|
|
38
49
|
content?: string | null;
|
|
50
|
+
name?: string;
|
|
51
|
+
prefix?: true;
|
|
39
52
|
reasoning_content?: string;
|
|
40
53
|
tool_calls?: Array<DeepSeekMessageToolCall>;
|
|
41
54
|
}
|
|
@@ -100,14 +113,39 @@ export const deepSeekErrorSchema = z.object({
|
|
|
100
113
|
|
|
101
114
|
export type DeepSeekErrorData = z.infer<typeof deepSeekErrorSchema>;
|
|
102
115
|
|
|
116
|
+
const deepseekChatLogprobSchema = z.object({
|
|
117
|
+
token: z.string(),
|
|
118
|
+
logprob: z.number(),
|
|
119
|
+
bytes: z.array(z.number()).nullable(),
|
|
120
|
+
top_logprobs: z.array(
|
|
121
|
+
z.object({
|
|
122
|
+
token: z.string(),
|
|
123
|
+
logprob: z.number(),
|
|
124
|
+
bytes: z.array(z.number()).nullable(),
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const deepseekChatLogprobsSchema = z
|
|
130
|
+
.object({
|
|
131
|
+
content: z.array(deepseekChatLogprobSchema).nullish(),
|
|
132
|
+
reasoning_content: z.array(deepseekChatLogprobSchema).nullish(),
|
|
133
|
+
})
|
|
134
|
+
.nullish();
|
|
135
|
+
|
|
136
|
+
export type DeepSeekChatLogprob = z.infer<typeof deepseekChatLogprobSchema>;
|
|
137
|
+
|
|
103
138
|
// limited version of the schema, focussed on what is needed for the implementation
|
|
104
139
|
// this approach limits breakages when the API changes and increases efficiency
|
|
105
140
|
export const deepseekChatResponseSchema = z.object({
|
|
106
141
|
id: z.string().nullish(),
|
|
107
142
|
created: z.number().nullish(),
|
|
108
143
|
model: z.string().nullish(),
|
|
144
|
+
object: z.literal('chat.completion').nullish(),
|
|
145
|
+
system_fingerprint: z.string().nullish(),
|
|
109
146
|
choices: z.array(
|
|
110
147
|
z.object({
|
|
148
|
+
index: z.number().nullish(),
|
|
111
149
|
message: z.object({
|
|
112
150
|
role: z.literal('assistant').nullish(),
|
|
113
151
|
content: z.string().nullish(),
|
|
@@ -116,6 +154,7 @@ export const deepseekChatResponseSchema = z.object({
|
|
|
116
154
|
.array(
|
|
117
155
|
z.object({
|
|
118
156
|
id: z.string().nullish(),
|
|
157
|
+
type: z.literal('function').nullish(),
|
|
119
158
|
function: z.object({
|
|
120
159
|
name: z.string(),
|
|
121
160
|
arguments: z.string(),
|
|
@@ -124,6 +163,7 @@ export const deepseekChatResponseSchema = z.object({
|
|
|
124
163
|
)
|
|
125
164
|
.nullish(),
|
|
126
165
|
}),
|
|
166
|
+
logprobs: deepseekChatLogprobsSchema,
|
|
127
167
|
finish_reason: z.string().nullish(),
|
|
128
168
|
}),
|
|
129
169
|
),
|
|
@@ -139,8 +179,11 @@ export const deepseekChatChunkSchema = lazySchema(() =>
|
|
|
139
179
|
id: z.string().nullish(),
|
|
140
180
|
created: z.number().nullish(),
|
|
141
181
|
model: z.string().nullish(),
|
|
182
|
+
object: z.literal('chat.completion.chunk').nullish(),
|
|
183
|
+
system_fingerprint: z.string().nullish(),
|
|
142
184
|
choices: z.array(
|
|
143
185
|
z.object({
|
|
186
|
+
index: z.number().nullish(),
|
|
144
187
|
delta: z
|
|
145
188
|
.object({
|
|
146
189
|
role: z.enum(['assistant']).nullish(),
|
|
@@ -151,6 +194,7 @@ export const deepseekChatChunkSchema = lazySchema(() =>
|
|
|
151
194
|
z.object({
|
|
152
195
|
index: z.number(),
|
|
153
196
|
id: z.string().nullish(),
|
|
197
|
+
type: z.literal('function').nullish(),
|
|
154
198
|
function: z.object({
|
|
155
199
|
name: z.string().nullish(),
|
|
156
200
|
arguments: z.string().nullish(),
|
|
@@ -160,6 +204,7 @@ export const deepseekChatChunkSchema = lazySchema(() =>
|
|
|
160
204
|
.nullish(),
|
|
161
205
|
})
|
|
162
206
|
.nullish(),
|
|
207
|
+
logprobs: deepseekChatLogprobsSchema,
|
|
163
208
|
finish_reason: z.string().nullish(),
|
|
164
209
|
}),
|
|
165
210
|
),
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type LanguageModelV3GenerateResult,
|
|
9
9
|
type LanguageModelV3StreamPart,
|
|
10
10
|
type LanguageModelV3StreamResult,
|
|
11
|
+
type SharedV3Warning,
|
|
11
12
|
} from '@ai-sdk/provider';
|
|
12
13
|
import {
|
|
13
14
|
combineHeaders,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
deepseekChatChunkSchema,
|
|
29
30
|
deepseekChatResponseSchema,
|
|
30
31
|
deepSeekErrorSchema,
|
|
32
|
+
type DeepSeekChatLogprob,
|
|
31
33
|
type DeepSeekChatTokenUsage,
|
|
32
34
|
} from './deepseek-chat-api-types';
|
|
33
35
|
import {
|
|
@@ -43,10 +45,38 @@ export type DeepSeekChatConfig = {
|
|
|
43
45
|
headers: () => Record<string, string | undefined>;
|
|
44
46
|
url: (options: { modelId: string; path: string }) => string;
|
|
45
47
|
fetch?: FetchFunction;
|
|
48
|
+
supportsAssistantPrefixCompletion?: boolean;
|
|
49
|
+
supportsPenaltySampling?: boolean;
|
|
50
|
+
supportsStrictToolCalls?: boolean;
|
|
46
51
|
supportsThinking?: boolean;
|
|
47
52
|
supportsStructuredOutputs?: boolean;
|
|
48
53
|
};
|
|
49
54
|
|
|
55
|
+
function mapDeepSeekProviderReasoningEffort({
|
|
56
|
+
reasoningEffort,
|
|
57
|
+
warnings,
|
|
58
|
+
}: {
|
|
59
|
+
reasoningEffort: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
60
|
+
warnings: SharedV3Warning[];
|
|
61
|
+
}): 'low' | 'high' | 'max' {
|
|
62
|
+
const mapped =
|
|
63
|
+
reasoningEffort === 'medium'
|
|
64
|
+
? 'high'
|
|
65
|
+
: reasoningEffort === 'xhigh'
|
|
66
|
+
? 'max'
|
|
67
|
+
: reasoningEffort;
|
|
68
|
+
|
|
69
|
+
if (mapped !== reasoningEffort) {
|
|
70
|
+
warnings.push({
|
|
71
|
+
type: 'compatibility',
|
|
72
|
+
feature: 'reasoningEffort',
|
|
73
|
+
details: `reasoningEffort "${reasoningEffort}" is not a canonical DeepSeek value. mapped to "${mapped}".`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return mapped;
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
51
81
|
readonly specificationVersion = 'v3';
|
|
52
82
|
|
|
@@ -102,20 +132,42 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
102
132
|
|
|
103
133
|
const supportsStructuredOutputs =
|
|
104
134
|
this.config.supportsStructuredOutputs === true;
|
|
135
|
+
const supportsPenaltySampling =
|
|
136
|
+
this.config.supportsPenaltySampling === true;
|
|
105
137
|
|
|
106
|
-
const { messages, warnings } = convertToDeepSeekChatMessages({
|
|
138
|
+
const { messages, warnings } = await convertToDeepSeekChatMessages({
|
|
107
139
|
prompt,
|
|
108
140
|
responseFormat,
|
|
109
141
|
modelId: this.modelId,
|
|
142
|
+
providerOptionsName: this.providerOptionsName,
|
|
143
|
+
supportsAssistantPrefixCompletion:
|
|
144
|
+
this.config.supportsAssistantPrefixCompletion,
|
|
110
145
|
supportsStructuredOutputs,
|
|
111
146
|
});
|
|
147
|
+
const allWarnings = [...warnings];
|
|
112
148
|
|
|
113
149
|
if (topK != null) {
|
|
114
|
-
|
|
150
|
+
allWarnings.push({ type: 'unsupported', feature: 'topK' });
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
if (seed != null) {
|
|
118
|
-
|
|
154
|
+
allWarnings.push({ type: 'unsupported', feature: 'seed' });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!supportsPenaltySampling && frequencyPenalty != null) {
|
|
158
|
+
allWarnings.push({
|
|
159
|
+
type: 'other',
|
|
160
|
+
message:
|
|
161
|
+
'frequencyPenalty is deprecated by DeepSeek and has been omitted. Remove frequencyPenalty from the request.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!supportsPenaltySampling && presencePenalty != null) {
|
|
166
|
+
allWarnings.push({
|
|
167
|
+
type: 'other',
|
|
168
|
+
message:
|
|
169
|
+
'presencePenalty is deprecated by DeepSeek and has been omitted. Remove presencePenalty from the request.',
|
|
170
|
+
});
|
|
119
171
|
}
|
|
120
172
|
|
|
121
173
|
const {
|
|
@@ -125,23 +177,75 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
125
177
|
} = prepareTools({
|
|
126
178
|
tools,
|
|
127
179
|
toolChoice,
|
|
180
|
+
supportsStrictToolCalls: this.config.supportsStrictToolCalls,
|
|
128
181
|
});
|
|
182
|
+
allWarnings.push(...toolWarnings);
|
|
183
|
+
|
|
184
|
+
const thinkingType = deepseekOptions.thinking?.type;
|
|
185
|
+
if (thinkingType === 'adaptive') {
|
|
186
|
+
allWarnings.push({
|
|
187
|
+
type: 'compatibility',
|
|
188
|
+
feature: 'thinking.type',
|
|
189
|
+
details:
|
|
190
|
+
'thinking.type "adaptive" is not a canonical DeepSeek value. mapped to "enabled".',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
129
193
|
|
|
130
194
|
const thinking =
|
|
131
195
|
this.config.supportsThinking === false
|
|
132
196
|
? undefined
|
|
133
|
-
:
|
|
134
|
-
? { type:
|
|
197
|
+
: thinkingType != null
|
|
198
|
+
? { type: thinkingType === 'adaptive' ? 'enabled' : thinkingType }
|
|
135
199
|
: undefined;
|
|
136
200
|
|
|
201
|
+
const isThinkingEnabled =
|
|
202
|
+
this.config.supportsThinking !== false &&
|
|
203
|
+
thinking?.type !== 'disabled' &&
|
|
204
|
+
(thinking != null ||
|
|
205
|
+
this.modelId === 'deepseek-reasoner' ||
|
|
206
|
+
this.modelId.includes('deepseek-v4'));
|
|
207
|
+
|
|
208
|
+
if (isThinkingEnabled && temperature != null) {
|
|
209
|
+
allWarnings.push({
|
|
210
|
+
type: 'unsupported',
|
|
211
|
+
feature: 'temperature',
|
|
212
|
+
details:
|
|
213
|
+
"temperature has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use temperature.",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (isThinkingEnabled && topP != null) {
|
|
218
|
+
allWarnings.push({
|
|
219
|
+
type: 'unsupported',
|
|
220
|
+
feature: 'topP',
|
|
221
|
+
details:
|
|
222
|
+
"topP has no effect when DeepSeek thinking is enabled. Set providerOptions.deepseek.thinking.type to 'disabled' to use topP.",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const reasoningEffort =
|
|
227
|
+
deepseekOptions.reasoningEffort != null
|
|
228
|
+
? mapDeepSeekProviderReasoningEffort({
|
|
229
|
+
reasoningEffort: deepseekOptions.reasoningEffort,
|
|
230
|
+
warnings: allWarnings,
|
|
231
|
+
})
|
|
232
|
+
: undefined;
|
|
233
|
+
|
|
137
234
|
return {
|
|
138
235
|
args: {
|
|
139
236
|
model: this.modelId,
|
|
237
|
+
...((deepseekOptions.logprobs === true ||
|
|
238
|
+
deepseekOptions.topLogprobs != null) && { logprobs: true }),
|
|
239
|
+
...(deepseekOptions.topLogprobs != null && {
|
|
240
|
+
top_logprobs: deepseekOptions.topLogprobs,
|
|
241
|
+
}),
|
|
140
242
|
max_tokens: maxOutputTokens,
|
|
141
|
-
temperature,
|
|
142
|
-
top_p: topP,
|
|
143
|
-
frequency_penalty:
|
|
144
|
-
|
|
243
|
+
temperature: isThinkingEnabled ? undefined : temperature,
|
|
244
|
+
top_p: isThinkingEnabled ? undefined : topP,
|
|
245
|
+
frequency_penalty: supportsPenaltySampling
|
|
246
|
+
? frequencyPenalty
|
|
247
|
+
: undefined,
|
|
248
|
+
presence_penalty: supportsPenaltySampling ? presencePenalty : undefined,
|
|
145
249
|
response_format:
|
|
146
250
|
responseFormat?.type === 'json'
|
|
147
251
|
? supportsStructuredOutputs && responseFormat.schema != null
|
|
@@ -161,12 +265,15 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
161
265
|
tools: deepseekTools,
|
|
162
266
|
tool_choice: deepseekToolChoices,
|
|
163
267
|
thinking,
|
|
268
|
+
...(deepseekOptions.userId != null && {
|
|
269
|
+
user_id: deepseekOptions.userId,
|
|
270
|
+
}),
|
|
164
271
|
...(thinking?.type !== 'disabled' &&
|
|
165
|
-
|
|
166
|
-
reasoning_effort:
|
|
272
|
+
reasoningEffort != null && {
|
|
273
|
+
reasoning_effort: reasoningEffort,
|
|
167
274
|
}),
|
|
168
275
|
},
|
|
169
|
-
warnings:
|
|
276
|
+
warnings: allWarnings,
|
|
170
277
|
};
|
|
171
278
|
}
|
|
172
279
|
|
|
@@ -235,6 +342,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
235
342
|
[this.providerOptionsName]: {
|
|
236
343
|
promptCacheHitTokens: responseBody.usage?.prompt_cache_hit_tokens,
|
|
237
344
|
promptCacheMissTokens: responseBody.usage?.prompt_cache_miss_tokens,
|
|
345
|
+
...(responseBody.object != null && {
|
|
346
|
+
responseObject: responseBody.object,
|
|
347
|
+
}),
|
|
348
|
+
...(choice.index != null && { choiceIndex: choice.index }),
|
|
349
|
+
...(choice.message.role != null && {
|
|
350
|
+
messageRole: choice.message.role,
|
|
351
|
+
}),
|
|
352
|
+
...(choice.message.tool_calls != null && {
|
|
353
|
+
toolCallTypes: choice.message.tool_calls
|
|
354
|
+
.map(toolCall => toolCall.type)
|
|
355
|
+
.filter(type => type != null),
|
|
356
|
+
}),
|
|
357
|
+
...(choice.logprobs != null && { logprobs: choice.logprobs }),
|
|
358
|
+
...(responseBody.system_fingerprint != null && {
|
|
359
|
+
systemFingerprint: responseBody.system_fingerprint,
|
|
360
|
+
}),
|
|
238
361
|
},
|
|
239
362
|
},
|
|
240
363
|
request: { body: args },
|
|
@@ -288,10 +411,17 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
288
411
|
raw: undefined,
|
|
289
412
|
};
|
|
290
413
|
let usage: DeepSeekChatTokenUsage | undefined = undefined;
|
|
414
|
+
let systemFingerprint: string | undefined = undefined;
|
|
291
415
|
let isFirstChunk = true;
|
|
292
416
|
const providerOptionsName = this.providerOptionsName;
|
|
293
417
|
let isActiveReasoning = false;
|
|
294
418
|
let isActiveText = false;
|
|
419
|
+
let responseObject: 'chat.completion.chunk' | undefined;
|
|
420
|
+
let choiceIndex: number | undefined;
|
|
421
|
+
let messageRole: 'assistant' | undefined;
|
|
422
|
+
const toolCallTypes = new Map<number, 'function'>();
|
|
423
|
+
const contentLogprobs: DeepSeekChatLogprob[] = [];
|
|
424
|
+
const reasoningLogprobs: DeepSeekChatLogprob[] = [];
|
|
295
425
|
|
|
296
426
|
return {
|
|
297
427
|
stream: response.pipeThrough(
|
|
@@ -337,8 +467,22 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
337
467
|
usage = value.usage;
|
|
338
468
|
}
|
|
339
469
|
|
|
470
|
+
if (value.object != null) {
|
|
471
|
+
responseObject = value.object;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// The fingerprint is repeated on stream chunks; keep the latest
|
|
475
|
+
// non-null value in case it changes during the response.
|
|
476
|
+
if (value.system_fingerprint != null) {
|
|
477
|
+
systemFingerprint = value.system_fingerprint;
|
|
478
|
+
}
|
|
479
|
+
|
|
340
480
|
const choice = value.choices[0];
|
|
341
481
|
|
|
482
|
+
if (choice?.index != null) {
|
|
483
|
+
choiceIndex = choice.index;
|
|
484
|
+
}
|
|
485
|
+
|
|
342
486
|
if (choice?.finish_reason != null) {
|
|
343
487
|
finishReason = {
|
|
344
488
|
unified: mapDeepSeekFinishReason(choice.finish_reason),
|
|
@@ -346,12 +490,24 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
346
490
|
};
|
|
347
491
|
}
|
|
348
492
|
|
|
493
|
+
if (choice?.logprobs?.content != null) {
|
|
494
|
+
contentLogprobs.push(...choice.logprobs.content);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (choice?.logprobs?.reasoning_content != null) {
|
|
498
|
+
reasoningLogprobs.push(...choice.logprobs.reasoning_content);
|
|
499
|
+
}
|
|
500
|
+
|
|
349
501
|
if (choice?.delta == null) {
|
|
350
502
|
return;
|
|
351
503
|
}
|
|
352
504
|
|
|
353
505
|
const delta = choice.delta;
|
|
354
506
|
|
|
507
|
+
if (delta.role != null) {
|
|
508
|
+
messageRole = delta.role;
|
|
509
|
+
}
|
|
510
|
+
|
|
355
511
|
// enqueue reasoning before text deltas:
|
|
356
512
|
const reasoningContent = delta.reasoning_content;
|
|
357
513
|
if (reasoningContent) {
|
|
@@ -403,6 +559,10 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
403
559
|
}
|
|
404
560
|
|
|
405
561
|
for (const toolCallDelta of delta.tool_calls) {
|
|
562
|
+
if (toolCallDelta.type != null) {
|
|
563
|
+
toolCallTypes.set(toolCallDelta.index, toolCallDelta.type);
|
|
564
|
+
}
|
|
565
|
+
|
|
406
566
|
const index = toolCallDelta.index;
|
|
407
567
|
|
|
408
568
|
if (toolCalls[index] == null) {
|
|
@@ -513,6 +673,26 @@ export class DeepSeekChatLanguageModel implements LanguageModelV3 {
|
|
|
513
673
|
usage?.prompt_cache_hit_tokens ?? undefined,
|
|
514
674
|
promptCacheMissTokens:
|
|
515
675
|
usage?.prompt_cache_miss_tokens ?? undefined,
|
|
676
|
+
...(responseObject != null && { responseObject }),
|
|
677
|
+
...(choiceIndex != null && { choiceIndex }),
|
|
678
|
+
...(messageRole != null && { messageRole }),
|
|
679
|
+
...(toolCallTypes.size > 0 && {
|
|
680
|
+
toolCallTypes: [...toolCallTypes.entries()]
|
|
681
|
+
.sort(([left], [right]) => left - right)
|
|
682
|
+
.map(([, type]) => type),
|
|
683
|
+
}),
|
|
684
|
+
...((contentLogprobs.length > 0 ||
|
|
685
|
+
reasoningLogprobs.length > 0) && {
|
|
686
|
+
logprobs: {
|
|
687
|
+
...(contentLogprobs.length > 0 && {
|
|
688
|
+
content: contentLogprobs,
|
|
689
|
+
}),
|
|
690
|
+
...(reasoningLogprobs.length > 0 && {
|
|
691
|
+
reasoning_content: reasoningLogprobs,
|
|
692
|
+
}),
|
|
693
|
+
},
|
|
694
|
+
}),
|
|
695
|
+
...(systemFingerprint != null && { systemFingerprint }),
|
|
516
696
|
},
|
|
517
697
|
},
|
|
518
698
|
});
|
|
@@ -2,31 +2,57 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
|
|
3
3
|
// https://api-docs.deepseek.com/quick_start/pricing
|
|
4
4
|
export type DeepSeekChatModelId =
|
|
5
|
-
| 'deepseek-
|
|
6
|
-
| 'deepseek-
|
|
5
|
+
| 'deepseek-v4-flash'
|
|
6
|
+
| 'deepseek-v4-pro'
|
|
7
7
|
| 'deepseek-v4-flash-vision-exp'
|
|
8
|
+
// Retired aliases remain assignable through the string escape hatch, but are
|
|
9
|
+
// intentionally omitted from first-class editor suggestions.
|
|
8
10
|
| (string & {});
|
|
9
11
|
|
|
10
12
|
export const deepseekLanguageModelOptions = z.object({
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* Whether to return log probabilities for generated tokens.
|
|
15
|
+
*/
|
|
16
|
+
logprobs: z.boolean().optional(),
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Number of most likely tokens to return at each token position.
|
|
20
|
+
*
|
|
21
|
+
* Setting this option automatically enables `logprobs`.
|
|
22
|
+
*/
|
|
23
|
+
topLogprobs: z.number().int().min(0).max(20).optional(),
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An opaque identifier for the end user. DeepSeek uses this identifier for
|
|
27
|
+
* content-safety tracing and request isolation.
|
|
13
28
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
29
|
+
* Must contain only ASCII letters, numbers, underscores, and hyphens, and
|
|
30
|
+
* must be at most 512 characters long.
|
|
31
|
+
*/
|
|
32
|
+
userId: z
|
|
33
|
+
.string()
|
|
34
|
+
.regex(/^[a-zA-Z0-9_-]+$/, 'userId must match /^[a-zA-Z0-9_-]+$/')
|
|
35
|
+
.max(512, 'userId must be at most 512 characters long')
|
|
36
|
+
.optional(),
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Type of thinking to use. Defaults to `enabled`.
|
|
16
40
|
*/
|
|
17
41
|
thinking: z
|
|
18
42
|
.object({
|
|
43
|
+
// `adaptive` is accepted at runtime for backwards compatibility and
|
|
44
|
+
// mapped to `enabled`, but is intentionally excluded from the exported
|
|
45
|
+
// provider options type.
|
|
19
46
|
type: z.enum(['adaptive', 'enabled', 'disabled']).optional(),
|
|
20
47
|
})
|
|
21
48
|
.optional(),
|
|
22
49
|
|
|
23
50
|
/**
|
|
24
51
|
* Controls the thinking strength for DeepSeek V4 reasoning models.
|
|
25
|
-
*
|
|
26
|
-
* DeepSeek's API accepts `low`, `medium`, `high`, `xhigh`, and `max`.
|
|
27
|
-
* Per their docs, `low` and `medium` are mapped to `high`, and `xhigh`
|
|
28
|
-
* is mapped to `max` server-side for compatibility with other providers.
|
|
29
52
|
*/
|
|
53
|
+
// `medium` and `xhigh` are accepted at runtime for backwards compatibility
|
|
54
|
+
// and mapped to canonical DeepSeek values, but are intentionally excluded
|
|
55
|
+
// from the exported provider options type.
|
|
30
56
|
reasoningEffort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
|
|
31
57
|
|
|
32
58
|
/**
|
|
@@ -37,6 +63,71 @@ export const deepseekLanguageModelOptions = z.object({
|
|
|
37
63
|
strictJsonSchema: z.boolean().optional(),
|
|
38
64
|
});
|
|
39
65
|
|
|
40
|
-
export type DeepSeekLanguageModelOptions =
|
|
41
|
-
|
|
66
|
+
export type DeepSeekLanguageModelOptions = {
|
|
67
|
+
/**
|
|
68
|
+
* Whether to return log probabilities for generated tokens.
|
|
69
|
+
*/
|
|
70
|
+
logprobs?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Number of most likely tokens to return at each token position.
|
|
74
|
+
*
|
|
75
|
+
* Setting this option automatically enables `logprobs`.
|
|
76
|
+
*/
|
|
77
|
+
topLogprobs?: number;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* An opaque identifier for the end user. DeepSeek uses this identifier for
|
|
81
|
+
* content-safety tracing and request isolation.
|
|
82
|
+
*
|
|
83
|
+
* Must contain only ASCII letters, numbers, underscores, and hyphens, and
|
|
84
|
+
* must be at most 512 characters long.
|
|
85
|
+
*/
|
|
86
|
+
userId?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Controls whether thinking mode is enabled. Defaults to `enabled`.
|
|
90
|
+
*/
|
|
91
|
+
thinking?: {
|
|
92
|
+
type?: 'enabled' | 'disabled';
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Controls the thinking strength for DeepSeek V4 reasoning models.
|
|
97
|
+
*/
|
|
98
|
+
reasoningEffort?: 'low' | 'high' | 'max';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether to use strict JSON schema validation for structured outputs.
|
|
102
|
+
* Only applies when the serving endpoint supports JSON schema response
|
|
103
|
+
* formats (e.g. Azure). Defaults to `true`.
|
|
104
|
+
*/
|
|
105
|
+
strictJsonSchema?: boolean;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const deepseekMessageProviderOptions = z.object({
|
|
109
|
+
/**
|
|
110
|
+
* The name of the participant represented by the message.
|
|
111
|
+
*
|
|
112
|
+
* Supported on system, user, and assistant messages.
|
|
113
|
+
*/
|
|
114
|
+
name: z.string().optional(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export type DeepSeekMessageProviderOptions = z.infer<
|
|
118
|
+
typeof deepseekMessageProviderOptions
|
|
119
|
+
>;
|
|
120
|
+
|
|
121
|
+
export const deepseekAssistantMessageProviderOptions =
|
|
122
|
+
deepseekMessageProviderOptions.extend({
|
|
123
|
+
/**
|
|
124
|
+
* Whether the assistant message content is a prefix that DeepSeek should
|
|
125
|
+
* continue. This beta feature is only supported on the final assistant
|
|
126
|
+
* message when using a beta base URL.
|
|
127
|
+
*/
|
|
128
|
+
prefix: z.literal(true).optional(),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export type DeepSeekAssistantMessageProviderOptions = z.infer<
|
|
132
|
+
typeof deepseekAssistantMessageProviderOptions
|
|
42
133
|
>;
|
|
@@ -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 LanguageModelV3CallOptions,
|
|
4
|
+
type SharedV3Warning,
|
|
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: LanguageModelV3CallOptions['tools'];
|
|
15
17
|
toolChoice?: LanguageModelV3CallOptions['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
|
@@ -61,9 +61,8 @@ export interface DeepSeekProvider extends ProviderV3 {
|
|
|
61
61
|
export function createDeepSeek(
|
|
62
62
|
options: DeepSeekProviderSettings = {},
|
|
63
63
|
): DeepSeekProvider {
|
|
64
|
-
const baseURL =
|
|
65
|
-
options.baseURL ?? 'https://api.deepseek.com'
|
|
66
|
-
);
|
|
64
|
+
const baseURL =
|
|
65
|
+
withoutTrailingSlash(options.baseURL) ?? 'https://api.deepseek.com';
|
|
67
66
|
|
|
68
67
|
const getHeaders = () =>
|
|
69
68
|
withUserAgentSuffix(
|
|
@@ -84,6 +83,8 @@ export function createDeepSeek(
|
|
|
84
83
|
url: ({ path }) => `${baseURL}${path}`,
|
|
85
84
|
headers: getHeaders,
|
|
86
85
|
fetch: options.fetch,
|
|
86
|
+
supportsAssistantPrefixCompletion: baseURL.endsWith('/beta'),
|
|
87
|
+
supportsStrictToolCalls: baseURL.endsWith('/beta'),
|
|
87
88
|
});
|
|
88
89
|
};
|
|
89
90
|
|