From e255afa45f4bbc4d5d75bcb9f0d8fd7a73b2afb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Mugnolo?= Date: Tue, 12 May 2026 11:53:33 -0300 Subject: [PATCH] Stream Anthropic responses uncompressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Net::HTTP auto-inflates the upstream gzip, which buffers SSE chunks until Cloudflare flushes its deflate state — turning ~100 events into 2 bursts and pushing first-chunk arrival from ~1s to ~15s on a 22s response. Set Accept-Encoding: identity on streaming requests. Non-streaming responses keep gzip. --- lib/ruby_llm/protocols/anthropic/streaming.rb | 6 ++++++ .../protocols/anthropic/streaming_spec.rb | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/ruby_llm/protocols/anthropic/streaming.rb b/lib/ruby_llm/protocols/anthropic/streaming.rb index 11fecf3f1..2557cfc1d 100644 --- a/lib/ruby_llm/protocols/anthropic/streaming.rb +++ b/lib/ruby_llm/protocols/anthropic/streaming.rb @@ -9,6 +9,12 @@ class Anthropic module Streaming private + def stream_response(payload, additional_headers = {}, &) + # Avoid Net::HTTP's auto-inflate: Cloudflare's gzip flushes + # infrequently for SSE, batching chunks until each flush. + super(payload, additional_headers.merge('Accept-Encoding' => 'identity'), &) + end + def stream_url completion_url end diff --git a/spec/ruby_llm/protocols/anthropic/streaming_spec.rb b/spec/ruby_llm/protocols/anthropic/streaming_spec.rb index 5342768b6..3790d08b0 100644 --- a/spec/ruby_llm/protocols/anthropic/streaming_spec.rb +++ b/spec/ruby_llm/protocols/anthropic/streaming_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe RubyLLM::Protocols::Anthropic::Streaming do + include_context 'with configured RubyLLM' + let(:protocol) do RubyLLM::Protocols::Anthropic.allocate.tap do |object| object.instance_variable_set(:@model, instance_double(RubyLLM::Model, id: 'claude-sonnet-4-5')) @@ -21,4 +23,21 @@ expect(chunk.finish_reason).to eq('end_turn') end + + it 'sends Accept-Encoding: identity on streaming requests' do + captured = nil + + stub_request(:post, %r{api\.anthropic\.com/v1/messages}) + .with { |req| captured = req.headers['Accept-Encoding'] } + .to_return( + status: 200, + body: '', + headers: { 'Content-Type' => 'text/event-stream' } + ) + + chat = RubyLLM.chat(model: 'claude-haiku-4-5', provider: :anthropic) + chat.ask('hi') { |_chunk| nil } + + expect(captured).to eq('identity') + end end