bedrock-wrapper 2.4.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +63 -2
- package/bedrock-models.js +78 -0
- package/bedrock-wrapper.js +378 -85
- package/example-converse-api.js +116 -0
- package/interactive-example.js +18 -10
- package/logs/e4cf59ef-9d22-45bf-9c6c-53e3cb9efda3/notification.json +58 -0
- package/logs/e4cf59ef-9d22-45bf-9c6c-53e3cb9efda3/post_tool_use.json +7977 -0
- package/logs/e4cf59ef-9d22-45bf-9c6c-53e3cb9efda3/pre_tool_use.json +2541 -0
- package/logs/e4cf59ef-9d22-45bf-9c6c-53e3cb9efda3/stop.json +86 -0
- package/logs/e4cf59ef-9d22-45bf-9c6c-53e3cb9efda3/user_prompt_submit.json +86 -0
- package/package.json +12 -5
- package/test-converse-api.js +347 -0
- package/test-models.js +96 -20
- package/test-stop-sequences.js +171 -43
- package/test-vision.js +88 -28
package/bedrock-wrapper.js
CHANGED
|
@@ -15,6 +15,7 @@ import { bedrock_models } from "./bedrock-models.js";
|
|
|
15
15
|
import {
|
|
16
16
|
BedrockRuntimeClient,
|
|
17
17
|
InvokeModelCommand, InvokeModelWithResponseStreamCommand,
|
|
18
|
+
ConverseCommand, ConverseStreamCommand,
|
|
18
19
|
} from "@aws-sdk/client-bedrock-runtime";
|
|
19
20
|
// helper functions
|
|
20
21
|
import {
|
|
@@ -62,14 +63,101 @@ async function processImage(imageInput) {
|
|
|
62
63
|
return processedImage.toString('base64');
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
function processReasoningTags(text, awsModel) {
|
|
67
|
+
if (!text) return text;
|
|
68
|
+
|
|
69
|
+
// Check if this is a GPT-OSS model (has reasoning tags)
|
|
70
|
+
const hasReasoningTags = text.includes('<reasoning>') && text.includes('</reasoning>');
|
|
71
|
+
|
|
72
|
+
if (!hasReasoningTags) {
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If model should preserve reasoning, return as-is
|
|
77
|
+
if (awsModel.preserve_reasoning) {
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Strip reasoning tags for non-thinking GPT-OSS models
|
|
82
|
+
return text.replace(/<reasoning>[\s\S]*?<\/reasoning>/g, '').trim();
|
|
83
|
+
}
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
// Convert messages to Converse API format
|
|
86
|
+
async function convertToConverseFormat(messages) {
|
|
87
|
+
const converseMessages = [];
|
|
88
|
+
let systemPrompts = [];
|
|
89
|
+
|
|
90
|
+
for (const msg of messages) {
|
|
91
|
+
if (msg.role === "system") {
|
|
92
|
+
// System messages are handled separately in Converse API
|
|
93
|
+
if (typeof msg.content === 'string') {
|
|
94
|
+
systemPrompts.push({ text: msg.content });
|
|
95
|
+
} else if (Array.isArray(msg.content)) {
|
|
96
|
+
// Extract text from content array
|
|
97
|
+
const textContent = msg.content
|
|
98
|
+
.filter(item => item.type === 'text')
|
|
99
|
+
.map(item => item.text || item)
|
|
100
|
+
.join('\n');
|
|
101
|
+
if (textContent) {
|
|
102
|
+
systemPrompts.push({ text: textContent });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Convert user and assistant messages
|
|
107
|
+
let content = [];
|
|
108
|
+
|
|
109
|
+
if (typeof msg.content === 'string') {
|
|
110
|
+
content = [{ text: msg.content }];
|
|
111
|
+
} else if (Array.isArray(msg.content)) {
|
|
112
|
+
for (const item of msg.content) {
|
|
113
|
+
if (item.type === 'text') {
|
|
114
|
+
content.push({ text: item.text || item });
|
|
115
|
+
} else if (item.type === 'image') {
|
|
116
|
+
// Handle image content
|
|
117
|
+
if (item.source && item.source.data) {
|
|
118
|
+
content.push({
|
|
119
|
+
image: {
|
|
120
|
+
format: 'jpeg',
|
|
121
|
+
source: {
|
|
122
|
+
bytes: Buffer.from(item.source.data, 'base64')
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} else if (item.type === 'image_url') {
|
|
128
|
+
// Process image URL to base64
|
|
129
|
+
const processedImage = await processImage(
|
|
130
|
+
typeof item.image_url === 'string' ?
|
|
131
|
+
item.image_url :
|
|
132
|
+
item.image_url.url
|
|
133
|
+
);
|
|
134
|
+
content.push({
|
|
135
|
+
image: {
|
|
136
|
+
format: 'jpeg',
|
|
137
|
+
source: {
|
|
138
|
+
bytes: Buffer.from(processedImage, 'base64')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Only add messages with actual content (Converse API doesn't allow empty content)
|
|
147
|
+
if (content.length > 0) {
|
|
148
|
+
converseMessages.push({
|
|
149
|
+
role: msg.role,
|
|
150
|
+
content: content
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { messages: converseMessages, system: systemPrompts };
|
|
157
|
+
}
|
|
71
158
|
|
|
72
|
-
|
|
159
|
+
// Process messages for Invoke API (complex model-specific formatting)
|
|
160
|
+
async function processMessagesForInvoke(messages, awsModel) {
|
|
73
161
|
let message_cleaned = [];
|
|
74
162
|
let system_message = "";
|
|
75
163
|
|
|
@@ -130,15 +218,17 @@ export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObjec
|
|
|
130
218
|
message_cleaned.push({role: "assistant", content: ""});
|
|
131
219
|
}
|
|
132
220
|
}
|
|
221
|
+
|
|
222
|
+
return { message_cleaned, system_message };
|
|
223
|
+
}
|
|
133
224
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// format prompt message from message array
|
|
225
|
+
// Build prompt for Invoke API (model-specific formatting)
|
|
226
|
+
function buildInvokePrompt(message_cleaned, awsModel) {
|
|
137
227
|
if (awsModel.messages_api) {
|
|
138
228
|
// convert message array to prompt object if model supports messages api
|
|
139
|
-
|
|
229
|
+
return message_cleaned;
|
|
140
230
|
} else {
|
|
141
|
-
prompt = awsModel.bos_text;
|
|
231
|
+
let prompt = awsModel.bos_text;
|
|
142
232
|
let eom_text_inserted = false;
|
|
143
233
|
|
|
144
234
|
for (let i = 0; i < message_cleaned.length; i++) {
|
|
@@ -194,45 +284,13 @@ export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObjec
|
|
|
194
284
|
prompt += `\n${awsModel.eom_text}`;
|
|
195
285
|
}
|
|
196
286
|
}
|
|
287
|
+
return prompt;
|
|
197
288
|
}
|
|
289
|
+
}
|
|
198
290
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let max_gen_tokens = max_tokens <= awsModel.max_supported_response_tokens ? max_tokens : awsModel.max_supported_response_tokens;
|
|
205
|
-
|
|
206
|
-
if (awsModel.special_request_schema?.thinking?.type === "enabled") {
|
|
207
|
-
// temperature may only be set to 1 when thinking is enabled
|
|
208
|
-
temperature = 1;
|
|
209
|
-
// top_p must be unset when thinking is enabled
|
|
210
|
-
top_p = undefined;
|
|
211
|
-
// bugget_tokens can not be greater than 80% of max_gen_tokens
|
|
212
|
-
let budget_tokens = awsModel.special_request_schema?.thinking?.budget_tokens;
|
|
213
|
-
if (budget_tokens > (max_gen_tokens * 0.8)) {
|
|
214
|
-
budget_tokens = Math.floor(max_gen_tokens * 0.8);
|
|
215
|
-
}
|
|
216
|
-
if (budget_tokens < 1024) {
|
|
217
|
-
budget_tokens = 1024;
|
|
218
|
-
}
|
|
219
|
-
// if awsModel.special_request_schema?.thinking?.budget_tokens, set it to budget_tokens
|
|
220
|
-
if (awsModel.special_request_schema?.thinking?.budget_tokens) {
|
|
221
|
-
awsModel.special_request_schema.thinking.budget_tokens = budget_tokens;
|
|
222
|
-
// max_gen_tokens has to be greater than budget_tokens
|
|
223
|
-
if (max_gen_tokens <= budget_tokens) {
|
|
224
|
-
// make max_gen_tokens 20% greater than budget_tokens
|
|
225
|
-
max_gen_tokens = Math.floor(budget_tokens * 1.2);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// if (logging) {
|
|
231
|
-
// console.log("\nMax tokens:", max_gen_tokens);
|
|
232
|
-
// }
|
|
233
|
-
|
|
234
|
-
// Format the request payload using the model's native structure.
|
|
235
|
-
const request = awsModel.messages_api ? (() => {
|
|
291
|
+
// Build request object for Invoke API (model-specific)
|
|
292
|
+
function buildInvokeRequest(prompt, awsModel, max_gen_tokens, temperature, top_p, stop_sequences, stop, system_message) {
|
|
293
|
+
if (awsModel.messages_api) {
|
|
236
294
|
// Check if this is a Nova model (has schemaVersion in special_request_schema)
|
|
237
295
|
if (awsModel.special_request_schema?.schemaVersion === "messages-v1") {
|
|
238
296
|
// Nova model format - convert messages to Nova's expected format
|
|
@@ -304,44 +362,36 @@ export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObjec
|
|
|
304
362
|
...awsModel.special_request_schema
|
|
305
363
|
};
|
|
306
364
|
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
msg.content
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// Create a Bedrock Runtime client in the AWS Region of your choice
|
|
332
|
-
const client = new BedrockRuntimeClient({
|
|
333
|
-
region: region,
|
|
334
|
-
credentials: {
|
|
335
|
-
accessKeyId: accessKeyId,
|
|
336
|
-
secretAccessKey: secretAccessKey,
|
|
337
|
-
},
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
if (logging) {
|
|
341
|
-
console.log("\nFinal request:", JSON.stringify(request, null, 2));
|
|
365
|
+
} else {
|
|
366
|
+
return {
|
|
367
|
+
prompt: typeof prompt === 'string' ? prompt : {
|
|
368
|
+
messages: prompt.map(msg => ({
|
|
369
|
+
role: msg.role,
|
|
370
|
+
content: Array.isArray(msg.content) ?
|
|
371
|
+
msg.content.map(item =>
|
|
372
|
+
item.type === 'text' ? item.text : item
|
|
373
|
+
).join('\n') :
|
|
374
|
+
msg.content
|
|
375
|
+
}))
|
|
376
|
+
},
|
|
377
|
+
// Optional inference parameters:
|
|
378
|
+
[awsModel.max_tokens_param_name]: max_gen_tokens,
|
|
379
|
+
temperature: temperature,
|
|
380
|
+
top_p: top_p,
|
|
381
|
+
...(() => {
|
|
382
|
+
const stopSequencesValue = stop_sequences || stop;
|
|
383
|
+
return awsModel.stop_sequences_param_name && stopSequencesValue ? {
|
|
384
|
+
[awsModel.stop_sequences_param_name]: Array.isArray(stopSequencesValue) ? stopSequencesValue : [stopSequencesValue]
|
|
385
|
+
} : {};
|
|
386
|
+
})(),
|
|
387
|
+
...awsModel.special_request_schema
|
|
388
|
+
};
|
|
342
389
|
}
|
|
390
|
+
}
|
|
343
391
|
|
|
344
|
-
|
|
392
|
+
// Execute Invoke API call (streaming and non-streaming)
|
|
393
|
+
async function* executeInvokeAPI(client, request, awsModelId, shouldStream, awsModel, include_thinking_data) {
|
|
394
|
+
if (shouldStream) {
|
|
345
395
|
const responseStream = await client.send(
|
|
346
396
|
new InvokeModelWithResponseStreamCommand({
|
|
347
397
|
contentType: "application/json",
|
|
@@ -361,6 +411,8 @@ export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObjec
|
|
|
361
411
|
is_thinking = false;
|
|
362
412
|
result = `</think>\n\n${result}`;
|
|
363
413
|
}
|
|
414
|
+
// Process reasoning tags for GPT-OSS models
|
|
415
|
+
result = processReasoningTags(result, awsModel);
|
|
364
416
|
yield result;
|
|
365
417
|
} else {
|
|
366
418
|
if (include_thinking_data && awsModel.thinking_response_chunk_element) {
|
|
@@ -417,6 +469,9 @@ export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObjec
|
|
|
417
469
|
text_result = "";
|
|
418
470
|
}
|
|
419
471
|
|
|
472
|
+
// Process reasoning tags for GPT-OSS models
|
|
473
|
+
text_result = processReasoningTags(text_result, awsModel);
|
|
474
|
+
|
|
420
475
|
let result = thinking_result ? `<think>${thinking_result}</think>\n\n${text_result}` : text_result;
|
|
421
476
|
|
|
422
477
|
// Ensure final result is a string, in case thinking_result was also empty
|
|
@@ -424,7 +479,245 @@ export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObjec
|
|
|
424
479
|
result = "";
|
|
425
480
|
}
|
|
426
481
|
yield result;
|
|
427
|
-
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export async function* bedrockWrapper(awsCreds, openaiChatCompletionsCreateObject, { logging = false, useConverseAPI = false } = {} ) {
|
|
486
|
+
const { region, accessKeyId, secretAccessKey } = awsCreds;
|
|
487
|
+
let { messages, model, max_tokens, stream, temperature, top_p, include_thinking_data, stop, stop_sequences } = openaiChatCompletionsCreateObject;
|
|
488
|
+
|
|
489
|
+
let {awsModelId, awsModel} = findAwsModelWithId(model);
|
|
490
|
+
|
|
491
|
+
// Create a Bedrock Runtime client
|
|
492
|
+
const client = new BedrockRuntimeClient({
|
|
493
|
+
region: region,
|
|
494
|
+
credentials: {
|
|
495
|
+
accessKeyId: accessKeyId,
|
|
496
|
+
secretAccessKey: secretAccessKey,
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Calculate max tokens (shared between both APIs)
|
|
501
|
+
let max_gen_tokens = max_tokens <= awsModel.max_supported_response_tokens ? max_tokens : awsModel.max_supported_response_tokens;
|
|
502
|
+
|
|
503
|
+
// Check if model supports streaming
|
|
504
|
+
const modelSupportsStreaming = awsModel.streaming_supported !== false;
|
|
505
|
+
const shouldStream = stream && modelSupportsStreaming;
|
|
506
|
+
|
|
507
|
+
// ============================
|
|
508
|
+
// CONVERSE API PATH (SIMPLIFIED)
|
|
509
|
+
// ============================
|
|
510
|
+
if (useConverseAPI) {
|
|
511
|
+
// Convert messages to Converse API format (no model-specific complexity)
|
|
512
|
+
const { messages: converseMessages, system: systemPrompts } = await convertToConverseFormat(messages);
|
|
513
|
+
|
|
514
|
+
// Build inference configuration (handle thinking mode for Claude models)
|
|
515
|
+
const inferenceConfig = {
|
|
516
|
+
maxTokens: max_gen_tokens,
|
|
517
|
+
temperature: temperature,
|
|
518
|
+
...(top_p !== undefined && { topP: top_p })
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// Handle thinking mode for Claude models
|
|
522
|
+
let budget_tokens;
|
|
523
|
+
if (awsModel.special_request_schema?.thinking?.type === "enabled") {
|
|
524
|
+
// Apply thinking mode constraints for Converse API
|
|
525
|
+
inferenceConfig.temperature = 1; // temperature must be 1 for thinking
|
|
526
|
+
delete inferenceConfig.topP; // top_p must be unset for thinking
|
|
527
|
+
|
|
528
|
+
// Calculate thinking budget configuration
|
|
529
|
+
budget_tokens = awsModel.special_request_schema?.thinking?.budget_tokens;
|
|
530
|
+
if (budget_tokens > (max_gen_tokens * 0.8)) {
|
|
531
|
+
budget_tokens = Math.floor(max_gen_tokens * 0.8);
|
|
532
|
+
}
|
|
533
|
+
if (budget_tokens < 1024) {
|
|
534
|
+
budget_tokens = 1024;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Ensure max tokens is sufficient for thinking
|
|
538
|
+
if (inferenceConfig.maxTokens <= budget_tokens) {
|
|
539
|
+
inferenceConfig.maxTokens = Math.floor(budget_tokens * 1.2);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Add stop sequences if provided (unified format)
|
|
544
|
+
const stopSequencesValue = stop_sequences || stop;
|
|
545
|
+
if (stopSequencesValue) {
|
|
546
|
+
inferenceConfig.stopSequences = Array.isArray(stopSequencesValue) ?
|
|
547
|
+
stopSequencesValue : [stopSequencesValue];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Build the Converse API request (simple, unified format)
|
|
551
|
+
const converseRequest = {
|
|
552
|
+
modelId: awsModelId,
|
|
553
|
+
messages: converseMessages,
|
|
554
|
+
inferenceConfig: inferenceConfig
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// Add system prompts if any
|
|
558
|
+
if (systemPrompts.length > 0) {
|
|
559
|
+
converseRequest.system = systemPrompts;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Add thinking configuration for Claude models
|
|
563
|
+
if (awsModel.special_request_schema?.thinking?.type === "enabled") {
|
|
564
|
+
converseRequest.additionalModelRequestFields = {
|
|
565
|
+
thinking: {
|
|
566
|
+
type: "enabled",
|
|
567
|
+
budget_tokens: budget_tokens
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
if (awsModel.special_request_schema?.anthropic_beta) {
|
|
572
|
+
converseRequest.additionalModelRequestFields.anthropic_beta = awsModel.special_request_schema.anthropic_beta;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (logging) {
|
|
577
|
+
console.log("\nConverse API request:", JSON.stringify(converseRequest, null, 2));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (shouldStream) {
|
|
581
|
+
// Use ConverseStream for streaming responses
|
|
582
|
+
const responseStream = await client.send(new ConverseStreamCommand(converseRequest));
|
|
583
|
+
|
|
584
|
+
let is_thinking = false;
|
|
585
|
+
let should_think = include_thinking_data && awsModel.special_request_schema?.thinking?.type === "enabled";
|
|
586
|
+
|
|
587
|
+
for await (const event of responseStream.stream) {
|
|
588
|
+
if (event.contentBlockDelta) {
|
|
589
|
+
const text = event.contentBlockDelta.delta?.text;
|
|
590
|
+
const thinking = event.contentBlockDelta.delta?.thinking;
|
|
591
|
+
const reasoningContent = event.contentBlockDelta.delta?.reasoningContent;
|
|
592
|
+
|
|
593
|
+
// Handle Claude thinking data (streaming) - check both reasoningContent and thinking
|
|
594
|
+
const thinkingText = reasoningContent?.reasoningText?.text || thinking;
|
|
595
|
+
if (should_think && thinkingText) {
|
|
596
|
+
if (!is_thinking) {
|
|
597
|
+
is_thinking = true;
|
|
598
|
+
yield `<think>${thinkingText}`;
|
|
599
|
+
} else {
|
|
600
|
+
yield thinkingText;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Handle regular text content
|
|
604
|
+
else if (text) {
|
|
605
|
+
// End thinking mode if we were in it
|
|
606
|
+
if (is_thinking) {
|
|
607
|
+
is_thinking = false;
|
|
608
|
+
yield `</think>\n\n${text}`;
|
|
609
|
+
} else {
|
|
610
|
+
// Process reasoning tags for GPT-OSS models only
|
|
611
|
+
const processedText = processReasoningTags(text, awsModel);
|
|
612
|
+
if (processedText) {
|
|
613
|
+
yield processedText;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Close thinking tag if still open
|
|
621
|
+
if (is_thinking) {
|
|
622
|
+
yield "</think>";
|
|
623
|
+
}
|
|
624
|
+
} else {
|
|
625
|
+
// Use Converse for non-streaming responses
|
|
626
|
+
const response = await client.send(new ConverseCommand(converseRequest));
|
|
627
|
+
|
|
628
|
+
if (logging) {
|
|
629
|
+
console.log("\nConverse API response:", JSON.stringify(response, null, 2));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Extract text and thinking from response (handle Claude thinking)
|
|
633
|
+
if (response.output && response.output.message && response.output.message.content) {
|
|
634
|
+
let thinking_result = "";
|
|
635
|
+
let text_result = "";
|
|
636
|
+
|
|
637
|
+
for (const contentBlock of response.output.message.content) {
|
|
638
|
+
// Extract thinking data for Claude models (from reasoningContent)
|
|
639
|
+
if (include_thinking_data && contentBlock.reasoningContent &&
|
|
640
|
+
awsModel.special_request_schema?.thinking?.type === "enabled") {
|
|
641
|
+
const reasoningText = contentBlock.reasoningContent.reasoningText?.text;
|
|
642
|
+
if (reasoningText) {
|
|
643
|
+
thinking_result += reasoningText;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Also check for legacy thinking field format
|
|
648
|
+
if (include_thinking_data && contentBlock.thinking &&
|
|
649
|
+
awsModel.special_request_schema?.thinking?.type === "enabled") {
|
|
650
|
+
thinking_result += contentBlock.thinking;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Extract regular text content
|
|
654
|
+
if (contentBlock.text) {
|
|
655
|
+
text_result += contentBlock.text;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Process reasoning tags for GPT-OSS models
|
|
660
|
+
text_result = processReasoningTags(text_result, awsModel);
|
|
661
|
+
|
|
662
|
+
// Combine thinking and text for Claude models
|
|
663
|
+
let result = thinking_result ? `<think>${thinking_result}</think>\n\n${text_result}` : text_result;
|
|
664
|
+
|
|
665
|
+
if (result) {
|
|
666
|
+
yield result;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return; // Exit early when using Converse API
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ============================
|
|
674
|
+
// INVOKE API PATH (COMPLEX, MODEL-SPECIFIC)
|
|
675
|
+
// ============================
|
|
676
|
+
|
|
677
|
+
// Process messages for Invoke API (complex, model-specific)
|
|
678
|
+
const { message_cleaned, system_message } = await processMessagesForInvoke(messages, awsModel);
|
|
679
|
+
|
|
680
|
+
// Build prompt for Invoke API (complex, model-specific)
|
|
681
|
+
const prompt = buildInvokePrompt(message_cleaned, awsModel);
|
|
682
|
+
|
|
683
|
+
if (logging) {
|
|
684
|
+
console.log("\nFinal formatted prompt:", prompt);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Handle thinking mode adjustments (Invoke API specific)
|
|
688
|
+
if (awsModel.special_request_schema?.thinking?.type === "enabled") {
|
|
689
|
+
// temperature may only be set to 1 when thinking is enabled
|
|
690
|
+
temperature = 1;
|
|
691
|
+
// top_p must be unset when thinking is enabled
|
|
692
|
+
top_p = undefined;
|
|
693
|
+
// budget_tokens can not be greater than 80% of max_gen_tokens
|
|
694
|
+
let budget_tokens = awsModel.special_request_schema?.thinking?.budget_tokens;
|
|
695
|
+
if (budget_tokens > (max_gen_tokens * 0.8)) {
|
|
696
|
+
budget_tokens = Math.floor(max_gen_tokens * 0.8);
|
|
697
|
+
}
|
|
698
|
+
if (budget_tokens < 1024) {
|
|
699
|
+
budget_tokens = 1024;
|
|
700
|
+
}
|
|
701
|
+
// if awsModel.special_request_schema?.thinking?.budget_tokens, set it to budget_tokens
|
|
702
|
+
if (awsModel.special_request_schema?.thinking?.budget_tokens) {
|
|
703
|
+
awsModel.special_request_schema.thinking.budget_tokens = budget_tokens;
|
|
704
|
+
// max_gen_tokens has to be greater than budget_tokens
|
|
705
|
+
if (max_gen_tokens <= budget_tokens) {
|
|
706
|
+
// make max_gen_tokens 20% greater than budget_tokens
|
|
707
|
+
max_gen_tokens = Math.floor(budget_tokens * 1.2);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Build request for Invoke API (complex, model-specific)
|
|
713
|
+
const request = buildInvokeRequest(prompt, awsModel, max_gen_tokens, temperature, top_p, stop_sequences, stop, system_message);
|
|
714
|
+
|
|
715
|
+
if (logging) {
|
|
716
|
+
console.log("\nFinal request:", JSON.stringify(request, null, 2));
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Execute Invoke API call (complex, model-specific response parsing)
|
|
720
|
+
yield* executeInvokeAPI(client, request, awsModelId, shouldStream, awsModel, include_thinking_data);
|
|
428
721
|
}
|
|
429
722
|
|
|
430
723
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// ================================================================================
|
|
2
|
+
// == Example: Using the AWS Bedrock Converse API with bedrock-wrapper ==
|
|
3
|
+
// ================================================================================
|
|
4
|
+
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { bedrockWrapper } from "./bedrock-wrapper.js";
|
|
7
|
+
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
const AWS_REGION = process.env.AWS_REGION;
|
|
11
|
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
|
|
12
|
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
const awsCreds = {
|
|
16
|
+
region: AWS_REGION,
|
|
17
|
+
accessKeyId: AWS_ACCESS_KEY_ID,
|
|
18
|
+
secretAccessKey: AWS_SECRET_ACCESS_KEY,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Example conversation with system prompt
|
|
22
|
+
const messages = [
|
|
23
|
+
{
|
|
24
|
+
role: "user",
|
|
25
|
+
content: "What are the benefits of using the Converse API over the Invoke API?"
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const openaiChatCompletionsCreateObject = {
|
|
30
|
+
messages,
|
|
31
|
+
model: "Claude-3-Haiku", // Works with any supported model
|
|
32
|
+
max_tokens: 500,
|
|
33
|
+
stream: true, // Can be true or false
|
|
34
|
+
temperature: 0.7,
|
|
35
|
+
top_p: 0.9,
|
|
36
|
+
stop: ["END", "STOP"] // Optional stop sequences
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
console.log("=".repeat(60));
|
|
40
|
+
console.log("Example: AWS Bedrock Converse API");
|
|
41
|
+
console.log("=".repeat(60));
|
|
42
|
+
console.log("\nUsing model:", openaiChatCompletionsCreateObject.model);
|
|
43
|
+
console.log("Streaming:", openaiChatCompletionsCreateObject.stream);
|
|
44
|
+
console.log("\nResponse:");
|
|
45
|
+
console.log("-".repeat(40));
|
|
46
|
+
|
|
47
|
+
let completeResponse = "";
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Use the Converse API by setting useConverseAPI: true
|
|
51
|
+
for await (const chunk of bedrockWrapper(awsCreds, openaiChatCompletionsCreateObject, {
|
|
52
|
+
useConverseAPI: true, // ← Enable Converse API
|
|
53
|
+
logging: false // Set to true to see API requests/responses
|
|
54
|
+
})) {
|
|
55
|
+
completeResponse += chunk;
|
|
56
|
+
process.stdout.write(chunk); // Display streamed output
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log("\n" + "-".repeat(40));
|
|
60
|
+
console.log("\n✅ Successfully used the Converse API!");
|
|
61
|
+
|
|
62
|
+
// Uncomment to see the complete response
|
|
63
|
+
// console.log("\nComplete Response:", completeResponse);
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("\n❌ Error:", error.message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Example 2: Comparing Invoke API vs Converse API
|
|
70
|
+
console.log("\n" + "=".repeat(60));
|
|
71
|
+
console.log("Comparing Invoke API vs Converse API");
|
|
72
|
+
console.log("=".repeat(60));
|
|
73
|
+
|
|
74
|
+
const simpleMessage = [
|
|
75
|
+
{ role: "user", content: "What is 2+2? Answer in one word." }
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const compareRequest = {
|
|
79
|
+
messages: simpleMessage,
|
|
80
|
+
model: "Claude-3-Haiku",
|
|
81
|
+
max_tokens: 50,
|
|
82
|
+
stream: false,
|
|
83
|
+
temperature: 0.1,
|
|
84
|
+
top_p: 0.9
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Test with Invoke API
|
|
88
|
+
console.log("\n1. Using Invoke API (default):");
|
|
89
|
+
let invokeResponse = "";
|
|
90
|
+
const invokeStart = Date.now();
|
|
91
|
+
const invokeGen = await bedrockWrapper(awsCreds, compareRequest, { useConverseAPI: false });
|
|
92
|
+
for await (const data of invokeGen) {
|
|
93
|
+
invokeResponse += data;
|
|
94
|
+
}
|
|
95
|
+
const invokeTime = Date.now() - invokeStart;
|
|
96
|
+
console.log(` Response: ${invokeResponse}`);
|
|
97
|
+
console.log(` Time: ${invokeTime}ms`);
|
|
98
|
+
|
|
99
|
+
// Test with Converse API
|
|
100
|
+
console.log("\n2. Using Converse API:");
|
|
101
|
+
let converseResponse = "";
|
|
102
|
+
const converseStart = Date.now();
|
|
103
|
+
const converseGen = await bedrockWrapper(awsCreds, compareRequest, { useConverseAPI: true });
|
|
104
|
+
for await (const data of converseGen) {
|
|
105
|
+
converseResponse += data;
|
|
106
|
+
}
|
|
107
|
+
const converseTime = Date.now() - converseStart;
|
|
108
|
+
console.log(` Response: ${converseResponse}`);
|
|
109
|
+
console.log(` Time: ${converseTime}ms`);
|
|
110
|
+
|
|
111
|
+
console.log("\n" + "=".repeat(60));
|
|
112
|
+
console.log("✨ Example complete!");
|
|
113
|
+
console.log("=".repeat(60));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().catch(console.error);
|