class RubyLLM::Tool
A Tool is an action an AI model can call during a chat. Subclasses describe themselves with ::description, declare their arguments, and implement execute:
class Weather < RubyLLM::Tool description "Gets current weather for a location" def execute(latitude:, longitude:) response = Faraday.get "https://api.open-meteo.com/v1/forecast", latitude: latitude, longitude: longitude, current: "temperature_2m,wind_speed_10m" JSON.parse(response.body) end end chat.with_tools(Weather).ask "What's the weather in Berlin?"
When no parameters are declared, the argument schema is inferred from execute’s keyword arguments: required keywords become required string parameters and optional keywords become optional ones. Use ::parameter or ::parameters when arguments need explicit types, descriptions, or structure.
Public Class Methods
Source
# File lib/ruby_llm/tool.rb, line 233 def defer @deferred = true end
Keeps this tool’s definition out of the model’s context until the provider’s tool search loads it. Pass +defer: false+ to Chat#with_tools to offer it up front in one chat. See Chat#deferred_tools.
class DeepResearch < RubyLLM::Tool description "Runs a multi-step web investigation" defer end
# File lib/ruby_llm/tool.rb, line 100 def description(text = nil) return @description unless text @description = text end
Sets the description the model sees for this tool, or returns the current description when called without an argument.
class Weather < RubyLLM::Tool description "Gets current weather for a location" end
# File lib/ruby_llm/tool.rb, line 199 def max_run_time(seconds = nil) @max_run_time = seconds if seconds @max_run_time end
Sets how long a run of this tool may take before a persisted chat treats it as crashed, overriding Configuration#max_run_time. Another process that resumes the chat after that time runs the call again. Without an argument, returns the declared value, or nil.
class GenerateReport < RubyLLM::Tool max_run_time 2.hours end
Source
# File lib/ruby_llm/tool.rb, line 117 def parameter(name, **) declared_parameters[name] = Parameter.new(name, **) end
Declares a parameter for the tool. options accepts type: (defaults to 'string'), description:, and required: (defaults to true).
class Distance < RubyLLM::Tool description "Calculates distance between two cities" parameter :origin, description: "Origin city name" parameter :destination, description: "Destination city name" parameter :units, type: :string, description: "metric or imperial", required: false end
# File lib/ruby_llm/tool.rb, line 142 def parameters(schema = nil, &block) if schema.nil? && block.nil? raise ArgumentError, 'parameters requires a schema or a block; declare single arguments with parameter' end @parameters_schema_definition = SchemaDefinition.new(schema:, block:) self end
Sets the JSON Schema for the tool’s arguments. Accepts a schema hash, a Schematist::Schema class or instance, or a block written in the schematist DSL. Returns self.
class Scheduler < RubyLLM::Tool description "Books a meeting" parameters do object :window, description: "Time window to reserve" do string :start, description: "ISO8601 start time" string :finish, description: "ISO8601 end time" end array :participants, of: :string end end
Raises ArgumentError when called without a schema or a block.
# File lib/ruby_llm/tool.rb, line 215 def provider_options(options = (get = true)) return @provider_options ||= {} if get raise ArgumentError, 'provider_options does not accept nil' if options.nil? @provider_options = options.to_h self end
Sets provider-specific metadata, such as Anthropic’s cache_control hints, merged verbatim into the tool payload sent to the provider. Without an argument, returns the current options.
provider_options cache_control: { type: "ephemeral" }
Raises ArgumentError if options is nil.
# File lib/ruby_llm/tool.rb, line 177 def requires_approval(&resolver) @requires_approval = true @approval_resolver = resolver end
Declares that this tool must be approved before it executes. The conversation loop pauses the tool call until a decision is recorded with Chat#approve or Chat#deny, so Chat#complete returns cleanly and can be called again once the decision exists. In Rails the decision persists on the tool call record and survives process restarts.
class IssueRefund < RubyLLM::Tool requires_approval def execute(order_id:) Refunds.issue!(order_id) end end
Pass a block to resolve the decision yourself instead of using the recorded one. The block receives the ToolCall and returns true to execute, false to deny, or nil while the decision is pending.
The block never runs at class definition. The loop consults it whenever it needs the decision, which can be several times while the call is pending, including after a crashed job resumes, so write it as an idempotent read. If it also creates the approval request, make that a find-or-create.
requires_approval { |tool_call| Approvals.status(tool_call.id) }
Source
# File lib/ruby_llm/tool.rb, line 79 def tool_name return @tool_name if @tool_name normalized = name.to_s.dup.force_encoding('UTF-8').unicode_normalize(:nfkd) ascii_name = normalized.encode('ASCII', replace: '').gsub(/[^a-zA-Z0-9_-]/, '-') derived = Support::Utils.underscore(ascii_name).delete_suffix('_tool') @tool_name = derived if name derived end
Returns the name the model calls this tool by, derived from the class name: underscored, reduced to ASCII, with a trailing “_tool” removed. Override this method to choose a different name.
WeatherLookup.tool_name # => "weather_lookup"
Public Instance Methods
# File lib/ruby_llm/tool.rb, line 340 def call(tool_call: nil, **arguments) normalized_args = arguments.transform_keys(&:to_sym) validation_error = validate_keyword_arguments(normalized_args) return { error: "Invalid tool arguments: #{validation_error}" } if validation_error RubyLLM.logger.debug { "Tool #{name} called with: #{normalized_args.inspect}" } normalized_args[TOOL_CALL_KEYWORD] = tool_call if execute_accepts_tool_call? result = execute(**normalized_args) RubyLLM.logger.debug { "Tool #{name} returned: #{result.inspect}" } result end
Source
# File lib/ruby_llm/tool.rb, line 283 def description self.class.description end
Returns the tool description declared on the class with ::description.
Source
# File lib/ruby_llm/tool.rb, line 367 def execute(...) raise NotImplementedError, 'Subclasses must implement #execute' end
Runs the tool with the arguments chosen by the model. Subclasses must implement this method; the base implementation raises NotImplementedError. The return value is sent back to the model. Return a Hash like { error: "..." } to report a recoverable failure.
Declare an optional tool_call: keyword to receive the ToolCall being executed. The keyword is reserved: it never appears in the tool’s argument schema and is filled in by RubyLLM, not by the model.
def execute(query:, tool_call: nil) AuditLog.create!(tool_call_id: tool_call&.id) Search.run(query) end
Source
# File lib/ruby_llm/tool.rb, line 300 def max_run_time self.class.max_run_time end
Returns the run time declared on the class with ::max_run_time, or nil to use Configuration#max_run_time.
Source
# File lib/ruby_llm/tool.rb, line 278 def name self.class.tool_name end
Returns the name the model calls this tool by, delegating to ::tool_name. Override either one to choose a different name.
WeatherLookup.new.name # => "weather_lookup"
Source
# File lib/ruby_llm/tool.rb, line 318 def parameters_schema return @parameters_schema if defined?(@parameters_schema) @parameters_schema = begin definition = self.class.parameters_schema_definition if definition&.present? definition.json_schema elsif declared_parameters.any? SchemaDefinition.from_parameters(declared_parameters)&.json_schema else SchemaDefinition.from_parameters(inferred_parameters, allow_empty: true)&.json_schema end end end
Returns the JSON Schema for the tool’s arguments, whether declared explicitly or inferred from the execute signature.
# File lib/ruby_llm/tool.rb, line 385 def progress(message = nil, value: nil, total: nil) Support::ProgressReporter.report(Progress.new(value:, total:, message:)) nil end
Reports what the tool is doing while execute runs, so an application can show it before the result arrives. Chat#after_tool_progress callbacks receive it as a Progress. Pass value: and total: when you can count the work. Outside a chat, the report goes nowhere.
def execute(url:) progress "Downloading #{File.basename(url)}" pages = Scanner.pages(url) pages.each_with_index.map do |page, index| progress "Reading page #{index + 1} of #{pages.size}", value: index + 1, total: pages.size page.text end.join("\n") end
Source
# File lib/ruby_llm/tool.rb, line 309 def provider_options self.class.provider_options end
Returns the provider-specific tool metadata declared on the class.
# File lib/ruby_llm/tool.rb, line 290 def requires_approval?(tool_call = nil) # rubocop:disable Lint/UnusedMethodArgument self.class.requires_approval? end
Returns whether tool_call must be approved before it runs: whether this tool was declared with ::requires_approval. A subclass may override it to decide from the call’s arguments.