fix(llms): send max_completion_tokens for gpt-6 ids on OpenAI-compatible endpoints (#15137)

The reasoning-era model-id matcher only covered o1/o3/o4 and gpt-5, so
gpt-6 requests (gpt-6-sol, gpt-6.1-sol, gpt-6-1-sol) kept max_tokens and
OpenAI/Azure rejected them. Widen the gpt pattern to gpt-5 through gpt-9,
keeping the version digit single so Azure's gpt-35-turbo stays out.
This commit is contained in:
Mikołaj Kondratek
2026-10-16 11:14:20 -07:00
committed by GitHub
parent 44b388b998
commit b549ceb4bb
2 changed files with 36 additions and 10 deletions
+12 -9
View File
@@ -247,7 +247,8 @@ export function isQwenModel(options: {
}
// OpenAI reasoning-era chat models: the o-series (o1/o3/o4, including -mini,
// -pro, and dated variants) and the gpt-5 family (including gpt-5-chat).
// -pro, and dated variants) and gpt-5 and every later single-digit family
// (gpt-5-chat, gpt-5.6-sol, gpt-6-sol, gpt-6.1-sol, ...).
// These models diverge from classic chat-completions parameter rules — most
// importantly they reject `max_tokens` and require `max_completion_tokens`.
// Detection is an id-pattern fallback (mirroring the legacy extension's
@@ -257,11 +258,13 @@ export function isQwenModel(options: {
// match, while namespaced ids like "openai/o3-mini" do.
//
// Maintenance:
// - If OpenAI ships a new family with the same parameter rules (e.g. gpt-6),
// add a pattern here and cases to the positive/negative lists in
// `vendors/openai-compatible.test.ts`. The failure mode until then is the
// loud, self-describing OpenAI 400 ("'max_tokens' is not supported with
// this model. Use 'max_completion_tokens' instead."), not silent breakage.
// - New gpt families up to gpt-9 match without changes. The version digit
// stays single on purpose: two digits would match Azure's `gpt-35-turbo`.
// For a new family outside this pattern, add one here and cases to the
// positive/negative lists in `vendors/openai-compatible.test.ts`. The
// failure mode until then is the loud, self-describing OpenAI 400
// ("'max_tokens' is not supported with this model. Use
// 'max_completion_tokens' instead."), not silent breakage.
// - Keep the boundary anchoring on BOTH sides of each pattern; loosening it
// risks renaming the parameter for unrelated third-party models.
// - Only `withMaxCompletionTokensForReasoningModels` in
@@ -269,8 +272,8 @@ export function isQwenModel(options: {
// `@ai-sdk/openai-compatible` maps `max_completion_tokens` itself (as
// `@ai-sdk/openai` already does), delete that transform and this helper.
const OPENAI_O_SERIES_MODEL_ID_PATTERN = /(^|[^a-z0-9])o[134](?=$|[^a-z0-9])/;
const OPENAI_GPT5_FAMILY_MODEL_ID_PATTERN =
/(^|[^a-z0-9])gpt-?5(?=$|[^a-z0-9])/;
const OPENAI_GPT5_AND_LATER_MODEL_ID_PATTERN =
/(^|[^a-z0-9])gpt-?[5-9](?=$|[^a-z0-9])/;
export function isOpenAIReasoningEraModelId(
modelId: string | undefined,
@@ -282,7 +285,7 @@ export function isOpenAIReasoningEraModelId(
return (
OPENAI_O_SERIES_MODEL_ID_PATTERN.test(normalized) ||
OPENAI_GPT5_FAMILY_MODEL_ID_PATTERN.test(normalized)
OPENAI_GPT5_AND_LATER_MODEL_ID_PATTERN.test(normalized)
);
}
@@ -11,7 +11,7 @@ import {
} from "./openai-compatible";
describe("isOpenAIReasoningEraModelId", () => {
it("matches o-series and gpt-5-family model ids", () => {
it("matches o-series and gpt-5-and-later model ids", () => {
for (const modelId of [
"o1",
"o1-mini",
@@ -32,6 +32,14 @@ describe("isOpenAIReasoningEraModelId", () => {
"openai/gpt-5.2",
"GPT-5-Mini",
"azure-gpt-5-mini",
"gpt-5.6-sol",
"gpt-6",
"gpt-6-sol",
"gpt-6.1-sol",
"gpt-6-1-sol",
"openai/gpt-6-luna",
"azure/gpt-6-astra",
"gpt-7",
]) {
expect(isOpenAIReasoningEraModelId(modelId), modelId).toBe(true);
}
@@ -57,6 +65,10 @@ describe("isOpenAIReasoningEraModelId", () => {
"chatgpt-5",
"somegpt-5-custom",
"xgpt5",
"gpt-35-turbo-16k",
"gpt-55",
"gpt-6o",
"somegpt-6-custom",
]) {
expect(isOpenAIReasoningEraModelId(modelId), modelId).toBe(false);
}
@@ -132,6 +144,17 @@ describe("createOpenAICompatibleProviderModule wire format", () => {
expect(requestBody()).not.toHaveProperty("max_tokens");
});
it("sends max_completion_tokens instead of max_tokens for gpt-6 streaming requests", async () => {
const { requestBody } = await stream({ modelId: "gpt-6.1-sol" });
expect(requestBody()).toMatchObject({
model: "gpt-6.1-sol",
max_completion_tokens: 8_192,
stream: true,
});
expect(requestBody()).not.toHaveProperty("max_tokens");
});
it("sends max_completion_tokens instead of max_tokens for o-series streaming requests", async () => {
const { requestBody } = await stream({ modelId: "o3-mini" });