fix: add support for additional DeepSeek models in OpenRouter

- Add deepseek-chat prefix detection for V3 chat models (deepseek-chat-v3.1, deepseek-chat-v3-0324, etc.)
- Add deepseek-prover prefix detection for prover/reasoning models (deepseek-prover-v2)
- Prover models now correctly use R1 format like other reasoning models
- Chat models use standard format with native tool support
- Added comprehensive tests for new model patterns
This commit is contained in:
Roo Code 2025-12-03 23:14:57 +00:00
parent 7ce8ff1b9f
commit 5e934f0a9c
3 changed files with 146 additions and 13 deletions

View file

@ -339,8 +339,8 @@ describe("OpenRouterHandler", () => {
})
})
describe("DeepSeek V3 Model Handling", () => {
// Note: OpenRouter only has deepseek/deepseek-v3.2, not deepseek/deepseek-v3
describe("DeepSeek Model Handling", () => {
// Test DeepSeek V3.2 model (uses standard format)
it("should NOT use R1 format for DeepSeek V3.2 models", async () => {
const deepseek32Handler = new OpenRouterHandler({
...mockOptions,
@ -422,5 +422,131 @@ describe("OpenRouterHandler", () => {
undefined,
)
})
// Test DeepSeek Chat V3.1 model (uses standard format, NOT R1)
it("should NOT use R1 format for DeepSeek Chat V3.1 models", async () => {
const deepseekChatHandler = new OpenRouterHandler({
...mockOptions,
openRouterModelId: "deepseek/deepseek-chat-v3.1",
})
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 = deepseekChatHandler.createMessage("system prompt", [])
const chunks = []
for await (const chunk of generator) {
chunks.push(chunk)
}
// Verify that the messages were NOT converted to R1 format (system role preserved)
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
messages: expect.arrayContaining([
expect.objectContaining({ role: "system", content: expect.anything() }),
]),
}),
undefined,
)
})
// Test DeepSeek R1-0528 variant (uses R1 format)
it("should use R1 format for DeepSeek R1-0528 models", async () => {
const deepseekR1Handler = new OpenRouterHandler({
...mockOptions,
openRouterModelId: "deepseek/deepseek-r1-0528",
})
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,
)
})
// Test DeepSeek Prover V2 model (uses R1 format as it's a reasoning model)
it("should use R1 format for DeepSeek Prover V2 models", async () => {
const deepseekProverHandler = new OpenRouterHandler({
...mockOptions,
openRouterModelId: "deepseek/deepseek-prover-v2",
})
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 = deepseekProverHandler.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,
)
})
})
})

View file

@ -266,12 +266,12 @@ export const parseOpenRouterModel = ({
modelInfo.maxTokens = 32768
}
// Configure DeepSeek V3 models properly
// Configure DeepSeek V3/Chat models properly
// These models support standard tool calling but NOT R1 format
// OpenRouter uses deepseek/deepseek-v3 and deepseek/deepseek-v3.2 naming
// OpenRouter uses deepseek/deepseek-v3, deepseek/deepseek-v3.2, deepseek/deepseek-chat-v3.1, etc.
if (
id.startsWith("deepseek/deepseek-v3") || // Matches deepseek/deepseek-v3, deepseek/deepseek-v3.2, etc.
id === "deepseek/deepseek-chat"
id.startsWith("deepseek/deepseek-v3") || // Matches deepseek/deepseek-v3, deepseek/deepseek-v3.2, deepseek/deepseek-v3.1-terminus, etc.
id.startsWith("deepseek/deepseek-chat") // Matches deepseek/deepseek-chat, deepseek/deepseek-chat-v3.1, deepseek/deepseek-chat-v3-0324, etc.
) {
// Ensure these models are marked as supporting native tools
// but NOT reasoning format (they're not R1 models)

View file

@ -135,9 +135,13 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
...convertToOpenAiMessages(messages),
]
// DeepSeek R1 models require user instead of system role.
// Note: DeepSeek V3 models (deepseek-v3, deepseek-v3.2, etc.) do NOT use R1 format
if (modelId.startsWith("deepseek/deepseek-r1") || modelId === "perplexity/sonar-reasoning") {
// DeepSeek R1 and Prover models require user instead of system role.
// Note: DeepSeek V3/Chat models (deepseek-v3, deepseek-v3.2, deepseek-chat-v3.1, etc.) do NOT use R1 format
if (
modelId.startsWith("deepseek/deepseek-r1") ||
modelId.startsWith("deepseek/deepseek-prover") ||
modelId === "perplexity/sonar-reasoning"
) {
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
}
@ -389,12 +393,15 @@ 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, v3.2, etc.) use standard settings
const isDeepSeekR1 = id.startsWith("deepseek/deepseek-r1") || id === "perplexity/sonar-reasoning"
// Only DeepSeek R1 and Prover models use special temperature and topP settings
// DeepSeek V3/Chat models (v3, v3.2, chat-v3.1, etc.) use standard settings
const isDeepSeekR1 =
id.startsWith("deepseek/deepseek-r1") ||
id.startsWith("deepseek/deepseek-prover") ||
id === "perplexity/sonar-reasoning"
const isDeepSeekV3 =
id.startsWith("deepseek/deepseek-v3") || // Matches deepseek/deepseek-v3, deepseek/deepseek-v3.2, etc.
id === "deepseek/deepseek-chat"
id.startsWith("deepseek/deepseek-chat") // Matches deepseek/deepseek-chat, deepseek/deepseek-chat-v3.1, etc.
const params = getModelParams({
format: "openrouter",