diff --git a/open-sse/translator/paramSupport.ts b/open-sse/translator/paramSupport.ts index caad2ab5a3..85dcec35ae 100644 --- a/open-sse/translator/paramSupport.ts +++ b/open-sse/translator/paramSupport.ts @@ -63,7 +63,12 @@ const STRIP_RULES: StripRule[] = [ // MoonshotAI/kimi-cli#1124), and by upstream decolua/9router#2460. Scoped to // OmniRoute's actual volcengine Kimi id (not a broad /kimi/i regex) so it // never clamps an unrelated future Kimi listing whose Ark cap may differ. - { provider: "volcengine", match: /^kimi-k2-5-260127$/, maxOutputCap: 32768, clampToModelMaxOutput: true }, + { + provider: "volcengine", + match: /^kimi-k2-5-260127$/, + maxOutputCap: 32768, + clampToModelMaxOutput: true, + }, // #7364: Z.AI's glm-4.6v vision endpoint enforces a 32768 max_tokens ceiling // server-side and 400s when a client sends a larger explicit max_tokens (e.g. a // client defaulting to 65536). Scoped to both wire paths that can reach this @@ -75,6 +80,19 @@ const STRIP_RULES: StripRule[] = [ // glmProvider.ts, maxOutputTokens: 32768, so clampToModelMaxOutput suffices). { provider: "zai", match: /^glm-4\.6v$/i, maxOutputCap: 32768 }, { provider: "glm", match: /^glm-4\.6v$/i, clampToModelMaxOutput: true }, + // Azure gpt-4o-mini deployments cap completion tokens at 16384 and 400 on + // anything larger: "max_tokens is too large: 32000. This model supports at + // most 16384 completion tokens". OmniRoute's own tool-calling floor + // (DEFAULT_MIN_TOKENS = 32000, applied by adjustMaxTokens) raises even a tiny + // explicit max_tokens to 32000 whenever tools are present, so every agentic + // client trips this on its first turn. PROVIDER_MAX_TOKENS is not the right + // lever here: it is provider-wide, and the same Azure resource also serves + // GPT-5 deployments whose ceiling is far higher. Azure deployment names are + // operator-chosen, hence a prefix match rather than an exact id, and the + // models are passthrough (no catalog maxOutputTokens for clampToModelMaxOutput + // to read), hence the fixed cap. + { provider: "azure-openai", match: /^gpt-4o-mini/i, maxOutputCap: 16384 }, + { provider: "azure-ai", match: /^gpt-4o-mini/i, maxOutputCap: 16384 }, ]; function matches(rule: StripRule, model: string): boolean { diff --git a/tests/unit/azure-max-output-clamp.test.ts b/tests/unit/azure-max-output-clamp.test.ts new file mode 100644 index 0000000000..4b3e0a231d --- /dev/null +++ b/tests/unit/azure-max-output-clamp.test.ts @@ -0,0 +1,58 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +import { stripUnsupportedParams } from "../../open-sse/translator/paramSupport.ts"; + +/** + * Regression guard for the Azure gpt-4o-mini completion-token ceiling. + * + * Observed against a live Azure deployment: + * azure-openai/gpt-4o-mini-dz + * -> 400 "max_tokens is too large: 32000. This model supports at most + * 16384 completion tokens, whereas you provided 32000." + * + * The 32000 is OmniRoute's own doing: `adjustMaxTokens` raises any smaller + * max_tokens to DEFAULT_MIN_TOKENS (32000) whenever tools are present, so an + * agentic client trips this on its first turn even when it asked for far less. + */ + +test("azure gpt-4o-mini clamps max_tokens to the 16384 ceiling", () => { + const out = stripUnsupportedParams("azure-openai", "gpt-4o-mini-dz", { + max_tokens: 32000, + messages: [], + }) as Record; + + assert.equal(out.max_tokens, 16384); +}); + +test("the clamp applies on the azure-ai wire path too", () => { + const out = stripUnsupportedParams("azure-ai", "gpt-4o-mini", { + max_completion_tokens: 32000, + }) as Record; + + assert.equal(out.max_completion_tokens, 16384); +}); + +test("a value already under the ceiling is left alone", () => { + const out = stripUnsupportedParams("azure-openai", "gpt-4o-mini", { + max_tokens: 800, + }) as Record; + + assert.equal(out.max_tokens, 800); +}); + +test("the clamp is scoped — larger Azure deployments keep their budget", () => { + const out = stripUnsupportedParams("azure-ai", "gpt-5.1", { + max_tokens: 32000, + }) as Record; + + assert.equal(out.max_tokens, 32000); +}); + +test("the clamp does not leak to gpt-4o-mini on other providers", () => { + const out = stripUnsupportedParams("openai", "gpt-4o-mini", { + max_tokens: 32000, + }) as Record; + + assert.equal(out.max_tokens, 32000); +});