@librechat/agents 3.1.53 → 3.1.54
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/dist/cjs/llm/bedrock/utils/message_outputs.cjs +16 -5
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +16 -5
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/types/llm/bedrock/utils/message_outputs.d.ts +1 -1
- package/package.json +1 -1
- package/src/llm/bedrock/llm.spec.ts +233 -4
- package/src/llm/bedrock/utils/message_outputs.ts +51 -11
- package/src/scripts/bedrock-cache-debug.ts +250 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var messages = require('@langchain/core/messages');
|
|
4
3
|
var outputs = require('@langchain/core/outputs');
|
|
4
|
+
var messages = require('@langchain/core/messages');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Utility functions for converting Bedrock Converse responses to LangChain messages.
|
|
@@ -127,18 +127,29 @@ function handleConverseStreamContentBlockStart(contentBlockStart) {
|
|
|
127
127
|
* Handle a metadata event from Bedrock Converse stream.
|
|
128
128
|
*/
|
|
129
129
|
function handleConverseStreamMetadata(metadata, extra) {
|
|
130
|
-
const
|
|
131
|
-
const
|
|
130
|
+
const usage = metadata.usage;
|
|
131
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
132
|
+
const outputTokens = usage?.outputTokens ?? 0;
|
|
133
|
+
const cacheRead = usage?.cacheReadInputTokens;
|
|
134
|
+
const cacheWrite = usage?.cacheWriteInputTokens;
|
|
132
135
|
const usage_metadata = {
|
|
133
136
|
input_tokens: inputTokens,
|
|
134
137
|
output_tokens: outputTokens,
|
|
135
|
-
total_tokens:
|
|
138
|
+
total_tokens: usage?.totalTokens ?? inputTokens + outputTokens,
|
|
136
139
|
};
|
|
140
|
+
if (cacheRead != null || cacheWrite != null) {
|
|
141
|
+
usage_metadata.input_token_details = {
|
|
142
|
+
cache_read: cacheRead ?? 0,
|
|
143
|
+
cache_creation: cacheWrite ?? 0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
137
146
|
return new outputs.ChatGenerationChunk({
|
|
138
147
|
text: '',
|
|
139
148
|
message: new messages.AIMessageChunk({
|
|
140
149
|
content: '',
|
|
141
|
-
usage_metadata: extra.streamUsage
|
|
150
|
+
usage_metadata: extra.streamUsage
|
|
151
|
+
? usage_metadata
|
|
152
|
+
: undefined,
|
|
142
153
|
response_metadata: {
|
|
143
154
|
// Use the same key as returned from the Converse API
|
|
144
155
|
metadata,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message_outputs.cjs","sources":["../../../../../src/llm/bedrock/utils/message_outputs.ts"],"sourcesContent":["/**\n * Utility functions for converting Bedrock Converse responses to LangChain messages.\n * Ported from @langchain/aws common.js\n */\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type {\n BedrockMessage,\n ConverseResponse,\n ContentBlockDeltaEvent,\n ConverseStreamMetadataEvent,\n ContentBlockStartEvent,\n ReasoningContentBlock,\n ReasoningContentBlockDelta,\n MessageContentReasoningBlock,\n MessageContentReasoningBlockReasoningTextPartial,\n MessageContentReasoningBlockRedacted,\n} from '../types';\n\n/**\n * Convert a Bedrock reasoning block delta to a LangChain partial reasoning block.\n */\nexport function bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n reasoningContent: ReasoningContentBlockDelta\n):\n | MessageContentReasoningBlockReasoningTextPartial\n | MessageContentReasoningBlockRedacted {\n const { text, redactedContent, signature } =\n reasoningContent as ReasoningContentBlockDelta & {\n text?: string;\n redactedContent?: Uint8Array;\n signature?: string;\n };\n\n if (typeof text === 'string') {\n return {\n type: 'reasoning_content',\n reasoningText: { text },\n };\n }\n if (signature != null) {\n return {\n type: 'reasoning_content',\n reasoningText: { signature },\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock reasoning block to a LangChain reasoning block.\n */\nexport function bedrockReasoningBlockToLangchainReasoningBlock(\n reasoningContent: ReasoningContentBlock\n): MessageContentReasoningBlock {\n const { reasoningText, redactedContent } =\n reasoningContent as ReasoningContentBlock & {\n reasoningText?: { text?: string; signature?: string };\n redactedContent?: Uint8Array;\n };\n\n if (reasoningText != null) {\n return {\n type: 'reasoning_content',\n reasoningText: reasoningText,\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock Converse message to a LangChain message.\n */\nexport function convertConverseMessageToLangChainMessage(\n message: BedrockMessage,\n responseMetadata: Omit<ConverseResponse, 'output'>\n): AIMessage {\n if (message.content == null) {\n throw new Error('No message content found in response.');\n }\n if (message.role !== 'assistant') {\n throw new Error(\n `Unsupported message role received in ChatBedrockConverse response: ${message.role}`\n );\n }\n\n let requestId: string | undefined;\n if (\n '$metadata' in responseMetadata &&\n responseMetadata.$metadata != null &&\n typeof responseMetadata.$metadata === 'object' &&\n 'requestId' in responseMetadata.$metadata\n ) {\n requestId = responseMetadata.$metadata.requestId as string;\n }\n\n let tokenUsage:\n | { input_tokens: number; output_tokens: number; total_tokens: number }\n | undefined;\n if (responseMetadata.usage != null) {\n const input_tokens = responseMetadata.usage.inputTokens ?? 0;\n const output_tokens = responseMetadata.usage.outputTokens ?? 0;\n tokenUsage = {\n input_tokens,\n output_tokens,\n total_tokens:\n responseMetadata.usage.totalTokens ?? input_tokens + output_tokens,\n };\n }\n\n if (\n message.content.length === 1 &&\n 'text' in message.content[0] &&\n typeof message.content[0].text === 'string'\n ) {\n return new AIMessage({\n content: message.content[0].text,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n } else {\n const toolCalls: Array<{\n id?: string;\n name: string;\n args: Record<string, unknown>;\n type: 'tool_call';\n }> = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content: any[] = [];\n\n message.content.forEach((c) => {\n if (\n 'toolUse' in c &&\n c.toolUse != null &&\n c.toolUse.name != null &&\n c.toolUse.name !== '' &&\n c.toolUse.input != null &&\n typeof c.toolUse.input === 'object'\n ) {\n toolCalls.push({\n id: c.toolUse.toolUseId,\n name: c.toolUse.name,\n args: c.toolUse.input as Record<string, unknown>,\n type: 'tool_call',\n });\n } else if ('text' in c && typeof c.text === 'string') {\n content.push({ type: 'text', text: c.text });\n } else if ('reasoningContent' in c && c.reasoningContent != null) {\n content.push(\n bedrockReasoningBlockToLangchainReasoningBlock(c.reasoningContent)\n );\n } else {\n content.push(c);\n }\n });\n\n return new AIMessage({\n content: content.length ? content : '',\n tool_calls: toolCalls.length ? toolCalls : undefined,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n }\n}\n\n/**\n * Handle a content block delta event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockDelta(\n contentBlockDelta: ContentBlockDeltaEvent\n): ChatGenerationChunk {\n if (contentBlockDelta.delta == null) {\n throw new Error('No delta found in content block.');\n }\n\n if (typeof contentBlockDelta.delta.text === 'string') {\n return new ChatGenerationChunk({\n text: contentBlockDelta.delta.text,\n message: new AIMessageChunk({\n content: contentBlockDelta.delta.text,\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.toolUse != null) {\n const index = contentBlockDelta.contentBlockIndex;\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n args: contentBlockDelta.delta.toolUse.input as string,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.reasoningContent != null) {\n const reasoningBlock =\n bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n contentBlockDelta.delta.reasoningContent\n );\n let reasoningText = '';\n if ('reasoningText' in reasoningBlock) {\n reasoningText = reasoningBlock.reasoningText.text ?? '';\n } else if ('redactedContent' in reasoningBlock) {\n reasoningText = reasoningBlock.redactedContent;\n }\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: [reasoningBlock],\n additional_kwargs: {\n // Set reasoning_content for stream handler to detect reasoning mode\n reasoning_content: reasoningText,\n },\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else {\n throw new Error(\n `Unsupported content block type(s): ${JSON.stringify(contentBlockDelta.delta, null, 2)}`\n );\n }\n}\n\n/**\n * Handle a content block start event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockStart(\n contentBlockStart: ContentBlockStartEvent\n): ChatGenerationChunk | null {\n const index = contentBlockStart.contentBlockIndex;\n\n if (contentBlockStart.start?.toolUse != null) {\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n name: contentBlockStart.start.toolUse.name,\n id: contentBlockStart.start.toolUse.toolUseId,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: index,\n },\n }),\n });\n }\n\n // Return null for non-tool content block starts (text blocks don't need special handling)\n return null;\n}\n\n/**\n * Handle a metadata event from Bedrock Converse stream.\n */\nexport function handleConverseStreamMetadata(\n metadata: ConverseStreamMetadataEvent,\n extra: { streamUsage: boolean }\n): ChatGenerationChunk {\n const inputTokens = metadata.usage?.inputTokens ?? 0;\n const outputTokens = metadata.usage?.outputTokens ?? 0;\n const usage_metadata = {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n total_tokens: metadata.usage?.totalTokens ?? inputTokens + outputTokens,\n };\n\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n usage_metadata: extra.streamUsage ? usage_metadata : undefined,\n response_metadata: {\n // Use the same key as returned from the Converse API\n metadata,\n },\n }),\n });\n}\n"],"names":["ChatGenerationChunk","AIMessageChunk"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message_outputs.cjs","sources":["../../../../../src/llm/bedrock/utils/message_outputs.ts"],"sourcesContent":["/**\n * Utility functions for converting Bedrock Converse responses to LangChain messages.\n * Ported from @langchain/aws common.js\n */\nimport { ChatGenerationChunk } from '@langchain/core/outputs';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport type { UsageMetadata } from '@langchain/core/messages';\nimport type {\n BedrockMessage,\n ConverseResponse,\n ContentBlockDeltaEvent,\n ConverseStreamMetadataEvent,\n ContentBlockStartEvent,\n ReasoningContentBlock,\n ReasoningContentBlockDelta,\n MessageContentReasoningBlock,\n MessageContentReasoningBlockReasoningTextPartial,\n MessageContentReasoningBlockRedacted,\n} from '../types';\n\n/**\n * Convert a Bedrock reasoning block delta to a LangChain partial reasoning block.\n */\nexport function bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n reasoningContent: ReasoningContentBlockDelta\n):\n | MessageContentReasoningBlockReasoningTextPartial\n | MessageContentReasoningBlockRedacted {\n const { text, redactedContent, signature } =\n reasoningContent as ReasoningContentBlockDelta & {\n text?: string;\n redactedContent?: Uint8Array;\n signature?: string;\n };\n\n if (typeof text === 'string') {\n return {\n type: 'reasoning_content',\n reasoningText: { text },\n };\n }\n if (signature != null) {\n return {\n type: 'reasoning_content',\n reasoningText: { signature },\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock reasoning block to a LangChain reasoning block.\n */\nexport function bedrockReasoningBlockToLangchainReasoningBlock(\n reasoningContent: ReasoningContentBlock\n): MessageContentReasoningBlock {\n const { reasoningText, redactedContent } =\n reasoningContent as ReasoningContentBlock & {\n reasoningText?: { text?: string; signature?: string };\n redactedContent?: Uint8Array;\n };\n\n if (reasoningText != null) {\n return {\n type: 'reasoning_content',\n reasoningText: reasoningText,\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock Converse message to a LangChain message.\n */\nexport function convertConverseMessageToLangChainMessage(\n message: BedrockMessage,\n responseMetadata: Omit<ConverseResponse, 'output'>\n): AIMessage {\n if (message.content == null) {\n throw new Error('No message content found in response.');\n }\n if (message.role !== 'assistant') {\n throw new Error(\n `Unsupported message role received in ChatBedrockConverse response: ${message.role}`\n );\n }\n\n let requestId: string | undefined;\n if (\n '$metadata' in responseMetadata &&\n responseMetadata.$metadata != null &&\n typeof responseMetadata.$metadata === 'object' &&\n 'requestId' in responseMetadata.$metadata\n ) {\n requestId = responseMetadata.$metadata.requestId as string;\n }\n\n let tokenUsage:\n | {\n input_tokens: number;\n output_tokens: number;\n total_tokens: number;\n input_token_details?: {\n cache_read: number;\n cache_creation: number;\n };\n }\n | undefined;\n if (responseMetadata.usage != null) {\n const usage = responseMetadata.usage as NonNullable<\n typeof responseMetadata.usage\n > & {\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n };\n const input_tokens = usage.inputTokens ?? 0;\n const output_tokens = usage.outputTokens ?? 0;\n const cacheRead = usage.cacheReadInputTokens;\n const cacheWrite = usage.cacheWriteInputTokens;\n tokenUsage = {\n input_tokens,\n output_tokens,\n total_tokens: usage.totalTokens ?? input_tokens + output_tokens,\n };\n if (cacheRead != null || cacheWrite != null) {\n tokenUsage.input_token_details = {\n cache_read: cacheRead ?? 0,\n cache_creation: cacheWrite ?? 0,\n };\n }\n }\n\n if (\n message.content.length === 1 &&\n 'text' in message.content[0] &&\n typeof message.content[0].text === 'string'\n ) {\n return new AIMessage({\n content: message.content[0].text,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n } else {\n const toolCalls: Array<{\n id?: string;\n name: string;\n args: Record<string, unknown>;\n type: 'tool_call';\n }> = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content: any[] = [];\n\n message.content.forEach((c) => {\n if (\n 'toolUse' in c &&\n c.toolUse != null &&\n c.toolUse.name != null &&\n c.toolUse.name !== '' &&\n c.toolUse.input != null &&\n typeof c.toolUse.input === 'object'\n ) {\n toolCalls.push({\n id: c.toolUse.toolUseId,\n name: c.toolUse.name,\n args: c.toolUse.input as Record<string, unknown>,\n type: 'tool_call',\n });\n } else if ('text' in c && typeof c.text === 'string') {\n content.push({ type: 'text', text: c.text });\n } else if ('reasoningContent' in c && c.reasoningContent != null) {\n content.push(\n bedrockReasoningBlockToLangchainReasoningBlock(c.reasoningContent)\n );\n } else {\n content.push(c);\n }\n });\n\n return new AIMessage({\n content: content.length ? content : '',\n tool_calls: toolCalls.length ? toolCalls : undefined,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n }\n}\n\n/**\n * Handle a content block delta event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockDelta(\n contentBlockDelta: ContentBlockDeltaEvent\n): ChatGenerationChunk {\n if (contentBlockDelta.delta == null) {\n throw new Error('No delta found in content block.');\n }\n\n if (typeof contentBlockDelta.delta.text === 'string') {\n return new ChatGenerationChunk({\n text: contentBlockDelta.delta.text,\n message: new AIMessageChunk({\n content: contentBlockDelta.delta.text,\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.toolUse != null) {\n const index = contentBlockDelta.contentBlockIndex;\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n args: contentBlockDelta.delta.toolUse.input as string,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.reasoningContent != null) {\n const reasoningBlock =\n bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n contentBlockDelta.delta.reasoningContent\n );\n let reasoningText = '';\n if ('reasoningText' in reasoningBlock) {\n reasoningText = reasoningBlock.reasoningText.text ?? '';\n } else if ('redactedContent' in reasoningBlock) {\n reasoningText = reasoningBlock.redactedContent;\n }\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: [reasoningBlock],\n additional_kwargs: {\n // Set reasoning_content for stream handler to detect reasoning mode\n reasoning_content: reasoningText,\n },\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else {\n throw new Error(\n `Unsupported content block type(s): ${JSON.stringify(contentBlockDelta.delta, null, 2)}`\n );\n }\n}\n\n/**\n * Handle a content block start event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockStart(\n contentBlockStart: ContentBlockStartEvent\n): ChatGenerationChunk | null {\n const index = contentBlockStart.contentBlockIndex;\n\n if (contentBlockStart.start?.toolUse != null) {\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n name: contentBlockStart.start.toolUse.name,\n id: contentBlockStart.start.toolUse.toolUseId,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: index,\n },\n }),\n });\n }\n\n // Return null for non-tool content block starts (text blocks don't need special handling)\n return null;\n}\n\n/**\n * Handle a metadata event from Bedrock Converse stream.\n */\nexport function handleConverseStreamMetadata(\n metadata: ConverseStreamMetadataEvent,\n extra: { streamUsage: boolean }\n): ChatGenerationChunk {\n const usage = metadata.usage as\n | (NonNullable<ConverseStreamMetadataEvent['usage']> & {\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n })\n | undefined;\n const inputTokens = usage?.inputTokens ?? 0;\n const outputTokens = usage?.outputTokens ?? 0;\n const cacheRead = usage?.cacheReadInputTokens;\n const cacheWrite = usage?.cacheWriteInputTokens;\n\n const usage_metadata: Record<string, unknown> = {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n total_tokens: usage?.totalTokens ?? inputTokens + outputTokens,\n };\n\n if (cacheRead != null || cacheWrite != null) {\n usage_metadata.input_token_details = {\n cache_read: cacheRead ?? 0,\n cache_creation: cacheWrite ?? 0,\n };\n }\n\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n usage_metadata: extra.streamUsage\n ? (usage_metadata as UsageMetadata)\n : undefined,\n response_metadata: {\n // Use the same key as returned from the Converse API\n metadata,\n },\n }),\n });\n}\n"],"names":["ChatGenerationChunk","AIMessageChunk"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AIMessageChunk } from '@langchain/core/messages';
|
|
2
1
|
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
2
|
+
import { AIMessageChunk } from '@langchain/core/messages';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Utility functions for converting Bedrock Converse responses to LangChain messages.
|
|
@@ -125,18 +125,29 @@ function handleConverseStreamContentBlockStart(contentBlockStart) {
|
|
|
125
125
|
* Handle a metadata event from Bedrock Converse stream.
|
|
126
126
|
*/
|
|
127
127
|
function handleConverseStreamMetadata(metadata, extra) {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
128
|
+
const usage = metadata.usage;
|
|
129
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
130
|
+
const outputTokens = usage?.outputTokens ?? 0;
|
|
131
|
+
const cacheRead = usage?.cacheReadInputTokens;
|
|
132
|
+
const cacheWrite = usage?.cacheWriteInputTokens;
|
|
130
133
|
const usage_metadata = {
|
|
131
134
|
input_tokens: inputTokens,
|
|
132
135
|
output_tokens: outputTokens,
|
|
133
|
-
total_tokens:
|
|
136
|
+
total_tokens: usage?.totalTokens ?? inputTokens + outputTokens,
|
|
134
137
|
};
|
|
138
|
+
if (cacheRead != null || cacheWrite != null) {
|
|
139
|
+
usage_metadata.input_token_details = {
|
|
140
|
+
cache_read: cacheRead ?? 0,
|
|
141
|
+
cache_creation: cacheWrite ?? 0,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
135
144
|
return new ChatGenerationChunk({
|
|
136
145
|
text: '',
|
|
137
146
|
message: new AIMessageChunk({
|
|
138
147
|
content: '',
|
|
139
|
-
usage_metadata: extra.streamUsage
|
|
148
|
+
usage_metadata: extra.streamUsage
|
|
149
|
+
? usage_metadata
|
|
150
|
+
: undefined,
|
|
140
151
|
response_metadata: {
|
|
141
152
|
// Use the same key as returned from the Converse API
|
|
142
153
|
metadata,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message_outputs.mjs","sources":["../../../../../src/llm/bedrock/utils/message_outputs.ts"],"sourcesContent":["/**\n * Utility functions for converting Bedrock Converse responses to LangChain messages.\n * Ported from @langchain/aws common.js\n */\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport { ChatGenerationChunk } from '@langchain/core/outputs';\nimport type {\n BedrockMessage,\n ConverseResponse,\n ContentBlockDeltaEvent,\n ConverseStreamMetadataEvent,\n ContentBlockStartEvent,\n ReasoningContentBlock,\n ReasoningContentBlockDelta,\n MessageContentReasoningBlock,\n MessageContentReasoningBlockReasoningTextPartial,\n MessageContentReasoningBlockRedacted,\n} from '../types';\n\n/**\n * Convert a Bedrock reasoning block delta to a LangChain partial reasoning block.\n */\nexport function bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n reasoningContent: ReasoningContentBlockDelta\n):\n | MessageContentReasoningBlockReasoningTextPartial\n | MessageContentReasoningBlockRedacted {\n const { text, redactedContent, signature } =\n reasoningContent as ReasoningContentBlockDelta & {\n text?: string;\n redactedContent?: Uint8Array;\n signature?: string;\n };\n\n if (typeof text === 'string') {\n return {\n type: 'reasoning_content',\n reasoningText: { text },\n };\n }\n if (signature != null) {\n return {\n type: 'reasoning_content',\n reasoningText: { signature },\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock reasoning block to a LangChain reasoning block.\n */\nexport function bedrockReasoningBlockToLangchainReasoningBlock(\n reasoningContent: ReasoningContentBlock\n): MessageContentReasoningBlock {\n const { reasoningText, redactedContent } =\n reasoningContent as ReasoningContentBlock & {\n reasoningText?: { text?: string; signature?: string };\n redactedContent?: Uint8Array;\n };\n\n if (reasoningText != null) {\n return {\n type: 'reasoning_content',\n reasoningText: reasoningText,\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock Converse message to a LangChain message.\n */\nexport function convertConverseMessageToLangChainMessage(\n message: BedrockMessage,\n responseMetadata: Omit<ConverseResponse, 'output'>\n): AIMessage {\n if (message.content == null) {\n throw new Error('No message content found in response.');\n }\n if (message.role !== 'assistant') {\n throw new Error(\n `Unsupported message role received in ChatBedrockConverse response: ${message.role}`\n );\n }\n\n let requestId: string | undefined;\n if (\n '$metadata' in responseMetadata &&\n responseMetadata.$metadata != null &&\n typeof responseMetadata.$metadata === 'object' &&\n 'requestId' in responseMetadata.$metadata\n ) {\n requestId = responseMetadata.$metadata.requestId as string;\n }\n\n let tokenUsage:\n | { input_tokens: number; output_tokens: number; total_tokens: number }\n | undefined;\n if (responseMetadata.usage != null) {\n const input_tokens = responseMetadata.usage.inputTokens ?? 0;\n const output_tokens = responseMetadata.usage.outputTokens ?? 0;\n tokenUsage = {\n input_tokens,\n output_tokens,\n total_tokens:\n responseMetadata.usage.totalTokens ?? input_tokens + output_tokens,\n };\n }\n\n if (\n message.content.length === 1 &&\n 'text' in message.content[0] &&\n typeof message.content[0].text === 'string'\n ) {\n return new AIMessage({\n content: message.content[0].text,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n } else {\n const toolCalls: Array<{\n id?: string;\n name: string;\n args: Record<string, unknown>;\n type: 'tool_call';\n }> = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content: any[] = [];\n\n message.content.forEach((c) => {\n if (\n 'toolUse' in c &&\n c.toolUse != null &&\n c.toolUse.name != null &&\n c.toolUse.name !== '' &&\n c.toolUse.input != null &&\n typeof c.toolUse.input === 'object'\n ) {\n toolCalls.push({\n id: c.toolUse.toolUseId,\n name: c.toolUse.name,\n args: c.toolUse.input as Record<string, unknown>,\n type: 'tool_call',\n });\n } else if ('text' in c && typeof c.text === 'string') {\n content.push({ type: 'text', text: c.text });\n } else if ('reasoningContent' in c && c.reasoningContent != null) {\n content.push(\n bedrockReasoningBlockToLangchainReasoningBlock(c.reasoningContent)\n );\n } else {\n content.push(c);\n }\n });\n\n return new AIMessage({\n content: content.length ? content : '',\n tool_calls: toolCalls.length ? toolCalls : undefined,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n }\n}\n\n/**\n * Handle a content block delta event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockDelta(\n contentBlockDelta: ContentBlockDeltaEvent\n): ChatGenerationChunk {\n if (contentBlockDelta.delta == null) {\n throw new Error('No delta found in content block.');\n }\n\n if (typeof contentBlockDelta.delta.text === 'string') {\n return new ChatGenerationChunk({\n text: contentBlockDelta.delta.text,\n message: new AIMessageChunk({\n content: contentBlockDelta.delta.text,\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.toolUse != null) {\n const index = contentBlockDelta.contentBlockIndex;\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n args: contentBlockDelta.delta.toolUse.input as string,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.reasoningContent != null) {\n const reasoningBlock =\n bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n contentBlockDelta.delta.reasoningContent\n );\n let reasoningText = '';\n if ('reasoningText' in reasoningBlock) {\n reasoningText = reasoningBlock.reasoningText.text ?? '';\n } else if ('redactedContent' in reasoningBlock) {\n reasoningText = reasoningBlock.redactedContent;\n }\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: [reasoningBlock],\n additional_kwargs: {\n // Set reasoning_content for stream handler to detect reasoning mode\n reasoning_content: reasoningText,\n },\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else {\n throw new Error(\n `Unsupported content block type(s): ${JSON.stringify(contentBlockDelta.delta, null, 2)}`\n );\n }\n}\n\n/**\n * Handle a content block start event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockStart(\n contentBlockStart: ContentBlockStartEvent\n): ChatGenerationChunk | null {\n const index = contentBlockStart.contentBlockIndex;\n\n if (contentBlockStart.start?.toolUse != null) {\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n name: contentBlockStart.start.toolUse.name,\n id: contentBlockStart.start.toolUse.toolUseId,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: index,\n },\n }),\n });\n }\n\n // Return null for non-tool content block starts (text blocks don't need special handling)\n return null;\n}\n\n/**\n * Handle a metadata event from Bedrock Converse stream.\n */\nexport function handleConverseStreamMetadata(\n metadata: ConverseStreamMetadataEvent,\n extra: { streamUsage: boolean }\n): ChatGenerationChunk {\n const inputTokens = metadata.usage?.inputTokens ?? 0;\n const outputTokens = metadata.usage?.outputTokens ?? 0;\n const usage_metadata = {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n total_tokens: metadata.usage?.totalTokens ?? inputTokens + outputTokens,\n };\n\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n usage_metadata: extra.streamUsage ? usage_metadata : undefined,\n response_metadata: {\n // Use the same key as returned from the Converse API\n metadata,\n },\n }),\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"message_outputs.mjs","sources":["../../../../../src/llm/bedrock/utils/message_outputs.ts"],"sourcesContent":["/**\n * Utility functions for converting Bedrock Converse responses to LangChain messages.\n * Ported from @langchain/aws common.js\n */\nimport { ChatGenerationChunk } from '@langchain/core/outputs';\nimport { AIMessage, AIMessageChunk } from '@langchain/core/messages';\nimport type { UsageMetadata } from '@langchain/core/messages';\nimport type {\n BedrockMessage,\n ConverseResponse,\n ContentBlockDeltaEvent,\n ConverseStreamMetadataEvent,\n ContentBlockStartEvent,\n ReasoningContentBlock,\n ReasoningContentBlockDelta,\n MessageContentReasoningBlock,\n MessageContentReasoningBlockReasoningTextPartial,\n MessageContentReasoningBlockRedacted,\n} from '../types';\n\n/**\n * Convert a Bedrock reasoning block delta to a LangChain partial reasoning block.\n */\nexport function bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n reasoningContent: ReasoningContentBlockDelta\n):\n | MessageContentReasoningBlockReasoningTextPartial\n | MessageContentReasoningBlockRedacted {\n const { text, redactedContent, signature } =\n reasoningContent as ReasoningContentBlockDelta & {\n text?: string;\n redactedContent?: Uint8Array;\n signature?: string;\n };\n\n if (typeof text === 'string') {\n return {\n type: 'reasoning_content',\n reasoningText: { text },\n };\n }\n if (signature != null) {\n return {\n type: 'reasoning_content',\n reasoningText: { signature },\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock reasoning block to a LangChain reasoning block.\n */\nexport function bedrockReasoningBlockToLangchainReasoningBlock(\n reasoningContent: ReasoningContentBlock\n): MessageContentReasoningBlock {\n const { reasoningText, redactedContent } =\n reasoningContent as ReasoningContentBlock & {\n reasoningText?: { text?: string; signature?: string };\n redactedContent?: Uint8Array;\n };\n\n if (reasoningText != null) {\n return {\n type: 'reasoning_content',\n reasoningText: reasoningText,\n };\n }\n if (redactedContent != null) {\n return {\n type: 'reasoning_content',\n redactedContent: Buffer.from(redactedContent).toString('base64'),\n };\n }\n throw new Error('Invalid reasoning content');\n}\n\n/**\n * Convert a Bedrock Converse message to a LangChain message.\n */\nexport function convertConverseMessageToLangChainMessage(\n message: BedrockMessage,\n responseMetadata: Omit<ConverseResponse, 'output'>\n): AIMessage {\n if (message.content == null) {\n throw new Error('No message content found in response.');\n }\n if (message.role !== 'assistant') {\n throw new Error(\n `Unsupported message role received in ChatBedrockConverse response: ${message.role}`\n );\n }\n\n let requestId: string | undefined;\n if (\n '$metadata' in responseMetadata &&\n responseMetadata.$metadata != null &&\n typeof responseMetadata.$metadata === 'object' &&\n 'requestId' in responseMetadata.$metadata\n ) {\n requestId = responseMetadata.$metadata.requestId as string;\n }\n\n let tokenUsage:\n | {\n input_tokens: number;\n output_tokens: number;\n total_tokens: number;\n input_token_details?: {\n cache_read: number;\n cache_creation: number;\n };\n }\n | undefined;\n if (responseMetadata.usage != null) {\n const usage = responseMetadata.usage as NonNullable<\n typeof responseMetadata.usage\n > & {\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n };\n const input_tokens = usage.inputTokens ?? 0;\n const output_tokens = usage.outputTokens ?? 0;\n const cacheRead = usage.cacheReadInputTokens;\n const cacheWrite = usage.cacheWriteInputTokens;\n tokenUsage = {\n input_tokens,\n output_tokens,\n total_tokens: usage.totalTokens ?? input_tokens + output_tokens,\n };\n if (cacheRead != null || cacheWrite != null) {\n tokenUsage.input_token_details = {\n cache_read: cacheRead ?? 0,\n cache_creation: cacheWrite ?? 0,\n };\n }\n }\n\n if (\n message.content.length === 1 &&\n 'text' in message.content[0] &&\n typeof message.content[0].text === 'string'\n ) {\n return new AIMessage({\n content: message.content[0].text,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n } else {\n const toolCalls: Array<{\n id?: string;\n name: string;\n args: Record<string, unknown>;\n type: 'tool_call';\n }> = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const content: any[] = [];\n\n message.content.forEach((c) => {\n if (\n 'toolUse' in c &&\n c.toolUse != null &&\n c.toolUse.name != null &&\n c.toolUse.name !== '' &&\n c.toolUse.input != null &&\n typeof c.toolUse.input === 'object'\n ) {\n toolCalls.push({\n id: c.toolUse.toolUseId,\n name: c.toolUse.name,\n args: c.toolUse.input as Record<string, unknown>,\n type: 'tool_call',\n });\n } else if ('text' in c && typeof c.text === 'string') {\n content.push({ type: 'text', text: c.text });\n } else if ('reasoningContent' in c && c.reasoningContent != null) {\n content.push(\n bedrockReasoningBlockToLangchainReasoningBlock(c.reasoningContent)\n );\n } else {\n content.push(c);\n }\n });\n\n return new AIMessage({\n content: content.length ? content : '',\n tool_calls: toolCalls.length ? toolCalls : undefined,\n response_metadata: responseMetadata,\n usage_metadata: tokenUsage,\n id: requestId,\n });\n }\n}\n\n/**\n * Handle a content block delta event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockDelta(\n contentBlockDelta: ContentBlockDeltaEvent\n): ChatGenerationChunk {\n if (contentBlockDelta.delta == null) {\n throw new Error('No delta found in content block.');\n }\n\n if (typeof contentBlockDelta.delta.text === 'string') {\n return new ChatGenerationChunk({\n text: contentBlockDelta.delta.text,\n message: new AIMessageChunk({\n content: contentBlockDelta.delta.text,\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.toolUse != null) {\n const index = contentBlockDelta.contentBlockIndex;\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n args: contentBlockDelta.delta.toolUse.input as string,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else if (contentBlockDelta.delta.reasoningContent != null) {\n const reasoningBlock =\n bedrockReasoningDeltaToLangchainPartialReasoningBlock(\n contentBlockDelta.delta.reasoningContent\n );\n let reasoningText = '';\n if ('reasoningText' in reasoningBlock) {\n reasoningText = reasoningBlock.reasoningText.text ?? '';\n } else if ('redactedContent' in reasoningBlock) {\n reasoningText = reasoningBlock.redactedContent;\n }\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: [reasoningBlock],\n additional_kwargs: {\n // Set reasoning_content for stream handler to detect reasoning mode\n reasoning_content: reasoningText,\n },\n response_metadata: {\n contentBlockIndex: contentBlockDelta.contentBlockIndex,\n },\n }),\n });\n } else {\n throw new Error(\n `Unsupported content block type(s): ${JSON.stringify(contentBlockDelta.delta, null, 2)}`\n );\n }\n}\n\n/**\n * Handle a content block start event from Bedrock Converse stream.\n */\nexport function handleConverseStreamContentBlockStart(\n contentBlockStart: ContentBlockStartEvent\n): ChatGenerationChunk | null {\n const index = contentBlockStart.contentBlockIndex;\n\n if (contentBlockStart.start?.toolUse != null) {\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n tool_call_chunks: [\n {\n name: contentBlockStart.start.toolUse.name,\n id: contentBlockStart.start.toolUse.toolUseId,\n index,\n type: 'tool_call_chunk',\n },\n ],\n response_metadata: {\n contentBlockIndex: index,\n },\n }),\n });\n }\n\n // Return null for non-tool content block starts (text blocks don't need special handling)\n return null;\n}\n\n/**\n * Handle a metadata event from Bedrock Converse stream.\n */\nexport function handleConverseStreamMetadata(\n metadata: ConverseStreamMetadataEvent,\n extra: { streamUsage: boolean }\n): ChatGenerationChunk {\n const usage = metadata.usage as\n | (NonNullable<ConverseStreamMetadataEvent['usage']> & {\n cacheReadInputTokens?: number;\n cacheWriteInputTokens?: number;\n })\n | undefined;\n const inputTokens = usage?.inputTokens ?? 0;\n const outputTokens = usage?.outputTokens ?? 0;\n const cacheRead = usage?.cacheReadInputTokens;\n const cacheWrite = usage?.cacheWriteInputTokens;\n\n const usage_metadata: Record<string, unknown> = {\n input_tokens: inputTokens,\n output_tokens: outputTokens,\n total_tokens: usage?.totalTokens ?? inputTokens + outputTokens,\n };\n\n if (cacheRead != null || cacheWrite != null) {\n usage_metadata.input_token_details = {\n cache_read: cacheRead ?? 0,\n cache_creation: cacheWrite ?? 0,\n };\n }\n\n return new ChatGenerationChunk({\n text: '',\n message: new AIMessageChunk({\n content: '',\n usage_metadata: extra.streamUsage\n ? (usage_metadata as UsageMetadata)\n : undefined,\n response_metadata: {\n // Use the same key as returned from the Converse API\n metadata,\n },\n }),\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Utility functions for converting Bedrock Converse responses to LangChain messages.
|
|
3
3
|
* Ported from @langchain/aws common.js
|
|
4
4
|
*/
|
|
5
|
-
import { AIMessage } from '@langchain/core/messages';
|
|
6
5
|
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
6
|
+
import { AIMessage } from '@langchain/core/messages';
|
|
7
7
|
import type { BedrockMessage, ConverseResponse, ContentBlockDeltaEvent, ConverseStreamMetadataEvent, ContentBlockStartEvent, ReasoningContentBlock, ReasoningContentBlockDelta, MessageContentReasoningBlock, MessageContentReasoningBlockReasoningTextPartial, MessageContentReasoningBlockRedacted } from '../types';
|
|
8
8
|
/**
|
|
9
9
|
* Convert a Bedrock reasoning block delta to a LangChain partial reasoning block.
|
package/package.json
CHANGED
|
@@ -5,16 +5,24 @@ config();
|
|
|
5
5
|
import { expect, test, describe, jest } from '@jest/globals';
|
|
6
6
|
import {
|
|
7
7
|
AIMessage,
|
|
8
|
-
|
|
8
|
+
ToolMessage,
|
|
9
9
|
HumanMessage,
|
|
10
10
|
SystemMessage,
|
|
11
|
-
|
|
11
|
+
AIMessageChunk,
|
|
12
12
|
} from '@langchain/core/messages';
|
|
13
13
|
import { concat } from '@langchain/core/utils/stream';
|
|
14
14
|
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
BedrockRuntimeClient,
|
|
17
|
+
ConverseCommand,
|
|
18
|
+
} from '@aws-sdk/client-bedrock-runtime';
|
|
19
|
+
import type { ConverseResponse } from '@aws-sdk/client-bedrock-runtime';
|
|
20
|
+
import {
|
|
21
|
+
convertConverseMessageToLangChainMessage,
|
|
22
|
+
handleConverseStreamMetadata,
|
|
23
|
+
convertToConverseMessages,
|
|
24
|
+
} from './utils';
|
|
16
25
|
import { CustomChatBedrockConverse, ServiceTierType } from './index';
|
|
17
|
-
import { convertToConverseMessages } from './utils';
|
|
18
26
|
|
|
19
27
|
jest.setTimeout(120000);
|
|
20
28
|
|
|
@@ -429,6 +437,164 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
429
437
|
});
|
|
430
438
|
});
|
|
431
439
|
|
|
440
|
+
describe('handleConverseStreamMetadata - cache token extraction', () => {
|
|
441
|
+
test('should extract cacheReadInputTokens and cacheWriteInputTokens into input_token_details', () => {
|
|
442
|
+
const metadata = {
|
|
443
|
+
usage: {
|
|
444
|
+
inputTokens: 13,
|
|
445
|
+
outputTokens: 5,
|
|
446
|
+
totalTokens: 10849,
|
|
447
|
+
cacheReadInputTokens: 10831,
|
|
448
|
+
cacheWriteInputTokens: 0,
|
|
449
|
+
},
|
|
450
|
+
metrics: { latencyMs: 1000 },
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const chunk = handleConverseStreamMetadata(metadata, {
|
|
454
|
+
streamUsage: true,
|
|
455
|
+
});
|
|
456
|
+
const msg = chunk.message as AIMessageChunk;
|
|
457
|
+
|
|
458
|
+
expect(msg.usage_metadata).toEqual({
|
|
459
|
+
input_tokens: 13,
|
|
460
|
+
output_tokens: 5,
|
|
461
|
+
total_tokens: 10849,
|
|
462
|
+
input_token_details: {
|
|
463
|
+
cache_read: 10831,
|
|
464
|
+
cache_creation: 0,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test('should not include input_token_details when no cache tokens present', () => {
|
|
470
|
+
const metadata = {
|
|
471
|
+
usage: {
|
|
472
|
+
inputTokens: 100,
|
|
473
|
+
outputTokens: 50,
|
|
474
|
+
totalTokens: 150,
|
|
475
|
+
},
|
|
476
|
+
metrics: { latencyMs: 500 },
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const chunk = handleConverseStreamMetadata(metadata, {
|
|
480
|
+
streamUsage: true,
|
|
481
|
+
});
|
|
482
|
+
const msg = chunk.message as AIMessageChunk;
|
|
483
|
+
|
|
484
|
+
expect(msg.usage_metadata).toEqual({
|
|
485
|
+
input_tokens: 100,
|
|
486
|
+
output_tokens: 50,
|
|
487
|
+
total_tokens: 150,
|
|
488
|
+
});
|
|
489
|
+
expect(msg.usage_metadata?.input_token_details).toBeUndefined();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('should include input_token_details when only cacheWriteInputTokens is present', () => {
|
|
493
|
+
const metadata = {
|
|
494
|
+
usage: {
|
|
495
|
+
inputTokens: 50,
|
|
496
|
+
outputTokens: 10,
|
|
497
|
+
totalTokens: 10060,
|
|
498
|
+
cacheWriteInputTokens: 10000,
|
|
499
|
+
},
|
|
500
|
+
metrics: { latencyMs: 800 },
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const chunk = handleConverseStreamMetadata(metadata, {
|
|
504
|
+
streamUsage: true,
|
|
505
|
+
});
|
|
506
|
+
const msg = chunk.message as AIMessageChunk;
|
|
507
|
+
|
|
508
|
+
expect(msg.usage_metadata?.input_token_details).toEqual({
|
|
509
|
+
cache_read: 0,
|
|
510
|
+
cache_creation: 10000,
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test('should return undefined usage_metadata when streamUsage is false', () => {
|
|
515
|
+
const metadata = {
|
|
516
|
+
usage: {
|
|
517
|
+
inputTokens: 13,
|
|
518
|
+
outputTokens: 5,
|
|
519
|
+
totalTokens: 10849,
|
|
520
|
+
cacheReadInputTokens: 10831,
|
|
521
|
+
cacheWriteInputTokens: 0,
|
|
522
|
+
},
|
|
523
|
+
metrics: { latencyMs: 1000 },
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const chunk = handleConverseStreamMetadata(metadata, {
|
|
527
|
+
streamUsage: false,
|
|
528
|
+
});
|
|
529
|
+
const msg = chunk.message as AIMessageChunk;
|
|
530
|
+
|
|
531
|
+
expect(msg.usage_metadata).toBeUndefined();
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe('convertConverseMessageToLangChainMessage - cache token extraction', () => {
|
|
536
|
+
const makeResponseMetadata = (
|
|
537
|
+
usage: Record<string, number>
|
|
538
|
+
): Omit<ConverseResponse, 'output'> =>
|
|
539
|
+
({
|
|
540
|
+
usage,
|
|
541
|
+
stopReason: 'end_turn',
|
|
542
|
+
metrics: undefined,
|
|
543
|
+
$metadata: { requestId: 'test-id' },
|
|
544
|
+
}) as unknown as Omit<ConverseResponse, 'output'>;
|
|
545
|
+
|
|
546
|
+
test('should extract cache tokens in non-streaming response', () => {
|
|
547
|
+
const message = {
|
|
548
|
+
role: 'assistant' as const,
|
|
549
|
+
content: [{ text: 'Hello!' }],
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const result = convertConverseMessageToLangChainMessage(
|
|
553
|
+
message,
|
|
554
|
+
makeResponseMetadata({
|
|
555
|
+
inputTokens: 20,
|
|
556
|
+
outputTokens: 5,
|
|
557
|
+
totalTokens: 10856,
|
|
558
|
+
cacheReadInputTokens: 10831,
|
|
559
|
+
cacheWriteInputTokens: 0,
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
expect(result.usage_metadata).toEqual({
|
|
564
|
+
input_tokens: 20,
|
|
565
|
+
output_tokens: 5,
|
|
566
|
+
total_tokens: 10856,
|
|
567
|
+
input_token_details: {
|
|
568
|
+
cache_read: 10831,
|
|
569
|
+
cache_creation: 0,
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('should not include input_token_details when no cache tokens in non-streaming response', () => {
|
|
575
|
+
const message = {
|
|
576
|
+
role: 'assistant' as const,
|
|
577
|
+
content: [{ text: 'Hello!' }],
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const result = convertConverseMessageToLangChainMessage(
|
|
581
|
+
message,
|
|
582
|
+
makeResponseMetadata({
|
|
583
|
+
inputTokens: 100,
|
|
584
|
+
outputTokens: 50,
|
|
585
|
+
totalTokens: 150,
|
|
586
|
+
})
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
expect(result.usage_metadata).toEqual({
|
|
590
|
+
input_tokens: 100,
|
|
591
|
+
output_tokens: 50,
|
|
592
|
+
total_tokens: 150,
|
|
593
|
+
});
|
|
594
|
+
expect(result.usage_metadata?.input_token_details).toBeUndefined();
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
432
598
|
describe('convertToConverseMessages', () => {
|
|
433
599
|
test('should convert basic messages', () => {
|
|
434
600
|
const { converseMessages, converseSystem } = convertToConverseMessages([
|
|
@@ -647,4 +813,67 @@ describe.skip('Integration tests', () => {
|
|
|
647
813
|
expect(reasoningBlocks.length).toBeGreaterThanOrEqual(0);
|
|
648
814
|
}
|
|
649
815
|
});
|
|
816
|
+
|
|
817
|
+
test('cache tokens should populate input_token_details', async () => {
|
|
818
|
+
const client = new BedrockRuntimeClient({
|
|
819
|
+
region: integrationArgs.region,
|
|
820
|
+
credentials: integrationArgs.credentials,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Large system prompt (>1024 tokens) to meet Bedrock's minimum cache threshold
|
|
824
|
+
const largeSystemPrompt = [
|
|
825
|
+
'You are an expert assistant.',
|
|
826
|
+
...Array(200).fill(
|
|
827
|
+
'This is padding content to exceed the minimum token threshold for Bedrock prompt caching. '
|
|
828
|
+
),
|
|
829
|
+
'When answering, be brief and direct.',
|
|
830
|
+
].join(' ');
|
|
831
|
+
|
|
832
|
+
const systemBlocks = [
|
|
833
|
+
{ text: largeSystemPrompt },
|
|
834
|
+
{ cachePoint: { type: 'default' as const } },
|
|
835
|
+
];
|
|
836
|
+
|
|
837
|
+
const converseArgs = {
|
|
838
|
+
modelId: 'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
|
839
|
+
system: systemBlocks,
|
|
840
|
+
inferenceConfig: { maxTokens: 50 },
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// Call 1: populate the cache (may be a write or read if already warm)
|
|
844
|
+
await client.send(
|
|
845
|
+
new ConverseCommand({
|
|
846
|
+
...converseArgs,
|
|
847
|
+
messages: [{ role: 'user', content: [{ text: 'Say hello.' }] }],
|
|
848
|
+
})
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
// Call 2: should read from cache — this is the one we assert on
|
|
852
|
+
const response = await client.send(
|
|
853
|
+
new ConverseCommand({
|
|
854
|
+
...converseArgs,
|
|
855
|
+
messages: [
|
|
856
|
+
{ role: 'user', content: [{ text: 'Say hello.' }] },
|
|
857
|
+
{ role: 'assistant', content: [{ text: 'Hello!' }] },
|
|
858
|
+
{ role: 'user', content: [{ text: 'Say goodbye.' }] },
|
|
859
|
+
],
|
|
860
|
+
})
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// Feed raw response through convertConverseMessageToLangChainMessage
|
|
864
|
+
const result = convertConverseMessageToLangChainMessage(
|
|
865
|
+
response.output!.message!,
|
|
866
|
+
response
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
expect(result.usage_metadata).toBeDefined();
|
|
870
|
+
expect(result.usage_metadata!.input_tokens).toBeGreaterThan(0);
|
|
871
|
+
expect(result.usage_metadata!.output_tokens).toBeGreaterThan(0);
|
|
872
|
+
|
|
873
|
+
// Cache should have been populated by call 1, so call 2 should show cache reads
|
|
874
|
+
expect(result.usage_metadata!.input_token_details).toBeDefined();
|
|
875
|
+
expect(
|
|
876
|
+
result.usage_metadata!.input_token_details!.cache_read
|
|
877
|
+
).toBeGreaterThan(0);
|
|
878
|
+
});
|
|
650
879
|
});
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Utility functions for converting Bedrock Converse responses to LangChain messages.
|
|
3
3
|
* Ported from @langchain/aws common.js
|
|
4
4
|
*/
|
|
5
|
-
import { AIMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
6
5
|
import { ChatGenerationChunk } from '@langchain/core/outputs';
|
|
6
|
+
import { AIMessage, AIMessageChunk } from '@langchain/core/messages';
|
|
7
|
+
import type { UsageMetadata } from '@langchain/core/messages';
|
|
7
8
|
import type {
|
|
8
9
|
BedrockMessage,
|
|
9
10
|
ConverseResponse,
|
|
@@ -107,17 +108,38 @@ export function convertConverseMessageToLangChainMessage(
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
let tokenUsage:
|
|
110
|
-
| {
|
|
111
|
+
| {
|
|
112
|
+
input_tokens: number;
|
|
113
|
+
output_tokens: number;
|
|
114
|
+
total_tokens: number;
|
|
115
|
+
input_token_details?: {
|
|
116
|
+
cache_read: number;
|
|
117
|
+
cache_creation: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
111
120
|
| undefined;
|
|
112
121
|
if (responseMetadata.usage != null) {
|
|
113
|
-
const
|
|
114
|
-
|
|
122
|
+
const usage = responseMetadata.usage as NonNullable<
|
|
123
|
+
typeof responseMetadata.usage
|
|
124
|
+
> & {
|
|
125
|
+
cacheReadInputTokens?: number;
|
|
126
|
+
cacheWriteInputTokens?: number;
|
|
127
|
+
};
|
|
128
|
+
const input_tokens = usage.inputTokens ?? 0;
|
|
129
|
+
const output_tokens = usage.outputTokens ?? 0;
|
|
130
|
+
const cacheRead = usage.cacheReadInputTokens;
|
|
131
|
+
const cacheWrite = usage.cacheWriteInputTokens;
|
|
115
132
|
tokenUsage = {
|
|
116
133
|
input_tokens,
|
|
117
134
|
output_tokens,
|
|
118
|
-
total_tokens:
|
|
119
|
-
responseMetadata.usage.totalTokens ?? input_tokens + output_tokens,
|
|
135
|
+
total_tokens: usage.totalTokens ?? input_tokens + output_tokens,
|
|
120
136
|
};
|
|
137
|
+
if (cacheRead != null || cacheWrite != null) {
|
|
138
|
+
tokenUsage.input_token_details = {
|
|
139
|
+
cache_read: cacheRead ?? 0,
|
|
140
|
+
cache_creation: cacheWrite ?? 0,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
if (
|
|
@@ -285,19 +307,37 @@ export function handleConverseStreamMetadata(
|
|
|
285
307
|
metadata: ConverseStreamMetadataEvent,
|
|
286
308
|
extra: { streamUsage: boolean }
|
|
287
309
|
): ChatGenerationChunk {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
310
|
+
const usage = metadata.usage as
|
|
311
|
+
| (NonNullable<ConverseStreamMetadataEvent['usage']> & {
|
|
312
|
+
cacheReadInputTokens?: number;
|
|
313
|
+
cacheWriteInputTokens?: number;
|
|
314
|
+
})
|
|
315
|
+
| undefined;
|
|
316
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
317
|
+
const outputTokens = usage?.outputTokens ?? 0;
|
|
318
|
+
const cacheRead = usage?.cacheReadInputTokens;
|
|
319
|
+
const cacheWrite = usage?.cacheWriteInputTokens;
|
|
320
|
+
|
|
321
|
+
const usage_metadata: Record<string, unknown> = {
|
|
291
322
|
input_tokens: inputTokens,
|
|
292
323
|
output_tokens: outputTokens,
|
|
293
|
-
total_tokens:
|
|
324
|
+
total_tokens: usage?.totalTokens ?? inputTokens + outputTokens,
|
|
294
325
|
};
|
|
295
326
|
|
|
327
|
+
if (cacheRead != null || cacheWrite != null) {
|
|
328
|
+
usage_metadata.input_token_details = {
|
|
329
|
+
cache_read: cacheRead ?? 0,
|
|
330
|
+
cache_creation: cacheWrite ?? 0,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
296
334
|
return new ChatGenerationChunk({
|
|
297
335
|
text: '',
|
|
298
336
|
message: new AIMessageChunk({
|
|
299
337
|
content: '',
|
|
300
|
-
usage_metadata: extra.streamUsage
|
|
338
|
+
usage_metadata: extra.streamUsage
|
|
339
|
+
? (usage_metadata as UsageMetadata)
|
|
340
|
+
: undefined,
|
|
301
341
|
response_metadata: {
|
|
302
342
|
// Use the same key as returned from the Converse API
|
|
303
343
|
metadata,
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug script to investigate cache token omission in Bedrock responses.
|
|
3
|
+
*
|
|
4
|
+
* This script:
|
|
5
|
+
* 1. Makes a streaming call to Bedrock and logs the raw metadata event
|
|
6
|
+
* 2. Shows exactly what fields the AWS SDK returns in usage (including cache tokens)
|
|
7
|
+
* 3. Shows what our handleConverseStreamMetadata produces vs what it should produce
|
|
8
|
+
* 4. Makes a multi-turn call to trigger caching and verify cache tokens appear
|
|
9
|
+
*/
|
|
10
|
+
import { config } from 'dotenv';
|
|
11
|
+
config();
|
|
12
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
13
|
+
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
14
|
+
import { concat } from '@langchain/core/utils/stream';
|
|
15
|
+
import {
|
|
16
|
+
ConverseStreamCommand,
|
|
17
|
+
BedrockRuntimeClient,
|
|
18
|
+
} from '@aws-sdk/client-bedrock-runtime';
|
|
19
|
+
import { CustomChatBedrockConverse } from '@/llm/bedrock';
|
|
20
|
+
|
|
21
|
+
const region = process.env.BEDROCK_AWS_REGION ?? 'us-east-1';
|
|
22
|
+
const credentials = {
|
|
23
|
+
accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID!,
|
|
24
|
+
secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY!,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const MODEL_ID = 'us.anthropic.claude-sonnet-4-5-20250929-v1:0';
|
|
28
|
+
|
|
29
|
+
// A long system prompt to increase likelihood of cache usage
|
|
30
|
+
// Bedrock requires minimum 1024 tokens for prompt caching to activate
|
|
31
|
+
const SYSTEM_PROMPT = `You are an expert assistant. Here is a large context block to help trigger cache behavior:
|
|
32
|
+
|
|
33
|
+
${Array(200).fill('This is padding content to make the prompt large enough to trigger Bedrock prompt caching. The minimum requirement for Anthropic models on Bedrock is 1024 tokens in the cached prefix. We need to ensure this prompt is well above that threshold. ').join('')}
|
|
34
|
+
|
|
35
|
+
When answering, be brief and direct.`;
|
|
36
|
+
|
|
37
|
+
async function rawSdkCall(): Promise<void> {
|
|
38
|
+
console.log('='.repeat(60));
|
|
39
|
+
console.log('TEST 1: Raw AWS SDK call - inspect metadata.usage directly');
|
|
40
|
+
console.log('='.repeat(60));
|
|
41
|
+
|
|
42
|
+
const client = new BedrockRuntimeClient({ region, credentials });
|
|
43
|
+
|
|
44
|
+
// First call - should create cache
|
|
45
|
+
// Use cachePoint block to explicitly enable prompt caching
|
|
46
|
+
console.log('\n--- Call 1 (cache write expected) ---');
|
|
47
|
+
const command1 = new ConverseStreamCommand({
|
|
48
|
+
modelId: MODEL_ID,
|
|
49
|
+
system: [{ text: SYSTEM_PROMPT }, { cachePoint: { type: 'default' } }],
|
|
50
|
+
messages: [{ role: 'user', content: [{ text: 'What is 2+2?' }] }],
|
|
51
|
+
inferenceConfig: { maxTokens: 100 },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const response1 = await client.send(command1);
|
|
55
|
+
if (response1.stream) {
|
|
56
|
+
for await (const event of response1.stream) {
|
|
57
|
+
if (event.metadata != null) {
|
|
58
|
+
console.log('\nRAW metadata event (Call 1):');
|
|
59
|
+
console.dir(event.metadata, { depth: null });
|
|
60
|
+
console.log('\nRAW metadata.usage:');
|
|
61
|
+
console.dir(event.metadata.usage, { depth: null });
|
|
62
|
+
console.log('\nSpecific cache fields:');
|
|
63
|
+
console.log(
|
|
64
|
+
' cacheReadInputTokens:',
|
|
65
|
+
(event.metadata.usage as Record<string, unknown>)
|
|
66
|
+
?.cacheReadInputTokens
|
|
67
|
+
);
|
|
68
|
+
console.log(
|
|
69
|
+
' cacheWriteInputTokens:',
|
|
70
|
+
(event.metadata.usage as Record<string, unknown>)
|
|
71
|
+
?.cacheWriteInputTokens
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Second call - should read from cache
|
|
78
|
+
console.log('\n--- Call 2 (cache read expected) ---');
|
|
79
|
+
const command2 = new ConverseStreamCommand({
|
|
80
|
+
modelId: MODEL_ID,
|
|
81
|
+
system: [{ text: SYSTEM_PROMPT }, { cachePoint: { type: 'default' } }],
|
|
82
|
+
messages: [
|
|
83
|
+
{ role: 'user', content: [{ text: 'What is 2+2?' }] },
|
|
84
|
+
{ role: 'assistant', content: [{ text: '4' }] },
|
|
85
|
+
{ role: 'user', content: [{ text: 'And what is 3+3?' }] },
|
|
86
|
+
],
|
|
87
|
+
inferenceConfig: { maxTokens: 100 },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const response2 = await client.send(command2);
|
|
91
|
+
if (response2.stream) {
|
|
92
|
+
for await (const event of response2.stream) {
|
|
93
|
+
if (event.metadata != null) {
|
|
94
|
+
console.log('\nRAW metadata event (Call 2):');
|
|
95
|
+
console.dir(event.metadata, { depth: null });
|
|
96
|
+
console.log('\nRAW metadata.usage:');
|
|
97
|
+
console.dir(event.metadata.usage, { depth: null });
|
|
98
|
+
console.log('\nSpecific cache fields:');
|
|
99
|
+
console.log(
|
|
100
|
+
' cacheReadInputTokens:',
|
|
101
|
+
(event.metadata.usage as Record<string, unknown>)
|
|
102
|
+
?.cacheReadInputTokens
|
|
103
|
+
);
|
|
104
|
+
console.log(
|
|
105
|
+
' cacheWriteInputTokens:',
|
|
106
|
+
(event.metadata.usage as Record<string, unknown>)
|
|
107
|
+
?.cacheWriteInputTokens
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function wrapperStreamCallNoCachePoint(): Promise<void> {
|
|
115
|
+
console.log('\n' + '='.repeat(60));
|
|
116
|
+
console.log(
|
|
117
|
+
'TEST 2: CustomChatBedrockConverse stream (NO cachePoint) - check usage_metadata'
|
|
118
|
+
);
|
|
119
|
+
console.log('='.repeat(60));
|
|
120
|
+
console.log('(Without cachePoint, Bedrock does NOT return cache tokens)');
|
|
121
|
+
|
|
122
|
+
const model = new CustomChatBedrockConverse({
|
|
123
|
+
model: MODEL_ID,
|
|
124
|
+
region,
|
|
125
|
+
credentials,
|
|
126
|
+
maxTokens: 100,
|
|
127
|
+
streaming: true,
|
|
128
|
+
streamUsage: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
console.log('\n--- Wrapper Call (no cachePoint) ---');
|
|
132
|
+
const messages1 = [new HumanMessage(SYSTEM_PROMPT + '\n\nWhat is 2+2?')];
|
|
133
|
+
let finalChunk1: AIMessageChunk | undefined;
|
|
134
|
+
|
|
135
|
+
for await (const chunk of await model.stream(messages1)) {
|
|
136
|
+
finalChunk1 = finalChunk1 ? concat(finalChunk1, chunk) : chunk;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(
|
|
140
|
+
'\nFinal usage_metadata:',
|
|
141
|
+
JSON.stringify(finalChunk1!.usage_metadata)
|
|
142
|
+
);
|
|
143
|
+
console.log('(No cache tokens expected since no cachePoint block was sent)');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function wrapperStreamCallWithCachePoint(): Promise<void> {
|
|
147
|
+
console.log('\n' + '='.repeat(60));
|
|
148
|
+
console.log(
|
|
149
|
+
'TEST 3: Raw SDK with cachePoint -> verify handleConverseStreamMetadata extracts cache tokens'
|
|
150
|
+
);
|
|
151
|
+
console.log('='.repeat(60));
|
|
152
|
+
|
|
153
|
+
// We use the raw SDK with cachePoint to trigger caching, then verify
|
|
154
|
+
// that our handleConverseStreamMetadata function properly extracts cache fields
|
|
155
|
+
const { handleConverseStreamMetadata } = await import(
|
|
156
|
+
'@/llm/bedrock/utils/message_outputs'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const client = new BedrockRuntimeClient({ region, credentials });
|
|
160
|
+
|
|
161
|
+
// Call 1 - establish cache
|
|
162
|
+
console.log('\n--- Call 1 (cache write) ---');
|
|
163
|
+
const command1 = new ConverseStreamCommand({
|
|
164
|
+
modelId: MODEL_ID,
|
|
165
|
+
system: [{ text: SYSTEM_PROMPT }, { cachePoint: { type: 'default' } }],
|
|
166
|
+
messages: [{ role: 'user', content: [{ text: 'What is 2+2?' }] }],
|
|
167
|
+
inferenceConfig: { maxTokens: 100 },
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const response1 = await client.send(command1);
|
|
171
|
+
if (response1.stream) {
|
|
172
|
+
for await (const event of response1.stream) {
|
|
173
|
+
if (event.metadata != null) {
|
|
174
|
+
console.log('Raw usage:', JSON.stringify(event.metadata.usage));
|
|
175
|
+
|
|
176
|
+
// Test our handler
|
|
177
|
+
const chunk = handleConverseStreamMetadata(event.metadata, {
|
|
178
|
+
streamUsage: true,
|
|
179
|
+
});
|
|
180
|
+
console.log(
|
|
181
|
+
'handleConverseStreamMetadata output usage_metadata:',
|
|
182
|
+
JSON.stringify(chunk.message.usage_metadata)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const hasDetails =
|
|
186
|
+
chunk.message.usage_metadata?.input_token_details != null;
|
|
187
|
+
console.log(
|
|
188
|
+
`Has input_token_details: ${hasDetails}`,
|
|
189
|
+
hasDetails
|
|
190
|
+
? JSON.stringify(chunk.message.usage_metadata!.input_token_details)
|
|
191
|
+
: '(MISSING - BUG!)'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Call 2 - read from cache
|
|
198
|
+
console.log('\n--- Call 2 (cache read) ---');
|
|
199
|
+
const command2 = new ConverseStreamCommand({
|
|
200
|
+
modelId: MODEL_ID,
|
|
201
|
+
system: [{ text: SYSTEM_PROMPT }, { cachePoint: { type: 'default' } }],
|
|
202
|
+
messages: [
|
|
203
|
+
{ role: 'user', content: [{ text: 'What is 2+2?' }] },
|
|
204
|
+
{ role: 'assistant', content: [{ text: '4' }] },
|
|
205
|
+
{ role: 'user', content: [{ text: 'What is 3+3?' }] },
|
|
206
|
+
],
|
|
207
|
+
inferenceConfig: { maxTokens: 100 },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const response2 = await client.send(command2);
|
|
211
|
+
if (response2.stream) {
|
|
212
|
+
for await (const event of response2.stream) {
|
|
213
|
+
if (event.metadata != null) {
|
|
214
|
+
console.log('Raw usage:', JSON.stringify(event.metadata.usage));
|
|
215
|
+
|
|
216
|
+
const chunk = handleConverseStreamMetadata(event.metadata, {
|
|
217
|
+
streamUsage: true,
|
|
218
|
+
});
|
|
219
|
+
console.log(
|
|
220
|
+
'handleConverseStreamMetadata output usage_metadata:',
|
|
221
|
+
JSON.stringify(chunk.message.usage_metadata)
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const hasDetails =
|
|
225
|
+
chunk.message.usage_metadata?.input_token_details != null;
|
|
226
|
+
console.log(
|
|
227
|
+
`Has input_token_details: ${hasDetails}`,
|
|
228
|
+
hasDetails
|
|
229
|
+
? JSON.stringify(chunk.message.usage_metadata!.input_token_details)
|
|
230
|
+
: '(MISSING - BUG!)'
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function main(): Promise<void> {
|
|
238
|
+
console.log('Bedrock Cache Token Debug Script');
|
|
239
|
+
console.log(`Model: ${MODEL_ID}`);
|
|
240
|
+
console.log(`Region: ${region}\n`);
|
|
241
|
+
|
|
242
|
+
await rawSdkCall();
|
|
243
|
+
await wrapperStreamCallNoCachePoint();
|
|
244
|
+
await wrapperStreamCallWithCachePoint();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
main().catch((err) => {
|
|
248
|
+
console.error('Fatal error:', err);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
});
|