RubyLLM 1.0

I released RubyLLM 1.0 today.

When I started building Chat with Work, I wanted to write this:

chat = RubyLLM.chat
chat.ask "What's the best way to learn Ruby?"

And have it work regardless of model or provider. No provider-specific client classes, no different response formats, no ceremony. Just a conversation.

That’s RubyLLM. One API for OpenAI, Claude, Gemini, DeepSeek, and more.

What it looks like

chat = RubyLLM.chat
embedding = RubyLLM.embed("Ruby is elegant")
image = RubyLLM.paint("a sunset over mountains")

Switch models whenever you want. Don’t specify one and you get a sensible default:

chat = RubyLLM.chat(model: 'claude-3-5-sonnet')
chat.with_model('gpt-4o-mini')

Tool calling is a Ruby class, not JSON Schema gymnastics:

class Search < RubyLLM::Tool
  description "Searches our knowledge base"
  param :query, desc: "Search query"
  param :limit, type: :integer, desc: "Max results", required: false

  def execute(query:, limit: 5)
    Document.search(query).limit(limit).map(&:title)
  end
end

chat.with_tool(Search).ask "Find our product documentation"

Streaming works the same way everywhere:

chat.ask "Write a story about Ruby" do |chunk|
  print chunk.content
end

Token tracking is built in:

response = chat.ask "Explain Ruby modules"
puts "This cost #{response.input_tokens + response.output_tokens} tokens"

Rails is a first-class citizen:

class Chat < ApplicationRecord
  acts_as_chat
end

chat = Chat.create!(model_id: 'gemini-2.0-flash')
chat.ask "Hello"  # Everything persisted automatically

Vision, PDFs, and audio through the same interface:

chat.ask "What's in this image?", with: { image: "photo.jpg" }
chat.ask "Summarize this document", with: { pdf: "contract.pdf" }
chat.ask "Transcribe this recording", with: { audio: "meeting.wav" }

Dependencies: Faraday, Zeitwerk, and a tiny event parser. That’s it.

RubyLLM already powers Chat with Work in production. gem install ruby_llm and rubyllm.com has the rest.

Newsletter