mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: use DashScope-compatible parameters in Qwen Code provider to fix 400 errors
Replace OpenAI-specific parameters that DashScope does not support: - max_completion_tokens -> max_tokens - Remove parallel_tool_calls (unsupported) - Remove stream_options (unsupported) - Override convertToolsForOpenAI to skip strict mode (unsupported) Fixes #12067
This commit is contained in:
parent
c3cae397a1
commit
91e27664a2
2 changed files with 140 additions and 22 deletions
|
|
@ -89,19 +89,19 @@ describe("QwenCodeHandler Native Tools", () => {
|
|||
})
|
||||
await stream.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
tools: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "function",
|
||||
function: expect.objectContaining({
|
||||
name: "test_tool",
|
||||
}),
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs.tools).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "function",
|
||||
function: expect.objectContaining({
|
||||
name: "test_tool",
|
||||
}),
|
||||
]),
|
||||
parallel_tool_calls: true,
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
)
|
||||
// DashScope does not support parallel_tool_calls
|
||||
expect(callArgs).not.toHaveProperty("parallel_tool_calls")
|
||||
})
|
||||
|
||||
it("should include tool_choice when provided", async () => {
|
||||
|
|
@ -145,7 +145,8 @@ describe("QwenCodeHandler Native Tools", () => {
|
|||
const callArgs = mockCreate.mock.calls[mockCreate.mock.calls.length - 1][0]
|
||||
expect(callArgs).toHaveProperty("tools")
|
||||
expect(callArgs).toHaveProperty("tool_choice")
|
||||
expect(callArgs).toHaveProperty("parallel_tool_calls", true)
|
||||
// DashScope does not support parallel_tool_calls
|
||||
expect(callArgs).not.toHaveProperty("parallel_tool_calls")
|
||||
})
|
||||
|
||||
it("should yield tool_call_partial chunks during streaming", async () => {
|
||||
|
|
@ -215,7 +216,7 @@ describe("QwenCodeHandler Native Tools", () => {
|
|||
})
|
||||
})
|
||||
|
||||
it("should set parallel_tool_calls based on metadata", async () => {
|
||||
it("should not include parallel_tool_calls even when metadata provides it (DashScope unsupported)", async () => {
|
||||
mockCreate.mockImplementationOnce(() => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
|
|
@ -231,11 +232,9 @@ describe("QwenCodeHandler Native Tools", () => {
|
|||
})
|
||||
await stream.next()
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parallel_tool_calls: true,
|
||||
}),
|
||||
)
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
// DashScope does not support parallel_tool_calls - should never be sent
|
||||
expect(callArgs).not.toHaveProperty("parallel_tool_calls")
|
||||
})
|
||||
|
||||
it("should yield tool_call_end events when finish_reason is tool_calls", async () => {
|
||||
|
|
@ -370,4 +369,98 @@ describe("QwenCodeHandler Native Tools", () => {
|
|||
expect(endChunks).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DashScope API Compatibility", () => {
|
||||
it("should use max_tokens instead of max_completion_tokens", async () => {
|
||||
mockCreate.mockImplementationOnce(() => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "Test response" } }],
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const stream = handler.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
})
|
||||
await stream.next()
|
||||
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).toHaveProperty("max_tokens")
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
|
||||
it("should not include stream_options", async () => {
|
||||
mockCreate.mockImplementationOnce(() => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "Test response" } }],
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const stream = handler.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
})
|
||||
await stream.next()
|
||||
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("stream_options")
|
||||
})
|
||||
|
||||
it("should not include parallel_tool_calls", async () => {
|
||||
mockCreate.mockImplementationOnce(() => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "Test response" } }],
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const stream = handler.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
})
|
||||
await stream.next()
|
||||
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).not.toHaveProperty("parallel_tool_calls")
|
||||
})
|
||||
|
||||
it("should not set strict: true on tool definitions", async () => {
|
||||
mockCreate.mockImplementationOnce(() => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [{ delta: { content: "Test response" } }],
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
const stream = handler.createMessage("test prompt", [], {
|
||||
taskId: "test-task-id",
|
||||
tools: testTools,
|
||||
})
|
||||
await stream.next()
|
||||
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
const tools = callArgs.tools
|
||||
expect(tools).toBeDefined()
|
||||
for (const tool of tools) {
|
||||
// DashScope does not support strict mode
|
||||
expect(tool.function).not.toHaveProperty("strict")
|
||||
}
|
||||
})
|
||||
|
||||
it("should use max_tokens in completePrompt", async () => {
|
||||
mockCreate.mockImplementationOnce(() => ({
|
||||
choices: [{ message: { content: "response" } }],
|
||||
}))
|
||||
|
||||
await handler.completePrompt("test prompt")
|
||||
|
||||
const callArgs = mockCreate.mock.calls[0][0]
|
||||
expect(callArgs).toHaveProperty("max_tokens")
|
||||
expect(callArgs).not.toHaveProperty("max_completion_tokens")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -219,16 +219,19 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
|
|||
|
||||
const convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
|
||||
|
||||
// DashScope's OpenAI-compatible API does not support several OpenAI-specific
|
||||
// parameters. Using them causes a 400 Bad Request error. Specifically:
|
||||
// - max_completion_tokens -> use max_tokens instead
|
||||
// - parallel_tool_calls -> not supported
|
||||
// - stream_options -> not supported
|
||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: model.id,
|
||||
temperature: 0,
|
||||
messages: convertedMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
max_completion_tokens: model.info.maxTokens,
|
||||
max_tokens: model.info.maxTokens,
|
||||
tools: this.convertToolsForOpenAI(metadata?.tools),
|
||||
tool_choice: metadata?.tool_choice,
|
||||
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
|
||||
}
|
||||
|
||||
const stream = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions))
|
||||
|
|
@ -321,6 +324,28 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
|
|||
return { id, info }
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to skip strict mode for DashScope compatibility.
|
||||
* DashScope's OpenAI-compatible API does not support OpenAI's strict mode
|
||||
* on tool definitions (strict: true, additionalProperties: false enforcement).
|
||||
* Sending these causes a 400 Bad Request error.
|
||||
*/
|
||||
protected override convertToolsForOpenAI(tools: any[] | undefined): any[] | undefined {
|
||||
if (!tools) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return tools
|
||||
.filter((tool) => tool.type === "function")
|
||||
.map((tool) => ({
|
||||
...tool,
|
||||
function: {
|
||||
...tool.function,
|
||||
// Do not set strict: true - DashScope does not support it
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
await this.ensureAuthenticated()
|
||||
const client = this.ensureClient()
|
||||
|
|
@ -329,7 +354,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
|
|||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||
model: model.id,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
max_completion_tokens: model.info.maxTokens,
|
||||
max_tokens: model.info.maxTokens,
|
||||
}
|
||||
|
||||
const response = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue