From 81f4af55b5cc80bf9d1db6445aaf7ca35315f08e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 15 Jan 2026 22:48:48 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=A8=20feat:=20Anthropic=20Beta=20Suppo?= =?UTF-8?q?rt=20for=20Bedrock=20(#11371)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🪨 feat: Anthropic Beta Support for Bedrock - Updated the Bedrock input parser to dynamically generate `anthropic_beta` headers based on the model identifier. - Added a new utility function `getBedrockAnthropicBetaHeaders` to determine applicable headers for various Anthropic models. - Modified existing tests to reflect changes in expected `anthropic_beta` values, including new test cases for full model IDs. * test: Update Bedrock Input Parser Tests for Beta Headers - Modified the test case for explicit thinking configuration to reflect the addition of `anthropic_beta` headers. - Ensured that the test now verifies the presence of specific beta header values in the additional model request fields. --- packages/data-provider/specs/bedrock.spec.ts | 52 +++++++++++++++----- packages/data-provider/src/bedrock.ts | 35 ++++++++++++- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/packages/data-provider/specs/bedrock.spec.ts b/packages/data-provider/specs/bedrock.spec.ts index 2a0de6937a..c731d18d5e 100644 --- a/packages/data-provider/specs/bedrock.spec.ts +++ b/packages/data-provider/specs/bedrock.spec.ts @@ -14,7 +14,7 @@ describe('bedrockInputParser', () => { expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); }); - test('should match anthropic.claude-sonnet-4 model', () => { + test('should match anthropic.claude-sonnet-4 model with 1M context header', () => { const input = { model: 'anthropic.claude-sonnet-4', }; @@ -22,10 +22,13 @@ describe('bedrockInputParser', () => { const additionalFields = result.additionalModelRequestFields as Record; expect(additionalFields.thinking).toBe(true); expect(additionalFields.thinkingBudget).toBe(2000); - expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); + expect(additionalFields.anthropic_beta).toEqual([ + 'output-128k-2025-02-19', + 'context-1m-2025-08-07', + ]); }); - test('should match anthropic.claude-opus-5 model', () => { + test('should match anthropic.claude-opus-5 model without 1M context header', () => { const input = { model: 'anthropic.claude-opus-5', }; @@ -36,7 +39,7 @@ describe('bedrockInputParser', () => { expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); }); - test('should match anthropic.claude-haiku-6 model', () => { + test('should match anthropic.claude-haiku-6 model without 1M context header', () => { const input = { model: 'anthropic.claude-haiku-6', }; @@ -47,7 +50,7 @@ describe('bedrockInputParser', () => { expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); }); - test('should match anthropic.claude-4-sonnet model', () => { + test('should match anthropic.claude-4-sonnet model with 1M context header', () => { const input = { model: 'anthropic.claude-4-sonnet', }; @@ -55,10 +58,13 @@ describe('bedrockInputParser', () => { const additionalFields = result.additionalModelRequestFields as Record; expect(additionalFields.thinking).toBe(true); expect(additionalFields.thinkingBudget).toBe(2000); - expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); + expect(additionalFields.anthropic_beta).toEqual([ + 'output-128k-2025-02-19', + 'context-1m-2025-08-07', + ]); }); - test('should match anthropic.claude-4.5-sonnet model', () => { + test('should match anthropic.claude-4.5-sonnet model with 1M context header', () => { const input = { model: 'anthropic.claude-4.5-sonnet', }; @@ -66,10 +72,13 @@ describe('bedrockInputParser', () => { const additionalFields = result.additionalModelRequestFields as Record; expect(additionalFields.thinking).toBe(true); expect(additionalFields.thinkingBudget).toBe(2000); - expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); + expect(additionalFields.anthropic_beta).toEqual([ + 'output-128k-2025-02-19', + 'context-1m-2025-08-07', + ]); }); - test('should match anthropic.claude-4-7-sonnet model', () => { + test('should match anthropic.claude-4-7-sonnet model with 1M context header', () => { const input = { model: 'anthropic.claude-4-7-sonnet', }; @@ -77,7 +86,24 @@ describe('bedrockInputParser', () => { const additionalFields = result.additionalModelRequestFields as Record; expect(additionalFields.thinking).toBe(true); expect(additionalFields.thinkingBudget).toBe(2000); - expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']); + expect(additionalFields.anthropic_beta).toEqual([ + 'output-128k-2025-02-19', + 'context-1m-2025-08-07', + ]); + }); + + test('should match anthropic.claude-sonnet-4-20250514-v1:0 with full model ID', () => { + const input = { + model: 'anthropic.claude-sonnet-4-20250514-v1:0', + }; + const result = bedrockInputParser.parse(input) as BedrockConverseInput; + const additionalFields = result.additionalModelRequestFields as Record; + expect(additionalFields.thinking).toBe(true); + expect(additionalFields.thinkingBudget).toBe(2000); + expect(additionalFields.anthropic_beta).toEqual([ + 'output-128k-2025-02-19', + 'context-1m-2025-08-07', + ]); }); test('should not match non-Claude models', () => { @@ -110,7 +136,7 @@ describe('bedrockInputParser', () => { expect(additionalFields?.anthropic_beta).toBeUndefined(); }); - test('should respect explicit thinking configuration', () => { + test('should respect explicit thinking configuration but still add beta headers', () => { const input = { model: 'anthropic.claude-sonnet-4', thinking: false, @@ -119,6 +145,10 @@ describe('bedrockInputParser', () => { const additionalFields = result.additionalModelRequestFields as Record; expect(additionalFields.thinking).toBeUndefined(); expect(additionalFields.thinkingBudget).toBeUndefined(); + expect(additionalFields.anthropic_beta).toEqual([ + 'output-128k-2025-02-19', + 'context-1m-2025-08-07', + ]); }); test('should respect custom thinking budget', () => { diff --git a/packages/data-provider/src/bedrock.ts b/packages/data-provider/src/bedrock.ts index b37fdc25e1..4df6bd6b65 100644 --- a/packages/data-provider/src/bedrock.ts +++ b/packages/data-provider/src/bedrock.ts @@ -15,6 +15,36 @@ type AnthropicInput = BedrockConverseInput & { AnthropicReasoning; }; +/** + * Gets the appropriate anthropic_beta headers for Bedrock Anthropic models. + * Bedrock uses `anthropic_beta` (with underscore) in additionalModelRequestFields. + * + * @param model - The Bedrock model identifier (e.g., "anthropic.claude-sonnet-4-20250514-v1:0") + * @returns Array of beta header strings, or empty array if not applicable + */ +function getBedrockAnthropicBetaHeaders(model: string): string[] { + const betaHeaders: string[] = []; + + const isClaudeThinkingModel = + model.includes('anthropic.claude-3-7-sonnet') || + /anthropic\.claude-(?:[4-9](?:\.\d+)?(?:-\d+)?-(?:sonnet|opus|haiku)|(?:sonnet|opus|haiku)-[4-9])/.test( + model, + ); + + const isSonnet4PlusModel = + /anthropic\.claude-(?:sonnet-[4-9]|[4-9](?:\.\d+)?(?:-\d+)?-sonnet)/.test(model); + + if (isClaudeThinkingModel) { + betaHeaders.push('output-128k-2025-02-19'); + } + + if (isSonnet4PlusModel) { + betaHeaders.push('context-1m-2025-08-07'); + } + + return betaHeaders; +} + export const bedrockInputSchema = s.tConversationSchema .pick({ /* LibreChat params; optionType: 'conversation' */ @@ -138,7 +168,10 @@ export const bedrockInputParser = s.tConversationSchema additionalFields.thinkingBudget = 2000; } if (typedData.model.includes('anthropic.')) { - additionalFields.anthropic_beta = ['output-128k-2025-02-19']; + const betaHeaders = getBedrockAnthropicBetaHeaders(typedData.model); + if (betaHeaders.length > 0) { + additionalFields.anthropic_beta = betaHeaders; + } } } else if (additionalFields.thinking != null || additionalFields.thinkingBudget != null) { delete additionalFields.thinking;