mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix: add proper support for DeepSeek V3 models in OpenRouter
- DeepSeek V3 models (v3, 3.2, 3.2-exp) now correctly use standard message format instead of R1 format - Added explicit model detection to differentiate between DeepSeek R1 and V3 models - Ensured V3 models have proper tool calling support and reasonable token limits - Added comprehensive tests to verify correct behavior for both V3 and R1 models Fixes #9779
This commit is contained in:
parent
822343ccdf
commit
d48fb302aa
3 changed files with 151 additions and 1 deletions
|
|
@ -338,4 +338,129 @@ describe("OpenRouterHandler", () => {
|
|||
await expect(handler.completePrompt("test prompt")).rejects.toThrow("Unexpected error")
|
||||
})
|
||||
})
|
||||
|
||||
describe("DeepSeek V3 Model Handling", () => {
|
||||
it("should NOT use R1 format for DeepSeek V3 models", async () => {
|
||||
const deepseekV3Handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "deepseek/deepseek-v3",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "test" }, finish_reason: null }],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [{ delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
const generator = deepseekV3Handler.createMessage("system prompt", [])
|
||||
const chunks = []
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Verify that the messages were NOT converted to R1 format
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({ role: "system", content: expect.anything() }),
|
||||
]),
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should NOT use R1 format for DeepSeek 3.2 models", async () => {
|
||||
const deepseek32Handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "deepseek/deepseek-3.2",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "test" }, finish_reason: null }],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [{ delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
const generator = deepseek32Handler.createMessage("system prompt", [])
|
||||
const chunks = []
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Verify that the messages were NOT converted to R1 format
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({ role: "system", content: expect.anything() }),
|
||||
]),
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
|
||||
it("should still use R1 format for DeepSeek R1 models", async () => {
|
||||
const deepseekR1Handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "deepseek/deepseek-r1",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "test" }, finish_reason: null }],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [{ delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5 },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
const generator = deepseekR1Handler.createMessage("system prompt", [])
|
||||
const chunks = []
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Verify that the messages WERE converted to R1 format (user role instead of system)
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: expect.arrayContaining([
|
||||
expect.objectContaining({ role: "user", content: expect.anything() }),
|
||||
]),
|
||||
}),
|
||||
undefined,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -266,5 +266,23 @@ export const parseOpenRouterModel = ({
|
|||
modelInfo.maxTokens = 32768
|
||||
}
|
||||
|
||||
// Configure DeepSeek V3 models properly
|
||||
// These models support standard tool calling but NOT R1 format
|
||||
if (
|
||||
id.startsWith("deepseek/deepseek-v3") ||
|
||||
id.startsWith("deepseek/deepseek-3") ||
|
||||
id === "deepseek/deepseek-chat"
|
||||
) {
|
||||
// Ensure these models are marked as supporting native tools
|
||||
// but NOT reasoning format (they're not R1 models)
|
||||
if (modelInfo.supportsNativeTools === undefined) {
|
||||
modelInfo.supportsNativeTools = true
|
||||
}
|
||||
// Ensure reasonable max tokens if not set
|
||||
if (!modelInfo.maxTokens || modelInfo.maxTokens < 8192) {
|
||||
modelInfo.maxTokens = 8192
|
||||
}
|
||||
}
|
||||
|
||||
return modelInfo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
// DeepSeek highly recommends using user instead of system role.
|
||||
// DeepSeek R1 models require user instead of system role.
|
||||
// Note: DeepSeek V3 models (deepseek-v3, deepseek-3.2, etc.) do NOT use R1 format
|
||||
if (modelId.startsWith("deepseek/deepseek-r1") || modelId === "perplexity/sonar-reasoning") {
|
||||
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
}
|
||||
|
|
@ -388,7 +389,13 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
info = this.endpoints[this.options.openRouterSpecificProvider]
|
||||
}
|
||||
|
||||
// Only DeepSeek R1 models use special temperature and topP settings
|
||||
// DeepSeek V3 models (v3, 3.2, 3.2-exp) use standard settings
|
||||
const isDeepSeekR1 = id.startsWith("deepseek/deepseek-r1") || id === "perplexity/sonar-reasoning"
|
||||
const isDeepSeekV3 =
|
||||
id.startsWith("deepseek/deepseek-v3") ||
|
||||
id.startsWith("deepseek/deepseek-3") ||
|
||||
id === "deepseek/deepseek-chat"
|
||||
|
||||
const params = getModelParams({
|
||||
format: "openrouter",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue