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

Commit 65e9230

Browse files
committed
Preserve empty DeepSeek reasoning chunks
1 parent 67457a3 commit 65e9230

4 files changed

Lines changed: 100 additions & 4 deletions

File tree

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,51 @@ describe("DeepSeekHandler", () => {
439439
expect(reasoningChunks[1].text).toBe(" I'll analyze step by step.")
440440
})
441441

442+
it("should preserve empty reasoning_content chunks for DeepSeek V4", async () => {
443+
mockCreate.mockImplementationOnce(async () => ({
444+
[Symbol.asyncIterator]: async function* () {
445+
yield {
446+
choices: [
447+
{
448+
delta: { reasoning_content: "" },
449+
index: 0,
450+
},
451+
],
452+
usage: null,
453+
}
454+
yield {
455+
choices: [
456+
{
457+
delta: {},
458+
index: 0,
459+
finish_reason: "stop",
460+
},
461+
],
462+
usage: {
463+
prompt_tokens: 10,
464+
completion_tokens: 5,
465+
total_tokens: 15,
466+
},
467+
}
468+
},
469+
}))
470+
471+
const reasonerHandler = new DeepSeekHandler({
472+
...mockOptions,
473+
apiModelId: "deepseek-v4-pro",
474+
})
475+
476+
const chunks: any[] = []
477+
for await (const chunk of reasonerHandler.createMessage(systemPrompt, messages)) {
478+
chunks.push(chunk)
479+
}
480+
481+
expect(chunks.filter((chunk) => chunk.type === "reasoning")).toContainEqual({
482+
type: "reasoning",
483+
text: "",
484+
})
485+
})
486+
442487
it("should pass thinking parameter for DeepSeek V4 models", async () => {
443488
const reasonerHandler = new DeepSeekHandler({
444489
...mockOptions,

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,57 @@ describe("OpenAiHandler", () => {
463463
expect(callArgs.reasoning_effort).toBeUndefined()
464464
})
465465

466+
it("should preserve empty DeepSeek V4 reasoning_content chunks for OpenAI-compatible endpoints", async () => {
467+
mockCreate.mockImplementationOnce(async () => ({
468+
[Symbol.asyncIterator]: async function* () {
469+
yield {
470+
choices: [
471+
{
472+
delta: { reasoning_content: "" },
473+
index: 0,
474+
},
475+
],
476+
usage: null,
477+
}
478+
yield {
479+
choices: [
480+
{
481+
delta: {},
482+
index: 0,
483+
finish_reason: "stop",
484+
},
485+
],
486+
usage: {
487+
prompt_tokens: 10,
488+
completion_tokens: 5,
489+
total_tokens: 15,
490+
},
491+
}
492+
},
493+
}))
494+
495+
const deepSeekV4Handler = new OpenAiHandler({
496+
...mockOptions,
497+
openAiBaseUrl: "https://api.deepseek.com",
498+
openAiModelId: "deepseek-v4-flash",
499+
openAiCustomModelInfo: {
500+
contextWindow: 1_000_000,
501+
supportsPromptCache: true,
502+
preserveReasoning: true,
503+
},
504+
})
505+
506+
const chunks: any[] = []
507+
for await (const chunk of deepSeekV4Handler.createMessage(systemPrompt, messages)) {
508+
chunks.push(chunk)
509+
}
510+
511+
expect(chunks.filter((chunk) => chunk.type === "reasoning")).toContainEqual({
512+
type: "reasoning",
513+
text: "",
514+
})
515+
})
516+
466517
it("should include max_tokens when includeMaxTokens is true", async () => {
467518
const optionsWithMaxTokens: ApiHandlerOptions = {
468519
...mockOptions,

src/api/providers/deepseek.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ export class DeepSeekHandler extends OpenAiHandler {
125125

126126
// Handle reasoning_content from DeepSeek's interleaved thinking
127127
// This is the proper way DeepSeek sends thinking content in streaming
128-
if ("reasoning_content" in delta && delta.reasoning_content) {
128+
if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
129129
yield {
130130
type: "reasoning",
131-
text: (delta.reasoning_content as string) || "",
131+
text: delta.reasoning_content,
132132
}
133133
}
134134

src/api/providers/openai.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
231231
}
232232
}
233233

234-
if ("reasoning_content" in delta && delta.reasoning_content) {
234+
if ("reasoning_content" in delta && typeof delta.reasoning_content === "string") {
235235
yield {
236236
type: "reasoning",
237-
text: (delta.reasoning_content as string | undefined) || "",
237+
text: delta.reasoning_content,
238238
}
239239
}
240240

0 commit comments

Comments
 (0)