Skip to content

Tool Calling

ActiveAgent supports multi-turn tool calling, allowing agents to:

  • Call tools (agent actions) during generation
  • Receive tool results as part of the conversation
  • Continue generation with tool results to provide final answers
  • Chain multiple tool calls to solve complex tasks

How Tool Calling Works

When an agent needs to use a tool during generation:

  1. The agent requests a tool call with specific parameters
  2. ActiveAgent executes the corresponding action method
  3. The tool result is added to the conversation as a "tool" message
  4. Generation continues automatically with the tool result
  5. The agent can make additional tool calls or provide a final response

Basic Example

Here's a simple calculator agent that can perform arithmetic operations:

ruby
class CalculatorAgent < ApplicationAgent
  generate_with :openai, model: "gpt-4o-mini", instructions: "You are a calculator assistant. Use the available tools to perform calculations."

  def add
    result = params[:a].to_f + params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def subtract
    result = params[:a].to_f - params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def multiply
    result = params[:a].to_f * params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def divide
    if params[:b].to_f == 0
      prompt(content_type: "text/plain") do |format|
        format.text { render plain: "Error: Division by zero" }
      end
    else
      result = params[:a].to_f / params[:b].to_f
      prompt(content_type: "text/plain") do |format|
        format.text { render plain: result.to_s }
      end
    end
  end

  def calculate_area
    width = params[:width].to_f
    height = params[:height].to_f
    result = width * height
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end
end

When asked to add numbers, the agent will:

ruby
message = "Add 2 and 3"
prompt = CalculatorAgent.with(message: message).prompt_context
response = prompt.generate_now

The conversation flow includes:

Response Example

activeagent/test/agents/multi_turn_tool_test.rb:12

ruby
# Response object
#<ActiveAgent::GenerationProvider::Response:0x3d68
  @message=#<ActiveAgent::ActionPrompt::Message:0x3d7c
    @action_id=nil,
    @action_name=nil,
    @action_requested=false,
    @charset="UTF-8",
    @content="The sum of 2 and 3 is 5.",
    @role=:assistant>
  @prompt=#<ActiveAgent::ActionPrompt::Prompt:0x3d90 ...>
  @content_type="text/plain"
  @raw_response={...}>

# Message content
response.message.content # => "The sum of 2 and 3 is 5."

Chaining Multiple Tool Calls

Agents can chain multiple tool calls to solve complex tasks:

ruby
message = "Calculate the area of a 5x10 rectangle, then multiply by 2"
prompt = CalculatorAgent.with(message: message).prompt_context
response = prompt.generate_now

This results in a sequence of tool calls:

Response Example

activeagent/test/agents/multi_turn_tool_test.rb:48

ruby
# Response object
#<ActiveAgent::GenerationProvider::Response:0x3de0
  @message=#<ActiveAgent::ActionPrompt::Message:0x3df4
    @action_id=nil,
    @action_name=nil,
    @action_requested=false,
    @charset="UTF-8",
    @content="The area of the 5x10 rectangle is 50 square units. When multiplied by 2, the result is 100 square units.",
    @role=:assistant>
  @prompt=#<ActiveAgent::ActionPrompt::Prompt:0x3e08 ...>
  @content_type="text/plain"
  @raw_response={...}>

# Message content
response.message.content # => "The area of the 5x10 rectangle is 50 square units. When multiplied by 2, the result is 100 square units."

Tool Response Formats

Tools can return different types of content:

Plain Text Responses

ruby
class CalculatorAgent < ApplicationAgent
  generate_with :openai, model: "gpt-4o-mini", instructions: "You are a calculator assistant. Use the available tools to perform calculations."

  def add
    result = params[:a].to_f + params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def subtract
    result = params[:a].to_f - params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def multiply
    result = params[:a].to_f * params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def divide
    if params[:b].to_f == 0
      prompt(content_type: "text/plain") do |format|
        format.text { render plain: "Error: Division by zero" }
      end
    else
      result = params[:a].to_f / params[:b].to_f
      prompt(content_type: "text/plain") do |format|
        format.text { render plain: result.to_s }
      end
    end
  end

  def calculate_area
    width = params[:width].to_f
    height = params[:height].to_f
    result = width * height
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end
end

HTML/View Responses

ruby
class WeatherAgent < ApplicationAgent
  generate_with :openai, model: "gpt-4o-mini", instructions: "You are a weather assistant. Use the available tools to provide weather information."

  def get_temperature
    # Simulate getting current temperature
    temperature = 22.5 # Celsius
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: temperature.to_s }
    end
  end

  def get_weather_report
    prompt(content_type: "text/html") do |format|
      format.html { render "weather_report" }
    end
  end

  def convert_temperature
    from_unit = params[:from] || "celsius"
    to_unit = params[:to] || "fahrenheit"
    value = params[:value].to_f

    result = if from_unit.downcase == "celsius" && to_unit.downcase == "fahrenheit"
      (value * 9/5) + 32
    elsif from_unit.downcase == "fahrenheit" && to_unit.downcase == "celsius"
      (value - 32) * 5/9
    else
      value
    end

    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.round(2).to_s }
    end
  end
end

The weather report view:

html
<div class="weather-report">
  <h2>Current Weather Report</h2>
  <div class="temperature">22.5°C</div>
  <div class="conditions">Partly Cloudy</div>
  <div class="humidity">65%</div>
  <div class="wind">10 km/h NW</div>
</div>

Error Handling

Tools should handle errors gracefully:

ruby
class CalculatorAgent < ApplicationAgent
  generate_with :openai, model: "gpt-4o-mini", instructions: "You are a calculator assistant. Use the available tools to perform calculations."

  def add
    result = params[:a].to_f + params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def subtract
    result = params[:a].to_f - params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def multiply
    result = params[:a].to_f * params[:b].to_f
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end

  def divide
    if params[:b].to_f == 0
      prompt(content_type: "text/plain") do |format|
        format.text { render plain: "Error: Division by zero" }
      end
    else
      result = params[:a].to_f / params[:b].to_f
      prompt(content_type: "text/plain") do |format|
        format.text { render plain: result.to_s }
      end
    end
  end

  def calculate_area
    width = params[:width].to_f
    height = params[:height].to_f
    result = width * height
    prompt(content_type: "text/plain") do |format|
      format.text { render plain: result.to_s }
    end
  end
end

When an error occurs, the agent receives the error message and can provide appropriate guidance to the user.

Tool Schemas

Define tool schemas using JSON views to describe parameters:

ruby
json.name action_name
json.description "Add two numbers together"
json.parameters do
  json.type "object"
  json.properties do
    json.a do
      json.type "number"
      json.description "First number"
    end
    json.b do
      json.type "number"
      json.description "Second number"
    end
  end
  json.required [ "a", "b" ]
end

This schema tells the AI model:

  • The tool name and description
  • Required and optional parameters
  • Parameter types and descriptions

Implementation Details

The tool calling flow is handled by the perform_generation method:

  1. Initial Generation: The agent receives the user message and generates a response
  2. Tool Request: If the response includes requested_actions, tools are called
  3. Tool Execution: Each action is executed via perform_action
  4. Result Handling: Tool results are added as "tool" messages
  5. Continuation: Generation continues with continue_generation
  6. Completion: The process repeats until no more tools are requested

This creates a natural conversation flow where the agent can gather information through tools before providing a final answer.