Skip to content

Commit ac80bc7

Browse files
Backport: fix(amazon-bedrock): preserve empty text blocks when reasoning content is present (#14189)
This is an automated backport of #14175 to the release-v6.0 branch. FYI @aayush-kapoor Co-authored-by: Aayush Kapoor <83492835+aayush-kapoor@users.noreply.github.com>
1 parent 10afede commit ac80bc7

File tree

5 files changed

+145
-3
lines changed

5 files changed

+145
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ai-sdk/amazon-bedrock": patch
3+
---
4+
5+
fix(amazon-bedrock): preserve empty text blocks when reasoning content is present

packages/amazon-bedrock/src/bedrock-chat-language-model.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4612,6 +4612,76 @@ describe('doGenerate', () => {
46124612
`);
46134613
});
46144614

4615+
it('should preserve empty text blocks between reasoning blocks', async () => {
4616+
server.urls[generateUrl].response = {
4617+
type: 'json-value',
4618+
body: {
4619+
output: {
4620+
message: {
4621+
role: 'assistant',
4622+
content: [
4623+
{
4624+
reasoningContent: {
4625+
reasoningText: {
4626+
text: 'thinking...',
4627+
signature: 'sig-1',
4628+
},
4629+
},
4630+
},
4631+
{ text: '' },
4632+
{
4633+
reasoningContent: {
4634+
reasoningText: {
4635+
text: 'more thinking...',
4636+
signature: 'sig-2',
4637+
},
4638+
},
4639+
},
4640+
{ text: 'The answer is 42.' },
4641+
],
4642+
},
4643+
},
4644+
usage: { inputTokens: 4, outputTokens: 34, totalTokens: 38 },
4645+
stopReason: 'stop_sequence',
4646+
},
4647+
};
4648+
4649+
const result = await model.doGenerate({
4650+
prompt: TEST_PROMPT,
4651+
});
4652+
4653+
expect(result.content).toMatchInlineSnapshot(`
4654+
[
4655+
{
4656+
"providerMetadata": {
4657+
"bedrock": {
4658+
"signature": "sig-1",
4659+
},
4660+
},
4661+
"text": "thinking...",
4662+
"type": "reasoning",
4663+
},
4664+
{
4665+
"text": "",
4666+
"type": "text",
4667+
},
4668+
{
4669+
"providerMetadata": {
4670+
"bedrock": {
4671+
"signature": "sig-2",
4672+
},
4673+
},
4674+
"text": "more thinking...",
4675+
"type": "reasoning",
4676+
},
4677+
{
4678+
"text": "The answer is 42.",
4679+
"type": "text",
4680+
},
4681+
]
4682+
`);
4683+
});
4684+
46154685
it('should extract reasoning text without signature', async () => {
46164686
server.urls[generateUrl].response = {
46174687
type: 'json-value',

packages/amazon-bedrock/src/bedrock-chat-language-model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ export class BedrockChatLanguageModel implements LanguageModelV3 {
447447
// map response content to content array
448448
for (const part of response.output.message.content) {
449449
// text
450-
if (part.text) {
450+
if (part.text != null) {
451451
content.push({ type: 'text', text: part.text });
452452
}
453453

packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,70 @@ describe('assistant messages', () => {
10371037
system: [],
10381038
});
10391039
});
1040+
1041+
it('should preserve empty text blocks when reasoning blocks are present', async () => {
1042+
const result = await convertToBedrockChatMessages([
1043+
{
1044+
role: 'user',
1045+
content: [{ type: 'text', text: 'Hello' }],
1046+
},
1047+
{
1048+
role: 'assistant',
1049+
content: [
1050+
{
1051+
type: 'reasoning',
1052+
text: 'thinking...',
1053+
providerOptions: {
1054+
bedrock: { signature: 'sig-1' },
1055+
},
1056+
},
1057+
{ type: 'text', text: '' },
1058+
{
1059+
type: 'reasoning',
1060+
text: 'more thinking...',
1061+
providerOptions: {
1062+
bedrock: { signature: 'sig-2' },
1063+
},
1064+
},
1065+
{ type: 'text', text: 'response text' },
1066+
{
1067+
type: 'tool-call',
1068+
toolCallId: 'call-123',
1069+
toolName: 'test',
1070+
input: {},
1071+
},
1072+
],
1073+
},
1074+
]);
1075+
1076+
expect(result).toEqual({
1077+
messages: [
1078+
{
1079+
role: 'user',
1080+
content: [{ text: 'Hello' }],
1081+
},
1082+
{
1083+
role: 'assistant',
1084+
content: [
1085+
{
1086+
reasoningContent: {
1087+
reasoningText: { text: 'thinking...', signature: 'sig-1' },
1088+
},
1089+
},
1090+
{ text: '' },
1091+
{
1092+
reasoningContent: {
1093+
reasoningText: { text: 'more thinking...', signature: 'sig-2' },
1094+
},
1095+
},
1096+
{ text: 'response text' },
1097+
{ toolUse: { toolUseId: 'call-123', name: 'test', input: {} } },
1098+
],
1099+
},
1100+
],
1101+
system: [],
1102+
});
1103+
});
10401104
});
10411105

10421106
describe('tool messages', () => {

packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,15 +253,18 @@ export async function convertToBedrockChatMessages(
253253
const message = block.messages[j];
254254
const isLastMessage = j === block.messages.length - 1;
255255
const { content } = message;
256+
const hasReasoningBlocks = content.some(
257+
part => part.type === 'reasoning',
258+
);
256259

257260
for (let k = 0; k < content.length; k++) {
258261
const part = content[k];
259262
const isLastContentPart = k === content.length - 1;
260263

261264
switch (part.type) {
262265
case 'text': {
263-
// Skip empty text blocks
264-
if (!part.text.trim()) {
266+
// Skip empty text blocks unless reasoning blocks are present
267+
if (!part.text.trim() && !hasReasoningBlocks) {
265268
break;
266269
}
267270

0 commit comments

Comments
 (0)