Repository navigation
Python: Stream Bedrock responses with ConverseStream instead of returning them in one update - #8685
Conversation
…ning them in one update stream=True called the non-streaming Converse API and yielded a single update after the whole response had arrived, so time to first text was the full generation time. Call converse_stream and yield an update per event: text deltas, tool use start and input deltas (merged by call_id), messageStop finish reason and metadata usage. The event stream is closed when iteration ends. The non-streaming path is unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Stream failures must be propagated, and streamed updates must preserve the assistant role.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds incremental AWS Bedrock ConverseStream support for Python responses.
Changes:
- Streams text, tool calls, finish reasons, and usage metadata.
- Reliably closes event streams.
- Adds streaming and structured-output tests.
| File | Review |
|---|---|
python/packages/bedrock/tests/test_bedrock_structured_output.py |
Extends structured-output streaming stubs and coverage. |
python/packages/bedrock/tests/test_bedrock_client.py |
Tests streamed responses, tool calls, usage, finish reasons, and cleanup. |
python/packages/bedrock/agent_framework_bedrock/_chat_client.py |
Implements stream processing. Critical (2 votes): modeled stream errors are silently ignored. Moderate (1 vote): streamed updates omit the assistant role. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Copilot's review of microsoft#8685 noted that the ConverseStream updates carry no role. The other chat clients tag their streaming updates with role="assistant" (OpenAI, Mistral, Ollama), and the non-streaming Bedrock path already builds Message(role="assistant"). Without it, Agent.run(stream=True) yielded AgentResponseUpdate.role=None for Bedrock while the other providers yield "assistant". Set role="assistant" on every streamed update. The aggregated ChatResponse already defaulted to assistant and is unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for the Copilot review pass on this draft: both findings were worth checking, and the assistant-role one turned out to be a real gap. The stream-error finding is answered in its thread (botocore already raises, so no code change), and the missing role is fixed in 3885687. The runs behind each verdict are below.
Why the commit:
One assertion added to Stream error events, re-run today on this branch (real boto3 client against a local event-stream server, no AWS call) |
Eduard van Valkenburg (eavanvalkenburg)
left a comment
There was a problem hiding this comment.
Lucas Kim (@kimnamu) The current head is green, and the dedicated full-diff review found no remaining actionable issues with Bedrock ConverseStream text, tool, finish, usage, role propagation, structured-output finalization, or stream cleanup behavior.

Motivation & Context
Thanks for the Bedrock connector and for the Anthropic client's streaming code, whose tool-input delta pattern this PR follows.
stream=Truecalled non-streaming Converse and yielded one update at the end, so OpenAI GPT-6 Sol/Luna/Astra first text arrived 3-7x later than with ConverseStream.Description & Review Guide
_invoke_converse(request, stream=True)callsconverse_streamwith the same error handling. The streaming branch reads events in a worker thread and a new_process_converse_stream_eventmaps text deltas,toolUsestart/input deltas (merged bycall_id),messageStopandmetadatausage to updates. The stream is closed infinally; the non-streaming path is untouched.converse_streamconverse_streamus., TTFT vs rawconverse_streamglobal., TTFT vs rawconverse_streamus.)Agent.run(stream=True)tool round-trip, GPT-6 Sol / Luna / Astrastream=Truewithresponse_format.valueparsedCity(name='Tokyo', country='Japan')on Sol, Luna and Astra)get_response, public APIasyncio.to_thread(next, ...)loop. Reasoning deltas are skipped, like the non-streaming parser does.Related Issue
Fixes #8684
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.Raw perf medians on this branch (TTFT s, total s, output tokens, updates)