From 03ad7ecee7d51c291a98119d41460ed8c22de079 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sat, 14 Mar 2026 05:20:27 +0100 Subject: [PATCH] fix(ai): add qwen-chat-template compat mode closes #2020 --- packages/ai/src/providers/openai-completions.ts | 7 +++++-- packages/ai/src/types.ts | 4 ++-- packages/coding-agent/docs/custom-provider.md | 8 ++++++-- packages/coding-agent/docs/models.md | 4 +++- packages/coding-agent/src/core/model-registry.ts | 9 ++++++++- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index d208089e..343d127f 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -392,9 +392,12 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio params.tool_choice = options.toolChoice; } - if ((compat.thinkingFormat === "zai" || compat.thinkingFormat === "qwen") && model.reasoning) { - // Both Z.ai and Qwen use enable_thinking: boolean + if (compat.thinkingFormat === "zai" && model.reasoning) { (params as any).enable_thinking = !!options?.reasoningEffort; + } else if (compat.thinkingFormat === "qwen" && model.reasoning) { + (params as any).enable_thinking = !!options?.reasoningEffort; + } else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) { + (params as any).chat_template_kwargs = { enable_thinking: !!options?.reasoningEffort }; } else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) { // OpenAI-style reasoning_effort (params as any).reasoning_effort = mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap); diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 7b27beff..24699e63 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -270,8 +270,8 @@ export interface OpenAICompletionsCompat { requiresAssistantAfterToolResult?: boolean; /** Whether thinking blocks must be converted to text blocks with delimiters. Default: auto-detected from URL. */ requiresThinkingAsText?: boolean; - /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "zai" uses thinking: { type: "enabled" }, "qwen" uses enable_thinking: boolean. Default: "openai". */ - thinkingFormat?: "openai" | "zai" | "qwen"; + /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */ + thinkingFormat?: "openai" | "zai" | "qwen" | "qwen-chat-template"; /** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */ openRouterRouting?: OpenRouterRouting; /** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */ diff --git a/packages/coding-agent/docs/custom-provider.md b/packages/coding-agent/docs/custom-provider.md index f6a5b7c4..aff35489 100644 --- a/packages/coding-agent/docs/custom-provider.md +++ b/packages/coding-agent/docs/custom-provider.md @@ -183,11 +183,13 @@ models: [{ }, maxTokensField: "max_tokens", // instead of "max_completion_tokens" requiresToolResultName: true, // tool results need name field - thinkingFormat: "qwen" // uses enable_thinking: true + thinkingFormat: "qwen" // top-level enable_thinking: true } }] ``` +Use `qwen-chat-template` instead for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`. + > Migration note: Mistral moved from `openai-completions` to `mistral-conversations`. > Use `mistral-conversations` for native Mistral models. > If you intentionally route Mistral-compatible/custom endpoints through `openai-completions`, set `compat` flags explicitly as needed. @@ -586,7 +588,9 @@ interface ProviderModelConfig { requiresToolResultName?: boolean; requiresAssistantAfterToolResult?: boolean; requiresThinkingAsText?: boolean; - thinkingFormat?: "openai" | "zai" | "qwen"; + thinkingFormat?: "openai" | "zai" | "qwen" | "qwen-chat-template"; }; } ``` + +`qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`. diff --git a/packages/coding-agent/docs/models.md b/packages/coding-agent/docs/models.md index 5b5be4ae..713850dd 100644 --- a/packages/coding-agent/docs/models.md +++ b/packages/coding-agent/docs/models.md @@ -244,11 +244,13 @@ For providers with partial OpenAI compatibility, use the `compat` field. | `requiresToolResultName` | Include `name` on tool result messages | | `requiresAssistantAfterToolResult` | Insert an assistant message before a user message after tool results | | `requiresThinkingAsText` | Convert thinking blocks to plain text | -| `thinkingFormat` | Use `reasoning_effort`, `zai`, or `qwen` thinking parameters | +| `thinkingFormat` | Use `reasoning_effort`, `zai`, `qwen`, or `qwen-chat-template` thinking parameters | | `supportsStrictMode` | Include the `strict` field in tool definitions | | `openRouterRouting` | OpenRouter routing config passed to OpenRouter for model/provider selection | | `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) | +`qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`. + Example: ```json diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index 9fe01f0e..aeb80684 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -60,7 +60,14 @@ const OpenAICompletionsCompatSchema = Type.Object({ requiresToolResultName: Type.Optional(Type.Boolean()), requiresAssistantAfterToolResult: Type.Optional(Type.Boolean()), requiresThinkingAsText: Type.Optional(Type.Boolean()), - thinkingFormat: Type.Optional(Type.Union([Type.Literal("openai"), Type.Literal("zai"), Type.Literal("qwen")])), + thinkingFormat: Type.Optional( + Type.Union([ + Type.Literal("openai"), + Type.Literal("zai"), + Type.Literal("qwen"), + Type.Literal("qwen-chat-template"), + ]), + ), openRouterRouting: Type.Optional(OpenRouterRoutingSchema), vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema), supportsStrictMode: Type.Optional(Type.Boolean()),