Lesson 16: Agentic Tool Use Patterns
What Is Tool Use?
Tool use (also called function calling) is the mechanism by which you give Claude the ability to take actions in the world — calling APIs, running code, querying databases, reading files. Instead of just generating text, Claude can request that a tool be run, receive the result, and continue its reasoning.
This is the foundation of agentic AI. Without tool use, Claude is a brilliant advisor. With tool use, Claude is an executor.
How the Tool Call Loop Works
The loop has four steps that repeat until Claude has a final answer:
- You define tools — You tell Claude what tools exist, what they do, and what inputs they accept
- Claude calls a tool — Claude generates a
tool_usecontent block with the tool name and arguments - You run the tool — Your code executes the actual function and gets the result
- Claude receives the result — You return the result as a
tool_resultmessage, and Claude continues
User message → Claude thinks → Claude calls tool_A
→ Your code runs tool_A → Returns result
→ Claude continues → Claude calls tool_B
→ Your code runs tool_B → Returns result
→ Claude produces final answer
Tool Definition Structure
Define tools using JSON Schema to describe their inputs:
tools = [
{
"name": "search_codebase",
"description": "Search for files or code patterns in the repository. "
"Use this when you need to find where something is defined or used.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search term or pattern to look for"
},
"file_extension": {
"type": "string",
"description": "Optional file extension filter, e.g. '.py' or '.ts'",
},
"case_sensitive": {
"type": "boolean",
"description": "Whether to match case. Defaults to false.",
"default": False
}
},
"required": ["query"]
}
}
]
A Complete Tool Use Example
import anthropic
import subprocess
client = anthropic.Anthropic()
def run_bash_command(command: str) -> str:
"""The actual tool implementation."""
result = subprocess.run(
command, shell=True, capture_output=True, text=True, timeout=30
)
return result.stdout or result.stderr
def agent_loop(user_message: str):
tools = [{
"name": "run_command",
"description": "Run a bash command and return the output.",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "The bash command to run"}
},
"required": ["command"]
}
}]
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
tools=tools,
messages=messages
)
# No tool call — we have our final answer
if response.stop_reason == "end_turn":
return response.content[0].text
# Claude wants to call a tool
if response.stop_reason == "tool_use":
# Add Claude's response to message history
messages.append({"role": "assistant", "content": response.content})
# Process each tool call
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_bash_command(block.input["command"])
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# Return results to Claude
messages.append({"role": "user", "content": tool_results})
Parallel Tool Calls
Claude can request multiple tool calls in a single response when they're independent of each other. This is a significant performance optimization — instead of sequential round trips, multiple operations happen in one pass.
# Claude might return multiple tool_use blocks at once:
# block 1: search_codebase(query="database connection")
# block 2: search_codebase(query="connection pool")
# block 3: read_file(path="README.md")
# Run all three in parallel:
import concurrent.futures
tool_calls = [b for b in response.content if b.type == "tool_use"]
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {
executor.submit(dispatch_tool, block): block
for block in tool_calls
}
results = {futures[f].id: f.result() for f in concurrent.futures.as_completed(futures)}
Tool Design Principles
Write descriptions for Claude, not for humans. The description is what Claude reads to decide when to use a tool. Be explicit about when the tool is appropriate and when it is not.
# Weak description
"description": "Get weather data"
# Strong description
"description": "Get current weather conditions and 24-hour forecast for a city. "
"Use this when the user asks about current weather or today's forecast. "
"Do NOT use this for historical weather data."
Type everything. Use JSON Schema types and enums to constrain inputs. This prevents Claude from passing invalid arguments.
Return structured errors. If a tool fails, return an error message that Claude can reason about:
try:
result = run_query(sql)
except DatabaseError as e:
return f"ERROR: {e}. The query failed. Check syntax and table names."
Safety: Confirmation for Destructive Actions
Never let Claude automatically execute destructive or irreversible actions. Build in a confirmation step for anything that deletes data, sends external messages, or makes payments.
DESTRUCTIVE_TOOLS = {"delete_file", "send_email", "charge_card", "drop_table"}
def dispatch_tool(tool_name: str, tool_input: dict) -> str:
if tool_name in DESTRUCTIVE_TOOLS:
confirmation = input(f"Claude wants to run {tool_name}({tool_input}). Allow? [y/N]: ")
if confirmation.lower() != 'y':
return "Action cancelled by user."
return TOOL_REGISTRY[tool_name](**tool_input)
When to Hand Back to a Human
Agentic loops should not run forever or through ambiguity. Design your agent to pause and ask when:
- A required input is missing or ambiguous
- A destructive action is about to be taken
- The task has gone more steps than expected
- An error has occurred more than once
Include this in your system prompt:
If you are uncertain about what the user wants, ask one clarifying question
before proceeding. Do not assume. Do not proceed with destructive actions
without explicit confirmation.
Key Takeaways
- Tool use enables Claude to take real-world actions, not just generate text
- The tool call loop: define → Claude calls → you run → return result → repeat
- Claude can make parallel tool calls — process them concurrently for speed
- Write tool descriptions for Claude, not for humans — be explicit about when to use each tool
- Always require confirmation for destructive or irreversible operations
- Build in human checkpoints for ambiguous situations or unexpected errors