Skip to content

Commit 7af3637

Browse files
committed
request transforms for /v1/messages for bedrock
1 parent 8afc005 commit 7af3637

File tree

5 files changed

+495
-2
lines changed

5 files changed

+495
-2
lines changed

src/providers/bedrock/api.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,10 @@ const BedrockAPIConfig: BedrockAPIConfigInterface = {
221221
let endpoint = `/model/${uriEncodedModel}/invoke`;
222222
let streamEndpoint = `/model/${uriEncodedModel}/invoke-with-response-stream`;
223223
if (
224-
(mappedFn === 'chatComplete' || mappedFn === 'stream-chatComplete') &&
224+
(mappedFn === 'chatComplete' ||
225+
mappedFn === 'stream-chatComplete' ||
226+
mappedFn === 'messages' ||
227+
mappedFn === 'stream-messages') &&
225228
model &&
226229
!bedrockInvokeModels.includes(model)
227230
) {
@@ -233,7 +236,8 @@ const BedrockAPIConfig: BedrockAPIConfigInterface = {
233236
const jobId = gatewayRequestURL.split('/').at(jobIdIndex);
234237

235238
switch (mappedFn) {
236-
case 'chatComplete': {
239+
case 'chatComplete':
240+
case 'messages': {
237241
return endpoint;
238242
}
239243
case 'stream-chatComplete': {

src/providers/bedrock/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ import {
7575
} from './uploadFile';
7676
import { BedrockListFilesResponseTransform } from './listfiles';
7777
import { BedrockDeleteFileResponseTransform } from './deleteFile';
78+
import {
79+
BedrockConverseMessagesConfig,
80+
BedrockMessagesResponseTransform,
81+
} from './messages';
82+
7883
const BedrockConfig: ProviderConfigs = {
7984
api: BedrockAPIConfig,
8085
requestHandlers: {
@@ -99,10 +104,12 @@ const BedrockConfig: ProviderConfigs = {
99104
config = {
100105
complete: BedrockAnthropicCompleteConfig,
101106
chatComplete: BedrockConverseAnthropicChatCompleteConfig,
107+
messages: BedrockConverseMessagesConfig,
102108
api: BedrockAPIConfig,
103109
responseTransforms: {
104110
'stream-complete': BedrockAnthropicCompleteStreamChunkTransform,
105111
complete: BedrockAnthropicCompleteResponseTransform,
112+
messages: BedrockMessagesResponseTransform,
106113
},
107114
};
108115
break;

src/providers/bedrock/messages.ts

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
import { BEDROCK } from '../../globals';
2+
import {
3+
DocumentBlockParam,
4+
ImageBlockParam,
5+
RedactedThinkingBlockParam,
6+
TextBlockParam,
7+
ThinkingBlockParam,
8+
ToolResultBlockParam,
9+
ToolUseBlockParam,
10+
} from '../../types/MessagesRequest';
11+
import { MessagesResponse } from '../../types/messagesResponse';
12+
import { ErrorResponse, ProviderConfig } from '../types';
13+
import { generateInvalidProviderResponseError } from '../utils';
14+
import { BedrockErrorResponseTransform } from './chatComplete';
15+
import { BedrockErrorResponse } from './embed';
16+
import { BedrockMessagesParams } from './types';
17+
import {
18+
transformInferenceConfig,
19+
transformToolsConfig as transformToolConfig,
20+
} from './utils/messagesUtils';
21+
22+
const transformTextBlock = (textBlock: TextBlockParam) => {
23+
return {
24+
text: textBlock.text,
25+
...(textBlock.cache_control && {
26+
cachePoint: {
27+
type: 'default',
28+
},
29+
}),
30+
};
31+
};
32+
33+
const appendImageBlock = (
34+
transformedContent: any[],
35+
imageBlock: ImageBlockParam
36+
) => {
37+
if (imageBlock.source.type === 'base64') {
38+
transformedContent.push({
39+
image: {
40+
format: imageBlock.source.media_type.split('/')[1],
41+
source: {
42+
bytes: imageBlock.source.data,
43+
},
44+
},
45+
...(imageBlock.cache_control && {
46+
cachePoint: {
47+
type: 'default',
48+
},
49+
}),
50+
});
51+
} else if (imageBlock.source.type === 'url') {
52+
transformedContent.push({
53+
image: {
54+
format: imageBlock.source.media_type.split('/')[1],
55+
source: {
56+
s3Location: {
57+
uri: imageBlock.source.url,
58+
},
59+
},
60+
},
61+
...(imageBlock.cache_control && {
62+
cachePoint: {
63+
type: 'default',
64+
},
65+
}),
66+
});
67+
} else if (imageBlock.source.type === 'file') {
68+
// not supported
69+
}
70+
};
71+
72+
const appendDocumentBlock = (
73+
transformedContent: any[],
74+
documentBlock: DocumentBlockParam
75+
) => {
76+
if (documentBlock.source.type === 'base64') {
77+
transformedContent.push({
78+
document: {
79+
format: documentBlock.source.media_type.split('/')[1],
80+
source: {
81+
bytes: documentBlock.source.data,
82+
},
83+
},
84+
...(documentBlock.cache_control && {
85+
cachePoint: {
86+
type: 'default',
87+
},
88+
}),
89+
});
90+
} else if (documentBlock.source.type === 'url') {
91+
transformedContent.push({
92+
document: {
93+
format: documentBlock.source.media_type?.split('/')[1] || 'pdf',
94+
source: {
95+
s3Location: {
96+
uri: documentBlock.source.url,
97+
},
98+
},
99+
},
100+
...(documentBlock.cache_control && {
101+
cachePoint: {
102+
type: 'default',
103+
},
104+
}),
105+
});
106+
}
107+
};
108+
109+
const appendThinkingBlock = (
110+
transformedContent: any[],
111+
thinkingBlock: ThinkingBlockParam
112+
) => {
113+
transformedContent.push({
114+
reasoningContent: {
115+
reasoningText: {
116+
text: thinkingBlock.thinking,
117+
signature: thinkingBlock.signature,
118+
},
119+
},
120+
});
121+
};
122+
123+
const appendRedactedThinkingBlock = (
124+
transformedContent: any[],
125+
redactedThinkingBlock: RedactedThinkingBlockParam
126+
) => {
127+
transformedContent.push({
128+
reasoningContent: {
129+
redactedContent: redactedThinkingBlock.data,
130+
},
131+
});
132+
};
133+
134+
const appendToolUseBlock = (
135+
transformedContent: any[],
136+
toolUseBlock: ToolUseBlockParam
137+
) => {
138+
return {
139+
toolUse: {
140+
input: toolUseBlock.input,
141+
name: toolUseBlock.name,
142+
toolUseId: toolUseBlock.id,
143+
},
144+
...(toolUseBlock.cache_control && {
145+
cachePoint: {
146+
type: 'default',
147+
},
148+
}),
149+
};
150+
};
151+
152+
const appendToolResultBlock = (
153+
transformedContent: any[],
154+
toolResultBlock: ToolResultBlockParam
155+
) => {
156+
const content = toolResultBlock.content;
157+
const transformedToolResultContent: any[] = [];
158+
if (typeof content === 'string') {
159+
transformedToolResultContent.push({
160+
text: content,
161+
});
162+
} else if (Array.isArray(content)) {
163+
for (const item of content) {
164+
if (item.type === 'text') {
165+
transformedToolResultContent.push({
166+
text: item.text,
167+
});
168+
} else if (item.type === 'image') {
169+
// TODO: test this
170+
appendImageBlock(transformedToolResultContent, item);
171+
}
172+
}
173+
}
174+
return {
175+
toolResult: {
176+
toolUseId: toolResultBlock.tool_use_id,
177+
status: toolResultBlock.is_error ? 'error' : 'success',
178+
content: transformedToolResultContent,
179+
},
180+
...(toolResultBlock.cache_control && {
181+
cachePoint: {
182+
type: 'default',
183+
},
184+
}),
185+
};
186+
};
187+
188+
export const BedrockConverseMessagesConfig: ProviderConfig = {
189+
max_tokens: {
190+
param: 'inferenceConfig',
191+
required: false,
192+
transform: (params: BedrockMessagesParams) => {
193+
return transformInferenceConfig(params);
194+
},
195+
},
196+
messages: {
197+
param: 'messages',
198+
required: false,
199+
transform: (params: BedrockMessagesParams) => {
200+
const transformedMessages: any[] = [];
201+
for (const message of params.messages) {
202+
if (typeof message.content === 'string') {
203+
transformedMessages.push({
204+
role: message.role,
205+
content: [
206+
{
207+
text: message.content,
208+
},
209+
],
210+
});
211+
} else if (Array.isArray(message.content)) {
212+
const transformedContent: any[] = [];
213+
for (const content of message.content) {
214+
if (content.type === 'text') {
215+
transformedContent.push(transformTextBlock(content));
216+
} else if (content.type === 'image') {
217+
appendImageBlock(transformedContent, content);
218+
} else if (content.type === 'document') {
219+
appendDocumentBlock(transformedContent, content);
220+
} else if (content.type === 'thinking') {
221+
appendThinkingBlock(transformedContent, content);
222+
} else if (content.type === 'redacted_thinking') {
223+
appendRedactedThinkingBlock(transformedContent, content);
224+
} else if (content.type === 'tool_use') {
225+
appendToolUseBlock(transformedContent, content);
226+
} else if (content.type === 'tool_result') {
227+
appendToolResultBlock(transformedContent, content);
228+
}
229+
// not supported
230+
// else if (content.type === 'server_tool_use') {}
231+
// else if (content.type === 'web_search_tool_result') {}
232+
// else if (content.type === 'code_execution_tool_result') {}
233+
// else if (content.type === 'mcp_tool_use') {}
234+
// else if (content.type === 'mcp_tool_result') {}
235+
// else if (content.type === 'container_upload') {}
236+
}
237+
transformedMessages.push({
238+
role: message.role,
239+
content: transformedContent,
240+
});
241+
}
242+
}
243+
return transformedMessages;
244+
},
245+
},
246+
metadata: {
247+
param: 'requestMetadata',
248+
required: false,
249+
},
250+
stop_sequences: {
251+
param: 'inferenceConfig',
252+
required: false,
253+
transform: (params: BedrockMessagesParams) => {
254+
return transformInferenceConfig(params);
255+
},
256+
},
257+
system: {
258+
param: 'system',
259+
required: false,
260+
transform: (params: BedrockMessagesParams) => {
261+
const system = params.system;
262+
if (typeof system === 'string') {
263+
return [
264+
{
265+
text: system,
266+
},
267+
];
268+
} else if (Array.isArray(system)) {
269+
return system.map((item) => ({
270+
text: item.text,
271+
...(item.cache_control && {
272+
cachePoint: {
273+
type: 'default',
274+
},
275+
}),
276+
}));
277+
}
278+
},
279+
},
280+
temperature: {
281+
param: 'inferenceConfig',
282+
required: false,
283+
transform: (params: BedrockMessagesParams) => {
284+
return transformInferenceConfig(params);
285+
},
286+
},
287+
// this if for anthropic
288+
// thinking: {
289+
// param: 'thinking',
290+
// required: false,
291+
// },
292+
tool_choice: {
293+
param: 'toolChoice',
294+
required: false,
295+
transform: (params: BedrockMessagesParams) => {
296+
return transformToolConfig(params);
297+
},
298+
},
299+
tools: {
300+
param: 'toolConfig',
301+
required: false,
302+
transform: (params: BedrockMessagesParams) => {
303+
return transformToolConfig(params);
304+
},
305+
},
306+
// top_k: {
307+
// param: 'top_k',
308+
// required: false,
309+
// },
310+
top_p: {
311+
param: 'inferenceConfig',
312+
required: false,
313+
transform: (params: BedrockMessagesParams) => {
314+
return transformInferenceConfig(params);
315+
},
316+
},
317+
};
318+
319+
export const BedrockMessagesResponseTransform = (
320+
response: MessagesResponse | BedrockErrorResponse,
321+
responseStatus: number
322+
): MessagesResponse | ErrorResponse => {
323+
if (responseStatus !== 200 && 'error' in response) {
324+
return (
325+
BedrockErrorResponseTransform(response) ||
326+
generateInvalidProviderResponseError(response, BEDROCK)
327+
);
328+
}
329+
330+
if ('model' in response) return response;
331+
332+
return generateInvalidProviderResponseError(response, BEDROCK);
333+
};

0 commit comments

Comments
 (0)