RubyLLM 1.4-1.5.1: Three Releases in Three Days

3 months ago 2

We shipped three releases in three days. Wednesday, Friday, and Friday again. Each one fixed real problems. Each one shipped as soon as it was ready.

1.4.0: The Structured Output Release (Wednesday)

One of the biggest problem with LLMs is getting them to return data in the format you need.

We all had code like this:

# The old struggle response = chat.ask("Return user data as JSON. ONLY JSON. NO MARKDOWN.") begin data = JSON.parse(response.content.gsub(/```json\n?/, '').gsub(/```\n?/, '')) rescue JSON::ParserError # Hope and pray end

Now with structured output:

# Define your schema with the RubyLLM::Schema DSL class PersonSchema < RubyLLM::Schema string :name integer :age array :skills, of: :string end # Get perfectly structured JSON every time chat = RubyLLM.chat.with_schema(PersonSchema) response = chat.ask("Generate a Ruby developer profile") # => {"name" => "Yukihiro", "age" => 59, "skills" => ["Ruby", "C", "Language Design"]}

No more regex. No more parsing. Just data structures that work.

Oh, and Daniel Friis released RubyLLM::Schema just for the occasion, but you can use any gem you want with RubyLLM, or even write your own JSON schema from scratch.

Rails Generators: From Zero to Chat

We didn’t have Rails generators before. Now we do:

rails generate ruby_llm:install

This creates everything you need:

  • Migrations
  • Models with acts_as_chat, acts_as_message, and acts_as_tool_call
  • A clean initializer

Your Chat model works like any Rails model:

chat = Chat.create!(model: "gpt-4.1-nano") response = chat.ask("Explain Ruby blocks") # Messages are automatically persisted with proper associations

From rails new to working chat in under 5 minutes.

New callback to see what your AI is doing:

chat.on_tool_call do |tool_call| puts "🔧 AI is calling: #{tool_call.name}" puts " Arguments: #{tool_call.arguments}" Rails.logger.info "[AI Tool] #{tool_call.name}: #{tool_call.arguments}" end chat.ask("What's the weather in Tokyo?").with_tools([weather_tool]) # => 🔧 AI is calling: get_weather # Arguments: {"location": "Tokyo"}

Essential for debugging and auditing AI behavior.

Direct Parameter Provider Access

Need that one weird parameter? Use with_params:

# OpenAI's JSON mode chat.with_params(response_format: { type: "json_object" }) .ask("List Ruby features as JSON")

No waiting for us to wrap every provider option.

Critical Bug Fixes and Other Improvements in 1.4.0

  • Anthropic multiple tool calls: Was only processing the first tool call, silently ignoring the rest
  • Streaming errors: Now handled properly in both Faraday V1 and V2
  • Test fixtures: Removed 60MB of unnecessary test data
  • Message ordering: Fixed race conditions in streaming responses
  • JRuby support: Now officially tested and supported
  • Direct access to raw responses: Get the raw responses from Faraday for debugging
  • GPUStack support: A production-ready alternative to Ollama

Full release notes for 1.4.0 available on GitHub.

1.5.0: Two New Providers (Friday)

Mistral AI

63 models from France, from tiny to massive:

RubyLLM.configure do |config| config.mistral_api_key = ENV['MISTRAL_API_KEY'] end # Efficient small model chat = RubyLLM.chat(model: 'ministral-3b-latest') # Their flagship model chat = RubyLLM.chat(model: 'mistral-large-latest') # Vision with Pixtral vision = RubyLLM.chat(model: 'pixtral-12b-latest') vision.ask("What's in this image?", with: "path/to/image.jpg")

Perplexity

Real-time web search meets LLMs:

RubyLLM.configure do |config| config.perplexity_api_key = ENV['PERPLEXITY_API_KEY'] end # Get current information with web search chat = RubyLLM.chat(model: 'sonar-pro') response = chat.ask("What are the latest Ruby 3.4 features?") # Searches the web and returns current information

Full release notes for 1.5.0 available on GitHub.

Rails Generator Fixes

  • Fixed migration order (Chats → Messages → Tool Calls)
  • Fixed PostgreSQL detection that was broken by namespace collision
  • PostgreSQL users now get jsonb columns instead of json

1.5.1: Quick Fixes (Also Friday)

Found issues Friday afternoon. Fixed them. Shipped them. That’s it.

Why make users wait through the weekend with broken code?

  • Fixed Mistral model capabilities (was a Hash, should be Array)
  • Fixed Google Imagen output modality
  • Updated to JRuby 10.0.1.0
  • Added JSON schema validation for model registry

Full release notes for 1.5.1 available on GitHub.

The Philosophy: Ship When Ready

Three days. Three releases. Each one made someone’s code work better.

We could have waited. We could have bundled everything into one release next week. But every moment we wait is a moment someone’s dealing with a bug we already fixed.

The structured output in 1.4.0? People needed that since before RubyLLM existed. The PostgreSQL fix in 1.5.0? Someone’s migrations were failing Thursday. The Mistral capability fix? Breaking someone’s code Friday morning.

When code is ready, you ship.

What You Can Build Now

With structured output and multiple providers, you can build real features:

# Extract structured data from any text class InvoiceSchema < RubyLLM::Schema string :invoice_number date :date float :total array :line_items do object do string :description float :amount end end end # Use Mistral for cost-effective extraction extractor = RubyLLM.chat(model: 'ministral-8b-latest') .with_schema(InvoiceSchema) invoice_data = extractor.ask("Extract invoice details from: #{pdf_text}") # Reliable data extraction at a fraction of GPT-4's cost # Use Perplexity for current information researcher = RubyLLM.chat(model: 'sonar-deep-research') market_data = researcher.ask("Current Ruby job market trends in 2025") # Real-time data, not training cutoff guesses

Use It

Full backward compatibility. Your 1.0 code still runs. These releases just made everything better.

Read Entire Article