name: rubyllm-tools description: | Function calling for RubyLLM. Use this skill when creating tools that let AI call your Ruby code, declaring parameters with the params DSL, using tools in chat, monitoring tool calls with callbacks, handling tool security, and implementing advanced patterns like halt, provider-specific parameters, and concurrent tool execution (v1.16+). allowed-tools: - Bash(bundle *) - Bash(bin/rails *)
RubyLLM Tools
Let AI call your Ruby methods. Connect to databases, APIs, or any external system.
Creating a Tool
class Weather < RubyLLM::Tool
description "Get current weather for a location"
params do
string :latitude, description: "Latitude coordinate"
string :longitude, description: "Longitude coordinate"
end
def execute(latitude:, longitude:)
url = "https://api.open-meteo.com/v1/forecast?latitude=#{latitude}&longitude=#{longitude}¤t=temperature_2m,wind_speed_10m"
response = Faraday.get(url)
JSON.parse(response.body).to_json
end
end
Parameter Declaration
Method Signature Inference (v1.15+)
Required and optional keyword arguments are automatically inferred as tool parameters — no params block needed:
class Weather < RubyLLM::Tool
description "Get weather for a location"
def execute(latitude:, longitude:, units: "celsius")
# latitude, longitude → required params
# units → optional param (has default)
end
end
params DSL (v1.9+)
class Scheduler < RubyLLM::Tool
description "Book a meeting"
params do
object :window, description: "Time window" do
string :start, description: "ISO8601 start"
string :finish, description: "ISO8601 end"
end
array :participants, of: :string, description: "Email addresses"
end
def execute(window:, participants:)
# Implementation
end
end
param Helper (Simple Tools)
class Distance < RubyLLM::Tool
description "Calculate distance between cities"
param :origin, desc: "Origin city name"
param :destination, desc: "Destination city name"
def execute(origin:, destination:)
# Implementation
end
end
Using Tools
# Single tool
chat = RubyLLM.chat.with_tool(Weather)
response = chat.ask "Weather in Berlin? (52.52, 13.40)"
# Multiple tools
chat.with_tools(Weather, Calculator, SearchDB)
# Tool choice
chat.with_tools(Weather, choice: :auto) # AI decides
chat.with_tools(Weather, choice: :required) # Must call tool
chat.with_tools(Weather, choice: :none) # Disable tools
# Control parallel calls
chat.with_tools(Weather, calls: :many) # Multiple calls (default)
chat.with_tools(Weather, calls: :one) # One call per response
Concurrent Tool Execution (v1.16+)
When the LLM requests multiple tools in one turn, run them in parallel:
# Per-chat concurrency mode
chat.with_tools(Weather, StockPrice, Currency, concurrency: :threads) # OS threads
chat.with_tools(Weather, StockPrice, concurrency: :fibers) # async gem (single-threaded)
chat.with_tools(Weather, StockPrice, concurrency: false) # sequential (override global)
# Global default in configure block
RubyLLM.configure { |c| c.tool_concurrency = true } # true = :threads
- Results stream back as tools complete but are gathered before returning to the model
- In Rails, each call runs inside the Rails executor — connection pool and
CurrentAttributesare safe
Tool Monitoring
# v1.15+ callbacks
chat = RubyLLM.chat
.with_tool(Weather)
.before_tool_call { |tc| puts "Calling: #{tc.name} args=#{tc.arguments}" }
.after_tool_result { |result| puts "Result: #{result}" }
chat.ask "Weather?"
Deprecated (v1.15, removed in v2.0):
on_tool_callandon_tool_result. Replace withbefore_tool_callandafter_tool_result.
Rich Content from Tools
class AnalyzeTool < RubyLLM::Tool
description "Analyze and return with visualization"
param :data, desc: "Data to analyze"
def execute(data:)
chart_path = generate_chart(data)
# Return with attachment AI can see
RubyLLM::Content.new("Analysis complete", [chart_path])
end
end
Halt Tool Continuation
Skip AI commentary after tool execution:
class SaveFileTool < RubyLLM::Tool
description "Save content to file"
param :path, desc: "File path"
param :content, desc: "Content"
def execute(path:, content:)
File.write(path, content)
halt "Saved to #{path}" # Return directly, no AI commentary
end
end
Provider-Specific Parameters (v1.9+)
class TodoTool < RubyLLM::Tool
description "Add task to TODO list"
params do
string :title
end
with_params cache_control: { type: "ephemeral" } # Anthropic cache
def execute(title:)
Todo.create!(title:)
end
end
Security
⚠️ Treat tool arguments as untrusted user input
class SafeTool < RubyLLM::Tool
param :input, desc: "User input"
def execute(input:)
# Validate
raise ArgumentError if input.length > 1000
raise ArgumentError if input.match?(/[<>;]/)
# NEVER use: eval, system, exec, `
end
end
Error Handling
class WeatherTool < RubyLLM::Tool
def execute(city:)
return { error: "City too short" } if city.length < 3
Faraday.get("https://api.weather.com/#{city}")
rescue Faraday::ConnectionFailed
{ error: "Weather service unavailable" }
end
end
Error Strategy
- Recoverable (bad params, API down): Return
{ error: "message" } - Unrecoverable (missing config, DB down): Raise exception