
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
, andacts_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.
Tool Call Transparency
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 ofjson
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
gem 'ruby_llm', '~> 1.5'
Full backward compatibility. Your 1.0 code still runs. These releases just made everything better.