Skip to content

Commit 2265413

Browse files
authored
🪨 feat: Bedrock Support for Claude-4 Reasoning (#7517)
* 🗑️ chore: Update .gitignore to reflect AI-related files * chore: linting in Bedrock options.js * 🪨 feat: Bedrock Claude-4 Reasoning
1 parent 7e98702 commit 2265413

File tree

4 files changed

+124
-6
lines changed

4 files changed

+124
-6
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ bower_components/
5252
*.d.ts
5353
!vite-env.d.ts
5454

55-
# Cline
55+
# AI
5656
.clineignore
57+
.cursor
5758

5859
# Floobits
5960
.floo

api/server/services/Endpoints/bedrock/options.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ const getOptions = async ({ req, overrideModel, endpointOption }) => {
2525
let credentials = isUserProvided
2626
? await getUserKey({ userId: req.user.id, name: EModelEndpoint.bedrock })
2727
: {
28-
accessKeyId: BEDROCK_AWS_ACCESS_KEY_ID,
29-
secretAccessKey: BEDROCK_AWS_SECRET_ACCESS_KEY,
30-
...(BEDROCK_AWS_SESSION_TOKEN && { sessionToken: BEDROCK_AWS_SESSION_TOKEN }),
31-
};
28+
accessKeyId: BEDROCK_AWS_ACCESS_KEY_ID,
29+
secretAccessKey: BEDROCK_AWS_SECRET_ACCESS_KEY,
30+
...(BEDROCK_AWS_SESSION_TOKEN && { sessionToken: BEDROCK_AWS_SESSION_TOKEN }),
31+
};
3232

3333
if (!credentials) {
3434
throw new Error('Bedrock credentials not provided. Please provide them again.');
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { bedrockInputParser } from '../src/bedrock';
2+
import type { BedrockConverseInput } from '../src/bedrock';
3+
4+
describe('bedrockInputParser', () => {
5+
describe('Model Matching for Reasoning Configuration', () => {
6+
test('should match anthropic.claude-3-7-sonnet model', () => {
7+
const input = {
8+
model: 'anthropic.claude-3-7-sonnet',
9+
};
10+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
11+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
12+
expect(additionalFields.thinking).toBe(true);
13+
expect(additionalFields.thinkingBudget).toBe(2000);
14+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
15+
});
16+
17+
test('should match anthropic.claude-sonnet-4 model', () => {
18+
const input = {
19+
model: 'anthropic.claude-sonnet-4',
20+
};
21+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
22+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
23+
expect(additionalFields.thinking).toBe(true);
24+
expect(additionalFields.thinkingBudget).toBe(2000);
25+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
26+
});
27+
28+
test('should match anthropic.claude-opus-5 model', () => {
29+
const input = {
30+
model: 'anthropic.claude-opus-5',
31+
};
32+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
33+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
34+
expect(additionalFields.thinking).toBe(true);
35+
expect(additionalFields.thinkingBudget).toBe(2000);
36+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
37+
});
38+
39+
test('should match anthropic.claude-haiku-6 model', () => {
40+
const input = {
41+
model: 'anthropic.claude-haiku-6',
42+
};
43+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
44+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
45+
expect(additionalFields.thinking).toBe(true);
46+
expect(additionalFields.thinkingBudget).toBe(2000);
47+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
48+
});
49+
50+
test('should match anthropic.claude-4-sonnet model', () => {
51+
const input = {
52+
model: 'anthropic.claude-4-sonnet',
53+
};
54+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
55+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
56+
expect(additionalFields.thinking).toBe(true);
57+
expect(additionalFields.thinkingBudget).toBe(2000);
58+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
59+
});
60+
61+
test('should match anthropic.claude-4.5-sonnet model', () => {
62+
const input = {
63+
model: 'anthropic.claude-4.5-sonnet',
64+
};
65+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
66+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
67+
expect(additionalFields.thinking).toBe(true);
68+
expect(additionalFields.thinkingBudget).toBe(2000);
69+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
70+
});
71+
72+
test('should match anthropic.claude-4-7-sonnet model', () => {
73+
const input = {
74+
model: 'anthropic.claude-4-7-sonnet',
75+
};
76+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
77+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
78+
expect(additionalFields.thinking).toBe(true);
79+
expect(additionalFields.thinkingBudget).toBe(2000);
80+
expect(additionalFields.anthropic_beta).toEqual(['output-128k-2025-02-19']);
81+
});
82+
83+
test('should not match non-Claude models', () => {
84+
const input = {
85+
model: 'some-other-model',
86+
};
87+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
88+
expect(result.additionalModelRequestFields).toBeUndefined();
89+
});
90+
91+
test('should respect explicit thinking configuration', () => {
92+
const input = {
93+
model: 'anthropic.claude-sonnet-4',
94+
thinking: false,
95+
};
96+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
97+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
98+
expect(additionalFields.thinking).toBeUndefined();
99+
expect(additionalFields.thinkingBudget).toBeUndefined();
100+
});
101+
102+
test('should respect custom thinking budget', () => {
103+
const input = {
104+
model: 'anthropic.claude-sonnet-4',
105+
thinking: true,
106+
thinkingBudget: 3000,
107+
};
108+
const result = bedrockInputParser.parse(input) as BedrockConverseInput;
109+
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
110+
expect(additionalFields.thinking).toBe(true);
111+
expect(additionalFields.thinkingBudget).toBe(3000);
112+
});
113+
});
114+
});

packages/data-provider/src/bedrock.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ export const bedrockInputParser = s.tConversationSchema
119119
/** Default thinking and thinkingBudget for 'anthropic.claude-3-7-sonnet' models, if not defined */
120120
if (
121121
typeof typedData.model === 'string' &&
122-
typedData.model.includes('anthropic.claude-3-7-sonnet')
122+
(typedData.model.includes('anthropic.claude-3-7-sonnet') ||
123+
/anthropic\.claude-(?:[4-9](?:\.\d+)?(?:-\d+)?-(?:sonnet|opus|haiku)|(?:sonnet|opus|haiku)-[4-9])/.test(
124+
typedData.model,
125+
))
123126
) {
124127
if (additionalFields.thinking === undefined) {
125128
additionalFields.thinking = true;

0 commit comments

Comments
 (0)