From 4edc3d57d0f0411913801ac2cacf81270783bfc2 Mon Sep 17 00:00:00 2001 From: Nguyen Thanh Dat Date: Fri, 11 Sep 2026 04:12:57 +0700 Subject: [PATCH] fix(azure): match the generation, not one release, for max_completion_tokens (#13007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boarded with 13 sibling PRs into one worktree off release/v3.8.51 and validated as a set: 132 focused tests pass across all 15 test files in the batch, typecheck:core is clean, check-changelog-integrity reports no lost base bullets, and check-file-size is green. Your PR merged without conflict against its siblings. Thank you — the write-up made this reviewable: measuring the behaviour on the release tip and showing the before/after table meant the defect could be confirmed rather than taken on faith. --- .../fixes/12981-azure-generation-range.md | 1 + open-sse/executors/azureParamRules.ts | 13 ++++++-- tests/unit/azure-param-rules.test.ts | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixes/12981-azure-generation-range.md diff --git a/changelog.d/fixes/12981-azure-generation-range.md b/changelog.d/fixes/12981-azure-generation-range.md new file mode 100644 index 0000000000..2aca35be74 --- /dev/null +++ b/changelog.d/fixes/12981-azure-generation-range.md @@ -0,0 +1 @@ +- **fix(azure):** Deployments from GPT-6 onward now send `max_completion_tokens` instead of `max_tokens`, which Azure rejects with HTTP 400. The rule matched a literal `gpt-5`, so each new generation arrived broken; it now matches the generation range, while `gpt-35-turbo` still keeps `max_tokens`. diff --git a/open-sse/executors/azureParamRules.ts b/open-sse/executors/azureParamRules.ts index 4bd8eab22a..c38e5060a9 100644 --- a/open-sse/executors/azureParamRules.ts +++ b/open-sse/executors/azureParamRules.ts @@ -20,15 +20,24 @@ /** * Deployments that require `max_completion_tokens` instead of `max_tokens`. * - * Matches the GPT-5 family and the o1/o3/o4 reasoning series at a token + * Matches GPT-5 and later, and the o1/o3/o4 reasoning series, at a token * boundary, so a deployment named `my-gpt-5-prod` matches while an unrelated * `piston-o4-legacy`-style name does not match by accident. `gpt-chat-latest` * is listed explicitly: it is a moving alias that currently resolves to a * GPT-5-era model and rejects `max_tokens`, but carries no version number for * the boundary pattern to key on. + * + * The generation is a range rather than a literal `gpt-5`, because the rule is + * a property of the generation and not of one release: `gpt-6-astra` rejects + * `max_tokens` for exactly the reason `gpt-5` does, and pinning the literal + * meant every new family arrived broken (#12981). + * + * It is a range and not `\d+` on purpose. Azure's own name for GPT-3.5 is + * `gpt-35-turbo`, which takes `max_tokens` and would be caught by a digit-run. + * `1\d` keeps a future `gpt-10` working without letting `gpt-35` in. */ export const AZURE_COMPLETION_TOKEN_DEPLOYMENT = - /(?:^|[/_-])(?:gpt-5|o(?:1|3|4))(?:[._-]|$)|^gpt-chat-latest$/i; + /(?:^|[/_-])(?:gpt-(?:[5-9]|1\d)|o(?:1|3|4))(?:[._-]|$)|^gpt-chat-latest$/i; /** * Apply the Azure param rules to an already-translated Chat Completions body. diff --git a/tests/unit/azure-param-rules.test.ts b/tests/unit/azure-param-rules.test.ts index 78292835f2..e24f0a8c85 100644 --- a/tests/unit/azure-param-rules.test.ts +++ b/tests/unit/azure-param-rules.test.ts @@ -46,6 +46,37 @@ test("gpt-5 family converts max_tokens too", () => { } }); +test("generations after GPT-5 convert max_tokens too (#12981)", () => { + // The rule belongs to the generation, not to one release. gpt-6-astra is the + // deployment from the report; the rest are the next names Azure will use. + for (const model of ["gpt-6-astra", "gpt-6", "azure/gpt-7-mini", "gpt-9.1", "gpt-10-turbo"]) { + const out = applyAzureParamRules(model, { max_tokens: 100 }, { max_tokens: 100 }) as Record< + string, + unknown + >; + assert.equal(out.max_tokens, undefined, `${model} should drop max_tokens`); + assert.equal(out.max_completion_tokens, 100, `${model} should set max_completion_tokens`); + } +}); + +test("gpt-35-turbo is not a GPT-3.5 deployment caught by the generation range", () => { + // Azure's own name for GPT-3.5 has no dot, so a digit-run like `gpt-\d+` + // would match it and strip the max_tokens it actually requires. This is why + // the pattern is a range and stops at 19. + for (const model of ["gpt-35-turbo", "gpt-35-turbo-16k", "azure/gpt-35"]) { + assert.equal( + AZURE_COMPLETION_TOKEN_DEPLOYMENT.test(model), + false, + `${model} must keep max_tokens` + ); + const out = applyAzureParamRules(model, { max_tokens: 100 }, { max_tokens: 100 }) as Record< + string, + unknown + >; + assert.equal(out.max_tokens, 100, `${model} should pass through untouched`); + } +}); + test("reasoning_effort is dropped when tools are present", () => { const out = applyAzureParamRules( "gpt-5.1",