mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-10 03:28:03 +00:00
fix: add extra_body support for DeepSeek V3.1 Terminus reasoning control
- Use chat_template_kwargs with thinking parameter instead of reasoning param - Default to reasoning OFF for DeepSeek V3.1 Terminus - Enable reasoning only when explicitly requested via reasoning settings - Add comprehensive tests for the new functionality Fixes #8270
This commit is contained in:
parent
4405f5a0b3
commit
146d867db1
2 changed files with 197 additions and 2 deletions
|
|
@ -320,4 +320,157 @@ describe("OpenRouterHandler", () => {
|
|||
await expect(handler.completePrompt("test prompt")).rejects.toThrow("Unexpected error")
|
||||
})
|
||||
})
|
||||
|
||||
describe("DeepSeek V3.1 Terminus", () => {
|
||||
it("uses extra_body for reasoning control in createMessage", async () => {
|
||||
const handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "deepseek/deepseek-v3.1-terminus",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
id: "test-id",
|
||||
choices: [{ delta: { content: "test response" } }],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
// Test with reasoning disabled (default)
|
||||
await handler.createMessage("test", []).next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "deepseek/deepseek-v3.1-terminus",
|
||||
extra_body: {
|
||||
chat_template_kwargs: {
|
||||
thinking: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(mockCreate).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: expect.anything(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("enables thinking when reasoning is requested", async () => {
|
||||
// Mock getModels to return a model with reasoning capability
|
||||
const { getModels } = await import("../fetchers/modelCache")
|
||||
vitest.mocked(getModels).mockResolvedValueOnce({
|
||||
"deepseek/deepseek-v3.1-terminus": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.5,
|
||||
outputPrice: 1.5,
|
||||
description: "DeepSeek V3.1 Terminus",
|
||||
reasoningEffort: "high",
|
||||
},
|
||||
})
|
||||
|
||||
const handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "deepseek/deepseek-v3.1-terminus",
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
id: "test-id",
|
||||
choices: [{ delta: { content: "test response" } }],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
await handler.createMessage("test", []).next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "deepseek/deepseek-v3.1-terminus",
|
||||
extra_body: {
|
||||
chat_template_kwargs: {
|
||||
thinking: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("uses extra_body for reasoning control in completePrompt", async () => {
|
||||
const handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "deepseek/deepseek-v3.1-terminus",
|
||||
})
|
||||
|
||||
const mockResponse = { choices: [{ message: { content: "test completion" } }] }
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockResponse)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
await handler.completePrompt("test prompt")
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: "deepseek/deepseek-v3.1-terminus",
|
||||
extra_body: {
|
||||
chat_template_kwargs: {
|
||||
thinking: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(mockCreate).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
reasoning: expect.anything(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("does not use extra_body for other models", async () => {
|
||||
const handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "anthropic/claude-sonnet-4",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
id: "test-id",
|
||||
choices: [{ delta: { content: "test response" } }],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
await handler.createMessage("test", []).next()
|
||||
|
||||
expect(mockCreate).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
extra_body: expect.anything(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
|
|||
include_reasoning?: boolean
|
||||
// https://openrouter.ai/docs/use-cases/reasoning-tokens
|
||||
reasoning?: OpenRouterReasoningParams
|
||||
// For DeepSeek models that require extra_body
|
||||
extra_body?: {
|
||||
chat_template_kwargs?: {
|
||||
thinking?: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See `OpenAI.Chat.Completions.ChatCompletionChunk["usage"]`
|
||||
|
|
@ -141,6 +147,23 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
|
||||
const transforms = (this.options.openRouterUseMiddleOutTransform ?? true) ? ["middle-out"] : undefined
|
||||
|
||||
// For DeepSeek V3.1 Terminus, use extra_body to control reasoning
|
||||
const isDeepSeekV3Terminus = modelId === "deepseek/deepseek-v3.1-terminus"
|
||||
let extraBody: OpenRouterChatCompletionParams["extra_body"] = undefined
|
||||
|
||||
if (isDeepSeekV3Terminus) {
|
||||
// Default to reasoning OFF for DeepSeek V3.1 Terminus
|
||||
// Enable only if reasoning is explicitly requested
|
||||
const enableThinking = Boolean(
|
||||
reasoning && !reasoning.exclude && (reasoning.max_tokens || reasoning.effort),
|
||||
)
|
||||
extraBody = {
|
||||
chat_template_kwargs: {
|
||||
thinking: enableThinking,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// https://openrouter.ai/docs/transforms
|
||||
const completionParams: OpenRouterChatCompletionParams = {
|
||||
model: modelId,
|
||||
|
|
@ -160,7 +183,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
},
|
||||
}),
|
||||
...(transforms && { transforms }),
|
||||
...(reasoning && { reasoning }),
|
||||
// For DeepSeek V3.1 Terminus, use extra_body instead of reasoning param
|
||||
...(isDeepSeekV3Terminus ? { extra_body: extraBody } : reasoning && { reasoning }),
|
||||
}
|
||||
|
||||
let stream
|
||||
|
|
@ -248,6 +272,23 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
async completePrompt(prompt: string) {
|
||||
let { id: modelId, maxTokens, temperature, reasoning } = await this.fetchModel()
|
||||
|
||||
// For DeepSeek V3.1 Terminus, use extra_body to control reasoning
|
||||
const isDeepSeekV3Terminus = modelId === "deepseek/deepseek-v3.1-terminus"
|
||||
let extraBody: OpenRouterChatCompletionParams["extra_body"] = undefined
|
||||
|
||||
if (isDeepSeekV3Terminus) {
|
||||
// Default to reasoning OFF for DeepSeek V3.1 Terminus
|
||||
// Enable only if reasoning is explicitly requested
|
||||
const enableThinking = Boolean(
|
||||
reasoning && !reasoning.exclude && (reasoning.max_tokens || reasoning.effort),
|
||||
)
|
||||
extraBody = {
|
||||
chat_template_kwargs: {
|
||||
thinking: enableThinking,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const completionParams: OpenRouterChatCompletionParams = {
|
||||
model: modelId,
|
||||
max_tokens: maxTokens,
|
||||
|
|
@ -263,7 +304,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
allow_fallbacks: false,
|
||||
},
|
||||
}),
|
||||
...(reasoning && { reasoning }),
|
||||
// For DeepSeek V3.1 Terminus, use extra_body instead of reasoning param
|
||||
...(isDeepSeekV3Terminus ? { extra_body: extraBody } : reasoning && { reasoning }),
|
||||
}
|
||||
|
||||
let response
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue