All the code you need for today's workshop. Copy-paste freely — this isn't a typing contest. Sections marked LIVE CODE are written together with the instructor. Follow along!
rails new task_pilot --css=tailwind
cd task_pilotbundle add ruby_llm --git "https://github.com/crmne/ruby_llm.git"bin/rails generate ruby_llm:installbin/rails db:migratebin/rails ruby_llm:load_modelsbin/rails credentials:editAdd openai_api_key: sk-your-key-here and save.
bin/rails generate ruby_llm:chat_uibin/devVisit http://localhost:3000/chats
bin/rails console# Pure RubyLLM — no database involved
chat = RubyLLM.chat
response = chat.ask("Hi! What's 2 + 2?")
puts response.content
# Follow-up — the AI remembers context
response = chat.ask("What's RubyLLM?")
puts response.content
# With Rails integration — persisted to database
chat = Chat.create!
chat.ask("Hello from the database!")
Chat.last
Message.last
Message.last.content
Message.last.roleType exit to leave the console.
Checkpoint:
git checkout part-1-complete
class ChatResponseJob < ApplicationJob
def perform(chat_id, content)
chat = Chat.find(chat_id)
chat.with_instructions(<<~PROMPT)
You are TaskPilot, an AI task management assistant. You help users
organize their work and life by managing their todo list.
Be concise, friendly, and action-oriented. When users describe tasks,
help them break things down into clear, actionable items.
Always respond in a helpful, encouraging tone.
PROMPT
chat.ask(content) do |chunk|
if chunk.content && !chunk.content.empty?
message = chat.messages.last
message.broadcast_append_chunk(chunk.content)
end
end
end
end<% system ||= local_assigns[:message] %>
<!-- system: <%= system.content %> -->Checkpoint:
git checkout part-2-complete
bin/rails generate ruby_llm:tool Weatherclass WeatherTool < RubyLLM::Tool
description "Get current weather"
param :latitude
param :longitude
def execute(latitude:, longitude:)
url = "https://api.open-meteo.com/v1/forecast?latitude=#{latitude}&longitude=#{longitude}¤t=temperature_2m,wind_speed_10m"
JSON.parse(Faraday.get(url).body)
end
endAdd after with_instructions:
chat.with_tool(WeatherTool)bin/rails generate scaffold Todo title:string description:text status:string priority:string category:string due_date:date
bin/rails db:migrateTodo.create!(title: "Buy groceries", description: "Milk, eggs, bread, and coffee", priority: "high", due_date: Date.today)
Todo.create!(title: "Schedule dentist appointment", priority: "medium", due_date: Date.today)
Todo.create!(title: "Read RubyLLM docs", description: "Focus on tools and agents", priority: "low", due_date: 3.days.from_now)
Todo.create!(title: "Prepare workshop demo", description: "Test all code examples", priority: "high", due_date: 1.day.from_now)
Todo.create!(title: "Reply to Sarah's email", priority: "medium")bin/rails db:seedbin/rails generate ruby_llm:tool ListTodosLIVE CODE — Follow the instructor to implement
app/tools/list_todos_tool.rb
chat.with_instructions(<<~PROMPT)
You are TaskPilot, an AI task management assistant. You help users
organize their work and life by managing their todo list.
Be concise, friendly, and action-oriented. When users describe tasks,
help them break things down into clear, actionable items.
Always respond in a helpful, encouraging tone.
Today's date is #{Date.today} (#{Date.today.strftime("%A")}).
PROMPT
chat.with_tools(WeatherTool, ListTodosTool)bin/rails generate ruby_llm:tool CreateTodoLIVE CODE — Follow the instructor to implement
app/tools/create_todo_tool.rb
class CreateTodoTool < RubyLLM::Tool
description "Creates a new todo item."
params do
string :title
string :description, required: false
string :priority, enum: %w[low medium high], required: false
string :due_date, format: "date", required: false
end
def execute(title:, description: nil, priority: "medium", due_date: nil)
todo = Todo.create!(title:, description:, priority:, due_date:, status: "pending")
"Created todo #{todo.to_json}"
end
endbin/rails generate ruby_llm:tool CompleteTodoclass CompleteTodoTool < RubyLLM::Tool
description "Marks a todo as completed"
param :id
def execute(id:)
todo = Todo.find_by(id: id)
if todo.nil?
"No todo found with id #{id}"
else
todo.update!(status: "completed")
"Completed todo ##{todo.id}: '#{todo.title}'"
end
end
endchat.with_tools(WeatherTool, ListTodosTool, CreateTodoTool, CompleteTodoTool)Checkpoint:
git checkout part-3-complete
bin/rails generate ruby_llm:agent TaskPilotYou are TaskPilot, an AI task management assistant. You help users
organize their work and life by managing their todo list.
Be concise, friendly, and action-oriented. When users describe tasks,
help them break things down into clear, actionable items.
Always respond in a helpful, encouraging tone.
Today's date is <%= Date.today %> (<%= Date.today.strftime("%A") %>).class TaskPilotAgent < RubyLLM::Agent
chat_model Chat
instructions
tools WeatherTool, ListTodosTool, CreateTodoTool, CompleteTodoTool
endclass ChatResponseJob < ApplicationJob
def perform(chat_id, content)
agent = TaskPilotAgent.find(chat_id)
agent.ask(content) do |chunk|
if chunk.content && !chunk.content.empty?
message = agent.messages.last
message.broadcast_append_chunk(chunk.content)
end
end
end
endLIVE CODE / DEMO — Follow the instructor. Watch steps 10-12 (no typing).
bin/rails generate ruby_llm:agent WebSearchclass WebSearchAgent < RubyLLM::Agent
chat_model "Chat"
model "gpt-5-search-api"
endbin/rails generate ruby_llm:tool WebSearchclass WebSearchTool < RubyLLM::Tool
description "Searches the web for current information. Use this when the user " \
"needs up-to-date facts, recent news, documentation, or anything " \
"that requires real-time web access."
param :query, desc: "The search query"
def execute(query:)
WebSearchAgent.create.ask(query).content
end
endclass TaskPilotAgent < RubyLLM::Agent
chat_model Chat
instructions
tools WeatherTool, ListTodosTool, CreateTodoTool, CompleteTodoTool, WebSearchTool
endCheckpoint:
git checkout part-4-complete
class SequentialPipelineJob < ApplicationJob
def perform(chat_id, content)
agent = TaskPilotAgent.find(chat_id)
# Stage 1: Research (creates its own visible chat)
research = WebSearchAgent.create.ask(content).content
# Stage 2: Act on research (uses the user's chat, with streaming)
prompt = <<~PROMPT
Based on this research:
#{research}
Now help the user with: #{content}
PROMPT
agent.ask(prompt) do |chunk|
if chunk.content && !chunk.content.empty?
message = agent.messages.last
message.broadcast_append_chunk(chunk.content)
end
end
end
endTo try it, change ChatResponseJob to SequentialPipelineJob in both messages_controller.rb and chats_controller.rb.
bundle add async#!/usr/bin/env ruby
require_relative "../config/environment"
require "async"
class SecurityReviewAgent < RubyLLM::Agent
instructions "Given code, review it for security issues."
end
class PerformanceReviewAgent < RubyLLM::Agent
instructions "Given code, review it for performance issues."
end
class StyleReviewAgent < RubyLLM::Agent
instructions "Given code, review style against Ruby conventions."
end
class ReviewSynthesizerAgent < RubyLLM::Agent
instructions "Given multiple code review reports, summarize prioritized findings."
end
code = ARGV.join(" ").presence || "def calculate(x); x * 2; end"
puts "Reviewing: #{code}\n\n"
result = Async do |task|
security = task.async do
puts "Security review starting..."
result = SecurityReviewAgent.new.ask(code).content
puts "Security review done."
result
end
performance = task.async do
puts "Performance review starting..."
result = PerformanceReviewAgent.new.ask(code).content
puts "Performance review done."
result
end
style = task.async do
puts "Style review starting..."
result = StyleReviewAgent.new.ask(code).content
puts "Style review done."
result
end
security = security.wait
performance = performance.wait
style = style.wait
puts "Synthesizing..."
ReviewSynthesizerAgent.new.ask(
"security: #{security}\n\n" \
"performance: #{performance}\n\n" \
"style: #{style}"
).content
end.wait
puts "\n#{result}"ruby bin/code_review.rb "def calculate(x); x * 2; end"If you fall behind, jump to any checkpoint:
git checkout part-1-complete # After Part 1: working chat UI
git checkout part-2-complete # After Part 2: system prompts
git checkout part-3-complete # After Part 3: tools
git checkout part-4-complete # After Part 4: agents, web search
# After switching:
bundle install
bin/rails db:migrate
bin/dev- Docs: rubyllm.com
- GitHub: github.com/crmne/ruby_llm
- RubyGems:
gem install ruby_llm