From 1c701364726e1d4b2d7bb84da8fbbe595e4ef1ac Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 22 Jul 2026 06:11:03 -0700 Subject: [PATCH] fix(ai): honor provider reasoning effort maps (#112632) --- .../src/providers/openai-completions.test.ts | 28 ++++++++++ .../ai/src/providers/openai-completions.ts | 55 +++++++++++-------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/packages/ai/src/providers/openai-completions.test.ts b/packages/ai/src/providers/openai-completions.test.ts index 60235192b0e1..8ff7f13bf246 100644 --- a/packages/ai/src/providers/openai-completions.test.ts +++ b/packages/ai/src/providers/openai-completions.test.ts @@ -228,6 +228,34 @@ describe("OpenAI-compatible completions params", () => { expect(mockOpenAIOptionsRef.payloads[0]).not.toHaveProperty("reasoning_effort"); }); + it.each([ + { name: "model compat mapping", reasoningEffort: "low", expected: "high" }, + { name: "thinkingLevelMap fallback", reasoningEffort: "medium", expected: "xhigh" }, + { name: "requested effort fallback", reasoningEffort: "high", expected: "high" }, + ] as const)( + "uses $name in the emitted reasoning_effort payload", + async ({ reasoningEffort, expected }) => { + mockChunksRef.chunks = [makeTextChunk("ok"), makeFinishChunk("stop")]; + const compatibleModel = { + ...reasoningModel, + provider: "custom-openai-compatible", + baseUrl: "https://third-party.test/v1", + thinkingLevelMap: { low: "medium", medium: "xhigh" }, + compat: { + supportsReasoningEffort: true, + reasoningEffortMap: { low: "high" }, + }, + } as unknown as Model<"openai-completions">; + + await streamOpenAICompletions(compatibleModel, context, { + apiKey: "sk-test", + reasoningEffort, + }).result(); + + expect(mockOpenAIOptionsRef.payloads[0]).toMatchObject({ reasoning_effort: expected }); + }, + ); + it("configures the OpenAI SDK client with the host-built model fetch", async () => { mockOpenAIOptionsRef.options = []; mockChunksRef.chunks = [makeTextChunk("ok"), makeFinishChunk("stop")]; diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index 3c7bc1058bf0..156f0b3f0976 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -18,6 +18,7 @@ import { calculateCost, clampThinkingLevel, } from "../model-utils.js"; +import { resolveOpenAIReasoningEffortMap } from "../transports/openai-reasoning-compat.js"; import type { AssistantMessage, CacheRetention, @@ -814,51 +815,57 @@ function buildParams( } } + // Provider compat is authoritative; keep model-level and literal values as fallbacks + // for catalogs that have not adopted reasoningEffortMap. + const reasoningEffortMap = resolveOpenAIReasoningEffortMap(model); + const reasoningEffort = + options?.reasoningEffort === undefined + ? undefined + : (reasoningEffortMap[options.reasoningEffort] ?? + model.thinkingLevelMap?.[options.reasoningEffort] ?? + options.reasoningEffort); + const reasoningEnabled = reasoningEffort !== undefined; + const offReasoningEffort = reasoningEffortMap.off ?? model.thinkingLevelMap?.off; + if (compat.thinkingFormat === "zai" && model.reasoning) { - params.thinking = options?.reasoningEffort + params.thinking = reasoningEnabled ? { type: "enabled", clear_thinking: false } : { type: "disabled" }; } else if (compat.thinkingFormat === "qwen" && model.reasoning) { - params.enable_thinking = Boolean(options?.reasoningEffort); + params.enable_thinking = reasoningEnabled; } else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) { params.chat_template_kwargs = { - enable_thinking: Boolean(options?.reasoningEffort), + enable_thinking: reasoningEnabled, preserve_thinking: true, }; } else if (compat.thinkingFormat === "deepseek" && model.reasoning) { - params.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" }; - if (options?.reasoningEffort && compat.supportsReasoningEffort) { - params.reasoning_effort = - model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort; + params.thinking = { type: reasoningEnabled ? "enabled" : "disabled" }; + if (reasoningEnabled && compat.supportsReasoningEffort) { + params.reasoning_effort = reasoningEffort; } } else if (compat.thinkingFormat === "openrouter" && model.reasoning) { // OpenRouter normalizes reasoning across providers via a nested reasoning object. const openRouterParams = params as typeof params & { reasoning?: { effort?: string } }; - if (options?.reasoningEffort) { - openRouterParams.reasoning = { - effort: model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort, - }; - } else if (model.thinkingLevelMap?.off !== null) { - openRouterParams.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" }; + if (reasoningEnabled) { + openRouterParams.reasoning = { effort: reasoningEffort }; + } else if (offReasoningEffort !== null) { + openRouterParams.reasoning = { effort: offReasoningEffort ?? "none" }; } } else if (compat.thinkingFormat === "together" && model.reasoning) { const togetherParams = params as Omit & { reasoning?: { enabled: boolean }; reasoning_effort?: string; }; - togetherParams.reasoning = { enabled: Boolean(options?.reasoningEffort) }; - if (options?.reasoningEffort && compat.supportsReasoningEffort) { - togetherParams.reasoning_effort = - model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort; + togetherParams.reasoning = { enabled: reasoningEnabled }; + if (reasoningEnabled && compat.supportsReasoningEffort) { + togetherParams.reasoning_effort = reasoningEffort; } - } else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) { + } else if (reasoningEnabled && model.reasoning && compat.supportsReasoningEffort) { // OpenAI-style reasoning_effort - params.reasoning_effort = - model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort; - } else if (!options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) { - const offValue = model.thinkingLevelMap?.off; - if (typeof offValue === "string") { - params.reasoning_effort = offValue; + params.reasoning_effort = reasoningEffort; + } else if (model.reasoning && compat.supportsReasoningEffort) { + if (typeof offReasoningEffort === "string") { + params.reasoning_effort = offReasoningEffort; } }