From 11c3da4f74bbd4580fbc8e0ca9caaf038c801655 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 21 May 2026 23:27:40 +0200 Subject: [PATCH] fix(ai): set bedrock claude default max tokens closes #4848 --- packages/ai/CHANGELOG.md | 4 +++ packages/ai/src/providers/amazon-bedrock.ts | 3 +- .../ai/test/bedrock-thinking-payload.test.ts | 34 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 3d20b95a..841f3fed 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Amazon Bedrock Claude requests to send the model output token cap by default, matching Anthropic requests and avoiding Bedrock's 4096-token default truncation ([#4848](https://github.com/earendil-works/pi/issues/4848)). + ## [0.75.4] - 2026-05-20 ### Changed diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index 5d89ac56..429098a6 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -183,12 +183,13 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt try { const client = new BedrockRuntimeClient(config); const cacheRetention = resolveCacheRetention(options.cacheRetention); + const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined); let commandInput = { modelId: model.id, messages: convertMessages(context, model, cacheRetention), system: buildSystemPrompt(context.systemPrompt, model, cacheRetention), inferenceConfig: { - ...(options.maxTokens !== undefined && { maxTokens: options.maxTokens }), + ...(inferenceMaxTokens !== undefined && { maxTokens: inferenceMaxTokens }), ...(options.temperature !== undefined && { temperature: options.temperature }), }, toolConfig: convertToolConfig(context.tools, options.toolChoice), diff --git a/packages/ai/test/bedrock-thinking-payload.test.ts b/packages/ai/test/bedrock-thinking-payload.test.ts index 55b9e82b..46a5277b 100644 --- a/packages/ai/test/bedrock-thinking-payload.test.ts +++ b/packages/ai/test/bedrock-thinking-payload.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { getModel } from "../src/models.ts"; import { type BedrockOptions, streamBedrock } from "../src/providers/amazon-bedrock.ts"; import type { Context, Model } from "../src/types.ts"; +import { hasBedrockCredentials } from "./bedrock-utils.ts"; interface BedrockThinkingPayload { additionalModelRequestFields?: { @@ -112,6 +113,39 @@ describe("Bedrock thinking payload", () => { }); }); +describe.skipIf(!hasBedrockCredentials())("Bedrock Claude max tokens E2E", () => { + it( + "uses the model maxTokens cap instead of Bedrock's 4096-token default for adaptive Claude models", + { retry: 2, timeout: 180000 }, + async () => { + const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-sonnet-4-6"); + const model: Model<"bedrock-converse-stream"> = { + ...baseModel, + maxTokens: 6000, + }; + + const response = await streamBedrock( + model, + { + systemPrompt: "You are a deterministic text generator. Follow the requested output format exactly.", + messages: [ + { + role: "user", + content: + "Output exactly 5200 repetitions of the token alpha, separated by single spaces. Do not number them. Do not use markdown. Do not add any other text.", + timestamp: Date.now(), + }, + ], + }, + { reasoning: "low" }, + ).result(); + + expect(response.stopReason, response.errorMessage).not.toBe("error"); + expect(response.usage.output).toBeGreaterThan(4096); + }, + ); +}); + describe("Application inference profile support", () => { it("uses adaptive thinking when model.name contains the model name but ARN does not", async () => { const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-opus-4-6-v1");