From 6ec3ae33d5fb2c5705c2041607740a66d10b70a6 Mon Sep 17 00:00:00 2001 From: Bruno Degomme Date: Thu, 2 Jul 2026 11:03:30 +0200 Subject: [PATCH] Fix streamed message model_id latching onto empty string Azure OpenAI's first stream chunk (the prompt filter chunk) carries "model": "". StreamAccumulator's `@model_id ||= chunk.model_id` treats the empty string as set, so the assembled message keeps an empty model_id, model lookup fails, and message.cost returns nil for every component despite valid token usage. Keep the first non-empty model_id instead. Co-Authored-By: Claude Fable 5 --- lib/ruby_llm/stream_accumulator.rb | 2 +- spec/ruby_llm/stream_accumulator_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/ruby_llm/stream_accumulator.rb b/lib/ruby_llm/stream_accumulator.rb index ccfbb335d..a9e0f3152 100644 --- a/lib/ruby_llm/stream_accumulator.rb +++ b/lib/ruby_llm/stream_accumulator.rb @@ -28,7 +28,7 @@ def initialize def add(chunk) RubyLLM.logger.debug { chunk.inspect } if RubyLLM.config.log_stream_debug - @model_id ||= chunk.model_id + @model_id = chunk.model_id if @model_id.to_s.empty? handle_chunk_content(chunk) accumulate_citations(chunk.citations) diff --git a/spec/ruby_llm/stream_accumulator_spec.rb b/spec/ruby_llm/stream_accumulator_spec.rb index 1d159a68e..b539601c4 100644 --- a/spec/ruby_llm/stream_accumulator_spec.rb +++ b/spec/ruby_llm/stream_accumulator_spec.rb @@ -4,6 +4,26 @@ RSpec.describe RubyLLM::StreamAccumulator do describe '#add' do + it 'ignores an empty model_id from an initial chunk' do + accumulator = described_class.new + + accumulator.add(RubyLLM::Chunk.new(role: :assistant, content: '', model_id: '')) + accumulator.add(RubyLLM::Chunk.new(role: :assistant, content: 'Hi', model_id: 'gpt-5.4-2026-03-05')) + + message = accumulator.to_message(nil) + expect(message.model_id).to eq('gpt-5.4-2026-03-05') + end + + it 'keeps the first non-empty model_id' do + accumulator = described_class.new + + accumulator.add(RubyLLM::Chunk.new(role: :assistant, content: 'Hi', model_id: 'model-a')) + accumulator.add(RubyLLM::Chunk.new(role: :assistant, content: '!', model_id: 'model-b')) + + message = accumulator.to_message(nil) + expect(message.model_id).to eq('model-a') + end + it 'handles tool call deltas that omit arguments' do accumulator = described_class.new tool_call = RubyLLM::ToolCall.new(id: 'call_1', name: 'weather', arguments: nil)