Fix Bedrock streaming dropping all chunks when Faraday env is nil#813
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #813 +/- ##
==========================================
+ Coverage 83.29% 83.34% +0.05%
==========================================
Files 172 172
Lines 8281 8282 +1
Branches 1347 1347
==========================================
+ Hits 6898 6903 +5
- Misses 887 888 +1
+ Partials 496 491 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
8321563 to
a1093e9
Compare
crmne
left a comment
There was a problem hiding this comment.
The bug fix and test coverage look right. Please tighten the shape before merging: keep the Faraday status-routing rule in one helper, and keep the comment/predicate small.
| if env&.status == 200 | ||
| on_chunk.call(chunk, env) | ||
| else | ||
| # A nil env means the status is not yet known (Faraday 2 with the |
There was a problem hiding this comment.
Let's keep this short, and treat unknown status the same whether env is nil or env.status is nil.
# Some adapters do not expose response status during on_data.
status = env&.status
if status && status != 200
on_failed_response.call(chunk, env)
else
on_chunk.call(chunk, env)
end| if env&.status == 200 | ||
| parse_stream_chunk(decoder, chunk, accumulator, &block) | ||
| else | ||
| # A nil env means the status is not yet known (Faraday 2 with the |
There was a problem hiding this comment.
Converse needs its own AWS Event Stream parser, but it can still use the existing Faraday on_data wrapper so this routing rule lives in one place.
faraday_v1 = Faraday::VERSION.start_with?('1')
on_data = RubyLLM::Streaming::FaradayHandlers.build(
faraday_v1: faraday_v1,
on_chunk: ->(chunk, _env) { parse_stream_chunk(decoder, chunk, accumulator, &block) },
on_failed_response: ->(chunk, env) { handle_failed_stream(chunk, env) }
)
if faraday_v1
req.options[:on_data] = on_data
else
req.options.on_data = on_data
endThe Faraday 2 on_data callback gates chunk parsing on `env&.status == 200`. With the net_http adapter, env is nil during streaming (the status is not yet known), so this is false for every chunk. Each valid AWS event-stream frame is then routed to the failed-response handler, which tries to JSON-parse binary eventstream bytes, fails, logs a "failed stream error chunk" debug line, and drops it. The result: ConverseStream responses come back completely empty (zero chunks, blank content, nil token counts) even though the HTTP request itself succeeds with status 200. Invert the guard so a nil env (status unknown) falls through to normal chunk parsing, and only a present env reporting a non-200 status is treated as a failure. Applied in both the Converse streaming module and the shared FaradayHandlers.v2_on_data helper.
Cover the regression where a nil Faraday env (Faraday 2 + net_http during streaming) caused every chunk to be routed to the failed-response handler and discarded. Tests exercise both the shared FaradayHandlers.v2_on_data helper and the Converse stream_response on_data proc directly, asserting that a nil env and a 200 env both parse the chunk while a present non-200 env triggers failure handling.
- Use `status = env&.status` pattern in FaradayHandlers#v2_on_data so nil env and nil status are treated identically with a shorter comment - Refactor Converse::Streaming to use FaradayHandlers.build instead of duplicating the Faraday status-routing logic inline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
49b5043 to
9fa1fc4
Compare
Inline let helpers as local variables in the FaradayHandlers tests to keep the memoized helper count within the configured limit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stream_url calls escape_model_id which is defined in Converse::Chat, not Converse::Streaming. Add a singleton method stub on the plain Object test double so the on_data routing specs can exercise stream_response without needing the full Chat module. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What this does
Fixes a bug where Bedrock ConverseStream responses come back completely empty (zero chunks, blank content, nil token counts) even though the HTTP request succeeds with status 200.
The Faraday 2
on_datacallback gates chunk parsing onenv&.status == 200. With thenet_httpadapter,envis nil during streaming (the status is not yet known when chunks arrive), so this condition is false for every chunk. Each valid AWS event-stream frame is then routed to the failed-response handler, which tries toJSON.parsebinary eventstream bytes, fails, logs a "failed stream error chunk" debug line, and silently drops it. The result is an empty streamed response.This affects any app on Faraday 2.x + the net_http adapter (Faraday's default) using Bedrock streaming.
Fix: invert the guard so a nil env (status unknown) falls through to normal chunk parsing, and only a present env reporting a non-200 status is treated as a failure. Applied in both spots that build the v2
on_dataproc:RubyLLM::Streaming::FaradayHandlers.v2_on_data(shared SSE handler)RubyLLM::Protocols::Converse::Streaming#stream_response(Bedrock ConverseStream)Reproduction
With Faraday 2.x + net_http, the debug log shows the data arriving fine (
{"delta":{"text":"BANANA"}}, usage tokens, status 200) but every frame hitting "Failed Bedrock stream error chunk". After the fix:chunks.size => 5,resp.content => "BANANA", tokens populated.Type of change
Scope check
Quality check
overcommit --installand all hooks passbundle exec rake vcr:record[provider_name]spec/ruby_llm/streaming_spec.rb,spec/ruby_llm/protocols/converse/streaming_spec.rb)models.json,aliases.json)AI-generated code
API changes