diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 0c5965f7ff6..5e9fdcc1a2c 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -243,6 +243,9 @@ const openAiSchema = baseProviderSettingsSchema.extend({ openAiStreamingEnabled: z.boolean().optional(), openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration. openAiHeaders: z.record(z.string(), z.string()).optional(), + // When enabled, adds strict: true to tool definitions and modifies schemas for OpenAI strict mode. + // Disable this for providers like kie.ai that don't support OpenAI's strict mode. + openAiStrictToolMode: z.boolean().optional(), }) const ollamaSchema = baseProviderSettingsSchema.extend({ diff --git a/src/api/providers/base-provider.ts b/src/api/providers/base-provider.ts index a6adeeadbd4..116179d2121 100644 --- a/src/api/providers/base-provider.ts +++ b/src/api/providers/base-provider.ts @@ -23,8 +23,12 @@ export abstract class BaseProvider implements ApiHandler { * Converts an array of tools to be compatible with OpenAI's strict mode. * Filters for function tools, applies schema conversion to their parameters, * and ensures all tools have consistent strict: true values. + * + * @param tools - Array of tools to convert + * @param useStrictMode - When true (default), adds strict: true and modifies schemas for OpenAI strict mode. + * When false, skips strict mode modifications for providers that don't support it (e.g. kie.ai). */ - protected convertToolsForOpenAI(tools: any[] | undefined): any[] | undefined { + protected convertToolsForOpenAI(tools: any[] | undefined, useStrictMode: boolean = true): any[] | undefined { if (!tools) { return undefined } @@ -38,6 +42,12 @@ export abstract class BaseProvider implements ApiHandler { // to preserve optional parameters from the MCP server schema const isMcp = isMcpTool(tool.function.name) + // When strict mode is disabled (for providers like kie.ai), don't add strict: true + // and don't modify the schema + if (!useStrictMode) { + return tool + } + return { ...tool, function: { diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 74cbb511138..510bdb955ca 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -152,6 +152,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl) + // Strict tool mode is enabled by default for backward compatibility + // Disable for providers like kie.ai that don't support OpenAI's strict mode + const useStrictToolMode = this.options.openAiStrictToolMode !== false + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), @@ -159,7 +163,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl stream: true as const, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), ...(reasoning && reasoning), - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? false, } @@ -221,13 +225,17 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl yield this.processUsageMetrics(lastUsage, modelInfo) } } else { + // Strict tool mode is enabled by default for backward compatibility + // Disable for providers like kie.ai that don't support OpenAI's strict mode + const useStrictToolMode = this.options.openAiStrictToolMode !== false + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { model: modelId, messages: deepseekReasoner ? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages]) : [systemMessage, ...convertToOpenAiMessages(messages)], // Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS) - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? false, } @@ -329,6 +337,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const modelInfo = this.getModel().info const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl) + // Strict tool mode is enabled by default for backward compatibility + // Disable for providers like kie.ai that don't support OpenAI's strict mode + const useStrictToolMode = this.options.openAiStrictToolMode !== false + if (this.options.openAiStreamingEnabled ?? true) { const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl) @@ -346,7 +358,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, temperature: undefined, // Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS) - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? false, } @@ -380,7 +392,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, temperature: undefined, // Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS) - tools: this.convertToolsForOpenAI(metadata?.tools), + tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode), tool_choice: metadata?.tool_choice, parallel_tool_calls: metadata?.parallelToolCalls ?? false, } diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index 4eea6f09f1b..c8b5da5e02e 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -158,6 +158,16 @@ export const OpenAICompatible = ({ onChange={handleInputChange("openAiStreamingEnabled", noTransform)}> {t("settings:modelInfo.enableStreaming")} +
+ + {t("settings:providers.openAi.strictToolMode")} + +
+ {t("settings:providers.openAi.strictToolModeDescription")} +
+