From 476bfe4d0152665f620ee611bde3b094934ad9aa Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 24 Feb 2025 21:39:30 -0500 Subject: [PATCH 01/26] feat(bedrock): Added bedrock reasoning support --- .../src/bedrock-chat-language-model.ts | 81 ++++++++++++------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index e15d2973d14d..b660f0e547df 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -51,7 +51,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { readonly modelId: BedrockChatModelId, private readonly settings: BedrockChatSettings, private readonly config: BedrockChatConfig, - ) {} + ) { } private getArgs({ mode, @@ -207,20 +207,20 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const providerMetadata = response.trace || response.usage ? { - bedrock: { - ...(response.trace && typeof response.trace === 'object' - ? { trace: response.trace as JSONObject } - : {}), - ...(response.usage && { - usage: { - cacheReadInputTokens: - response.usage?.cacheReadInputTokens ?? Number.NaN, - cacheWriteInputTokens: - response.usage?.cacheWriteInputTokens ?? Number.NaN, - }, - }), - }, - } + bedrock: { + ...(response.trace && typeof response.trace === 'object' + ? { trace: response.trace as JSONObject } + : {}), + ...(response.usage && { + usage: { + cacheReadInputTokens: + response.usage?.cacheReadInputTokens ?? Number.NaN, + cacheWriteInputTokens: + response.usage?.cacheWriteInputTokens ?? Number.NaN, + }, + }), + }, + } : undefined; return { @@ -246,6 +246,12 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawCall: { rawPrompt, rawSettings }, rawResponse: { headers: responseHeaders }, warnings, + reasoning: response.output?.message?.reasoningContent + ?.map(part => ({ + type: 'text', + text: part?.reasoningText?.text, + signature: part?.reasoningText?.signature, + })), ...(providerMetadata && { providerMetadata }), }; } @@ -345,23 +351,23 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const cacheUsage = value.metadata.usage?.cacheReadInputTokens != null || - value.metadata.usage?.cacheWriteInputTokens != null + value.metadata.usage?.cacheWriteInputTokens != null ? { - usage: { - cacheReadInputTokens: - value.metadata.usage?.cacheReadInputTokens ?? - Number.NaN, - cacheWriteInputTokens: - value.metadata.usage?.cacheWriteInputTokens ?? - Number.NaN, - }, - } + usage: { + cacheReadInputTokens: + value.metadata.usage?.cacheReadInputTokens ?? + Number.NaN, + cacheWriteInputTokens: + value.metadata.usage?.cacheWriteInputTokens ?? + Number.NaN, + }, + } : undefined; const trace = value.metadata.trace ? { - trace: value.metadata.trace as JSONObject, - } + trace: value.metadata.trace as JSONObject, + } : undefined; if (cacheUsage || trace) { @@ -385,6 +391,17 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { }); } + if ( + value.contentBlockDelta?.delta && + 'reasoningContent' in value.contentBlockDelta.delta && + value.contentBlockDelta.delta.reasoningContent + ) { + controller.enqueue({ + type: 'reasoning', + text: value.contentBlockDelta.delta.reasoningContent.text + }) + } + const contentBlockStart = value.contentBlockStart; if (contentBlockStart?.start?.toolUse != null) { const toolUse = contentBlockStart.start.toolUse; @@ -468,6 +485,11 @@ const BedrockToolUseSchema = z.object({ input: z.unknown(), }); +const BedrockReasoningTextSchema = z.object({ + signature: z.string().nullish(), + text: z.string(), +}); + // limited version of the schema, focussed on what is needed for the implementation // this approach limits breakages when the API changes and increases efficiency const BedrockResponseSchema = z.object({ @@ -482,6 +504,9 @@ const BedrockResponseSchema = z.object({ z.object({ text: z.string().nullish(), toolUse: BedrockToolUseSchema.nullish(), + reasoningContent: z.object({ + reasoningText: BedrockReasoningTextSchema + }).nullish() }), ), role: z.string(), @@ -508,6 +533,7 @@ const BedrockStreamSchema = z.object({ .union([ z.object({ text: z.string() }), z.object({ toolUse: z.object({ input: z.string() }) }), + z.object({ reasoningContent: z.object({ text: z.string() }) }) ]) .nullish(), }) @@ -551,3 +577,4 @@ const BedrockStreamSchema = z.object({ throttlingException: z.record(z.unknown()).nullish(), validationException: z.record(z.unknown()).nullish(), }); + From 69c4dd60f2a5a3cdb06387b5c677729673adf3ee Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 24 Feb 2025 21:43:28 -0500 Subject: [PATCH 02/26] fix(bedrock): In `doGenerate` make the reasoning be a reasoning part, not a text part --- packages/amazon-bedrock/src/bedrock-chat-language-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index b660f0e547df..a833dc2aaa9a 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -248,7 +248,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { warnings, reasoning: response.output?.message?.reasoningContent ?.map(part => ({ - type: 'text', + type: 'reasoning', text: part?.reasoningText?.text, signature: part?.reasoningText?.signature, })), From a7cfb3da0c6d0f1ae70dc17a353a98dd8dc1e585 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 24 Feb 2025 21:55:05 -0500 Subject: [PATCH 03/26] fix(bedrock): Was parsing geneate output reasoning part incorrectly --- .../src/bedrock-chat-language-model.ts | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index a833dc2aaa9a..0a31213bab07 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -51,7 +51,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { readonly modelId: BedrockChatModelId, private readonly settings: BedrockChatSettings, private readonly config: BedrockChatConfig, - ) { } + ) {} private getArgs({ mode, @@ -207,20 +207,20 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const providerMetadata = response.trace || response.usage ? { - bedrock: { - ...(response.trace && typeof response.trace === 'object' - ? { trace: response.trace as JSONObject } - : {}), - ...(response.usage && { - usage: { - cacheReadInputTokens: - response.usage?.cacheReadInputTokens ?? Number.NaN, - cacheWriteInputTokens: - response.usage?.cacheWriteInputTokens ?? Number.NaN, - }, - }), - }, - } + bedrock: { + ...(response.trace && typeof response.trace === 'object' + ? { trace: response.trace as JSONObject } + : {}), + ...(response.usage && { + usage: { + cacheReadInputTokens: + response.usage?.cacheReadInputTokens ?? Number.NaN, + cacheWriteInputTokens: + response.usage?.cacheWriteInputTokens ?? Number.NaN, + }, + }), + }, + } : undefined; return { @@ -246,11 +246,12 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawCall: { rawPrompt, rawSettings }, rawResponse: { headers: responseHeaders }, warnings, - reasoning: response.output?.message?.reasoningContent + reasoning: response.output?.message?.content + ?.filter(part => part.reasoningContent) ?.map(part => ({ - type: 'reasoning', - text: part?.reasoningText?.text, - signature: part?.reasoningText?.signature, + type: 'text', + text: part.reasoningContent?.reasoningText?.text, + signature: part.reasoningContent?.reasoningText?.signature, })), ...(providerMetadata && { providerMetadata }), }; @@ -351,23 +352,23 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const cacheUsage = value.metadata.usage?.cacheReadInputTokens != null || - value.metadata.usage?.cacheWriteInputTokens != null + value.metadata.usage?.cacheWriteInputTokens != null ? { - usage: { - cacheReadInputTokens: - value.metadata.usage?.cacheReadInputTokens ?? - Number.NaN, - cacheWriteInputTokens: - value.metadata.usage?.cacheWriteInputTokens ?? - Number.NaN, - }, - } + usage: { + cacheReadInputTokens: + value.metadata.usage?.cacheReadInputTokens ?? + Number.NaN, + cacheWriteInputTokens: + value.metadata.usage?.cacheWriteInputTokens ?? + Number.NaN, + }, + } : undefined; const trace = value.metadata.trace ? { - trace: value.metadata.trace as JSONObject, - } + trace: value.metadata.trace as JSONObject, + } : undefined; if (cacheUsage || trace) { @@ -398,8 +399,8 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { ) { controller.enqueue({ type: 'reasoning', - text: value.contentBlockDelta.delta.reasoningContent.text - }) + text: value.contentBlockDelta.delta.reasoningContent.text, + }); } const contentBlockStart = value.contentBlockStart; @@ -504,9 +505,11 @@ const BedrockResponseSchema = z.object({ z.object({ text: z.string().nullish(), toolUse: BedrockToolUseSchema.nullish(), - reasoningContent: z.object({ - reasoningText: BedrockReasoningTextSchema - }).nullish() + reasoningContent: z + .object({ + reasoningText: BedrockReasoningTextSchema, + }) + .nullish(), }), ), role: z.string(), @@ -533,7 +536,7 @@ const BedrockStreamSchema = z.object({ .union([ z.object({ text: z.string() }), z.object({ toolUse: z.object({ input: z.string() }) }), - z.object({ reasoningContent: z.object({ text: z.string() }) }) + z.object({ reasoningContent: z.object({ text: z.string() }) }), ]) .nullish(), }) @@ -577,4 +580,3 @@ const BedrockStreamSchema = z.object({ throttlingException: z.record(z.unknown()).nullish(), validationException: z.record(z.unknown()).nullish(), }); - From 2562bbee162654dfe9fcc309ad260981af91a5a6 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 24 Feb 2025 22:02:53 -0500 Subject: [PATCH 04/26] fix(bedrock): Fixed type checks and stream reasoning part --- .../src/bedrock-chat-language-model.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 0a31213bab07..0dda9af62a03 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -247,11 +247,13 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawResponse: { headers: responseHeaders }, warnings, reasoning: response.output?.message?.content - ?.filter(part => part.reasoningContent) + ?.filter(part => part.reasoningContent?.reasoningText?.text != null) ?.map(part => ({ - type: 'text', - text: part.reasoningContent?.reasoningText?.text, - signature: part.reasoningContent?.reasoningText?.signature, + type: 'text' as const, + text: part.reasoningContent?.reasoningText?.text!, + ...(part.reasoningContent?.reasoningText?.signature && { + signature: part.reasoningContent.reasoningText.signature, + }), })), ...(providerMetadata && { providerMetadata }), }; @@ -399,7 +401,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { ) { controller.enqueue({ type: 'reasoning', - text: value.contentBlockDelta.delta.reasoningContent.text, + textDelta: value.contentBlockDelta.delta.reasoningContent.text, }); } From 6cdb3ff3cae5eb617e476ee4cedbb4701bba39b7 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 24 Feb 2025 22:34:05 -0500 Subject: [PATCH 05/26] fix(bedrock): Fixed stream signature part --- .../src/bedrock-chat-language-model.ts | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 0dda9af62a03..bd5e25652fe5 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -399,10 +399,22 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { 'reasoningContent' in value.contentBlockDelta.delta && value.contentBlockDelta.delta.reasoningContent ) { - controller.enqueue({ - type: 'reasoning', - textDelta: value.contentBlockDelta.delta.reasoningContent.text, - }); + const reasoningContent = + value.contentBlockDelta.delta.reasoningContent; + if ('text' in reasoningContent && reasoningContent.text) { + controller.enqueue({ + type: 'reasoning', + textDelta: reasoningContent.text, + }); + } else if ( + 'signature' in reasoningContent && + reasoningContent.signature + ) { + controller.enqueue({ + type: 'reasoning-signature', + signature: reasoningContent.signature, + }); + } } const contentBlockStart = value.contentBlockStart; @@ -538,7 +550,12 @@ const BedrockStreamSchema = z.object({ .union([ z.object({ text: z.string() }), z.object({ toolUse: z.object({ input: z.string() }) }), - z.object({ reasoningContent: z.object({ text: z.string() }) }), + z.object({ + reasoningContent: z.union([ + z.object({ text: z.string() }), + z.object({ signature: z.string() }), + ]), + }), ]) .nullish(), }) From e7aeec01d2de5a8d4c12a3a2f65751347f8dc7bc Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 24 Feb 2025 23:15:28 -0500 Subject: [PATCH 06/26] fix(bedrock): Verified that signature part parses correctly on bedrock stream --- .../src/bedrock-chat-language-model.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index bd5e25652fe5..c16a904be279 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -551,13 +551,15 @@ const BedrockStreamSchema = z.object({ z.object({ text: z.string() }), z.object({ toolUse: z.object({ input: z.string() }) }), z.object({ - reasoningContent: z.union([ - z.object({ text: z.string() }), - z.object({ signature: z.string() }), - ]), + reasoningContent: z.object({ text: z.string() }), + }), + z.object({ + reasoningContent: z.object({ + signature: z.string(), + }), }), ]) - .nullish(), + .optional(), }) .nullish(), contentBlockStart: z From 31bef7c66fbdc92e580ed001f78c66ee71033b09 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Tue, 25 Feb 2025 19:51:09 -0500 Subject: [PATCH 07/26] fix(ai-sdk/amazon-bedrock): Fix tool calling with reasoning support on bedrock provider --- .../amazon-bedrock/src/bedrock-api-types.ts | 19 +++++++++++ .../src/convert-to-bedrock-chat-messages.ts | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/amazon-bedrock/src/bedrock-api-types.ts b/packages/amazon-bedrock/src/bedrock-api-types.ts index 904c6d4ded74..e4431c6cb546 100644 --- a/packages/amazon-bedrock/src/bedrock-api-types.ts +++ b/packages/amazon-bedrock/src/bedrock-api-types.ts @@ -132,6 +132,23 @@ export interface BedrockTextBlock { text: string; } +export interface BedrockReasoningContentBlock { + reasoningContent: { + reasoningText: { + text: string; + signature?: string; + }; + }; +} + +export interface BedrockRedactedReasoningContentBlock { + reasoningContent: { + redactedReasoning: { + data: string; + }; + }; +} + export type BedrockContentBlock = | BedrockDocumentBlock | BedrockGuardrailConverseContentBlock @@ -139,4 +156,6 @@ export type BedrockContentBlock = | BedrockTextBlock | BedrockToolResultBlock | BedrockToolUseBlock + | BedrockReasoningContentBlock + | BedrockRedactedReasoningContentBlock | BedrockCachePoint; diff --git a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts index a7ecc2edaa35..78b272c337f8 100644 --- a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts +++ b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts @@ -185,6 +185,35 @@ export function convertToBedrockChatMessages(prompt: LanguageModelV1Prompt): { break; } + case 'reasoning': { + bedrockContent.push({ + reasoningContent: { + reasoningText: { + // trim the last text part if it's the last message in the block + // because Bedrock does not allow trailing whitespace + // in pre-filled assistant responses + text: + isLastBlock && isLastMessage && isLastContentPart + ? part.text.trim() + : part.text, + signature: part.signature, + }, + }, + }); + break; + } + + case 'redacted-reasoning': { + bedrockContent.push({ + reasoningContent: { + redactedReasoning: { + data: part.data, + }, + }, + }); + break; + } + case 'tool-call': { bedrockContent.push({ toolUse: { @@ -225,6 +254,11 @@ type AssistantBlock = { type: 'assistant'; messages: Array; }; +type ThinkingBlock = { + type: 'thinking'; + thinking: string; + signature: string; +}; type UserBlock = { type: 'user'; messages: Array; From cdda477fe531322170570a391b45baabd0701c5e Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Thu, 27 Feb 2025 10:43:59 -0500 Subject: [PATCH 08/26] fix(provider/amazon-bedrock): Add redacted reasoning support to streamed responses --- packages/amazon-bedrock/src/bedrock-chat-language-model.ts | 3 +++ .../amazon-bedrock/src/convert-to-bedrock-chat-messages.ts | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index c16a904be279..51d12638fb43 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -558,6 +558,9 @@ const BedrockStreamSchema = z.object({ signature: z.string(), }), }), + z.object({ + reasoningContent: z.object({ data: z.string() }), + }), ]) .optional(), }) diff --git a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts index 78b272c337f8..116018832504 100644 --- a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts +++ b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts @@ -254,11 +254,6 @@ type AssistantBlock = { type: 'assistant'; messages: Array; }; -type ThinkingBlock = { - type: 'thinking'; - thinking: string; - signature: string; -}; type UserBlock = { type: 'user'; messages: Array; From df977b164ac66ce78ac8c640fd6dd437ef84ef55 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Fri, 28 Feb 2025 01:18:59 -0500 Subject: [PATCH 09/26] fix(provider/amazon-bedrock): Properly discriminate and handle reasoning vs redacted reasoning --- .../src/bedrock-chat-language-model.ts | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 51d12638fb43..038e0a86cb7a 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -247,14 +247,33 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawResponse: { headers: responseHeaders }, warnings, reasoning: response.output?.message?.content - ?.filter(part => part.reasoningContent?.reasoningText?.text != null) - ?.map(part => ({ - type: 'text' as const, - text: part.reasoningContent?.reasoningText?.text!, - ...(part.reasoningContent?.reasoningText?.signature && { - signature: part.reasoningContent.reasoningText.signature, - }), - })), + ?.filter( + part => + part.reasoningContent && + (('reasoningText' in part.reasoningContent && + part.reasoningContent.reasoningText.text != null) || + ('redactedReasoning' in part.reasoningContent && + part.reasoningContent.redactedReasoning.data != null)), + ) + ?.map(part => { + const reasoningContent = part.reasoningContent!; + + if ('reasoningText' in reasoningContent) { + return { + type: 'text' as const, + text: reasoningContent.reasoningText.text, + ...(reasoningContent.reasoningText.signature && { + signature: reasoningContent.reasoningText.signature, + }), + }; + } else { + // Must be redactedReasoning + return { + type: 'redacted' as const, + data: reasoningContent.redactedReasoning.data, + }; + } + }), ...(providerMetadata && { providerMetadata }), }; } @@ -414,6 +433,11 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { type: 'reasoning-signature', signature: reasoningContent.signature, }); + } else if ('data' in reasoningContent && reasoningContent.data) { + controller.enqueue({ + type: 'redacted-reasoning', + data: reasoningContent.data, + }); } } @@ -505,7 +529,11 @@ const BedrockReasoningTextSchema = z.object({ text: z.string(), }); -// limited version of the schema, focussed on what is needed for the implementation +const BedrockRedactedReasoningSchema = z.object({ + data: z.string(), +}); + +// limited version of the schema, focused on what is needed for the implementation // this approach limits breakages when the API changes and increases efficiency const BedrockResponseSchema = z.object({ metrics: z @@ -520,9 +548,14 @@ const BedrockResponseSchema = z.object({ text: z.string().nullish(), toolUse: BedrockToolUseSchema.nullish(), reasoningContent: z - .object({ - reasoningText: BedrockReasoningTextSchema, - }) + .union([ + z.object({ + reasoningText: BedrockReasoningTextSchema, + }), + z.object({ + redactedReasoning: BedrockRedactedReasoningSchema, + }), + ]) .nullish(), }), ), From 22c5486b55b28089d9c4106d1c242295a0b7bfc2 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Fri, 28 Feb 2025 01:49:06 -0500 Subject: [PATCH 10/26] feat(provider/amazon-bedrock): Add tests for reasoning, tool usage, and redacted reasoning --- .../src/bedrock-chat-language-model.test.ts | 329 +++++++++++++++++- 1 file changed, 315 insertions(+), 14 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts index f1d9ba6d6989..c3359cf2c789 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts @@ -963,11 +963,120 @@ describe('doStream', () => { messages: [{ role: 'user', content: [{ text: 'Hello' }] }], }); }); + + it('should stream reasoning text deltas', async () => { + setupMockEventStreamHandler(); + server.urls[streamUrl].response = { + type: 'stream-chunks', + chunks: [ + JSON.stringify({ + contentBlockDelta: { + contentBlockIndex: 0, + delta: { + reasoningContent: { text: 'I am thinking' }, + }, + }, + }) + '\n', + JSON.stringify({ + contentBlockDelta: { + contentBlockIndex: 0, + delta: { + reasoningContent: { text: ' about this problem...' }, + }, + }, + }) + '\n', + JSON.stringify({ + contentBlockDelta: { + contentBlockIndex: 0, + delta: { + reasoningContent: { signature: 'abc123signature' }, + }, + }, + }) + '\n', + JSON.stringify({ + contentBlockDelta: { + contentBlockIndex: 1, + delta: { text: 'Based on my reasoning, the answer is 42.' }, + }, + }) + '\n', + JSON.stringify({ + messageStop: { + stopReason: 'stop_sequence', + }, + }) + '\n', + ], + }; + + const { stream } = await model.doStream({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + }); + + expect(await convertReadableStreamToArray(stream)).toStrictEqual([ + { type: 'reasoning', textDelta: 'I am thinking' }, + { type: 'reasoning', textDelta: ' about this problem...' }, + { type: 'reasoning-signature', signature: 'abc123signature' }, + { + type: 'text-delta', + textDelta: 'Based on my reasoning, the answer is 42.', + }, + { + type: 'finish', + finishReason: 'stop', + usage: { promptTokens: NaN, completionTokens: NaN }, + }, + ]); + }); + + it('should stream redacted reasoning', async () => { + setupMockEventStreamHandler(); + server.urls[streamUrl].response = { + type: 'stream-chunks', + chunks: [ + JSON.stringify({ + contentBlockDelta: { + contentBlockIndex: 0, + delta: { + reasoningContent: { data: 'redacted-reasoning-data' }, + }, + }, + }) + '\n', + JSON.stringify({ + contentBlockDelta: { + contentBlockIndex: 1, + delta: { text: 'Here is my answer.' }, + }, + }) + '\n', + JSON.stringify({ + messageStop: { + stopReason: 'stop_sequence', + }, + }) + '\n', + ], + }; + + const { stream } = await model.doStream({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + }); + + expect(await convertReadableStreamToArray(stream)).toStrictEqual([ + { type: 'redacted-reasoning', data: 'redacted-reasoning-data' }, + { type: 'text-delta', textDelta: 'Here is my answer.' }, + { + type: 'finish', + finishReason: 'stop', + usage: { promptTokens: NaN, completionTokens: NaN }, + }, + ]); + }); }); describe('doGenerate', () => { function prepareJsonResponse({ - content = 'Hello, World!', + content = [{ type: 'text', text: 'Hello, World!' }], toolCalls = [], usage = { inputTokens: 4, @@ -978,8 +1087,19 @@ describe('doGenerate', () => { }, stopReason = 'stop_sequence', trace, + reasoningContent, }: { - content?: string; + content?: Array< + | { type: 'text'; text: string } + | { type: 'thinking'; thinking: string; signature: string } + | { type: 'tool_use'; id: string; name: string; input: unknown } + | { + reasoningContent: { + reasoningText: { text: string; signature?: string }; + }; + } + | { reasoningContent: { redactedReasoning: { data: string } } } + >; toolCalls?: Array<{ id?: string; name: string; @@ -994,22 +1114,71 @@ describe('doGenerate', () => { }; stopReason?: string; trace?: typeof mockTrace; + reasoningContent?: + | { + reasoningContent: { + reasoningText: { + text: string; + signature?: string; + }; + }; + } + | { + reasoningContent: { + redactedReasoning: { + data: string; + }; + }; + } + | Array< + | { + reasoningContent: { + reasoningText: { + text: string; + signature?: string; + }; + }; + } + | { + reasoningContent: { + redactedReasoning: { + data: string; + }; + }; + } + >; }) { + // Create a copy of the content array to avoid modifying the default parameter + const contentBlocks = [...content]; + + // Add tool calls + toolCalls.forEach(tool => { + contentBlocks.push({ + type: 'tool_use', + id: tool.id ?? 'tool-use-id', + name: tool.name, + input: tool.args, + }); + }); + + // Add reasoning content + if (reasoningContent) { + if (Array.isArray(reasoningContent)) { + reasoningContent.forEach(rc => { + contentBlocks.push(rc as any); + }); + } else { + contentBlocks.push(reasoningContent as any); + } + } + server.urls[generateUrl].response = { type: 'json-value', body: { output: { message: { role: 'assistant', - content: [ - { type: 'text', text: content }, - ...toolCalls.map(tool => ({ - type: 'tool_use', - toolUseId: tool.id ?? 'tool-use-id', - name: tool.name, - input: tool.args, - })), - ], + content: contentBlocks, }, }, usage, @@ -1020,7 +1189,7 @@ describe('doGenerate', () => { } it('should extract text response', async () => { - prepareJsonResponse({ content: 'Hello, World!' }); + prepareJsonResponse({ content: [{ type: 'text', text: 'Hello, World!' }] }); const { text } = await model.doGenerate({ inputFormat: 'prompt', @@ -1361,7 +1530,9 @@ describe('doGenerate', () => { }); it('should include providerOptions in the request for generate calls', async () => { - prepareJsonResponse({ content: 'Test generation' }); + prepareJsonResponse({ + content: [{ type: 'text', text: 'Test generation' }], + }); await model.doGenerate({ inputFormat: 'prompt', @@ -1381,7 +1552,7 @@ describe('doGenerate', () => { it('should include cache token usage in providerMetadata', async () => { prepareJsonResponse({ - content: 'Testing', + content: [{ type: 'text', text: 'Testing' }], usage: { inputTokens: 4, outputTokens: 34, @@ -1433,4 +1604,134 @@ describe('doGenerate', () => { messages: [{ role: 'user', content: [{ text: 'Hello' }] }], }); }); + + it('should extract reasoning text with signature', async () => { + const reasoningText = 'I need to think about this problem carefully...'; + const signature = 'abc123signature'; + + prepareJsonResponse({ + content: [{ type: 'text', text: 'The answer is 42.' }], + reasoningContent: { + reasoningContent: { + reasoningText: { + text: reasoningText, + signature: signature, + }, + }, + }, + }); + + const { reasoning, text } = await model.doGenerate({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + }); + + expect(text).toStrictEqual('The answer is 42.'); + expect(reasoning).toStrictEqual([ + { + type: 'text', + text: reasoningText, + signature: signature, + }, + ]); + }); + + it('should extract reasoning text without signature', async () => { + const reasoningText = 'I need to think about this problem carefully...'; + + prepareJsonResponse({ + content: [{ type: 'text', text: 'The answer is 42.' }], + reasoningContent: { + reasoningContent: { + reasoningText: { + text: reasoningText, + }, + }, + }, + }); + + const { reasoning, text } = await model.doGenerate({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + }); + + expect(text).toStrictEqual('The answer is 42.'); + expect(reasoning).toStrictEqual([ + { + type: 'text', + text: reasoningText, + }, + ]); + }); + + it('should extract redacted reasoning', async () => { + prepareJsonResponse({ + content: [{ type: 'text', text: 'The answer is 42.' }], + reasoningContent: { + reasoningContent: { + redactedReasoning: { + data: 'redacted-reasoning-data', + }, + }, + }, + }); + + const { reasoning, text } = await model.doGenerate({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + }); + + expect(text).toStrictEqual('The answer is 42.'); + expect(reasoning).toStrictEqual([ + { + type: 'redacted', + data: 'redacted-reasoning-data', + }, + ]); + }); + + it('should handle multiple reasoning blocks', async () => { + prepareJsonResponse({ + content: [{ type: 'text', text: 'The answer is 42.' }], + reasoningContent: [ + { + reasoningContent: { + reasoningText: { + text: 'First reasoning block', + signature: 'sig1', + }, + }, + }, + { + reasoningContent: { + redactedReasoning: { + data: 'redacted-data', + }, + }, + }, + ], + }); + + const { reasoning, text } = await model.doGenerate({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + }); + + expect(text).toStrictEqual('The answer is 42.'); + expect(reasoning).toStrictEqual([ + { + type: 'text', + text: 'First reasoning block', + signature: 'sig1', + }, + { + type: 'redacted', + data: 'redacted-data', + }, + ]); + }); }); From 6f23643828166a30ad2b111fb261990bb559bc32 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Fri, 28 Feb 2025 16:43:45 -0500 Subject: [PATCH 11/26] fix(provider/amazon-bedrock): Shape the reasoning details types correctly on doGenerate --- .../src/bedrock-chat-language-model.ts | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 038e0a86cb7a..a6b3dde63890 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -246,34 +246,35 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawCall: { rawPrompt, rawSettings }, rawResponse: { headers: responseHeaders }, warnings, - reasoning: response.output?.message?.content - ?.filter( - part => - part.reasoningContent && - (('reasoningText' in part.reasoningContent && - part.reasoningContent.reasoningText.text != null) || - ('redactedReasoning' in part.reasoningContent && - part.reasoningContent.redactedReasoning.data != null)), - ) - ?.map(part => { - const reasoningContent = part.reasoningContent!; - - if ('reasoningText' in reasoningContent) { - return { - type: 'text' as const, - text: reasoningContent.reasoningText.text, - ...(reasoningContent.reasoningText.signature && { - signature: reasoningContent.reasoningText.signature, - }), - }; - } else { - // Must be redactedReasoning - return { - type: 'redacted' as const, - data: reasoningContent.redactedReasoning.data, - }; - } - }), + reasoning: + response.output?.message?.content + ?.filter( + part => + part.reasoningContent && + (('reasoningText' in part.reasoningContent && + part.reasoningContent.reasoningText.text != null) || + ('redactedReasoning' in part.reasoningContent && + part.reasoningContent.redactedReasoning.data != null)), + ) + ?.map(part => { + const reasoningContent = part.reasoningContent!; + + if ('reasoningText' in reasoningContent) { + return { + type: 'text' as const, + text: reasoningContent.reasoningText.text, + ...(reasoningContent.reasoningText.signature && { + signature: reasoningContent.reasoningText.signature, + }), + }; + } else { + // Must be redactedReasoning + return { + type: 'redacted' as const, + data: reasoningContent.redactedReasoning.data, + }; + } + }) || undefined, ...(providerMetadata && { providerMetadata }), }; } From 468a750e032451459e4aed463f29bd61db866d25 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Fri, 28 Feb 2025 17:10:05 -0500 Subject: [PATCH 12/26] fix(provider/amazon-bedrock): Mimic doGenerate reasoning response formatting --- .../src/bedrock-chat-language-model.ts | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index a6b3dde63890..0302ac582edd 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -223,6 +223,35 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { } : undefined; + const reasoningDetails = (response.output?.message?.content || []) + .filter( + part => + part.reasoningContent && + (('reasoningText' in part.reasoningContent && + part.reasoningContent.reasoningText.text != null) || + ('redactedReasoning' in part.reasoningContent && + part.reasoningContent.redactedReasoning.data != null)), + ) + .map(part => { + const reasoningContent = part.reasoningContent!; + + if ('reasoningText' in reasoningContent) { + return { + type: 'text' as const, + text: reasoningContent.reasoningText.text, + ...(reasoningContent.reasoningText.signature && { + signature: reasoningContent.reasoningText.signature, + }), + }; + } else { + // Must be redactedReasoning + return { + type: 'redacted' as const, + data: reasoningContent.redactedReasoning.data, + }; + } + }); + return { text: response.output?.message?.content @@ -246,35 +275,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawCall: { rawPrompt, rawSettings }, rawResponse: { headers: responseHeaders }, warnings, - reasoning: - response.output?.message?.content - ?.filter( - part => - part.reasoningContent && - (('reasoningText' in part.reasoningContent && - part.reasoningContent.reasoningText.text != null) || - ('redactedReasoning' in part.reasoningContent && - part.reasoningContent.redactedReasoning.data != null)), - ) - ?.map(part => { - const reasoningContent = part.reasoningContent!; - - if ('reasoningText' in reasoningContent) { - return { - type: 'text' as const, - text: reasoningContent.reasoningText.text, - ...(reasoningContent.reasoningText.signature && { - signature: reasoningContent.reasoningText.signature, - }), - }; - } else { - // Must be redactedReasoning - return { - type: 'redacted' as const, - data: reasoningContent.redactedReasoning.data, - }; - } - }) || undefined, + reasoning: reasoningDetails, ...(providerMetadata && { providerMetadata }), }; } From ac5542186a87b8231f9df6fdb037424e4ff297b7 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Mon, 3 Mar 2025 14:36:23 -0500 Subject: [PATCH 13/26] fix(ai): Append reasoning details on turns fix(providers/amazon-bedrock): Return reasoning details on turns with tool results --- .../ai/core/generate-text/generate-text.ts | 32 ++++++++- .../src/bedrock-chat-language-model.ts | 72 +++++++++++-------- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/packages/ai/core/generate-text/generate-text.ts b/packages/ai/core/generate-text/generate-text.ts index 43c749114ce3..c99fceb9111c 100644 --- a/packages/ai/core/generate-text/generate-text.ts +++ b/packages/ai/core/generate-text/generate-text.ts @@ -469,9 +469,35 @@ A function that attempts to repair a tool call that failed to parse. ? text + stepText : stepText; - currentReasoningDetails = asReasoningDetails( - currentModelResponse.reasoning, - ); + // Only add reasoning details if they exist and aren't already included + if ( + currentModelResponse.reasoning && + currentModelResponse.reasoning.length > 0 + ) { + const newReasoningDetails = asReasoningDetails( + currentModelResponse.reasoning, + ); + // Avoid duplicate reasoning by checking if we already have this content + const reasoningExists = currentReasoningDetails.some( + existing => + JSON.stringify(existing) === JSON.stringify(newReasoningDetails), + ); + + if (!reasoningExists) { + // Handle both cases: single ReasoningDetail or array of ReasoningDetail + if (Array.isArray(newReasoningDetails)) { + currentReasoningDetails = [ + ...currentReasoningDetails, + ...newReasoningDetails, + ]; + } else { + currentReasoningDetails = [ + ...currentReasoningDetails, + newReasoningDetails, + ]; + } + } + } // sources: sources.push(...(currentModelResponse.sources ?? [])); diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 0302ac582edd..71c035767f65 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -4,6 +4,8 @@ import { LanguageModelV1CallWarning, LanguageModelV1FinishReason, LanguageModelV1ProviderMetadata, + LanguageModelV1ReasoningPart, + LanguageModelV1RedactedReasoningPart, LanguageModelV1StreamPart, UnsupportedFunctionalityError, } from '@ai-sdk/provider'; @@ -22,6 +24,8 @@ import { BedrockStopReason, BEDROCK_STOP_REASONS, BEDROCK_CACHE_POINT, + BedrockReasoningContentBlock, + BedrockRedactedReasoningContentBlock, } from './bedrock-api-types'; import { BedrockChatModelId, @@ -223,34 +227,44 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { } : undefined; - const reasoningDetails = (response.output?.message?.content || []) - .filter( - part => - part.reasoningContent && - (('reasoningText' in part.reasoningContent && - part.reasoningContent.reasoningText.text != null) || - ('redactedReasoning' in part.reasoningContent && - part.reasoningContent.redactedReasoning.data != null)), - ) - .map(part => { - const reasoningContent = part.reasoningContent!; - - if ('reasoningText' in reasoningContent) { - return { - type: 'text' as const, - text: reasoningContent.reasoningText.text, - ...(reasoningContent.reasoningText.signature && { - signature: reasoningContent.reasoningText.signature, - }), - }; - } else { - // Must be redactedReasoning - return { - type: 'redacted' as const, - data: reasoningContent.redactedReasoning.data, - }; - } - }); + // Extract reasoning details from all content parts that have reasoningContent + // This handles both tool use and non-tool use cases + let reasoningDetails: Array< + | { type: 'text'; text: string; signature?: string } + | { type: 'redacted'; data: string } + > = []; + + // Process message content if available + if (response.output?.message?.content) { + reasoningDetails = response.output.message.content + .filter( + part => + part.reasoningContent && + (('reasoningText' in part.reasoningContent && + part.reasoningContent.reasoningText.text != null) || + ('redactedReasoning' in part.reasoningContent && + part.reasoningContent.redactedReasoning.data != null)), + ) + .map(part => { + const reasoningContent = part.reasoningContent!; + + if ('reasoningText' in reasoningContent) { + return { + type: 'text' as const, + text: reasoningContent.reasoningText.text, + ...(reasoningContent.reasoningText.signature && { + signature: reasoningContent.reasoningText.signature, + }), + }; + } else { + // Must be redactedReasoning + return { + type: 'redacted' as const, + data: reasoningContent.redactedReasoning.data, + }; + } + }); + } return { text: @@ -275,7 +289,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawCall: { rawPrompt, rawSettings }, rawResponse: { headers: responseHeaders }, warnings, - reasoning: reasoningDetails, + ...(reasoningDetails.length > 0 && { reasoning: reasoningDetails }), ...(providerMetadata && { providerMetadata }), }; } From fdb87b5bff13e4d42d267b58dcc58f528727e71e Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Tue, 4 Mar 2025 11:51:58 -0500 Subject: [PATCH 14/26] fix(providers/amazon-bedrock): Fix json response test in bedrock --- .../src/bedrock-chat-language-model.test.ts | 99 ++++++++++--------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts index c3359cf2c789..d983373e894a 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts @@ -1077,7 +1077,6 @@ describe('doStream', () => { describe('doGenerate', () => { function prepareJsonResponse({ content = [{ type: 'text', text: 'Hello, World!' }], - toolCalls = [], usage = { inputTokens: 4, outputTokens: 34, @@ -1087,7 +1086,6 @@ describe('doGenerate', () => { }, stopReason = 'stop_sequence', trace, - reasoningContent, }: { content?: Array< | { type: 'text'; text: string } @@ -1148,29 +1146,34 @@ describe('doGenerate', () => { } >; }) { - // Create a copy of the content array to avoid modifying the default parameter - const contentBlocks = [...content]; - - // Add tool calls - toolCalls.forEach(tool => { - contentBlocks.push({ - type: 'tool_use', - id: tool.id ?? 'tool-use-id', - name: tool.name, - input: tool.args, - }); - }); - - // Add reasoning content - if (reasoningContent) { - if (Array.isArray(reasoningContent)) { - reasoningContent.forEach(rc => { - contentBlocks.push(rc as any); - }); - } else { - contentBlocks.push(reasoningContent as any); + // Map the content array to the format expected by the API + const contentBlocks = content.map(item => { + if ('type' in item) { + if (item.type === 'text') { + return { text: item.text }; + } else if (item.type === 'thinking') { + return { + reasoningContent: { + reasoningText: { + text: item.thinking, + signature: item.signature, + }, + }, + }; + } else if (item.type === 'tool_use') { + return { + toolUse: { + toolUseId: item.id, + name: item.name, + input: item.input, + }, + }; + } + } else if ('reasoningContent' in item) { + return item; } - } + return item; + }); server.urls[generateUrl].response = { type: 'json-value', @@ -1610,15 +1613,17 @@ describe('doGenerate', () => { const signature = 'abc123signature'; prepareJsonResponse({ - content: [{ type: 'text', text: 'The answer is 42.' }], - reasoningContent: { - reasoningContent: { - reasoningText: { - text: reasoningText, - signature: signature, + content: [ + { + reasoningContent: { + reasoningText: { + text: reasoningText, + signature: signature, + }, }, }, - }, + { type: 'text', text: 'The answer is 42.' }, + ], }); const { reasoning, text } = await model.doGenerate({ @@ -1641,14 +1646,16 @@ describe('doGenerate', () => { const reasoningText = 'I need to think about this problem carefully...'; prepareJsonResponse({ - content: [{ type: 'text', text: 'The answer is 42.' }], - reasoningContent: { - reasoningContent: { - reasoningText: { - text: reasoningText, + content: [ + { + reasoningContent: { + reasoningText: { + text: reasoningText, + }, }, }, - }, + { type: 'text', text: 'The answer is 42.' }, + ], }); const { reasoning, text } = await model.doGenerate({ @@ -1668,14 +1675,16 @@ describe('doGenerate', () => { it('should extract redacted reasoning', async () => { prepareJsonResponse({ - content: [{ type: 'text', text: 'The answer is 42.' }], - reasoningContent: { - reasoningContent: { - redactedReasoning: { - data: 'redacted-reasoning-data', + content: [ + { + reasoningContent: { + redactedReasoning: { + data: 'redacted-reasoning-data', + }, }, }, - }, + { type: 'text', text: 'The answer is 42.' }, + ], }); const { reasoning, text } = await model.doGenerate({ @@ -1695,8 +1704,7 @@ describe('doGenerate', () => { it('should handle multiple reasoning blocks', async () => { prepareJsonResponse({ - content: [{ type: 'text', text: 'The answer is 42.' }], - reasoningContent: [ + content: [ { reasoningContent: { reasoningText: { @@ -1712,6 +1720,7 @@ describe('doGenerate', () => { }, }, }, + { type: 'text', text: 'The answer is 42.' }, ], }); From 9988ee868b7eaef3a9b0e6abbe85a744d2268ea7 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Tue, 4 Mar 2025 13:25:35 -0500 Subject: [PATCH 15/26] docs(providers/amazon-bedrock): Add reasoning documentation tweak(providers/amazon-bedrock): Mimic anthropic approach for reasoning config --- .../01-ai-sdk-providers/08-amazon-bedrock.mdx | 34 ++++- .../src/bedrock-chat-language-model.ts | 131 ++++++++++++------ 2 files changed, 124 insertions(+), 41 deletions(-) diff --git a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx index cc44cf5e01eb..f91e2f000983 100644 --- a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx +++ b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx @@ -316,6 +316,38 @@ console.log( // } ``` +## Reasoning + +Amazon Bedrock has reasoning support for the `claude-3-7-sonnet-20250219` model. + +You can enable it using the `reasoning_config` provider option and specifying a thinking budget in tokens (minimum: `1024`, maximum: `64000`). + +````ts +import { bedrock } from 'ai-sdk/amazonBedrock'; +import { generateText } from 'ai; + +const { text, reasoning, reasoningDetails } = await generateText({ + model: bedrock('claude-3-7-sonnet-20250219'), + prompt: 'How many people will live in the world in 2040?', + providerOptions: { + bedrock: { + reasoning_config: + { + type: 'enabled', + budgetTokens: 12000 + }, + }, + }, +}); + +console.log(reasoning); // reasoning text +console.log(reasoningDetails); // reasoning details including redacted reasoning +console.log(text); // text response +``` + +See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details +on how to integrate reasoning into your chatbot. + ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | @@ -362,7 +394,7 @@ using the `.embedding()` factory method. ```ts const model = bedrock.embedding('amazon.titan-embed-text-v1'); -``` +```` Bedrock Titan embedding model amazon.titan-embed-text-v2:0 supports several aditional settings. You can pass them as an options argument: diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 71c035767f65..89163fd96ee9 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -1,4 +1,5 @@ import { + InvalidArgumentError, JSONObject, LanguageModelV1, LanguageModelV1CallWarning, @@ -117,6 +118,23 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const { system, messages } = convertToBedrockChatMessages(prompt); + // Parse thinking options from provider metadata + const reasoningConfigOptions = + BedrockReasoningConfigOptionsSchema.safeParse( + providerMetadata?.bedrock?.reasoning_config, + ); + + if (!reasoningConfigOptions.success) { + throw new InvalidArgumentError({ + argument: 'providerOptions.bedrock.reasoning_config', + message: 'invalid reasoning configuration options', + cause: reasoningConfigOptions.error, + }); + } + + const isThinking = reasoningConfigOptions.data?.type === 'enabled'; + const thinkingBudget = reasoningConfigOptions.data?.budget_tokens; + const inferenceConfig = { ...(maxTokens != null && { maxTokens }), ...(temperature != null && { temperature }), @@ -124,6 +142,41 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { ...(stopSequences != null && { stopSequences }), }; + // Adjust maxTokens if thinking is enabled + if (isThinking && thinkingBudget != null) { + if (inferenceConfig.maxTokens != null) { + inferenceConfig.maxTokens += thinkingBudget; + } else { + inferenceConfig.maxTokens = thinkingBudget + 4096; // Default + thinking budget maxTokens = 4096, TODO update default in v5 + } + // Add them to additional model request fields + // Add reasoning config to additionalModelRequestFields + this.settings.additionalModelRequestFields = { + ...this.settings.additionalModelRequestFields, + reasoning_config: { ...reasoningConfigOptions.data }, + }; + } + + // Remove temperature if thinking is enabled + if (isThinking && inferenceConfig.temperature != null) { + delete inferenceConfig.temperature; + warnings.push({ + type: 'unsupported-setting', + setting: 'temperature', + details: 'temperature is not supported when thinking is enabled', + }); + } + + // Remove topP if thinking is enabled + if (isThinking && inferenceConfig.topP != null) { + delete inferenceConfig.topP; + warnings.push({ + type: 'unsupported-setting', + setting: 'topP', + details: 'topP is not supported when thinking is enabled', + }); + } + const baseArgs: BedrockConverseInput = { system, additionalModelRequestFields: this.settings.additionalModelRequestFields, @@ -131,7 +184,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { inferenceConfig, }), messages, - ...providerMetadata?.bedrock, + ...(providerMetadata?.bedrock || {}), }; switch (type) { @@ -227,44 +280,35 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { } : undefined; - // Extract reasoning details from all content parts that have reasoningContent - // This handles both tool use and non-tool use cases - let reasoningDetails: Array< - | { type: 'text'; text: string; signature?: string } - | { type: 'redacted'; data: string } - > = []; - - // Process message content if available - if (response.output?.message?.content) { - reasoningDetails = response.output.message.content - .filter( - part => - part.reasoningContent && - (('reasoningText' in part.reasoningContent && - part.reasoningContent.reasoningText.text != null) || - ('redactedReasoning' in part.reasoningContent && - part.reasoningContent.redactedReasoning.data != null)), - ) - .map(part => { - const reasoningContent = part.reasoningContent!; - - if ('reasoningText' in reasoningContent) { - return { - type: 'text' as const, - text: reasoningContent.reasoningText.text, - ...(reasoningContent.reasoningText.signature && { - signature: reasoningContent.reasoningText.signature, - }), - }; - } else { - // Must be redactedReasoning - return { - type: 'redacted' as const, - data: reasoningContent.redactedReasoning.data, - }; - } - }); - } + const reasoning = response.output.message.content + .filter(content => content.reasoningContent) + .map(content => { + if ( + content.reasoningContent && + 'reasoningText' in content.reasoningContent + ) { + return { + type: 'text' as const, + text: content.reasoningContent.reasoningText.text, + ...(content.reasoningContent.reasoningText.signature && { + signature: content.reasoningContent.reasoningText.signature, + }), + }; + } else if ( + content.reasoningContent && + 'redactedReasoning' in content.reasoningContent + ) { + return { + type: 'redacted' as const, + data: content.reasoningContent.redactedReasoning.data || '', + }; + } else { + // Return undefined for unexpected structures + return undefined; + } + }) + // Filter out any undefined values + .filter((item): item is NonNullable => item !== undefined); return { text: @@ -289,7 +333,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { rawCall: { rawPrompt, rawSettings }, rawResponse: { headers: responseHeaders }, warnings, - ...(reasoningDetails.length > 0 && { reasoning: reasoningDetails }), + reasoning: reasoning.length > 0 ? reasoning : undefined, ...(providerMetadata && { providerMetadata }), }; } @@ -529,6 +573,13 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { } } +const BedrockReasoningConfigOptionsSchema = z + .object({ + type: z.union([z.literal('enabled'), z.literal('disabled')]), + budget_tokens: z.number().min(1024).max(64000).optional(), + }) + .optional(); + const BedrockStopReasonSchema = z.union([ z.enum(BEDROCK_STOP_REASONS), z.string(), From d3dc27bd70987a4ad9f12fdf7201c1962c35d94a Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Tue, 4 Mar 2025 13:29:53 -0500 Subject: [PATCH 16/26] chore: Add changeset --- .changeset/two-mayflies-shave.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/two-mayflies-shave.md diff --git a/.changeset/two-mayflies-shave.md b/.changeset/two-mayflies-shave.md new file mode 100644 index 000000000000..814190903b1a --- /dev/null +++ b/.changeset/two-mayflies-shave.md @@ -0,0 +1,8 @@ +--- +'@ai-sdk/amazon-bedrock': minor +'ai': minor +--- + +feat (ai/core): Add all reasoning parts in a generate text call + +feat (providers/amazon-bedrock): Add reasoning support to amazon-bedrock From 74c7bda0757ab1fb3de734db90f28f2246567dbc Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Tue, 4 Mar 2025 13:51:08 -0500 Subject: [PATCH 17/26] tweak(providers/amazon-bedrock): code cleanup --- packages/amazon-bedrock/src/bedrock-chat-language-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 89163fd96ee9..23b66a9a78ae 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -184,7 +184,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { inferenceConfig, }), messages, - ...(providerMetadata?.bedrock || {}), + ...providerMetadata?.bedrock, }; switch (type) { From d85704e65a68d8341f67440559037fc46a1c4d2f Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Wed, 5 Mar 2025 09:56:55 -0500 Subject: [PATCH 18/26] fix(ai/core): Revert change to generate-text tweak(providers/amazon-bedrock): optional to nullish for zod tweak(providers/amazon-bedrock): We don't need to remap the content since it's already coming in correctly fix(providers/amazon-bedrock): documentation formatting fix --- .changeset/two-mayflies-shave.md | 3 -- .../01-ai-sdk-providers/08-amazon-bedrock.mdx | 4 +-- .../ai/core/generate-text/generate-text.ts | 32 ++----------------- .../src/bedrock-chat-language-model.test.ts | 31 +----------------- .../src/bedrock-chat-language-model.ts | 6 ++-- 5 files changed, 9 insertions(+), 67 deletions(-) diff --git a/.changeset/two-mayflies-shave.md b/.changeset/two-mayflies-shave.md index 814190903b1a..376aece9f373 100644 --- a/.changeset/two-mayflies-shave.md +++ b/.changeset/two-mayflies-shave.md @@ -1,8 +1,5 @@ --- '@ai-sdk/amazon-bedrock': minor -'ai': minor --- -feat (ai/core): Add all reasoning parts in a generate text call - feat (providers/amazon-bedrock): Add reasoning support to amazon-bedrock diff --git a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx index f91e2f000983..f8dcbcf1c359 100644 --- a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx +++ b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx @@ -322,7 +322,7 @@ Amazon Bedrock has reasoning support for the `claude-3-7-sonnet-20250219` model. You can enable it using the `reasoning_config` provider option and specifying a thinking budget in tokens (minimum: `1024`, maximum: `64000`). -````ts +```ts import { bedrock } from 'ai-sdk/amazonBedrock'; import { generateText } from 'ai; @@ -394,7 +394,7 @@ using the `.embedding()` factory method. ```ts const model = bedrock.embedding('amazon.titan-embed-text-v1'); -```` +``` Bedrock Titan embedding model amazon.titan-embed-text-v2:0 supports several aditional settings. You can pass them as an options argument: diff --git a/packages/ai/core/generate-text/generate-text.ts b/packages/ai/core/generate-text/generate-text.ts index c99fceb9111c..43c749114ce3 100644 --- a/packages/ai/core/generate-text/generate-text.ts +++ b/packages/ai/core/generate-text/generate-text.ts @@ -469,35 +469,9 @@ A function that attempts to repair a tool call that failed to parse. ? text + stepText : stepText; - // Only add reasoning details if they exist and aren't already included - if ( - currentModelResponse.reasoning && - currentModelResponse.reasoning.length > 0 - ) { - const newReasoningDetails = asReasoningDetails( - currentModelResponse.reasoning, - ); - // Avoid duplicate reasoning by checking if we already have this content - const reasoningExists = currentReasoningDetails.some( - existing => - JSON.stringify(existing) === JSON.stringify(newReasoningDetails), - ); - - if (!reasoningExists) { - // Handle both cases: single ReasoningDetail or array of ReasoningDetail - if (Array.isArray(newReasoningDetails)) { - currentReasoningDetails = [ - ...currentReasoningDetails, - ...newReasoningDetails, - ]; - } else { - currentReasoningDetails = [ - ...currentReasoningDetails, - newReasoningDetails, - ]; - } - } - } + currentReasoningDetails = asReasoningDetails( + currentModelResponse.reasoning, + ); // sources: sources.push(...(currentModelResponse.sources ?? [])); diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts index d983373e894a..580c49423ca5 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts @@ -1146,42 +1146,13 @@ describe('doGenerate', () => { } >; }) { - // Map the content array to the format expected by the API - const contentBlocks = content.map(item => { - if ('type' in item) { - if (item.type === 'text') { - return { text: item.text }; - } else if (item.type === 'thinking') { - return { - reasoningContent: { - reasoningText: { - text: item.thinking, - signature: item.signature, - }, - }, - }; - } else if (item.type === 'tool_use') { - return { - toolUse: { - toolUseId: item.id, - name: item.name, - input: item.input, - }, - }; - } - } else if ('reasoningContent' in item) { - return item; - } - return item; - }); - server.urls[generateUrl].response = { type: 'json-value', body: { output: { message: { role: 'assistant', - content: contentBlocks, + content: content, }, }, usage, diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 23b66a9a78ae..de341cd65a2b 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -576,9 +576,9 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const BedrockReasoningConfigOptionsSchema = z .object({ type: z.union([z.literal('enabled'), z.literal('disabled')]), - budget_tokens: z.number().min(1024).max(64000).optional(), + budget_tokens: z.number().min(1024).max(64000).nullish(), }) - .optional(); + .nullish(); const BedrockStopReasonSchema = z.union([ z.enum(BEDROCK_STOP_REASONS), @@ -662,7 +662,7 @@ const BedrockStreamSchema = z.object({ reasoningContent: z.object({ data: z.string() }), }), ]) - .optional(), + .nullish(), }) .nullish(), contentBlockStart: z From 8eed8cdc3c67d213ab1aa7e3b0503a2e7e43c892 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Wed, 5 Mar 2025 11:25:14 -0500 Subject: [PATCH 19/26] feat(provider/amazon-bedrock): Add examples for amazon-bedrock reasoning --- .../amazon-bedrock-reasoning-chatbot.ts | 58 ++++++++++++++ .../generate-text/amazon-bedrock-reasoning.ts | 29 +++++++ .../amazon-bedrock-reasoning-chatbot.ts | 77 +++++++++++++++++++ .../amazon-bedrock-reasoning-fullstream.ts | 67 ++++++++++++++++ .../stream-text/amazon-bedrock-reasoning.ts | 35 +++++++++ 5 files changed, 266 insertions(+) create mode 100644 examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts create mode 100644 examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts create mode 100644 examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts create mode 100644 examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts create mode 100644 examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts diff --git a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts new file mode 100644 index 000000000000..789cafa57f40 --- /dev/null +++ b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts @@ -0,0 +1,58 @@ +import { bedrock } from '@ai-sdk/amazon-bedrock'; +import { CoreMessage, generateText } from 'ai'; +import 'dotenv/config'; +import * as readline from 'node:readline/promises'; +import { weatherTool } from '../tools/weather-tool'; + +const terminal = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const messages: CoreMessage[] = []; + +async function main() { + while (true) { + const userInput = await terminal.question('You: '); + messages.push({ role: 'user', content: userInput }); + + const { steps, response } = await generateText({ + model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + tools: { weatherTool }, + system: `You are a helpful, respectful and honest assistant.`, + messages, + maxSteps: 5, + providerOptions: { + bedrock: { + reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + }, + }, + }); + + for (const step of steps) { + console.log(step); + if (step.reasoning) { + console.log(`\x1b[36m${step.reasoning}\x1b[0m`); + } + + if (step.text) { + console.log(step.text); + } + + if (step.toolCalls) { + for (const toolCall of step.toolCalls) { + console.log( + `\x1b[33m${toolCall.toolName}\x1b[0m` + + JSON.stringify(toolCall.args), + ); + } + } + } + + console.log('\n'); + + messages.push(...response.messages); + } +} + +main().catch(console.error); diff --git a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts new file mode 100644 index 000000000000..4c92cb2641c0 --- /dev/null +++ b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts @@ -0,0 +1,29 @@ +import { bedrock } from '@ai-sdk/amazon-bedrock'; +import { generateText } from 'ai'; +import 'dotenv/config'; + +async function main() { + const result = await generateText({ + model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + prompt: 'How many "r"s are in the word "strawberry"?', + temperature: 0.5, // should get ignored (warning) + providerOptions: { + bedrock: { + reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + }, + }, + maxRetries: 0, + }); + + console.log('Reasoning:'); + console.log(result.reasoningDetails); + console.log(); + + console.log('Text:'); + console.log(result.text); + console.log(); + + console.log('Warnings:', result.warnings); +} + +main().catch(console.error); diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts new file mode 100644 index 000000000000..611e97445ff2 --- /dev/null +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts @@ -0,0 +1,77 @@ +import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; +import { CoreMessage, streamText, tool } from 'ai'; +import 'dotenv/config'; +import * as readline from 'node:readline/promises'; +import { z } from 'zod'; + +const bedrock = createAmazonBedrock({ + // example fetch wrapper that logs the input to the API call: + fetch: async (url, options) => { + console.log('URL', url); + console.log('Headers', JSON.stringify(options!.headers, null, 2)); + console.log( + `Body ${JSON.stringify(JSON.parse(options!.body! as string), null, 2)}`, + ); + return await fetch(url, options); + }, +}); + +const terminal = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const messages: CoreMessage[] = []; + +async function main() { + while (true) { + const userInput = await terminal.question('You: '); + + messages.push({ role: 'user', content: userInput }); + + const result = streamText({ + model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + messages, + tools: { + weather: tool({ + description: 'Get the weather in a location', + parameters: z.object({ + location: z + .string() + .describe('The location to get the weather for'), + }), + execute: async ({ location }) => ({ + location, + temperature: 72 + Math.floor(Math.random() * 21) - 10, + }), + }), + }, + maxSteps: 5, + maxRetries: 0, + providerOptions: { + bedrock: { + reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + }, + }, + onError: error => { + console.error(error); + }, + }); + + process.stdout.write('\nAssistant: '); + for await (const part of result.fullStream) { + if (part.type === 'reasoning') { + process.stdout.write('\x1b[34m' + part.textDelta + '\x1b[0m'); + } else if (part.type === 'redacted-reasoning') { + process.stdout.write('\x1b[31m' + '' + '\x1b[0m'); + } else if (part.type === 'text-delta') { + process.stdout.write(part.textDelta); + } + } + process.stdout.write('\n\n'); + + messages.push(...(await result.response).messages); + } +} + +main().catch(console.error); diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts new file mode 100644 index 000000000000..4b6e142e6882 --- /dev/null +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts @@ -0,0 +1,67 @@ +import { bedrock } from '@ai-sdk/amazon-bedrock'; +import { streamText, ToolCallPart, ToolResultPart } from 'ai'; +import 'dotenv/config'; +import { weatherTool } from '../tools/weather-tool'; + +async function main() { + const result = streamText({ + model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + tools: { + weather: weatherTool, + }, + prompt: 'What is the weather in San Francisco?', + providerOptions: { + bedrock: { + reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + }, + }, + maxSteps: 5, + }); + + let enteredReasoning = false; + let enteredText = false; + const toolCalls: ToolCallPart[] = []; + const toolResponses: ToolResultPart[] = []; + + for await (const part of result.fullStream) { + switch (part.type) { + case 'reasoning': { + if (!enteredReasoning) { + enteredReasoning = true; + console.log('\nREASONING:\n'); + } + process.stdout.write(part.textDelta); + break; + } + + case 'text-delta': { + if (!enteredText) { + enteredText = true; + console.log('\nTEXT:\n'); + } + process.stdout.write(part.textDelta); + break; + } + + case 'tool-call': { + toolCalls.push(part); + + process.stdout.write( + `\nTool call: '${part.toolName}' ${JSON.stringify(part.args)}`, + ); + break; + } + + case 'tool-result': { + toolResponses.push(part); + + process.stdout.write( + `\nTool response: '${part.toolName}' ${JSON.stringify(part.result)}`, + ); + break; + } + } + } +} + +main().catch(console.error); diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts new file mode 100644 index 000000000000..64e637e26c10 --- /dev/null +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts @@ -0,0 +1,35 @@ +import { bedrock } from '@ai-sdk/amazon-bedrock'; +import { streamText } from 'ai'; +import 'dotenv/config'; + +async function main() { + const result = streamText({ + model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + prompt: 'How many "r"s are in the word "strawberry"?', + temperature: 0.5, // should get ignored (warning) + onError: error => { + console.error(error); + }, + providerOptions: { + bedrock: { + reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + }, + }, + maxRetries: 0, + }); + + for await (const part of result.fullStream) { + if (part.type === 'reasoning') { + process.stdout.write('\x1b[34m' + part.textDelta + '\x1b[0m'); + } else if (part.type === 'redacted-reasoning') { + process.stdout.write('\x1b[31m' + '' + '\x1b[0m'); + } else if (part.type === 'text-delta') { + process.stdout.write(part.textDelta); + } + } + + console.log(); + console.log('Warnings:', await result.warnings); +} + +main().catch(console.error); From 050e3098c4b124bb2b656ba09c4a1e96e640649e Mon Sep 17 00:00:00 2001 From: Walter Korman Date: Wed, 5 Mar 2025 17:25:33 -0800 Subject: [PATCH 20/26] edit doc, add convert tests, simplify test types, share trim logic in convert --- .../01-ai-sdk-providers/08-amazon-bedrock.mdx | 10 +- .../src/bedrock-chat-language-model.test.ts | 47 ++--- .../convert-to-bedrock-chat-messages.test.ts | 169 ++++++++++++++++++ .../src/convert-to-bedrock-chat-messages.ts | 28 ++- 4 files changed, 203 insertions(+), 51 deletions(-) diff --git a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx index f8dcbcf1c359..7aa157567233 100644 --- a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx +++ b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx @@ -323,19 +323,15 @@ Amazon Bedrock has reasoning support for the `claude-3-7-sonnet-20250219` model. You can enable it using the `reasoning_config` provider option and specifying a thinking budget in tokens (minimum: `1024`, maximum: `64000`). ```ts -import { bedrock } from 'ai-sdk/amazonBedrock'; +import { bedrock } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai; const { text, reasoning, reasoningDetails } = await generateText({ - model: bedrock('claude-3-7-sonnet-20250219'), + model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), prompt: 'How many people will live in the world in 2040?', providerOptions: { bedrock: { - reasoning_config: - { - type: 'enabled', - budgetTokens: 12000 - }, + reasoningConfig: { type: 'enabled', budgetTokens: 12000 }, }, }, }); diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts index 580c49423ca5..f787c158eef0 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts @@ -6,6 +6,10 @@ import { import { BedrockChatLanguageModel } from './bedrock-chat-language-model'; import { vi } from 'vitest'; import { injectFetchHeaders } from './inject-fetch-headers'; +import { + BedrockReasoningContentBlock, + BedrockRedactedReasoningContentBlock, +} from './bedrock-api-types'; const TEST_PROMPT: LanguageModelV1Prompt = [ { role: 'system', content: 'System Prompt' }, @@ -1091,12 +1095,8 @@ describe('doGenerate', () => { | { type: 'text'; text: string } | { type: 'thinking'; thinking: string; signature: string } | { type: 'tool_use'; id: string; name: string; input: unknown } - | { - reasoningContent: { - reasoningText: { text: string; signature?: string }; - }; - } - | { reasoningContent: { redactedReasoning: { data: string } } } + | BedrockReasoningContentBlock + | BedrockRedactedReasoningContentBlock >; toolCalls?: Array<{ id?: string; @@ -1113,37 +1113,10 @@ describe('doGenerate', () => { stopReason?: string; trace?: typeof mockTrace; reasoningContent?: - | { - reasoningContent: { - reasoningText: { - text: string; - signature?: string; - }; - }; - } - | { - reasoningContent: { - redactedReasoning: { - data: string; - }; - }; - } + | BedrockReasoningContentBlock + | BedrockRedactedReasoningContentBlock | Array< - | { - reasoningContent: { - reasoningText: { - text: string; - signature?: string; - }; - }; - } - | { - reasoningContent: { - redactedReasoning: { - data: string; - }; - }; - } + BedrockReasoningContentBlock | BedrockRedactedReasoningContentBlock >; }) { server.urls[generateUrl].response = { @@ -1152,7 +1125,7 @@ describe('doGenerate', () => { output: { message: { role: 'assistant', - content: content, + content, }, }, usage, diff --git a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts index 4bb12ae1b420..e571670b8e7d 100644 --- a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts +++ b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts @@ -247,4 +247,173 @@ describe('assistant messages', () => { system: [], }); }); + + it('should properly convert reasoning content type', async () => { + const result = convertToBedrockChatMessages([ + { + role: 'user', + content: [{ type: 'text', text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { + type: 'reasoning', + text: 'This is my step-by-step reasoning process', + signature: 'test-signature', + }, + ], + }, + ]); + + expect(result).toEqual({ + messages: [ + { + role: 'user', + content: [{ text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { + reasoningContent: { + reasoningText: { + text: 'This is my step-by-step reasoning process', + signature: 'test-signature', + }, + }, + }, + ], + }, + ], + system: [], + }); + }); + + it('should properly convert redacted-reasoning content type', async () => { + const reasoningData = 'Redacted reasoning information'; + const result = convertToBedrockChatMessages([ + { + role: 'user', + content: [{ type: 'text', text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { + type: 'redacted-reasoning', + data: reasoningData, + }, + ], + }, + ]); + + expect(result).toEqual({ + messages: [ + { + role: 'user', + content: [{ text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { + reasoningContent: { + redactedReasoning: { + data: reasoningData, + }, + }, + }, + ], + }, + ], + system: [], + }); + }); + + it('should trim trailing whitespace from reasoning content when it is the last part', async () => { + const result = convertToBedrockChatMessages([ + { + role: 'user', + content: [{ type: 'text', text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { + type: 'reasoning', + text: 'This is my reasoning with trailing space ', + signature: 'test-signature', + }, + ], + }, + ]); + + expect(result).toEqual({ + messages: [ + { + role: 'user', + content: [{ text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { + reasoningContent: { + reasoningText: { + text: 'This is my reasoning with trailing space', + signature: 'test-signature', + }, + }, + }, + ], + }, + ], + system: [], + }); + }); + + it('should handle a mix of text and reasoning content types', async () => { + const result = convertToBedrockChatMessages([ + { + role: 'user', + content: [{ type: 'text', text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { type: 'text', text: 'My answer is 42.' }, + { + type: 'reasoning', + text: 'I calculated this by analyzing the meaning of life', + signature: 'reasoning-process', + }, + ], + }, + ]); + + expect(result).toEqual({ + messages: [ + { + role: 'user', + content: [{ text: 'Explain your reasoning' }], + }, + { + role: 'assistant', + content: [ + { text: 'My answer is 42.' }, + { + reasoningContent: { + reasoningText: { + text: 'I calculated this by analyzing the meaning of life', + signature: 'reasoning-process', + }, + }, + }, + ], + }, + ], + system: [], + }); + }); }); diff --git a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts index 116018832504..7bc1caa6d532 100644 --- a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts +++ b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts @@ -178,9 +178,12 @@ export function convertToBedrockChatMessages(prompt: LanguageModelV1Prompt): { // trim the last text part if it's the last message in the block // because Bedrock does not allow trailing whitespace // in pre-filled assistant responses - isLastBlock && isLastMessage && isLastContentPart - ? part.text.trim() - : part.text, + trimIfLast( + isLastBlock, + isLastMessage, + isLastContentPart, + part.text, + ), }); break; } @@ -192,10 +195,12 @@ export function convertToBedrockChatMessages(prompt: LanguageModelV1Prompt): { // trim the last text part if it's the last message in the block // because Bedrock does not allow trailing whitespace // in pre-filled assistant responses - text: - isLastBlock && isLastMessage && isLastContentPart - ? part.text.trim() - : part.text, + text: trimIfLast( + isLastBlock, + isLastMessage, + isLastContentPart, + part.text, + ), signature: part.signature, }, }, @@ -246,6 +251,15 @@ export function convertToBedrockChatMessages(prompt: LanguageModelV1Prompt): { return { system, messages }; } +function trimIfLast( + isLastBlock: boolean, + isLastMessage: boolean, + isLastContentPart: boolean, + text: string, +) { + return isLastBlock && isLastMessage && isLastContentPart ? text.trim() : text; +} + type SystemBlock = { type: 'system'; messages: Array; From 74e5a607505061e855b1d7bd3d90e7f2f4a42daa Mon Sep 17 00:00:00 2001 From: Walter Korman Date: Wed, 5 Mar 2025 17:40:58 -0800 Subject: [PATCH 21/26] fix typo in docs, add "us." prefix to model ids, add api doc link to budget_tokens schema value --- content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx | 4 ++-- .../src/generate-text/amazon-bedrock-reasoning-chatbot.ts | 2 +- .../ai-core/src/generate-text/amazon-bedrock-reasoning.ts | 2 +- .../src/stream-text/amazon-bedrock-reasoning-fullstream.ts | 2 +- examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts | 2 +- packages/amazon-bedrock/src/bedrock-chat-language-model.ts | 1 + 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx index 7aa157567233..794bcac5a020 100644 --- a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx +++ b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx @@ -324,10 +324,10 @@ You can enable it using the `reasoning_config` provider option and specifying a ```ts import { bedrock } from '@ai-sdk/amazon-bedrock'; -import { generateText } from 'ai; +import { generateText } from 'ai'; const { text, reasoning, reasoningDetails } = await generateText({ - model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + model: bedrock('us.anthropic.claude-3-7-sonnet-20250219-v1:0'), prompt: 'How many people will live in the world in 2040?', providerOptions: { bedrock: { diff --git a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts index 789cafa57f40..b5f3bf22529d 100644 --- a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts +++ b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts @@ -17,7 +17,7 @@ async function main() { messages.push({ role: 'user', content: userInput }); const { steps, response } = await generateText({ - model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + model: bedrock('us.anthropic.claude-3-7-sonnet-20250219-v1:0'), tools: { weatherTool }, system: `You are a helpful, respectful and honest assistant.`, messages, diff --git a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts index 4c92cb2641c0..0b0c5967e748 100644 --- a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts +++ b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts @@ -4,7 +4,7 @@ import 'dotenv/config'; async function main() { const result = await generateText({ - model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + model: bedrock('us.anthropic.claude-3-7-sonnet-20250219-v1:0'), prompt: 'How many "r"s are in the word "strawberry"?', temperature: 0.5, // should get ignored (warning) providerOptions: { diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts index 4b6e142e6882..8a7e9a86a938 100644 --- a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-fullstream.ts @@ -5,7 +5,7 @@ import { weatherTool } from '../tools/weather-tool'; async function main() { const result = streamText({ - model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + model: bedrock('us.anthropic.claude-3-7-sonnet-20250219-v1:0'), tools: { weather: weatherTool, }, diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts index 64e637e26c10..4be68165e635 100644 --- a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts @@ -4,7 +4,7 @@ import 'dotenv/config'; async function main() { const result = streamText({ - model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + model: bedrock('us.anthropic.claude-3-7-sonnet-20250219-v1:0'), prompt: 'How many "r"s are in the word "strawberry"?', temperature: 0.5, // should get ignored (warning) onError: error => { diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index de341cd65a2b..89ec8a269078 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -576,6 +576,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const BedrockReasoningConfigOptionsSchema = z .object({ type: z.union([z.literal('enabled'), z.literal('disabled')]), + // https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table budget_tokens: z.number().min(1024).max(64000).nullish(), }) .nullish(); From 7a157d82bc891285c6f0ff83cf95d780a0a5dd31 Mon Sep 17 00:00:00 2001 From: Walter Korman Date: Wed, 5 Mar 2025 17:58:23 -0800 Subject: [PATCH 22/26] rm unused imports, avoid duplicate reasoning_config in request body --- .../amazon-bedrock/src/bedrock-api-types.ts | 1 - .../src/bedrock-chat-language-model.test.ts | 34 +++++++++++++++++++ .../src/bedrock-chat-language-model.ts | 12 ++++--- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-api-types.ts b/packages/amazon-bedrock/src/bedrock-api-types.ts index e4431c6cb546..bc21e60e37a2 100644 --- a/packages/amazon-bedrock/src/bedrock-api-types.ts +++ b/packages/amazon-bedrock/src/bedrock-api-types.ts @@ -1,5 +1,4 @@ import { JSONObject } from '@ai-sdk/provider'; -import { Resolvable } from '@ai-sdk/provider-utils'; export interface BedrockConverseInput { system?: BedrockSystemMessages; diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts index f787c158eef0..28af858cbb2e 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts @@ -1687,4 +1687,38 @@ describe('doGenerate', () => { }, ]); }); + + it('should not duplicate reasoning_config in the request', async () => { + prepareJsonResponse({}); + + const reasoning_config = { + type: 'enabled', + budget_tokens: 12000, + }; + + await model.doGenerate({ + inputFormat: 'prompt', + mode: { type: 'regular' }, + prompt: TEST_PROMPT, + providerMetadata: { + bedrock: { + reasoning_config, + }, + }, + }); + + // Get the actual request body sent to the server + const requestBody = await server.calls[0].requestBody; + + // Verify reasoning_config is in additionalModelRequestFields + expect(requestBody.additionalModelRequestFields).toHaveProperty( + 'reasoning_config', + ); + expect(requestBody.additionalModelRequestFields.reasoning_config).toEqual( + reasoning_config, + ); + + // Verify it's NOT duplicated at the top level + expect(requestBody).not.toHaveProperty('reasoning_config'); + }); }); diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index 89ec8a269078..dd837d6fda7e 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -5,8 +5,6 @@ import { LanguageModelV1CallWarning, LanguageModelV1FinishReason, LanguageModelV1ProviderMetadata, - LanguageModelV1ReasoningPart, - LanguageModelV1RedactedReasoningPart, LanguageModelV1StreamPart, UnsupportedFunctionalityError, } from '@ai-sdk/provider'; @@ -24,9 +22,6 @@ import { BedrockConverseInput, BedrockStopReason, BEDROCK_STOP_REASONS, - BEDROCK_CACHE_POINT, - BedrockReasoningContentBlock, - BedrockRedactedReasoningContentBlock, } from './bedrock-api-types'; import { BedrockChatModelId, @@ -177,6 +172,13 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { }); } + // We embed the reasoning config in additionalModelRequestFields, so avoid + // duplication in the request. + const providerMetadataWithoutReasoningConfig = { ...providerMetadata }; + if (providerMetadataWithoutReasoningConfig?.bedrock?.reasoning_config) { + delete providerMetadataWithoutReasoningConfig.bedrock.reasoning_config; + } + const baseArgs: BedrockConverseInput = { system, additionalModelRequestFields: this.settings.additionalModelRequestFields, From c19c5e5887d273cf933d81d125a26a09f6d692fd Mon Sep 17 00:00:00 2001 From: Walter Korman Date: Wed, 5 Mar 2025 18:22:16 -0800 Subject: [PATCH 23/26] fix model id for one example --- .../ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts index 611e97445ff2..67b4a1020482 100644 --- a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts @@ -30,7 +30,7 @@ async function main() { messages.push({ role: 'user', content: userInput }); const result = streamText({ - model: bedrock('anthropic.claude-3-7-sonnet-20250219-v1:0'), + model: bedrock('us.anthropic.claude-3-7-sonnet-20250219-v1:0'), messages, tools: { weather: tool({ From 4ac00d3f11987119272abf8eb27e3d55a4f794b5 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Thu, 6 Mar 2025 10:49:23 -0500 Subject: [PATCH 24/26] fix(providers/amazon-bedrock): Reimplement provider reasoning config --- .../src/bedrock-chat-language-model.test.ts | 34 ------------------- .../src/bedrock-chat-language-model.ts | 9 +---- 2 files changed, 1 insertion(+), 42 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts index 28af858cbb2e..f787c158eef0 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts @@ -1687,38 +1687,4 @@ describe('doGenerate', () => { }, ]); }); - - it('should not duplicate reasoning_config in the request', async () => { - prepareJsonResponse({}); - - const reasoning_config = { - type: 'enabled', - budget_tokens: 12000, - }; - - await model.doGenerate({ - inputFormat: 'prompt', - mode: { type: 'regular' }, - prompt: TEST_PROMPT, - providerMetadata: { - bedrock: { - reasoning_config, - }, - }, - }); - - // Get the actual request body sent to the server - const requestBody = await server.calls[0].requestBody; - - // Verify reasoning_config is in additionalModelRequestFields - expect(requestBody.additionalModelRequestFields).toHaveProperty( - 'reasoning_config', - ); - expect(requestBody.additionalModelRequestFields.reasoning_config).toEqual( - reasoning_config, - ); - - // Verify it's NOT duplicated at the top level - expect(requestBody).not.toHaveProperty('reasoning_config'); - }); }); diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index dd837d6fda7e..a9ae668f738a 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -153,7 +153,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { } // Remove temperature if thinking is enabled - if (isThinking && inferenceConfig.temperature != null) { + if (isThinking && inferenceConfig.temperature !== null) { delete inferenceConfig.temperature; warnings.push({ type: 'unsupported-setting', @@ -172,13 +172,6 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { }); } - // We embed the reasoning config in additionalModelRequestFields, so avoid - // duplication in the request. - const providerMetadataWithoutReasoningConfig = { ...providerMetadata }; - if (providerMetadataWithoutReasoningConfig?.bedrock?.reasoning_config) { - delete providerMetadataWithoutReasoningConfig.bedrock.reasoning_config; - } - const baseArgs: BedrockConverseInput = { system, additionalModelRequestFields: this.settings.additionalModelRequestFields, From 3bde0d13af98ef3f6082ada3d94a5a2c6536c993 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Thu, 6 Mar 2025 11:37:50 -0500 Subject: [PATCH 25/26] tweak(examples/amazon-bedrock): Clean bedrock examples --- .../amazon-bedrock-reasoning-chatbot.ts | 2 +- .../generate-text/amazon-bedrock-reasoning.ts | 3 +- .../stream-text/amazon-bedrock-fullstream.ts | 64 +++++++++---------- .../amazon-bedrock-reasoning-chatbot.ts | 2 +- .../stream-text/amazon-bedrock-reasoning.ts | 3 +- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts index b5f3bf22529d..5d00c9d13698 100644 --- a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts +++ b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning-chatbot.ts @@ -24,7 +24,7 @@ async function main() { maxSteps: 5, providerOptions: { bedrock: { - reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + reasoning_config: { type: 'enabled', budget_tokens: 2048 }, }, }, }); diff --git a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts index 0b0c5967e748..161da51dd2e5 100644 --- a/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts +++ b/examples/ai-core/src/generate-text/amazon-bedrock-reasoning.ts @@ -9,10 +9,11 @@ async function main() { temperature: 0.5, // should get ignored (warning) providerOptions: { bedrock: { - reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + reasoning_config: { type: 'enabled', budget_tokens: 2048 }, }, }, maxRetries: 0, + maxSteps: 5, }); console.log('Reasoning:'); diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-fullstream.ts b/examples/ai-core/src/stream-text/amazon-bedrock-fullstream.ts index 5316abadac67..1aeb49c3fa4d 100644 --- a/examples/ai-core/src/stream-text/amazon-bedrock-fullstream.ts +++ b/examples/ai-core/src/stream-text/amazon-bedrock-fullstream.ts @@ -1,5 +1,5 @@ import { bedrock } from '@ai-sdk/amazon-bedrock'; -import { streamText } from 'ai'; +import { streamText, ToolCallPart, ToolResultPart } from 'ai'; import 'dotenv/config'; import { z } from 'zod'; import { weatherTool } from '../tools/weather-tool'; @@ -14,57 +14,51 @@ async function main() { }, }, prompt: 'What is the weather in San Francisco?', + maxSteps: 5, }); + let enteredReasoning = false; + let enteredText = false; + const toolCalls: ToolCallPart[] = []; + const toolResponses: ToolResultPart[] = []; + for await (const part of result.fullStream) { switch (part.type) { + case 'reasoning': { + if (!enteredReasoning) { + enteredReasoning = true; + console.log('\nREASONING:\n'); + } + process.stdout.write(part.textDelta); + break; + } + case 'text-delta': { - console.log('Text delta:', part.textDelta); + if (!enteredText) { + enteredText = true; + console.log('\nTEXT:\n'); + } + process.stdout.write(part.textDelta); break; } case 'tool-call': { - switch (part.toolName) { - case 'cityAttractions': { - console.log('TOOL CALL cityAttractions'); - console.log(`city: ${part.args.city}`); // string - break; - } - - case 'weather': { - console.log('TOOL CALL weather'); - console.log(`location: ${part.args.location}`); // string - break; - } - } + toolCalls.push(part); + process.stdout.write( + `\nTool call: '${part.toolName}' ${JSON.stringify(part.args)}`, + ); break; } case 'tool-result': { - switch (part.toolName) { - // NOT AVAILABLE (NO EXECUTE METHOD) - // case 'cityAttractions': { - // console.log('TOOL RESULT cityAttractions'); - // console.log(`city: ${part.args.city}`); // string - // console.log(`result: ${part.result}`); - // break; - // } - - case 'weather': { - console.log('TOOL RESULT weather'); - console.log(`location: ${part.args.location}`); // string - console.log(`temperature: ${part.result.temperature}`); // number - break; - } - } + toolResponses.push(part); + process.stdout.write( + `\nTool response: '${part.toolName}' ${JSON.stringify(part.result)}`, + ); break; } - - case 'error': - console.error('Error:', part.error); - break; } } } diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts index 67b4a1020482..7ddbe0aa0e38 100644 --- a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning-chatbot.ts @@ -50,7 +50,7 @@ async function main() { maxRetries: 0, providerOptions: { bedrock: { - reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + reasoning_config: { type: 'enabled', budget_tokens: 2048 }, }, }, onError: error => { diff --git a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts index 4be68165e635..dec79e1f8bfe 100644 --- a/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts +++ b/examples/ai-core/src/stream-text/amazon-bedrock-reasoning.ts @@ -12,10 +12,11 @@ async function main() { }, providerOptions: { bedrock: { - reasoning_config: { type: 'enabled', budget_tokens: 12000 }, + reasoning_config: { type: 'enabled', budget_tokens: 1024 }, }, }, maxRetries: 0, + maxSteps: 5, }); for await (const part of result.fullStream) { From 54c10c84bc00cd55ec5befcdc8cfaa7ab528a4d6 Mon Sep 17 00:00:00 2001 From: Und3rf10w Date: Thu, 6 Mar 2025 11:42:32 -0500 Subject: [PATCH 26/26] fix(providers/amazon-bedrock): Remove min max from reasoning config fix(providers/amazon-bedrock): change check on inferenceConfig.temperature when thinking is enabled --- packages/amazon-bedrock/src/bedrock-chat-language-model.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts index a9ae668f738a..e5b56a108de8 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-language-model.ts @@ -153,7 +153,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { } // Remove temperature if thinking is enabled - if (isThinking && inferenceConfig.temperature !== null) { + if (isThinking && inferenceConfig.temperature != null) { delete inferenceConfig.temperature; warnings.push({ type: 'unsupported-setting', @@ -295,7 +295,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { ) { return { type: 'redacted' as const, - data: content.reasoningContent.redactedReasoning.data || '', + data: content.reasoningContent.redactedReasoning.data ?? '', }; } else { // Return undefined for unexpected structures @@ -571,8 +571,7 @@ export class BedrockChatLanguageModel implements LanguageModelV1 { const BedrockReasoningConfigOptionsSchema = z .object({ type: z.union([z.literal('enabled'), z.literal('disabled')]), - // https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table - budget_tokens: z.number().min(1024).max(64000).nullish(), + budget_tokens: z.number().nullish(), }) .nullish();