AI Agents Tool Use: Building Software That Takes Actions, Not Just Answers Questions
A chatbot answers questions. An agent takes actions. When you tell an agent “find all overdue invoices and send reminder emails to those customers,” it queries your database, identifies the overdue accounts, drafts personalized emails, and sends them — autonomously. AI agents tool use is the pattern that makes this possible, and understanding it is essential for building the next generation of AI applications.
How Tool Use Works — The Mental Model
When you give an LLM access to tools, you’re not giving it the ability to execute code directly. Instead, you’re teaching it to express its intent in a structured format (JSON function calls) that your application code then executes. The flow is:
- User asks: “How many orders were placed today?”
- Model decides it needs the query_database tool and generates:
{"name": "query_database", "input": {"query": "SELECT COUNT(*) FROM orders WHERE date = CURRENT_DATE"}} - Your code executes the query and returns the result: “42”
- Model uses the result to respond: “There were 42 orders placed today.”
The model never touches your database directly. Your code is the intermediary that validates, executes, and returns results. This architecture means you control exactly what the agent can do, and every action goes through your security layer.
Building a Practical Agent with Claude
import anthropic
import json
from datetime import datetime
client = anthropic.Anthropic()
# Define tools the agent can use
# Each tool has a name, description, and JSON schema for parameters
tools = [
{
"name": "query_orders",
"description": "Query the orders database. Returns order data. Use for questions about orders, revenue, customers.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SQL SELECT query (read-only). Available tables: orders, customers, products."
},
"limit": {
"type": "integer",
"description": "Maximum rows to return",
"default": 100
}
},
"required": ["query"]
}
},
{
"name": "send_email",
"description": "Send an email to a customer. Use for notifications, reminders, and follow-ups.",
"input_schema": {
"type": "object",
"properties": {
"to": {"type": "string", "description": "Recipient email address"},
"subject": {"type": "string", "description": "Email subject line"},
"body": {"type": "string", "description": "Email body (plain text)"}
},
"required": ["to", "subject", "body"]
}
},
{
"name": "create_ticket",
"description": "Create a support ticket in the ticketing system.",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"description": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
"assignee": {"type": "string", "description": "Team or person to assign to"}
},
"required": ["title", "description", "priority"]
}
}
]
def execute_tool(name: str, input_data: dict) -> str:
"""Execute a tool call and return the result as a string"""
if name == "query_orders":
# CRITICAL: Validate the query is read-only
query = input_data["query"].strip().upper()
if not query.startswith("SELECT"):
return "Error: Only SELECT queries are allowed"
if any(kw in query for kw in ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"]):
return "Error: Destructive queries are not allowed"
# Execute against your actual database
results = db.execute(input_data["query"], limit=input_data.get("limit", 100))
return json.dumps(results, default=str)
elif name == "send_email":
# Add to email queue (don't send directly — allow review)
email_id = email_queue.add(
to=input_data["to"],
subject=input_data["subject"],
body=input_data["body"],
requires_approval=True # Human reviews before sending
)
return f"Email queued for review (ID: {email_id})"
elif name == "create_ticket":
ticket = ticketing.create(
title=input_data["title"],
description=input_data["description"],
priority=input_data["priority"],
assignee=input_data.get("assignee")
)
return f"Ticket created: {ticket.id} ({ticket.url})"
return f"Unknown tool: {name}"
def run_agent(user_request: str, max_iterations: int = 10) -> str:
"""Run the agent loop until it produces a final response or hits the limit"""
messages = [{"role": "user", "content": user_request}]
iterations = 0
while iterations < max_iterations:
iterations += 1
response = client.messages.create(
model="claude-sonnet-4-6-20250514",
max_tokens=4096,
system="""You are a business operations assistant with access to the orders
database, email system, and ticketing system. Use tools to gather information
and take actions. Always verify data before taking actions.
Be concise in your responses.""",
tools=tools,
messages=messages
)
# If the model is done talking (no tool calls), return its response
if response.stop_reason == "end_turn":
return "".join(
block.text for block in response.content if hasattr(block, "text")
)
# Process tool calls
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f" Tool: {block.name}({json.dumps(block.input)[:100]}...)")
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
messages.append({"role": "user", "content": tool_results})
return "Agent reached maximum iterations without completing the task."
# Example usage:
result = run_agent(
"Find all orders over $1000 from the past week that haven't been shipped yet. "
"Create a high-priority ticket for the shipping team with the details."
)
print(result)AI Agents Tool Use: Safety and Guardrails
An unsupervised agent with database write access and email sending capability is a security nightmare. Production agents need layered safety controls:
Tool-level permissions. Classify tools by risk: read-only tools (database queries, file reading) can execute freely. Write tools (sending emails, creating tickets) require human approval or operate in a sandbox. Destructive tools (deleting data, modifying permissions) should never be available to agents without explicit human authorization per action.
Input validation. Every tool call must be validated before execution. SQL queries must be read-only. Email recipients must be on an allowed list. File paths must be within allowed directories. Never trust the model's output as safe — validate it like you'd validate user input.
Output sanitization. Before returning tool results to the model, strip sensitive data. Database query results should have PII columns removed. API responses should have tokens and secrets redacted. The model doesn't need to see sensitive data to answer most questions about it.
Rate limiting and cost controls. Set maximum iterations per request (prevent infinite loops), maximum tokens per conversation (prevent runaway costs), and maximum tool calls per minute (prevent accidental DoS of your own systems).
Audit logging. Every tool call, its parameters, and its result should be logged. When something goes wrong (and it will), you need to reconstruct exactly what the agent did and why.
Common Pitfalls and How to Avoid Them
Pitfall 1: Too many tools. If you give the model 50 tools, it will struggle to choose the right one and often pick wrong tools. Keep the tool list focused — 5-10 tools for a specific domain. If you need more, use a routing layer that selects the relevant tool subset based on the query.
Pitfall 2: Vague tool descriptions. "Query the database" is a bad description. "Query the orders database for information about customer orders, products, and revenue. Available tables: orders(id, customer_id, total, status, created_at), products(id, name, price, category)." is a good one. The model uses these descriptions to decide which tool to use and how to call it.
Pitfall 3: Not handling errors gracefully. When a tool call fails, return a clear error message so the model can try a different approach. "Error: Table 'customers' does not exist. Available tables: orders, products, users" is actionable. "Error: 500 Internal Server Error" is not.
Pitfall 4: No iteration limit. Without a maximum iteration count, a confused agent will loop forever, calling tools repeatedly. Always set a max_iterations limit and return gracefully when exceeded.
Related Reading:
Resources:
In conclusion, AI agents tool use is the bridge between AI that talks and AI that does. Start with read-only tools, add write tools with human approval, implement comprehensive safety controls, and monitor everything. The agents you build today are the autonomous systems your organization will depend on tomorrow.