Skip to content
3 changes: 3 additions & 0 deletions server/src/routes/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,11 @@ router.post('/api/proxy/ai', async (req, res) => {
targetUrl = urlObj.toString();
}

// DeepSeek Reasoner does not support the reasoning parameter
const isDeepSeekReasoner = model.trim() === 'deepseek-reasoner';
const effectiveRequestBody = (
reasoningEffort
&& !isDeepSeekReasoner
&& typeof requestBody === 'object'
&& requestBody !== null
&& (apiType === 'openai' || apiType === 'openai-responses' || apiType === 'openai-compatible')
Expand Down
33 changes: 28 additions & 5 deletions src/services/aiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,20 @@ export class AIService {
}

private isDeepSeekReasonerModel(): boolean {
return this.getApiType() === 'openai' && this.config.model.trim() === 'deepseek-reasoner';
const apiType = this.getApiType();
return (apiType === 'openai' || apiType === 'openai-compatible') && this.config.model.trim() === 'deepseek-reasoner';
}

/**
* Check if the model is a DeepSeek model with default thinking enabled (e.g. deepseek-v4-pro, deepseek-v4-flash).
* These models consume max_tokens for reasoning, leaving 0 tokens for content if max_tokens is too low.
* We need to explicitly disable thinking for these models.
*/
private isDeepSeekThinkingModel(): boolean {
const model = this.config.model.trim().toLowerCase();
return (this.getApiType() === 'openai' || this.getApiType() === 'openai-compatible')
&& model.startsWith('deepseek-')
&& model !== 'deepseek-reasoner';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

private isMiMoModel(): boolean {
Expand Down Expand Up @@ -162,6 +175,7 @@ export class AIService {
{ role: 'user', content: options.user },
];
const isDeepSeekReasoner = this.isDeepSeekReasonerModel();
const isDeepSeekThinking = this.isDeepSeekThinkingModel();
const isMiMoModel = this.isMiMoModel();

const requestBody = apiType === 'openai-responses'
Expand All @@ -171,15 +185,15 @@ export class AIService {
temperature: options.temperature,
max_output_tokens: options.maxTokens,
...(reasoning ? { reasoning } : {}),
...(isMiMoModel ? { thinking: { type: 'disabled' } } : {}),
...(isMiMoModel || isDeepSeekThinking ? { thinking: { type: 'disabled' } } : {}),
}
: {
model: this.config.model,
messages,
max_tokens: options.maxTokens,
...(!isDeepSeekReasoner ? { temperature: options.temperature } : {}),
...(!isDeepSeekReasoner && reasoning && apiType !== 'openai-compatible' ? { reasoning } : {}),
...(isMiMoModel ? { thinking: { type: 'disabled' } } : {}),
...(isMiMoModel || isDeepSeekThinking ? { thinking: { type: 'disabled' } } : {}),
};

let data: Record<string, unknown>;
Expand Down Expand Up @@ -264,11 +278,18 @@ export class AIService {
return content;
}

// Only fall back to reasoning_content for the dedicated deepseek-reasoner model.
// Other DeepSeek models (e.g. deepseek-v4-flash, deepseek-v4-pro) may also return
// reasoning_content (the thinking chain), but we must not use it as the final answer.
const reasoningContent = message?.reasoning_content;
if (reasoningContent) {
if (reasoningContent && isDeepSeekReasoner) {
this.logAIRequestDebug(startTime, { apiType, model, configId }, { responseLength: reasoningContent.length }, httpDetails);
return reasoningContent;
}

if (!content && reasoningContent) {
logger.warn('ai', 'Model returned reasoning_content but empty content', { model, configId });
}
}

this.logAIRequestDebug(startTime, { apiType, model, configId }, { error: 'request failed' }, httpDetails);
Expand Down Expand Up @@ -479,7 +500,7 @@ ${options.user}` : options.user;
system,
user: prompt,
temperature: 0.3,
maxTokens: 700,
maxTokens: 1000,
signal,
});

Expand Down Expand Up @@ -587,8 +608,10 @@ ${repoInfo}

private parseAIResponse(content: string): { summary: string; tags: string[]; platforms: string[] } {
try {
// Strip thinking tags that some models embed in the content field (e.g. <think>...</think>)
const cleaned = content
.trim()
.replace(/<think>[\s\S]*?<\/think>/gi, '')
.replace(/^```(?:json)?\s*/i, '')
.replace(/\s*```$/i, '')
.trim();
Expand Down
Loading