From 03408fcecc05f918b3d6e9c613a12a18d2810eae Mon Sep 17 00:00:00 2001 From: Rick Blommers Date: Tue, 23 Sep 2025 13:30:38 +0200 Subject: [PATCH 1/2] Filter out reasoning messages from Mistral Mistral can return intermediate reasoning messages. These caused `TypeError: no implicit conversion of Array into String` when handling streamed responses. This commit skips these messages to ensure correct chunk handling. (Fixes #420) --- lib/ruby_llm/providers/mistral.rb | 1 + lib/ruby_llm/providers/mistral/streaming.rb | 30 ++++++++++++++++ .../providers/mistral/streaming_spec.rb | 36 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 lib/ruby_llm/providers/mistral/streaming.rb create mode 100644 spec/ruby_llm/providers/mistral/streaming_spec.rb diff --git a/lib/ruby_llm/providers/mistral.rb b/lib/ruby_llm/providers/mistral.rb index 18ddc266c..b67ef2656 100644 --- a/lib/ruby_llm/providers/mistral.rb +++ b/lib/ruby_llm/providers/mistral.rb @@ -7,6 +7,7 @@ class Mistral < OpenAI include Mistral::Chat include Mistral::Models include Mistral::Embeddings + include Mistral::Streaming def api_base 'https://api.mistral.ai/v1' diff --git a/lib/ruby_llm/providers/mistral/streaming.rb b/lib/ruby_llm/providers/mistral/streaming.rb new file mode 100644 index 000000000..d94493211 --- /dev/null +++ b/lib/ruby_llm/providers/mistral/streaming.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + class Mistral + # Streaming methods of the Misttral API integration + module Streaming + module_function + + def build_chunk(data) + Chunk.new( + role: :assistant, + model_id: data['model'], + content: extract_content(data), + tool_calls: parse_tool_calls(data.dig('choices', 0, 'delta', 'tool_calls'), parse_arguments: false), + input_tokens: data.dig('usage', 'prompt_tokens'), + output_tokens: data.dig('usage', 'completion_tokens') + ) + end + + def extract_content(data) + data = data.dig('choices', 0, 'delta', 'content') + return '' if data.is_a?(Array) || data.is_a?(Hash) + + data.to_s + end + end + end + end +end diff --git a/spec/ruby_llm/providers/mistral/streaming_spec.rb b/spec/ruby_llm/providers/mistral/streaming_spec.rb new file mode 100644 index 000000000..497a7d853 --- /dev/null +++ b/spec/ruby_llm/providers/mistral/streaming_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RubyLLM::Providers::Mistral::Streaming do + let(:test_obj) do + Object.new.extend(described_class).tap do |obj| + allow(obj).to receive(:parse_tool_calls).and_return([]) # ignore tool calls + end + end + + it 'correctly processes content on receiving reasoning messages (an array in choices.delta.content)' do + data = { + 'choices' => [{ + 'index' => 0, + 'delta' => { + 'content' => [{ + 'type' => 'thinking', + 'thinking' => [{ 'type' => 'text', 'text' => 'Okay' }] + }] + } + }] + } + expect(test_obj.send(:build_chunk, data).content).to eq('') + end + + it 'correctly processes content on receiving normal messages' do + data = { + 'choices' => [{ + 'index' => 0, + 'delta' => { 'content' => 'thecontent' } + }] + } + expect(test_obj.send(:build_chunk, data).content).to eq('thecontent') + end +end From 424644d732303ed02bf7a7c639d5f9efa04cd435 Mon Sep 17 00:00:00 2001 From: Rick Blommers Date: Tue, 23 Sep 2025 13:44:10 +0200 Subject: [PATCH 2/2] Update streaming.rb (spelling error in comment) --- lib/ruby_llm/providers/mistral/streaming.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ruby_llm/providers/mistral/streaming.rb b/lib/ruby_llm/providers/mistral/streaming.rb index d94493211..3d2dd289a 100644 --- a/lib/ruby_llm/providers/mistral/streaming.rb +++ b/lib/ruby_llm/providers/mistral/streaming.rb @@ -3,7 +3,7 @@ module RubyLLM module Providers class Mistral - # Streaming methods of the Misttral API integration + # Streaming methods of the Mistral API integration module Streaming module_function