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:
- The agent requests a tool call with specific parameters
- ActiveAgent executes the corresponding action method
- The tool result is added to the conversation as a "tool" message
- Generation continues automatically with the tool result
- 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:
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:
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
# 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:
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
# 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
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
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:
<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:
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:
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:
- Initial Generation: The agent receives the user message and generates a response
- Tool Request: If the response includes
requested_actions
, tools are called - Tool Execution: Each action is executed via
perform_action
- Result Handling: Tool results are added as "tool" messages
- Continuation: Generation continues with
continue_generation
- 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.