Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 345b1d7

Browse files
committed
feat: add DeepSeek V4 model support with thinking/reasoning_content handling
Add deepseek-v4-flash and deepseek-v4-pro model definitions with preserveReasoning enabled. Update the DeepSeek provider to use the model info preserveReasoning flag instead of hardcoding deepseek-reasoner. Also update the OpenAI compatible provider to recognize V4 model IDs. Closes #12203
1 parent ad25634 commit 345b1d7

4 files changed

Lines changed: 87 additions & 4 deletions

File tree

packages/types/src/providers/deepseek.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@ export const deepSeekModels = {
3232
cacheReadsPrice: 0.028, // $0.028 per million tokens (cache hit) - Updated Dec 9, 2025
3333
description: `DeepSeek-V3.2 (Thinking Mode) achieves performance comparable to OpenAI-o1 across math, code, and reasoning tasks. Supports Chain of Thought reasoning with up to 8K output tokens. Supports JSON output, tool calls, and chat prefix completion (beta).`,
3434
},
35+
"deepseek-v4-flash": {
36+
maxTokens: 8192,
37+
contextWindow: 128_000,
38+
supportsImages: false,
39+
supportsPromptCache: true,
40+
preserveReasoning: true,
41+
inputPrice: 0.28,
42+
outputPrice: 0.42,
43+
cacheWritesPrice: 0.28,
44+
cacheReadsPrice: 0.028,
45+
description: `DeepSeek V4 Flash with thinking/reasoning support. Requires reasoning_content to be passed back during tool call sequences.`,
46+
},
47+
"deepseek-v4-pro": {
48+
maxTokens: 8192,
49+
contextWindow: 128_000,
50+
supportsImages: false,
51+
supportsPromptCache: true,
52+
preserveReasoning: true,
53+
inputPrice: 0.28,
54+
outputPrice: 0.42,
55+
cacheWritesPrice: 0.28,
56+
cacheReadsPrice: 0.028,
57+
description: `DeepSeek V4 Pro with thinking/reasoning support. Requires reasoning_content to be passed back during tool call sequences.`,
58+
},
3559
} as const satisfies Record<string, ModelInfo>
3660

3761
// https://api-docs.deepseek.com/quick_start/parameter_settings

src/api/providers/__tests__/deepseek.spec.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ vi.mock("openai", () => {
3030
}
3131

3232
// Check if this is a reasoning_content test by looking at model
33-
const isReasonerModel = options.model?.includes("deepseek-reasoner")
33+
const isReasonerModel =
34+
options.model?.includes("deepseek-reasoner") || options.model?.includes("deepseek-v4")
3435
const isToolCallTest = options.tools?.length > 0
3536

3637
// Return async iterator for streaming
@@ -240,6 +241,24 @@ describe("DeepSeekHandler", () => {
240241
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
241242
})
242243

244+
it("should have preserveReasoning enabled for deepseek-v4-flash", () => {
245+
const handlerV4Flash = new DeepSeekHandler({
246+
...mockOptions,
247+
apiModelId: "deepseek-v4-flash",
248+
})
249+
const model = handlerV4Flash.getModel()
250+
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
251+
})
252+
253+
it("should have preserveReasoning enabled for deepseek-v4-pro", () => {
254+
const handlerV4Pro = new DeepSeekHandler({
255+
...mockOptions,
256+
apiModelId: "deepseek-v4-pro",
257+
})
258+
const model = handlerV4Pro.getModel()
259+
expect((model.info as ModelInfo).preserveReasoning).toBe(true)
260+
})
261+
243262
it("should NOT have preserveReasoning enabled for deepseek-chat", () => {
244263
// deepseek-chat doesn't use thinking mode, so no need to preserve reasoning
245264
const model = handler.getModel()
@@ -459,6 +478,44 @@ describe("DeepSeekHandler", () => {
459478
)
460479
})
461480

481+
it("should pass thinking parameter for deepseek-v4-flash model", async () => {
482+
const v4FlashHandler = new DeepSeekHandler({
483+
...mockOptions,
484+
apiModelId: "deepseek-v4-flash",
485+
})
486+
487+
const stream = v4FlashHandler.createMessage(systemPrompt, messages)
488+
for await (const _chunk of stream) {
489+
// Consume the stream
490+
}
491+
492+
expect(mockCreate).toHaveBeenCalledWith(
493+
expect.objectContaining({
494+
thinking: { type: "enabled" },
495+
}),
496+
{},
497+
)
498+
})
499+
500+
it("should pass thinking parameter for deepseek-v4-pro model", async () => {
501+
const v4ProHandler = new DeepSeekHandler({
502+
...mockOptions,
503+
apiModelId: "deepseek-v4-pro",
504+
})
505+
506+
const stream = v4ProHandler.createMessage(systemPrompt, messages)
507+
for await (const _chunk of stream) {
508+
// Consume the stream
509+
}
510+
511+
expect(mockCreate).toHaveBeenCalledWith(
512+
expect.objectContaining({
513+
thinking: { type: "enabled" },
514+
}),
515+
{},
516+
)
517+
})
518+
462519
it("should NOT pass thinking parameter for deepseek-chat model", async () => {
463520
const chatHandler = new DeepSeekHandler({
464521
...mockOptions,

src/api/providers/deepseek.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ export class DeepSeekHandler extends OpenAiHandler {
5555
const modelId = this.options.apiModelId ?? deepSeekDefaultModelId
5656
const { info: modelInfo } = this.getModel()
5757

58-
// Check if this is a thinking-enabled model (deepseek-reasoner)
59-
const isThinkingModel = modelId.includes("deepseek-reasoner")
58+
// Check if this is a thinking-enabled model by looking at the model info's preserveReasoning flag.
59+
// This covers deepseek-reasoner and newer models like deepseek-v4-flash/deepseek-v4-pro.
60+
const isThinkingModel = "preserveReasoning" in modelInfo && modelInfo.preserveReasoning === true
6061

6162
// Convert messages to R1 format (merges consecutive same-role messages)
6263
// This is required for DeepSeek which does not support successive messages with the same role

src/api/providers/openai.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
8989
const modelId = this.options.openAiModelId ?? ""
9090
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
9191
const isAzureAiInference = this._isAzureAiInference(modelUrl)
92-
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
92+
const deepseekReasoner =
93+
modelId.includes("deepseek-reasoner") || modelId.includes("deepseek-v4") || enabledR1Format
9394

9495
if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) {
9596
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata)

0 commit comments

Comments
 (0)