Using Tools with Agents¶
Tools let your agent take actions — call APIs, search databases, run calculations. This page covers how to attach tools to agents and how the tool-calling loop works. For deep dives on each tool type, see the Tools section.
Passing Tools to an Agent¶
Pass tools to an agent — the agent handles the entire tool-calling loop:
from fastaiagent import Agent, FunctionTool, RESTTool, LLMClient
agent = Agent(
name="assistant",
system_prompt="Use tools to answer questions.",
llm=LLMClient(provider="openai", model="gpt-4.1"),
tools=[
FunctionTool(name="calculate", fn=lambda expr: str(eval(expr))),
RESTTool(name="weather", url="https://api.weather.com/v1", method="GET"),
],
)
result = agent.run("What is 15% of 230, and what's the weather in Tokyo?")
# Agent calls both tools and combines results
How the Tool-Calling Loop Works¶
- Agent sends messages + tool schemas to the LLM
- LLM decides to call one or more tools (or respond directly)
- SDK executes the tools and sends results back to the LLM
- LLM generates a final response using the tool results
- This loop repeats up to
max_iterationstimes
By default the tool calls in a single turn run sequentially. See Parallel tool execution to opt into concurrency.
Parallel Tool Execution¶
When the LLM emits several tool calls in one turn, you can run them concurrently
instead of one after another. It's opt-in (default off) via AgentConfig:
from fastaiagent import Agent, AgentConfig, LLMClient
agent = Agent(
name="assistant",
llm=LLMClient(provider="openai", model="gpt-4o"),
tools=[get_weather, get_flights],
config=AgentConfig(
parallel_tools=True, # run a turn's tool calls concurrently (default: False)
max_parallel_tools=4, # cap concurrency with a semaphore (default: 4)
),
)
A turn's wall-clock becomes the slowest tool instead of the sum of all of
them. Results are re-ordered by call index, so message history and
result.tool_calls stay deterministic regardless of which tool finishes first.
When it stays sequential
For correctness, the parallel path is used only when none of these order/identity-sensitive features are engaged; otherwise the agent transparently falls back to sequential execution:
- a checkpointer is configured (per-tool checkpoints / crash recovery / HITL),
- middleware is attached (
wrap_toolsees calls in order), - managed governance is enrolled (
agent_idis set for policy/approvals).
Tools that share mutable RunContext state are your responsibility to make
concurrency-safe when you enable this.
The @tool Decorator¶
For quick tool creation:
from fastaiagent.tool import tool
@tool(name="calculate")
def calculate(expression: str) -> str:
"""Evaluate a math expression."""
return str(eval(expression))
# Use directly — it's a FunctionTool
result = calculate.execute({"expression": "2 + 2"})
Tool Types Overview¶
| Type | Use Case | Example |
|---|---|---|
FunctionTool |
Wrap any Python function | FunctionTool(name="calc", fn=my_func) |
RESTTool |
Call an HTTP API | RESTTool(name="weather", url="https://api.weather.com/v1", method="GET") |
MCPTool |
Connect to MCP server | MCPTool(name="search", server_url="http://localhost:3000") |
All three (and @tool) accept an optional replay_class (read_only / idempotent / side_effecting, default side_effecting) that controls inject-vs-execute during Agent Replay. See Tools → Replay safety.
ToolResult¶
Every tool execution returns a ToolResult:
| Field | Type | Description |
|---|---|---|
output |
Any |
The tool's return value |
error |
str \| None |
Error message if execution failed |
success |
bool |
True if no error |
metadata |
dict |
Extra info (e.g., HTTP status code for REST tools) |
result = tool.execute({"query": "test"})
if result.success:
print(result.output)
else:
print(f"Error: {result.error}")
Controlling Tool Usage¶
Configure how the agent uses tools via AgentConfig:
from fastaiagent import Agent, AgentConfig
agent = Agent(
name="configured-agent",
llm=LLMClient(provider="openai", model="gpt-4.1"),
tools=[my_tool],
config=AgentConfig(
max_iterations=5, # Max tool-calling loop iterations (default: 10)
tool_choice="auto", # "auto", "required", "none"
parallel_tools=False, # Run a turn's tool calls concurrently (default: False)
max_parallel_tools=4, # Concurrency cap when parallel_tools is on (default: 4)
),
)
| tool_choice | Behavior |
|---|---|
"auto" |
LLM decides whether to use tools (default) |
"required" |
LLM must call at least one tool |
"none" |
LLM cannot call tools |
Context & Dependency Injection¶
Tools that need runtime dependencies (DB connections, API clients, user sessions) can use RunContext for clean, type-safe dependency injection. See Context & Dependency Injection for the full guide.
Next Steps¶
- Tools Overview — Full tool type reference
- FunctionTool — Deep dive into Python function tools
- RESTTool — Deep dive into HTTP API tools
- MCPTool — Deep dive into MCP server tools
- Schema Drift Detection — Detect when tool responses change
- Agents — Core agent documentation