Tool Use in AI Agents
Tools are the hands of an AI agent — they let it reach out and interact with the world: searching the web, reading files, running code, calling APIs, and writing data. Understanding how tool use works helps you build agents that are both capable and safe.
What Is a Tool?
In the context of AI agents, a tool is a callable function with a typed schema. The agent's LLM decides when and how to invoke a tool; the framework executes the actual code and returns the result back to the model as an Observation.
Examples of built-in OpenClaw tools: web_search, fetch_url, file_reader, file_writer, python_executor, sql_query.
Tool Schema: Name, Description, Parameters
Every tool is described to the LLM via a JSON Schema. This schema tells the model what the tool does and what arguments it expects:
{
"name": "send_email",
"description": "Send an email to a recipient. Use only when explicitly asked to send email.",
"parameters": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient email address"
},
"subject": { "type": "string" },
"body": { "type": "string" }
},
"required": ["to", "subject", "body"]
}
}Function Calling Flow
- The LLM receives the user goal + tool schemas in its context.
- It decides a tool is needed and emits a structured tool call (JSON with function name + args).
- OpenClaw validates the args against the schema, then calls the Python function.
- The result is formatted as an Observation and appended to the context.
- The LLM continues reasoning with the new information.
Defining a Custom Tool
from openclaw import tool, Agent
@tool(description="Get the current price of a cryptocurrency in USD")
def get_crypto_price(symbol: str) -> float:
"""symbol: uppercase ticker, e.g. BTC, ETH"""
import requests
resp = requests.get(
f"https://api.coingecko.com/api/v3/simple/price",
params={"ids": symbol.lower(), "vs_currencies": "usd"},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
# Return price or raise if symbol unknown
if symbol.lower() not in data:
raise ValueError(f"Unknown ticker: {symbol}")
return data[symbol.lower()]["usd"]
agent = Agent(
model="gpt-4o-mini",
tools=[get_crypto_price],
)
print(agent.run("What is the current price of Bitcoin in USD?"))Tool Chaining
The output of one tool automatically becomes the input context for the next decision. The model chains tools naturally:
Thought: I need to search for data, then save it.
Action: web_search("Python 3.13 new features")
Observation: Python 3.13 adds JIT compiler, improved REPL, ...
Thought: Got the data. Now save it to a file.
Action: file_writer("py313-features.txt", "JIT compiler, improved REPL, ...")
Observation: Written 1,240 bytes to py313-features.txt.Tool Safety: Sandboxing and Permissions
tools:
file_writer:
allowed_dirs: ["./output/"] # restrict write paths
max_file_size_kb: 512
python_executor:
sandbox: true # run in isolated subprocess
timeout_seconds: 30
network_access: false # no outbound calls from code
web_search:
max_results: 10
safe_search: true- Tools extend an agent's capabilities beyond pure text generation.
- Each tool has a name, description, and JSON Schema parameter definition.
- The LLM decides when to call a tool; OpenClaw handles execution and returns results.
- Custom tools are Python functions decorated with
@tool. - Always restrict tool permissions to the minimum necessary scope.
Tool Security and Sandboxing
Tools that execute code, read files, or make network requests require careful scoping to prevent unintended access. Follow the principle of least privilege when registering tools:
from openclaw import tool
from pathlib import Path
ALLOWED_DIR = Path("/data/reports")
@tool(description="Read a report file. Only files in /data/reports are accessible.")
def read_report(filename: str) -> str:
# Prevent path traversal
safe_path = (ALLOWED_DIR / filename).resolve()
if not str(safe_path).startswith(str(ALLOWED_DIR)):
raise PermissionError(f"Access denied: {filename}")
return safe_path.read_text()
Asynchronous and Long-Running Tools
Some tools (API calls, database queries, file processing) can take seconds to complete. OpenClaw supports async tools to prevent blocking the event loop:
import asyncio, httpx
from openclaw import tool
@tool(description="Fetch data from external API asynchronously.")
async def fetch_api_data(url: str, params: dict = {}) -> dict:
async with httpx.AsyncClient(timeout=30) as client:
response = await client.get(url, params=params)
response.raise_for_status()
return response.json()
For tools that take more than 60 seconds, use OpenClaw’s long-running tool pattern with status polling — see the Tool Creation guide.