fix: improve Qwen-Code provider tool invocation and configuration

- Add support for tools/functions in API calls to fix tool invocation errors
- Add configurable temperature support (default 0.2 for better tool usage)
- Use max_completion_tokens instead of deprecated max_tokens parameter
- Add proper error handling using handleOpenAIError utility
- Extract and parse tool definitions from system prompt to help model understand available tools

This addresses the issue where subsequent tasks with the same question fail with
tool invocation errors like missing required parameters.

Fixes #8052
This commit is contained in:
Roo Code 2025-09-17 01:54:35 +00:00
parent 2263d86a20
commit c9e52f7477

View file

@ -10,6 +10,7 @@ import type { ApiHandlerOptions } from "../../shared/api"
import { convertToOpenAiMessages } from "../transform/openai-format"
import { ApiStream } from "../transform/stream"
import { handleOpenAIError } from "./utils/openai-error-handler"
import { BaseProvider } from "./base-provider"
import type { SingleCompletionHandler } from "../index"
@ -54,6 +55,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
private credentials: QwenOAuthCredentials | null = null
private client: OpenAI | undefined
private refreshPromise: Promise<QwenOAuthCredentials> | null = null
private readonly providerName = "QwenCode"
constructor(options: QwenCodeHandlerOptions) {
super()
@ -213,16 +215,27 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
const convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)]
// Extract tools from the system prompt if present
const tools = this.extractToolsFromSystemPrompt(systemPrompt)
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: model.id,
temperature: 0,
temperature: this.options.modelTemperature ?? 0.2, // Use a slightly higher default for better tool usage
messages: convertedMessages,
stream: true,
stream_options: { include_usage: true },
max_completion_tokens: model.info.maxTokens,
// Use max_completion_tokens instead of deprecated max_tokens
max_completion_tokens: this.options.modelMaxTokens || model.info.maxTokens,
// Add tools if they were found in the system prompt
...(tools.length > 0 && { tools }),
}
const stream = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions))
let stream
try {
stream = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions))
} catch (error) {
throw handleOpenAIError(error, this.providerName)
}
let fullContent = ""
@ -290,6 +303,78 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
return { id, info }
}
/**
* Extract tools from the system prompt if they are defined
* This helps the model understand what tools are available
*/
private extractToolsFromSystemPrompt(systemPrompt: string): OpenAI.Chat.Completions.ChatCompletionTool[] {
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = []
// Look for tool definitions in the system prompt
// This is a simple pattern matching approach - could be enhanced
const toolPattern = /<tool_name>(.*?)<\/tool_name>.*?<parameters>(.*?)<\/parameters>/gs
const matches = systemPrompt.matchAll(toolPattern)
for (const match of matches) {
const toolName = match[1].trim()
const parametersText = match[2].trim()
// Try to parse parameters into a schema
try {
// Create properties and required arrays with proper types
const properties: Record<string, any> = {}
const required: string[] = []
// Create a basic function schema for the tool
const tool: OpenAI.Chat.Completions.ChatCompletionTool = {
type: "function",
function: {
name: toolName,
description: `Tool: ${toolName}`,
parameters: {
type: "object",
properties: properties,
required: required,
},
},
}
// Parse parameter definitions if they follow a pattern
const paramPattern = /- (\w+):\s*\((\w+)\)\s*(.*)/g
const paramMatches = parametersText.matchAll(paramPattern)
for (const paramMatch of paramMatches) {
const paramName = paramMatch[1]
const paramType = paramMatch[2].toLowerCase()
const isRequired = paramMatch[3].includes("required")
// Map to JSON schema types
let schemaType = "string"
if (paramType === "boolean") schemaType = "boolean"
else if (paramType === "number" || paramType === "integer") schemaType = "number"
else if (paramType === "array") schemaType = "array"
else if (paramType === "object") schemaType = "object"
properties[paramName] = {
type: schemaType,
description: paramMatch[3],
}
if (isRequired) {
required.push(paramName)
}
}
tools.push(tool)
} catch (error) {
// If we can't parse the tool definition, skip it
console.debug(`Could not parse tool definition for ${toolName}:`, error)
}
}
return tools
}
async completePrompt(prompt: string): Promise<string> {
await this.ensureAuthenticated()
const client = this.ensureClient()
@ -298,10 +383,17 @@ 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,
// Use max_completion_tokens instead of deprecated max_tokens
max_completion_tokens: this.options.modelMaxTokens || model.info.maxTokens,
temperature: this.options.modelTemperature ?? 0.2,
}
const response = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions))
let response
try {
response = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions))
} catch (error) {
throw handleOpenAIError(error, this.providerName)
}
return response.choices[0]?.message.content || ""
}