Skip to content

Commit 06867d7

Browse files
authored
Merge pull request #578 from freedomsky11/fix/deepseek-v4-thinking-mode
fix(deepseek): disable thinking mode to fix EMPTY_MESSAGE
2 parents 7fc2f1f + b50c8d4 commit 06867d7

4 files changed

Lines changed: 96 additions & 5 deletions

File tree

src/commands/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export const MODEL_LIST = {
135135
'mistral-moderation-2411',
136136
'mistral-moderation-latest'
137137
],
138-
deepseek: ['deepseek-chat', 'deepseek-reasoner'],
138+
deepseek: ['deepseek-v4-flash', 'deepseek-v4-pro'],
139139

140140
// AI/ML API available chat-completion models
141141
// https://api.aimlapi.com/v1/models
@@ -896,7 +896,7 @@ export const RECOMMENDED_MODELS: Record<string, string> = {
896896
[OCO_AI_PROVIDER_ENUM.GEMINI]: 'gemini-1.5-flash',
897897
[OCO_AI_PROVIDER_ENUM.GROQ]: 'llama3-70b-8192',
898898
[OCO_AI_PROVIDER_ENUM.MISTRAL]: 'mistral-small-latest',
899-
[OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'deepseek-chat',
899+
[OCO_AI_PROVIDER_ENUM.DEEPSEEK]: 'deepseek-v4-flash',
900900
[OCO_AI_PROVIDER_ENUM.OPENROUTER]: 'openai/gpt-4o-mini',
901901
[OCO_AI_PROVIDER_ENUM.AIMLAPI]: 'gpt-4o-mini'
902902
};

src/engine/deepseek.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import { OpenAiEngine, OpenAiConfig } from './openAi';
77

88
export interface DeepseekConfig extends OpenAiConfig {}
99

10+
// DeepSeek supports an undocumented `thinking` field in the chat completion
11+
// request body (not part of the OpenAI type definitions). The JavaScript SDK
12+
// forwards unknown top-level fields as-is, so `thinking` must sit at the top
13+
// level of the request — wrapping it in `extra_body` (a Python SDK helper)
14+
// would send an `extra_body` field instead and leave thinking mode enabled.
15+
interface DeepseekChatCompletionParams
16+
extends OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming {
17+
thinking?: { type: 'enabled' | 'disabled' };
18+
}
19+
1020
export class DeepseekEngine extends OpenAiEngine {
1121
constructor(config: DeepseekConfig) {
1222
// Call OpenAIEngine constructor with forced Deepseek baseURL
@@ -21,12 +31,17 @@ export class DeepseekEngine extends OpenAiEngine {
2131
public generateCommitMessage = async (
2232
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
2333
): Promise<string | null> => {
24-
const params = {
34+
const params: DeepseekChatCompletionParams = {
2535
model: this.config.model,
2636
messages,
2737
temperature: 0,
2838
top_p: 0.1,
29-
max_tokens: this.config.maxTokensOutput
39+
max_tokens: this.config.maxTokensOutput,
40+
// DeepSeek V4: disable thinking mode (enabled by default with effort=high).
41+
// Thinking mode returns reasoning in `reasoning_content` and can leave
42+
// `content` empty when max_tokens is low, causing EMPTY_MESSAGE errors.
43+
// Commit message generation doesn't need chain-of-thought.
44+
thinking: { type: 'disabled' }
3045
};
3146

3247
try {

src/utils/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export function getRecommendedModel(provider: string): string | null {
202202
case OCO_AI_PROVIDER_ENUM.MISTRAL:
203203
return 'mistral-small-latest';
204204
case OCO_AI_PROVIDER_ENUM.DEEPSEEK:
205-
return 'deepseek-chat';
205+
return 'deepseek-v4-flash';
206206
case OCO_AI_PROVIDER_ENUM.OPENROUTER:
207207
return 'openai/gpt-4o-mini';
208208
case OCO_AI_PROVIDER_ENUM.AIMLAPI:

test/unit/deepseek.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { OpenAI } from 'openai';
2+
import { DeepseekEngine } from '../../src/engine/deepseek';
3+
4+
describe('DeepseekEngine', () => {
5+
const baseConfig = {
6+
apiKey: 'test-deepseek-key',
7+
maxTokensInput: 4096,
8+
maxTokensOutput: 256
9+
};
10+
11+
const messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam> = [
12+
{ role: 'system', content: 'system message' },
13+
{ role: 'user', content: 'diff --git a/file b/file' }
14+
];
15+
16+
it('disables thinking mode via a top-level request body field', async () => {
17+
const engine = new DeepseekEngine({
18+
...baseConfig,
19+
model: 'deepseek-v4-flash'
20+
});
21+
22+
const create = jest
23+
.spyOn(engine.client.chat.completions, 'create')
24+
.mockResolvedValue({
25+
choices: [{ message: { content: 'feat(deepseek): thinking off' } }]
26+
} as any);
27+
28+
await engine.generateCommitMessage(messages);
29+
30+
// The JavaScript OpenAI SDK forwards unknown top-level fields as-is, so
31+
// `thinking` must be a top-level key in the request body for DeepSeek to
32+
// honour it. See https://api-docs.deepseek.com/guides/thinking_mode
33+
expect(create).toHaveBeenCalledWith(
34+
expect.objectContaining({
35+
model: 'deepseek-v4-flash',
36+
max_tokens: 256,
37+
temperature: 0,
38+
top_p: 0.1,
39+
thinking: { type: 'disabled' }
40+
})
41+
);
42+
});
43+
44+
it('does not send extra_body (a Python SDK helper) in the request', async () => {
45+
const engine = new DeepseekEngine({
46+
...baseConfig,
47+
model: 'deepseek-v4-flash'
48+
});
49+
50+
const create = jest
51+
.spyOn(engine.client.chat.completions, 'create')
52+
.mockResolvedValue({
53+
choices: [{ message: { content: 'feat(deepseek): no extra_body' } }]
54+
} as any);
55+
56+
await engine.generateCommitMessage(messages);
57+
58+
// `extra_body` is only understood by the Python SDK. If it ever sneaks
59+
// back into the JS request, DeepSeek receives an `extra_body` field and
60+
// thinking mode stays enabled, re-introducing EMPTY_MESSAGE failures.
61+
expect(create).toHaveBeenCalledWith(
62+
expect.not.objectContaining({
63+
extra_body: expect.anything()
64+
})
65+
);
66+
});
67+
68+
it('uses the DeepSeek endpoint by default', () => {
69+
const engine = new DeepseekEngine({
70+
...baseConfig,
71+
model: 'deepseek-v4-flash'
72+
});
73+
74+
expect(engine.client.baseURL).toContain('api.deepseek.com');
75+
});
76+
});

0 commit comments

Comments
 (0)