mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-25 05:53:57 +00:00
fix(openai): resolve 400 error on cross-model switching with DeepSeek V4 thinking mode
This commit is contained in:
parent
fc15778e20
commit
a2d7b64349
3 changed files with 38 additions and 4 deletions
|
|
@ -99,7 +99,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
const modelId = this.options.openAiModelId ?? ""
|
||||
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
|
||||
const isAzureAiInference = this._isAzureAiInference(modelUrl)
|
||||
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
|
||||
const deepseekReasoner =
|
||||
modelId.includes("deepseek-reasoner") ||
|
||||
modelId.includes("deepseek-v4-pro") ||
|
||||
modelId.includes("deepseek-v4-flash") ||
|
||||
enabledR1Format
|
||||
|
||||
if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) {
|
||||
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata)
|
||||
|
|
@ -340,7 +344,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
})
|
||||
|
||||
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
|
||||
const deepseekReasoner = id.includes("deepseek-reasoner") || enabledR1Format
|
||||
const deepseekReasoner =
|
||||
id.includes("deepseek-reasoner") ||
|
||||
id.includes("deepseek-v4-pro") ||
|
||||
id.includes("deepseek-v4-flash") ||
|
||||
enabledR1Format
|
||||
|
||||
return {
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -317,6 +317,9 @@ export function convertToOpenAiMessages(
|
|||
if (mapped) {
|
||||
;(baseMessage as any).reasoning_details = mapped
|
||||
}
|
||||
if (messageWithDetails.reasoning_content !== undefined) {
|
||||
;(baseMessage as any).reasoning_content = messageWithDetails.reasoning_content
|
||||
}
|
||||
}
|
||||
|
||||
openAiMessages.push(baseMessage)
|
||||
|
|
@ -494,6 +497,11 @@ export function convertToOpenAiMessages(
|
|||
baseMessage.reasoning_details = mapped
|
||||
}
|
||||
|
||||
// Pass through reasoning_content for DeepSeek V4/R1 thinking mode
|
||||
if (messageWithDetails.reasoning_content !== undefined) {
|
||||
;(baseMessage as any).reasoning_content = messageWithDetails.reasoning_content
|
||||
}
|
||||
|
||||
// Add tool_calls after reasoning_details
|
||||
// Cannot be an empty array. API expects an array with minimum length 1, and will respond with an error if it's empty
|
||||
if (tool_calls.length > 0) {
|
||||
|
|
|
|||
|
|
@ -4469,6 +4469,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
|
||||
const cleanConversationHistory: (Anthropic.Messages.MessageParam | ReasoningItemForRequest)[] = []
|
||||
|
||||
// Check if the current model requires reasoning to be preserved (e.g., DeepSeek V4/R1).
|
||||
// When preserveReasoning is true, ALL assistant messages must include reasoning_content
|
||||
// to satisfy the API protocol. Messages from other models (e.g., Gemini) that lack
|
||||
// reasoning_content need to be backfilled with an empty string.
|
||||
const shouldPreserveForApi = this.api.getModel().info.preserveReasoning === true
|
||||
|
||||
for (const msg of messages) {
|
||||
// Standalone reasoning: send encrypted, skip plain text
|
||||
if (msg.type === "reasoning") {
|
||||
|
|
@ -4595,10 +4601,22 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
|
||||
// Default path for regular messages (no embedded reasoning)
|
||||
if (msg.role) {
|
||||
cleanConversationHistory.push({
|
||||
const baseMessage: any = {
|
||||
role: msg.role,
|
||||
content: msg.content as Anthropic.Messages.ContentBlockParam[] | string,
|
||||
})
|
||||
}
|
||||
|
||||
// DeepSeek thinking mode requires ALL assistant messages to have reasoning_content.
|
||||
// If a message from another model (e.g., Gemini) lacks it, add an empty placeholder
|
||||
// to prevent 400 errors when switching back to DeepSeek mid-conversation.
|
||||
if (msg.role === "assistant" && shouldPreserveForApi) {
|
||||
// Use a space instead of an empty string. Some proxies/gateways or
|
||||
// internal SDK logic may silently strip empty string fields, causing
|
||||
// DeepSeek to reject the request with 400.
|
||||
baseMessage.reasoning_content = " "
|
||||
}
|
||||
|
||||
cleanConversationHistory.push(baseMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue