Using Tools with RubyLLM
Tools allow AI models to call your Ruby code to perform actions or retrieve information. This guide explains how to create and use tools with RubyLLM.
What Are Tools?
Tools (also known as “functions” or “plugins”) let AI models:
- Recognize when external functionality is needed
- Call your Ruby code with appropriate parameters
- Use the results to enhance their responses
Common use cases include:
- Retrieving real-time data
- Performing calculations
- Accessing databases
- Controlling external systems
Creating a Tool
Tools are defined as Ruby classes that inherit from RubyLLM::Tool
:
class Calculator < RubyLLM::Tool
description "Performs arithmetic calculations"
param :expression,
type: :string,
desc: "A mathematical expression to evaluate (e.g. '2 + 2')"
def execute(expression:)
eval(expression).to_s
rescue StandardError => e
"Error: #{e.message}"
end
end
Tool Components
Each tool has these key elements:
- Description - Explains what the tool does, helping the AI decide when to use it
- Parameters - Define the inputs the tool expects
- Execute Method - The code that runs when the tool is called
Parameter Definition
Parameters accept several options:
param :parameter_name,
type: :string, # Data type (:string, :integer, :boolean, :array, :object)
desc: "Description", # Description of what the parameter does
required: true # Whether the parameter is required (default: true)
Using Tools in Chat
To use a tool, attach it to a chat:
# Create the chat
chat = RubyLLM.chat
# Add a tool
chat.with_tool(Calculator)
# Now you can ask questions that might require calculation
response = chat.ask "What's 123 * 456?"
# => "Let me calculate that for you. 123 * 456 = 56088."
Multiple Tools
You can provide multiple tools to a single chat:
class Weather < RubyLLM::Tool
description "Gets current weather for a location"
param :location,
desc: "City name or zip code"
def execute(location:)
# Simulate weather lookup
"72°F and sunny in #{location}"
end
end
# Add multiple tools
chat = RubyLLM.chat
.with_tools(Calculator, Weather)
# Ask questions that might use either tool
chat.ask "What's the temperature in New York City?"
chat.ask "If it's 72°F in NYC and 54°F in Boston, what's the average?"
Custom Initialization
Tools can have custom initialization:
class DocumentSearch < RubyLLM::Tool
description "Searches documents by relevance"
param :query,
desc: "The search query"
param :limit,
type: :integer,
desc: "Maximum number of results",
required: false
def initialize(database)
@database = database
end
def execute(query:, limit: 5)
# Search in @database
@database.search(query, limit: limit)
end
end
# Initialize with dependencies
search_tool = DocumentSearch.new(MyDatabase)
chat.with_tool(search_tool)
The Tool Execution Flow
Here’s what happens when a tool is used:
- You ask a question
- The model decides a tool is needed
- The model selects the tool and provides arguments
- RubyLLM calls your tool’s
execute
method - The result is sent back to the model
- The model incorporates the result into its response
For example:
response = chat.ask "What's 123 squared plus 456?"
# Behind the scenes:
# 1. Model decides it needs to calculate
# 2. Model calls Calculator with expression: "123 * 123 + 456"
# 3. Tool returns "15,585"
# 4. Model incorporates this in its response
Debugging Tools
Enable debugging to see tool calls in action:
# Enable debug logging
ENV['RUBYLLM_DEBUG'] = 'true'
# Make a request
chat.ask "What's 15329 divided by 437?"
# Console output:
# D, -- RubyLLM: Tool calculator called with: {"expression"=>"15329 / 437"}
# D, -- RubyLLM: Tool calculator returned: "35.078719"
Error Handling
Tools can handle errors gracefully:
class Calculator < RubyLLM::Tool
description "Performs arithmetic calculations"
param :expression,
type: :string,
desc: "Math expression to evaluate"
def execute(expression:)
eval(expression).to_s
rescue StandardError => e
# Return error as a result
{ error: "Error calculating #{expression}: #{e.message}" }
end
end
# When there's an error, the model will receive and explain it
chat.ask "What's 1/0?"
# => "I tried to calculate 1/0, but there was an error: divided by 0"
Advanced Tool Parameters
Tools can have complex parameter types:
class DataAnalysis < RubyLLM::Tool
description "Analyzes numerical data"
param :data,
type: :array,
desc: "Array of numbers to analyze"
param :operations,
type: :object,
desc: "Analysis operations to perform",
required: false
def execute(data:, operations: {mean: true, median: false})
result = {}
result[:mean] = data.sum.to_f / data.size if operations[:mean]
result[:median] = calculate_median(data) if operations[:median]
result
end
private
def calculate_median(data)
sorted = data.sort
mid = sorted.size / 2
sorted.size.odd? ? sorted[mid] : (sorted[mid-1] + sorted[mid]) / 2.0
end
end
When to Use Tools
Tools are best for:
- External data retrieval - Getting real-time information like weather, prices, or database records
- Computation - When calculations are complex or involve large numbers
- System integration - Connecting to external APIs or services
- Data processing - Working with files, formatting data, or analyzing information
- Stateful operations - When you need to maintain state between calls