Skip to content

fix: handle empty Bedrock stream response (messageStop without messageStart)#749

Open
pfolegmalkov wants to merge 2 commits intostrands-agents:mainfrom
pfolegmalkov:fix/empty-stream-response
Open

fix: handle empty Bedrock stream response (messageStop without messageStart)#749
pfolegmalkov wants to merge 2 commits intostrands-agents:mainfrom
pfolegmalkov:fix/empty-stream-response

Conversation

@pfolegmalkov
Copy link
Copy Markdown

Problem

When using AWS Bedrock Converse API, the model may return a modelMessageStopEvent without a preceding modelMessageStartEvent. This happens when the model produces an empty response — for example, after processing certain tool results (like ask_clarifying_questions) where the model determines it has nothing to add.

The current code throws ModelError('Stream ended without completing a message') because stoppedMessage is null (set only when messageRole is truthy, which requires modelMessageStartEvent).

This is valid Bedrock behavior — the Converse API spec allows messageStop events with a stop reason even when no content blocks were produced.

Fix

In src/models/model.ts, the modelMessageStopEvent handler now defaults to 'assistant' role when modelMessageStartEvent was not received:

// Before
if (messageRole) {
  stoppedMessage = new Message({ role: messageRole, content: [...contentBlocks] })
  finalStopReason = event.stopReason!
}

// After
stoppedMessage = new Message({ role: messageRole ?? 'assistant', content: [...contentBlocks] })
finalStopReason = event.stopReason!

The contentBlocks array will be empty in this case, which is the correct representation of an empty model response.

Test plan

Added 4 new test cases in src/models/__tests__/model.test.ts:

  • messageStop without messageStart → creates assistant message with empty content
  • content blocks before messageStop without messageStart → preserves accumulated blocks
  • toolUse stopReason without messageStart → handled correctly
  • completely empty stream → still throws ModelError (no events at all = real error)

All 1617 tests pass. Coverage on model.ts branch coverage improved (87.09% → 90.32%).

Reproduction

This issue was discovered in a production conversational search application using Bedrock Converse API with Claude Sonnet 4.6 and 13 tools. The model consistently returns empty responses after ask_clarifying_questions tool results, causing the agent loop to crash.

Checklist

  • Tests added
  • All existing tests pass (1617/1617)
  • Pre-commit hooks pass (lint, format, type-check)
  • No breaking changes — empty responses that previously threw now return a valid empty Message

…eStart)

Bedrock Converse API may return modelMessageStopEvent without a preceding
modelMessageStartEvent when the model produces an empty response (e.g., after
certain tool results like ask_clarifying_questions). Previously this threw
'Stream ended without completing a message'. Now defaults to assistant role
with empty content array, matching Bedrock's behavior.
4 new test cases in model.test.ts:
- messageStop without messageStart creates assistant message with empty content
- content blocks accumulated before messageStop without messageStart are preserved
- toolUse stopReason without messageStart handled correctly
- completely empty stream (no events) still throws ModelError

Covers the fix in model.ts where modelMessageStopEvent now defaults to
'assistant' role when modelMessageStartEvent was not received.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant