From 22654133873f754326ae48702e1b0c482f47049d Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 23 May 2025 00:42:51 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=A8=20feat:=20Bedrock=20Support=20for?= =?UTF-8?q?=20Claude-4=20Reasoning=20(#7517)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🗑️ chore: Update .gitignore to reflect AI-related files * chore: linting in Bedrock options.js * 🪨 feat: Bedrock Claude-4 Reasoning --- .gitignore | 3 +- .../services/Endpoints/bedrock/options.js | 8 +- packages/data-provider/specs/bedrock.spec.ts | 114 ++++++++++++++++++ packages/data-provider/src/bedrock.ts | 5 +- 4 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 packages/data-provider/specs/bedrock.spec.ts diff --git a/.gitignore b/.gitignore index 0b64a284b5..c24bc76b19 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,9 @@ bower_components/ *.d.ts !vite-env.d.ts -# Cline +# AI .clineignore +.cursor # Floobits .floo diff --git a/api/server/services/Endpoints/bedrock/options.js b/api/server/services/Endpoints/bedrock/options.js index 1936a8f483..da332060e9 100644 --- a/api/server/services/Endpoints/bedrock/options.js +++ b/api/server/services/Endpoints/bedrock/options.js @@ -25,10 +25,10 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => { let credentials = isUserProvided ? await getUserKey({ userId: req.user.id, name: EModelEndpoint.bedrock }) : { - accessKeyId: BEDROCK_AWS_ACCESS_KEY_ID, - secretAccessKey: BEDROCK_AWS_SECRET_ACCESS_KEY, - ...(BEDROCK_AWS_SESSION_TOKEN && { sessionToken: BEDROCK_AWS_SESSION_TOKEN }), - }; + accessKeyId: BEDROCK_AWS_ACCESS_KEY_ID, + secretAccessKey: BEDROCK_AWS_SECRET_ACCESS_KEY, + ...(BEDROCK_AWS_SESSION_TOKEN && { sessionToken: BEDROCK_AWS_SESSION_TOKEN }), + }; if (!credentials) { throw new Error('Bedrock credentials not provided. Please provide them again.'); diff --git a/packages/data-provider/specs/bedrock.spec.ts b/packages/data-provider/specs/bedrock.spec.ts new file mode 100644 index 0000000000..c569a1b5a7 --- /dev/null +++ b/packages/data-provider/specs/bedrock.spec.ts @@ -0,0 +1,114 @@ +import { bedrockInputParser } from '../src/bedrock'; +import type { BedrockConverseInput } from '../src/bedrock'; + +describe('bedrockInputParser', () => { + describe('Model Matching for Reasoning Configuration', () => { + test('should match anthropic.claude-3-7-sonnet model', () => { + const input = { + model: 'anthropic.claude-3-7-sonnet', + }; + 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']); + }); + + test('should match anthropic.claude-sonnet-4 model', () => { + const input = { + model: 'anthropic.claude-sonnet-4', + }; + 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']); + }); + + test('should match anthropic.claude-opus-5 model', () => { + const input = { + model: 'anthropic.claude-opus-5', + }; + 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']); + }); + + test('should match anthropic.claude-haiku-6 model', () => { + const input = { + model: 'anthropic.claude-haiku-6', + }; + 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']); + }); + + test('should match anthropic.claude-4-sonnet model', () => { + const input = { + model: 'anthropic.claude-4-sonnet', + }; + 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']); + }); + + test('should match anthropic.claude-4.5-sonnet model', () => { + const input = { + model: 'anthropic.claude-4.5-sonnet', + }; + 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']); + }); + + test('should match anthropic.claude-4-7-sonnet model', () => { + const input = { + model: 'anthropic.claude-4-7-sonnet', + }; + 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']); + }); + + test('should not match non-Claude models', () => { + const input = { + model: 'some-other-model', + }; + const result = bedrockInputParser.parse(input) as BedrockConverseInput; + expect(result.additionalModelRequestFields).toBeUndefined(); + }); + + test('should respect explicit thinking configuration', () => { + const input = { + model: 'anthropic.claude-sonnet-4', + thinking: false, + }; + const result = bedrockInputParser.parse(input) as BedrockConverseInput; + const additionalFields = result.additionalModelRequestFields as Record; + expect(additionalFields.thinking).toBeUndefined(); + expect(additionalFields.thinkingBudget).toBeUndefined(); + }); + + test('should respect custom thinking budget', () => { + const input = { + model: 'anthropic.claude-sonnet-4', + thinking: true, + thinkingBudget: 3000, + }; + const result = bedrockInputParser.parse(input) as BedrockConverseInput; + const additionalFields = result.additionalModelRequestFields as Record; + expect(additionalFields.thinking).toBe(true); + expect(additionalFields.thinkingBudget).toBe(3000); + }); + }); +}); diff --git a/packages/data-provider/src/bedrock.ts b/packages/data-provider/src/bedrock.ts index fac4d97101..fa9bc98eb8 100644 --- a/packages/data-provider/src/bedrock.ts +++ b/packages/data-provider/src/bedrock.ts @@ -119,7 +119,10 @@ export const bedrockInputParser = s.tConversationSchema /** Default thinking and thinkingBudget for 'anthropic.claude-3-7-sonnet' models, if not defined */ if ( typeof typedData.model === 'string' && - typedData.model.includes('anthropic.claude-3-7-sonnet') + (typedData.model.includes('anthropic.claude-3-7-sonnet') || + /anthropic\.claude-(?:[4-9](?:\.\d+)?(?:-\d+)?-(?:sonnet|opus|haiku)|(?:sonnet|opus|haiku)-[4-9])/.test( + typedData.model, + )) ) { if (additionalFields.thinking === undefined) { additionalFields.thinking = true;