@@ -18,6 +18,7 @@ import {
|
||||
type SystemContentBlock,
|
||||
type ToolChoice,
|
||||
type ToolConfiguration,
|
||||
type ToolResultContentBlock,
|
||||
ToolResultStatus,
|
||||
} from "@aws-sdk/client-bedrock-runtime";
|
||||
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
||||
@@ -28,6 +29,7 @@ import type {
|
||||
AssistantMessage,
|
||||
CacheRetention,
|
||||
Context,
|
||||
ImageContent,
|
||||
Model,
|
||||
SimpleStreamOptions,
|
||||
StopReason,
|
||||
@@ -86,6 +88,8 @@ export interface BedrockOptions extends StreamOptions {
|
||||
|
||||
type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; partialJson?: string };
|
||||
|
||||
const EMPTY_TEXT_PLACEHOLDER = "<empty>";
|
||||
|
||||
export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOptions> = (
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
context: Context,
|
||||
@@ -650,6 +654,29 @@ function normalizeToolCallId(id: string): string {
|
||||
return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
|
||||
}
|
||||
|
||||
function createNonBlankTextBlock(text: string): ContentBlock.TextMember | undefined {
|
||||
const sanitized = sanitizeSurrogates(text);
|
||||
return sanitized.trim().length === 0 ? undefined : { text: sanitized };
|
||||
}
|
||||
|
||||
function createRequiredTextBlock(text: string): ContentBlock.TextMember {
|
||||
return createNonBlankTextBlock(text) ?? { text: EMPTY_TEXT_PLACEHOLDER };
|
||||
}
|
||||
|
||||
function convertToolResultContent(content: (TextContent | ImageContent)[]): ToolResultContentBlock[] {
|
||||
const result: ToolResultContentBlock[] = [];
|
||||
for (const c of content) {
|
||||
if (c.type === "image") {
|
||||
result.push({ image: createImageBlock(c.mimeType, c.data) });
|
||||
} else {
|
||||
const textBlock = createNonBlankTextBlock(c.text);
|
||||
if (textBlock) result.push(textBlock);
|
||||
}
|
||||
}
|
||||
if (result.length === 0) result.push({ text: EMPTY_TEXT_PLACEHOLDER });
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertMessages(
|
||||
context: Context,
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
@@ -665,13 +692,15 @@ function convertMessages(
|
||||
case "user": {
|
||||
const content: ContentBlock[] = [];
|
||||
if (typeof m.content === "string") {
|
||||
content.push({ text: sanitizeSurrogates(m.content) });
|
||||
content.push(createRequiredTextBlock(m.content));
|
||||
} else {
|
||||
for (const c of m.content) {
|
||||
switch (c.type) {
|
||||
case "text":
|
||||
content.push({ text: sanitizeSurrogates(c.text) });
|
||||
case "text": {
|
||||
const textBlock = createNonBlankTextBlock(c.text);
|
||||
if (textBlock) content.push(textBlock);
|
||||
break;
|
||||
}
|
||||
case "image":
|
||||
content.push({ image: createImageBlock(c.mimeType, c.data) });
|
||||
break;
|
||||
@@ -679,8 +708,8 @@ function convertMessages(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (content.length === 0) content.push({ text: EMPTY_TEXT_PLACEHOLDER });
|
||||
}
|
||||
if (content.length === 0) continue;
|
||||
result.push({
|
||||
role: ConversationRole.USER,
|
||||
content,
|
||||
@@ -696,19 +725,22 @@ function convertMessages(
|
||||
const contentBlocks: ContentBlock[] = [];
|
||||
for (const c of m.content) {
|
||||
switch (c.type) {
|
||||
case "text":
|
||||
case "text": {
|
||||
// Skip empty text blocks
|
||||
if (c.text.trim().length === 0) continue;
|
||||
contentBlocks.push({ text: sanitizeSurrogates(c.text) });
|
||||
const textBlock = createNonBlankTextBlock(c.text);
|
||||
if (!textBlock) continue;
|
||||
contentBlocks.push(textBlock);
|
||||
break;
|
||||
}
|
||||
case "toolCall":
|
||||
contentBlocks.push({
|
||||
toolUse: { toolUseId: c.id, name: c.name, input: c.arguments },
|
||||
});
|
||||
break;
|
||||
case "thinking":
|
||||
case "thinking": {
|
||||
// Skip empty thinking blocks
|
||||
if (c.thinking.trim().length === 0) continue;
|
||||
const thinking = sanitizeSurrogates(c.thinking);
|
||||
if (thinking.trim().length === 0) continue;
|
||||
// Only Anthropic models support the signature field in reasoningText.
|
||||
// For other models, we omit the signature to avoid errors like:
|
||||
// "This model doesn't support the reasoningContent.reasoningText.signature field"
|
||||
@@ -717,12 +749,12 @@ function convertMessages(
|
||||
// persisted message lacks a signature, Bedrock rejects the replayed
|
||||
// reasoning block. Fall back to plain text, matching Anthropic.
|
||||
if (!c.thinkingSignature || c.thinkingSignature.trim().length === 0) {
|
||||
contentBlocks.push({ text: sanitizeSurrogates(c.thinking) });
|
||||
contentBlocks.push({ text: thinking });
|
||||
} else {
|
||||
contentBlocks.push({
|
||||
reasoningContent: {
|
||||
reasoningText: {
|
||||
text: sanitizeSurrogates(c.thinking),
|
||||
text: thinking,
|
||||
signature: c.thinkingSignature,
|
||||
},
|
||||
},
|
||||
@@ -731,11 +763,12 @@ function convertMessages(
|
||||
} else {
|
||||
contentBlocks.push({
|
||||
reasoningContent: {
|
||||
reasoningText: { text: sanitizeSurrogates(c.thinking) },
|
||||
reasoningText: { text: thinking },
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
@@ -759,11 +792,7 @@ function convertMessages(
|
||||
toolResults.push({
|
||||
toolResult: {
|
||||
toolUseId: m.toolCallId,
|
||||
content: m.content.map((c) =>
|
||||
c.type === "image"
|
||||
? { image: createImageBlock(c.mimeType, c.data) }
|
||||
: { text: sanitizeSurrogates(c.text) },
|
||||
),
|
||||
content: convertToolResultContent(m.content),
|
||||
status: m.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
|
||||
},
|
||||
});
|
||||
@@ -775,11 +804,7 @@ function convertMessages(
|
||||
toolResults.push({
|
||||
toolResult: {
|
||||
toolUseId: nextMsg.toolCallId,
|
||||
content: nextMsg.content.map((c) =>
|
||||
c.type === "image"
|
||||
? { image: createImageBlock(c.mimeType, c.data) }
|
||||
: { text: sanitizeSurrogates(c.text) },
|
||||
),
|
||||
content: convertToolResultContent(nextMsg.content),
|
||||
status: nextMsg.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user