fix(ai): clamp streamSimple max tokens

Clamps streamSimple max-token defaults against estimated context, addressing #5595.

closes #6061
This commit is contained in:
Mario Zechner
2026-06-25 14:10:05 +02:00
parent 49956a7cd0
commit 09f1059575
13 changed files with 198 additions and 20 deletions
@@ -92,7 +92,7 @@ describe("openai-completions empty tools handling", () => {
expect("tools" in (params as object)).toBe(false);
});
it("does not send default max token fields", async () => {
it("sends default maxTokens", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = { ...baseModel, api: "openai-completions" } as const;
@@ -106,7 +106,7 @@ describe("openai-completions empty tools handling", () => {
const params = mockState.lastParams as { max_tokens?: number; max_completion_tokens?: number };
expect(params.max_tokens).toBeUndefined();
expect(params.max_completion_tokens).toBeUndefined();
expect(params.max_completion_tokens).toBe(model.maxTokens);
});
it("sends explicit maxTokens", async () => {
@@ -126,6 +126,40 @@ describe("openai-completions empty tools handling", () => {
expect(params.max_completion_tokens).toBe(1234);
});
it("clamps default maxTokens to remaining context", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = { ...baseModel, api: "openai-completions", contextWindow: 10000, maxTokens: 8000 } as const;
await streamSimple(
model,
{
messages: [{ role: "user", content: "x".repeat(8000), timestamp: Date.now() }],
},
{ apiKey: "test" },
).result();
const params = mockState.lastParams as { max_tokens?: number; max_completion_tokens?: number };
expect(params.max_tokens).toBeUndefined();
expect(params.max_completion_tokens).toBe(3904);
});
it("clamps explicit maxTokens to remaining context", async () => {
const { compat: _compat, ...baseModel } = getModel("openai", "gpt-4o-mini")!;
const model = { ...baseModel, api: "openai-completions", contextWindow: 10000, maxTokens: 8000 } as const;
await streamSimple(
model,
{
messages: [{ role: "user", content: "x".repeat(8000), timestamp: Date.now() }],
},
{ apiKey: "test", maxTokens: 7000 },
).result();
const params = mockState.lastParams as { max_tokens?: number; max_completion_tokens?: number };
expect(params.max_tokens).toBeUndefined();
expect(params.max_completion_tokens).toBe(3904);
});
it("uses conservative OpenAI-compatible fields for Cloudflare AI Gateway /compat models", async () => {
process.env.CLOUDFLARE_API_KEY = "cf-token";
process.env.CLOUDFLARE_ACCOUNT_ID = "account-id";