diff --git a/apps/docs/content/features/reasoning.mdx b/apps/docs/content/features/reasoning.mdx index 56348a6903..9f9b220bc1 100644 --- a/apps/docs/content/features/reasoning.mdx +++ b/apps/docs/content/features/reasoning.mdx @@ -40,7 +40,7 @@ Add the `reasoning_effort` parameter directly to your request: - `medium` - Balanced reasoning for most tasks - `high` - Deep reasoning for complex problems - `xhigh` - Very deep reasoning for the most complex problems -- `max` - Highest reasoning tier, above `xhigh`. Supported by Anthropic thinking models and OpenAI GPT-5.6 models. Effort tiers are never downgraded by the gateway: providers that accept an effort parameter receive the value unchanged (unsupported values result in a provider error), while providers that take a thinking budget instead (Anthropic, Google) have each tier translated to a native budget +- `max` - Highest reasoning tier, above `xhigh`. Supported by Anthropic thinking models and OpenAI GPT-5.6 models. Effort tiers are never downgraded by the gateway: providers that accept an effort parameter receive the value unchanged (unsupported values result in a provider error), while providers that take a thinking budget instead (Anthropic, Google, Alibaba) have each tier translated to a native budget OpenAI's reasoning models do not all accept the same effort values. The @@ -78,7 +78,7 @@ Use the unified `reasoning` configuration object with an `effort` field: - `medium` - Balanced reasoning for most tasks - `high` - Deep reasoning for complex problems - `xhigh` - Very deep reasoning for the most complex problems -- `max` - Highest reasoning tier, above `xhigh`. Supported by Anthropic thinking models and OpenAI GPT-5.6 models. Effort tiers are never downgraded by the gateway: providers that accept an effort parameter receive the value unchanged (unsupported values result in a provider error), while providers that take a thinking budget instead (Anthropic, Google) have each tier translated to a native budget +- `max` - Highest reasoning tier, above `xhigh`. Supported by Anthropic thinking models and OpenAI GPT-5.6 models. Effort tiers are never downgraded by the gateway: providers that accept an effort parameter receive the value unchanged (unsupported values result in a provider error), while providers that take a thinking budget instead (Anthropic, Google, Alibaba) have each tier translated to a native budget ```bash curl -X POST "https://api.llmgateway.io/v1/chat/completions" \ @@ -142,7 +142,8 @@ For models that support it, you can specify an exact token budget for reasoning When `reasoning.max_tokens` is specified, it overrides `reasoning.effort` and `reasoning_effort`. Supported by Anthropic Claude and Google Gemini thinking - models. + models, plus Alibaba-hosted thinking models (forwarded as DashScope's + `thinking_budget`). ### Example Request diff --git a/packages/actions/src/prepare-request-body.spec.ts b/packages/actions/src/prepare-request-body.spec.ts index cc7933afd0..f0539a00cf 100644 --- a/packages/actions/src/prepare-request-body.spec.ts +++ b/packages/actions/src/prepare-request-body.spec.ts @@ -996,6 +996,258 @@ describe("prepareRequestBody - Moonshot thinking", () => { }); }); +describe("prepareRequestBody - Alibaba thinking", () => { + async function prepare(options: { + model: string; + reasoningEffort?: + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh" + | "max"; + reasoningMaxTokens?: number; + maxTokens?: number; + supportsReasoning?: boolean; + }) { + return (await prepareRequestBody( + "alibaba", + options.model, + null, + options.model, + [{ role: "user", content: "Hello!" }], + false, // stream + undefined, // temperature + options.maxTokens, // max_tokens + undefined, // top_p + undefined, // frequency_penalty + undefined, // presence_penalty + undefined, // response_format + undefined, // tools + undefined, // tool_choice + options.reasoningEffort, // reasoning_effort + options.supportsReasoning ?? true, // supportsReasoning + false, // isProd + 20, // maxImageSizeMB + null, // userPlan + undefined, // sensitive_word_check + undefined, // image_config + undefined, // effort + undefined, // imageGenerations + undefined, // webSearchTool + options.reasoningMaxTokens, // reasoning_max_tokens + )) as any; + } + + test("maps reasoning_effort to enable_thinking with a native thinking_budget and never forwards reasoning_effort", async () => { + const requestBody = await prepare({ + model: "qwen3.7-max", + reasoningEffort: "high", + }); + expect(requestBody.enable_thinking).toBe(true); + expect(requestBody.thinking_budget).toBe(24576); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("maps none to an explicit thinking disable", async () => { + const requestBody = await prepare({ + model: "qwen3.7-max", + reasoningEffort: "none", + }); + expect(requestBody.enable_thinking).toBe(false); + expect(requestBody.thinking_budget).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("keeps the provider default when no reasoning parameter is requested", async () => { + const requestBody = await prepare({ model: "qwen3.7-max" }); + expect(requestBody.enable_thinking).toBeUndefined(); + expect(requestBody.thinking_budget).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("forwards reasoning.max_tokens as thinking_budget verbatim", async () => { + const requestBody = await prepare({ + model: "glm-5.2", + reasoningMaxTokens: 1234, + }); + expect(requestBody.enable_thinking).toBe(true); + expect(requestBody.thinking_budget).toBe(1234); + }); + + test("keeps thinking_budget below the caller's max_tokens", async () => { + const requestBody = await prepare({ + model: "glm-5.2", + reasoningEffort: "medium", + maxTokens: 100, + }); + expect(requestBody.enable_thinking).toBe(true); + expect(requestBody.thinking_budget).toBe(99); + expect(requestBody.max_tokens).toBe(100); + }); + + test("sends nothing for mappings without budget-controlled thinking", async () => { + const requestBody = await prepare({ + model: "kimi-k2.5", + reasoningEffort: "high", + }); + expect(requestBody.enable_thinking).toBeUndefined(); + expect(requestBody.thinking_budget).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); +}); + +describe("prepareRequestBody - MiniMax thinking", () => { + async function prepare(options: { + model: string; + reasoningEffort?: + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh" + | "max"; + }) { + return (await prepareRequestBody( + "minimax", + options.model, + null, + options.model, + [{ role: "user", content: "Hello!" }], + false, // stream + undefined, // temperature + undefined, // max_tokens + undefined, // top_p + undefined, // frequency_penalty + undefined, // presence_penalty + undefined, // response_format + undefined, // tools + undefined, // tool_choice + options.reasoningEffort, // reasoning_effort + true, // supportsReasoning + )) as any; + } + + test("maps reasoning_effort to thinking adaptive and never forwards reasoning_effort", async () => { + const requestBody = await prepare({ + model: "minimax-m3", + reasoningEffort: "high", + }); + expect(requestBody.thinking).toEqual({ type: "adaptive" }); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("maps none to thinking disabled on models that can turn thinking off", async () => { + const requestBody = await prepare({ + model: "minimax-m3", + reasoningEffort: "none", + }); + expect(requestBody.thinking).toEqual({ type: "disabled" }); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("keeps the provider default when no reasoning_effort is requested", async () => { + const requestBody = await prepare({ model: "minimax-m3" }); + expect(requestBody.thinking).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("never sends disabled to always-on models (M2.x family)", async () => { + const requestBody = await prepare({ + model: "minimax-m2.7", + reasoningEffort: "none", + }); + expect(requestBody.thinking).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("sends thinking adaptive to always-on models when effort is requested", async () => { + const requestBody = await prepare({ + model: "minimax-m2.7", + reasoningEffort: "medium", + }); + expect(requestBody.thinking).toEqual({ type: "adaptive" }); + expect(requestBody.extra_body).toEqual({ reasoning_split: true }); + }); +}); + +describe("prepareRequestBody - Xiaomi thinking", () => { + async function prepare(options: { + model: string; + reasoningEffort?: + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh" + | "max"; + }) { + return (await prepareRequestBody( + "xiaomi", + options.model, + null, + options.model, + [{ role: "user", content: "Hello!" }], + false, // stream + undefined, // temperature + undefined, // max_tokens + undefined, // top_p + undefined, // frequency_penalty + undefined, // presence_penalty + undefined, // response_format + undefined, // tools + undefined, // tool_choice + options.reasoningEffort, // reasoning_effort + true, // supportsReasoning + )) as any; + } + + test("forwards native effort tiers verbatim without a thinking parameter", async () => { + const requestBody = await prepare({ + model: "mimo-v2.5-pro", + reasoningEffort: "high", + }); + expect(requestBody.reasoning_effort).toBe("high"); + expect(requestBody.thinking).toBeUndefined(); + }); + + test("maps none to thinking disabled and never forwards reasoning_effort", async () => { + const requestBody = await prepare({ + model: "mimo-v2.5-pro", + reasoningEffort: "none", + }); + expect(requestBody.thinking).toEqual({ type: "disabled" }); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("keeps the provider default when no reasoning_effort is requested", async () => { + const requestBody = await prepare({ model: "mimo-v2.5-pro" }); + expect(requestBody.thinking).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); + + test("forwards unsupported tiers verbatim so the provider rejects them", async () => { + const requestBody = await prepare({ + model: "mimo-v2.5", + reasoningEffort: "xhigh", + }); + expect(requestBody.reasoning_effort).toBe("xhigh"); + expect(requestBody.thinking).toBeUndefined(); + }); + + test("drops none for mappings that do not declare it", async () => { + const requestBody = await prepare({ + model: "mimo-v2-pro", + reasoningEffort: "none", + }); + expect(requestBody.thinking).toBeUndefined(); + expect(requestBody.reasoning_effort).toBeUndefined(); + }); +}); + describe("prepareRequestBody - reasoning_effort max", () => { async function prepare(options: { provider: Parameters[0]; diff --git a/packages/actions/src/prepare-request-body.ts b/packages/actions/src/prepare-request-body.ts index 7b6c1da19b..da66249a84 100644 --- a/packages/actions/src/prepare-request-body.ts +++ b/packages/actions/src/prepare-request-body.ts @@ -1005,10 +1005,11 @@ export async function prepareRequestBody( // `none` reasoning effort is handled natively by a few providers: // OpenAI/Azure forward it (their newer models accept it to turn reasoning - // off), and Google and Moonshot reason by default so they must explicitly - // disable thinking when asked. Every other provider treats the absence of - // reasoning_effort as "off" already, so normalize `none` away for them to - // avoid forwarding an unsupported enum value. + // off), and Google, Moonshot, Alibaba, MiniMax, and Xiaomi reason by + // default so they must explicitly disable thinking when asked. Every other + // provider treats the absence of reasoning_effort as "off" already, so + // normalize `none` away for them to avoid forwarding an unsupported enum + // value. const handlesNoneNatively = usedProvider === "openai" || usedProvider === "azure" || @@ -1017,6 +1018,9 @@ export async function prepareRequestBody( usedProvider === "google-vertex" || usedProvider === "quartz" || usedProvider === "moonshot" || + usedProvider === "alibaba" || + usedProvider === "minimax" || + usedProvider === "xiaomi" || providerMappingForOptions?.apiFormat === "openai-chat-completions"; if (reasoning_effort === "none" && !handlesNoneNatively) { reasoning_effort = undefined; @@ -2049,6 +2053,138 @@ export async function prepareRequestBody( } break; } + case "alibaba": { + if (stream) { + requestBody.stream_options = { + include_usage: true, + }; + } + if (response_format) { + requestBody.response_format = response_format; + } + + // Add optional parameters if they are provided + if (temperature !== undefined) { + requestBody.temperature = temperature; + } + if (max_tokens !== undefined) { + requestBody.max_tokens = max_tokens; + } + if (top_p !== undefined) { + requestBody.top_p = top_p; + } + if (frequency_penalty !== undefined) { + requestBody.frequency_penalty = frequency_penalty; + } + if (presence_penalty !== undefined) { + requestBody.presence_penalty = presence_penalty; + } + // DashScope doesn't recognize `reasoning_effort`; thinking is + // controlled via `enable_thinking` (boolean) and `thinking_budget` + // (max thinking tokens), and thinking models think by default. + // Mappings whose thinking is budget-controlled declare + // `reasoningMaxTokens`, so translate the unified reasoning parameters + // only for them: `none` becomes an explicit disable, every other tier + // becomes an explicit enable with a native budget (mirroring the + // Google tier-to-budget mapping), and an explicit + // `reasoning.max_tokens` is forwarded as the budget verbatim. When no + // reasoning parameter is set, send nothing and keep the provider + // default. + if ( + supportsReasoning && + providerMappingForOptions?.reasoningMaxTokens === true && + (reasoning_effort !== undefined || reasoning_max_tokens !== undefined) + ) { + if (reasoning_effort === "none" && reasoning_max_tokens === undefined) { + requestBody.enable_thinking = false; + } else { + const getThinkingBudget = (effort?: string) => { + switch (effort) { + case "minimal": + return 512; + case "low": + return 2048; + case "high": + return 24576; + case "xhigh": + case "max": + // DashScope has no tier above xhigh, so max shares its + // top thinking budget. + return 65536; + case "medium": + default: + return 8192; // Balanced default + } + }; + let thinkingBudget = + reasoning_max_tokens ?? getThinkingBudget(reasoning_effort); + // DashScope rejects requests where thinking_budget >= max_tokens + // for some models (verified live on glm-5.2), so keep the budget + // below the caller's completion limit. + if (max_tokens !== undefined && thinkingBudget >= max_tokens) { + thinkingBudget = Math.max(1, max_tokens - 1); + } + requestBody.enable_thinking = true; + requestBody.thinking_budget = thinkingBudget; + } + } + break; + } + case "minimax": { + if (stream) { + requestBody.stream_options = { + include_usage: true, + }; + } + if (response_format) { + requestBody.response_format = response_format; + } + + // Add optional parameters if they are provided + if (temperature !== undefined) { + requestBody.temperature = temperature; + } + if (max_tokens !== undefined) { + requestBody.max_tokens = max_tokens; + } + if (top_p !== undefined) { + requestBody.top_p = top_p; + } + if (frequency_penalty !== undefined) { + requestBody.frequency_penalty = frequency_penalty; + } + if (presence_penalty !== undefined) { + requestBody.presence_penalty = presence_penalty; + } + if (supportsReasoning) { + requestBody.extra_body = { + ...(requestBody.extra_body ?? {}), + reasoning_split: true, + }; + } + // MiniMax doesn't recognize `reasoning_effort`; its thinking models + // take a binary `thinking` parameter (`{ type: "adaptive" | "disabled" }`) + // and think by default. Map `none`/`minimal` to an explicit disable and + // every other tier to an explicit enable; when no effort is requested, + // send nothing and keep the provider default (thinking on). Only + // MiniMax-M3 can actually turn thinking off — the M2.x family silently + // ignores `"disabled"` and keeps thinking (verified live) — so mappings + // that can disable declare `none` in `reasoningEfforts` and disable + // requests collapse onto the minimum (thinking stays on) elsewhere. + if (supportsReasoning && reasoning_effort !== undefined) { + const wantsThinking = + reasoning_effort !== "none" && reasoning_effort !== "minimal"; + const canDisableThinking = + providerMappingForOptions?.reasoningEfforts?.includes("none") ?? + false; + if (wantsThinking) { + requestBody.thinking = { type: "adaptive" }; + } else if (canDisableThinking) { + requestBody.thinking = { type: "disabled" }; + } + } + break; + } case "anthropic": case "vertex-anthropic": { // Remove generic tool_choice that was added earlier @@ -3348,7 +3484,23 @@ export async function prepareRequestBody( if (presence_penalty !== undefined) { requestBody.presence_penalty = presence_penalty; } - if (reasoning_effort !== undefined) { + // Xiaomi natively accepts `reasoning_effort` low/medium/high (verified + // live: high consistently thinks longer than low) but rejects every + // other tier with a 400, and thinking models think by default. Forward + // the native tiers verbatim (unsupported ones surface the provider's + // 4xx per the no-downgrade rule) and translate `none` to the documented + // binary disable (`thinking: { type: "disabled" }`, verified to zero + // out reasoning tokens). Mappings that can turn thinking off declare + // `none` in `reasoningEfforts`; elsewhere `none` sends nothing and the + // provider default is kept. + if (reasoning_effort === "none") { + const canDisableThinking = + providerMappingForOptions?.reasoningEfforts?.includes("none") ?? + false; + if (supportsReasoning && canDisableThinking) { + requestBody.thinking = { type: "disabled" }; + } + } else if (reasoning_effort !== undefined) { requestBody.reasoning_effort = reasoning_effort; } break; @@ -3407,12 +3559,6 @@ export async function prepareRequestBody( requestBody.reasoning_effort = reasoning_effort; } } - if (usedProvider === "minimax" && supportsReasoning) { - requestBody.extra_body = { - ...(requestBody.extra_body ?? {}), - reasoning_split: true, - }; - } // Hybrid models that keep thinking off by default (e.g. DeepSeek V3.2 on // Novita) ignore `reasoning_effort` and require the vLLM chat-template // flag to turn reasoning on. Only set it when the caller asked for diff --git a/packages/models/src/models/alibaba.ts b/packages/models/src/models/alibaba.ts index 9d7cd1e0e3..e537047262 100644 --- a/packages/models/src/models/alibaba.ts +++ b/packages/models/src/models/alibaba.ts @@ -1281,6 +1281,7 @@ export const alibabaModels = [ contextSize: 256000, maxOutput: 32800, reasoning: true, + reasoningMaxTokens: true, reasoningOutput: "omit", streaming: true, vision: true, @@ -1338,6 +1339,7 @@ export const alibabaModels = [ contextSize: 1000000, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, reasoningOutput: "omit", streaming: true, vision: false, @@ -1356,6 +1358,7 @@ export const alibabaModels = [ "stream", "tools", "response_format", + "reasoning_effort", ], }, { @@ -1463,6 +1466,7 @@ export const alibabaModels = [ contextSize: 1000000, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, reasoningOutput: "omit", streaming: true, vision: true, @@ -1479,6 +1483,7 @@ export const alibabaModels = [ "stream", "tools", "response_format", + "reasoning_effort", ], }, ], @@ -1694,6 +1699,7 @@ export const alibabaModels = [ contextSize: 262144, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, streaming: true, vision: true, tools: true, @@ -1711,6 +1717,7 @@ export const alibabaModels = [ "stream", "response_format", "tools", + "reasoning_effort", ], }, { @@ -2648,6 +2655,7 @@ export const alibabaModels = [ contextSize: 262144, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, streaming: true, vision: false, tools: true, @@ -2663,6 +2671,7 @@ export const alibabaModels = [ "stream", "response_format", "tools", + "reasoning_effort", ], }, ], @@ -2686,6 +2695,7 @@ export const alibabaModels = [ contextSize: 262144, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, streaming: true, vision: true, tools: true, @@ -2703,6 +2713,7 @@ export const alibabaModels = [ "stream", "response_format", "tools", + "reasoning_effort", ], }, ], @@ -2725,6 +2736,7 @@ export const alibabaModels = [ contextSize: 262144, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, streaming: true, vision: true, tools: true, @@ -2742,6 +2754,7 @@ export const alibabaModels = [ "stream", "response_format", "tools", + "reasoning_effort", ], }, { @@ -2795,6 +2808,7 @@ export const alibabaModels = [ contextSize: 262144, maxOutput: 65536, reasoning: true, + reasoningMaxTokens: true, streaming: true, vision: false, tools: true, @@ -2810,6 +2824,7 @@ export const alibabaModels = [ "stream", "response_format", "tools", + "reasoning_effort", ], }, ], diff --git a/packages/models/src/models/deepseek.ts b/packages/models/src/models/deepseek.ts index 0901b84d77..d1035ae9ae 100644 --- a/packages/models/src/models/deepseek.ts +++ b/packages/models/src/models/deepseek.ts @@ -301,6 +301,7 @@ export const deepseekModels = [ maxOutput: 393216, streaming: true, reasoning: true, + reasoningMaxTokens: true, vision: false, tools: true, jsonOutput: true, @@ -410,6 +411,7 @@ export const deepseekModels = [ maxOutput: 393216, streaming: true, reasoning: true, + reasoningMaxTokens: true, vision: false, tools: true, jsonOutput: true, diff --git a/packages/models/src/models/minimax.ts b/packages/models/src/models/minimax.ts index e5869a07bf..d3890e0b9a 100644 --- a/packages/models/src/models/minimax.ts +++ b/packages/models/src/models/minimax.ts @@ -20,6 +20,17 @@ export const minimaxModels = [ maxOutput: 131072, streaming: true, reasoning: true, + // MiniMax thinking is a binary toggle (`thinking.type`), not a + // graduated effort: none/minimal disable it, low..max enable it. + reasoningEfforts: [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max", + ], splitTaggedReasoning: true, vision: true, tools: true, @@ -63,6 +74,10 @@ export const minimaxModels = [ maxOutput: 131100, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, @@ -124,6 +139,10 @@ export const minimaxModels = [ maxOutput: 131100, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, @@ -149,6 +168,10 @@ export const minimaxModels = [ maxOutput: 131100, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, @@ -261,6 +284,10 @@ export const minimaxModels = [ maxOutput: 131100, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, @@ -286,6 +313,10 @@ export const minimaxModels = [ maxOutput: 131072, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, @@ -311,6 +342,10 @@ export const minimaxModels = [ maxOutput: 131072, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, @@ -354,6 +389,10 @@ export const minimaxModels = [ maxOutput: 131072, streaming: true, reasoning: true, + // Thinking cannot be disabled for the M2.x family (`thinking.type: + // "disabled"` is silently ignored upstream), so `none`/`minimal` are + // not offered and thinking stays on at every tier. + reasoningEfforts: ["low", "medium", "high", "xhigh", "max"], splitTaggedReasoning: true, vision: false, tools: true, diff --git a/packages/models/src/models/xiaomi.ts b/packages/models/src/models/xiaomi.ts index 9cadc5c22f..8e267f4309 100644 --- a/packages/models/src/models/xiaomi.ts +++ b/packages/models/src/models/xiaomi.ts @@ -20,6 +20,9 @@ export const xiaomiModels = [ maxOutput: 131072, streaming: true, reasoning: true, + // Xiaomi accepts low/medium/high natively and rejects the other + // tiers with a 400; `none` maps to the binary thinking disable. + reasoningEfforts: ["none", "low", "medium", "high"], vision: false, tools: true, jsonOutput: true, @@ -71,6 +74,9 @@ export const xiaomiModels = [ maxOutput: 131072, streaming: true, reasoning: true, + // Xiaomi accepts low/medium/high natively and rejects the other + // tiers with a 400; `none` maps to the binary thinking disable. + reasoningEfforts: ["none", "low", "medium", "high"], vision: true, tools: true, jsonOutput: true, diff --git a/packages/models/src/models/zai.ts b/packages/models/src/models/zai.ts index 8b2de252f5..fc67f35fb1 100644 --- a/packages/models/src/models/zai.ts +++ b/packages/models/src/models/zai.ts @@ -124,6 +124,7 @@ export const zaiModels = [ maxOutput: 131072, streaming: true, reasoning: true, + reasoningMaxTokens: true, vision: false, tools: true, jsonOutput: true,