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;