---
title: "AI Agents"
track: "practical-ai"
difficulty: "intermediate"
estimatedTime: "30 min"
prerequisites: ["practical-ai/rag"]
tags: ["agents", "tool-use", "function-calling", "react", "multi-agent", "planning"]
---
## Introduction
An AI agent is a system that uses a language model as its reasoning engine to decide what actions to take, execute those actions, observe the results, and iterate until a task is complete. While a standard LLM call is a single input-output exchange, an agent operates in a **loop** — thinking, acting, and adapting.
Agents represent a fundamental shift from using LLMs as text generators to using them as autonomous decision-makers. They can browse the web, write and execute code, query databases, send emails, and orchestrate complex multi-step workflows.
## The Agent Loop
Every agent follows the same basic cycle:
```
Observe → Think → Act → Observe → Think → Act → ... → Done
```
1. **Observe** — receive the current state (user query, tool outputs, environment)
2. **Think** — the LLM reasons about what to do next
3. **Act** — execute a tool or produce a final response
4. **Repeat** until the task is complete
```python
def agent_loop(user_query, tools, max_steps=10):
"""Simplified agent loop."""
messages = [{"role": "user", "content": user_query}]
for step in range(max_steps):
# Think: ask the LLM what to do
response = call_llm(messages, tools=tools)
# Check if the agent wants to use a tool
if response.tool_use:
# Act: execute the tool
tool_name = response.tool_use.name
tool_input = response.tool_use.input
tool_result = execute_tool(tool_name, tool_input)
# Observe: add tool result to context
messages.append({"role": "assistant", "content": response})
messages.append({
"role": "user",
"content": [{"type": "tool_result", "content": tool_result}],
})
else:
# Done: the agent produced a final response
return response.text
return "Max steps reached without completing the task."
```
## Tool Use and Function Calling
Tools are the agent's hands. They bridge the gap between the LLM's reasoning ability and the real world. Modern APIs support **function calling** — you define tools with schemas, and the model decides when and how to call them.
### Defining Tools
```python
import anthropic
client = anthropic.Anthropic()
# Define tools the agent can use
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g., 'San Francisco'",
},
},
"required": ["city"],
},
},
{
"name": "search_web",
"description": "Search the web for current information.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query",
},
},
"required": ["query"],
},
},
]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=[{
"role": "user",
"content": "What's the weather like in Tokyo today?",
}],
)
# The model will respond with a tool_use block
for block in response.content:
if block.type == "tool_use":
print(f"Tool: {block.name}")
print(f"Input: {block.input}")
# Tool: get_weather
# Input: {"city": "Tokyo"}
```
### Executing Tools and Continuing
```python
def run_agent(user_message, tools, tool_implementations):
"""Complete agent with tool execution."""
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
messages=messages,
)
# Check if we need to execute tools
tool_uses = [b for b in response.content if b.type == "tool_use"]
if not tool_uses:
# No tool calls — return the text response
return response.content[0].text
# Execute each tool and collect results
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for tool_use in tool_uses:
# Run the actual tool
result = tool_implementations[tool_use.name](tool_use.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": str(result),
})
messages.append({"role": "user", "content": tool_results})
```
The quality of your tool descriptions is critical. The LLM decides which tool to use based on the name and description. Vague descriptions lead to wrong tool selections. Be specific: instead of "search for information," write "Search a product catalog by name, category, or price range. Returns up to 10 matching products."
## The ReAct Pattern
**ReAct** (Reasoning + Acting) is a foundational pattern where the agent alternates between reasoning (thinking through the problem) and acting (using tools). The reasoning step is explicit in the output.
```
Question: What is the population of the capital of France?
Thought: I need to find the capital of France first, then its population.
Action: search_web("capital of France")
Observation: The capital of France is Paris.
Thought: Now I need the population of Paris.
Action: search_web("population of Paris 2024")
Observation: The population of Paris is approximately 2.1 million.
Thought: I now have the answer.
Answer: The population of the capital of France (Paris) is approximately
2.1 million people.
```
The key insight is that **explicit reasoning improves tool selection and task decomposition**. Without the thought step, agents make more errors in choosing which tool to use and when.
## Planning and Reasoning
More complex agents use structured planning before executing:
### Plan-and-Execute
```python
system_prompt = """You are a research agent. When given a question:
1. PLAN: Break the question into sub-tasks
2. EXECUTE: Complete each sub-task using available tools
3. SYNTHESIZE: Combine results into a final answer
Always start by writing your plan before taking any actions."""
```
### Self-Reflection
Agents can evaluate their own outputs and correct mistakes:
```python
reflection_prompt = """Review your previous answer for:
1. Factual accuracy — are all claims supported by the retrieved data?
2. Completeness — did you address all parts of the question?
3. Coherence — does the answer flow logically?
If you find issues, fix them. If the answer is good, confirm it."""
```
## Building a Simple Agent
Here is a complete, working agent that can do math and look up information:
```python
import anthropic
import json
import math
client = anthropic.Anthropic()
# Tool implementations
def calculate(expression):
"""Safely evaluate a math expression."""
allowed = {
"abs": abs, "round": round,
"min": min, "max": max,
"sqrt": math.sqrt, "pow": pow,
"pi": math.pi, "e": math.e,
}
try:
return str(eval(expression, {"__builtins__": {}}, allowed))
except Exception as e:
return f"Error: {e}"
def get_current_date():
"""Get today's date."""
from datetime import date
return date.today().isoformat()
# Tool definitions for the API
tools = [
{
"name": "calculate",
"description": "Evaluate a mathematical expression. Supports +, -, *, /, sqrt, pow, pi, e.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression, e.g. 'sqrt(144) + pow(2, 10)'",
},
},
"required": ["expression"],
},
},
{
"name": "get_current_date",
"description": "Get today's date in ISO format (YYYY-MM-DD).",
"input_schema": {
"type": "object",
"properties": {},
},
},
]
tool_functions = {
"calculate": lambda args: calculate(args["expression"]),
"get_current_date": lambda args: get_current_date(),
}
def run_agent(question):
"""Run the agent to answer a question."""
messages = [{"role": "user", "content": question}]
print(f"\nQuestion: {question}")
print("-" * 50)
for step in range(5):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system="You are a helpful assistant with access to tools. Use them when needed.",
tools=tools,
messages=messages,
)
tool_uses = [b for b in response.content if b.type == "tool_use"]
text_blocks = [b for b in response.content if b.type == "text"]
if text_blocks:
for block in text_blocks:
print(f"Agent: {block.text}")
if not tool_uses:
return
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for tool_use in tool_uses:
print(f" [Tool: {tool_use.name}({json.dumps(tool_use.input)})]")
result = tool_functions[tool_use.name](tool_use.input)
print(f" [Result: {result}]")
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
# Try it
run_agent("What is the square root of 729 plus 2 to the power of 8?")
run_agent("What is today's date, and how many days until the end of the year?")
```
## Multi-Agent Systems
Complex tasks benefit from multiple specialized agents working together:
### Common Patterns
1. **Supervisor** — one agent delegates tasks to specialist agents
2. **Pipeline** — agents pass output sequentially (researcher -> writer -> reviewer)
3. **Debate** — multiple agents propose solutions and critique each other
4. **Swarm** — agents independently work on subtasks and merge results
```python
# Simplified multi-agent: Researcher + Writer
def research_agent(topic):
"""Agent that gathers information."""
return run_agent_with_tools(
f"Research this topic thoroughly: {topic}",
tools=[search_web, read_page],
)
def writer_agent(research, style):
"""Agent that writes based on research."""
return run_agent_with_tools(
f"Write a {style} article based on this research:\n{research}",
tools=[], # Writer doesn't need tools
)
# Pipeline execution
research = research_agent("quantum computing breakthroughs 2024")
article = writer_agent(research, style="beginner-friendly blog post")
```
Start with a single agent and simple tools. Multi-agent systems add complexity — coordinating agents, handling failures, and managing shared state is significantly harder than a single agent loop. Only add agents when a single agent genuinely cannot handle the task.
## Key Takeaways
- Agents use LLMs as reasoning engines in an observe-think-act loop
- Tool use (function calling) lets agents interact with the real world
- The ReAct pattern improves accuracy by alternating explicit reasoning with actions
- Good tool descriptions are essential — the LLM selects tools based on descriptions
- Multi-agent systems are powerful but complex; start with a single agent first
- Always set a maximum step limit to prevent infinite loops
- [ReAct: Synergizing Reasoning and Acting (Yao et al., 2022)](https://arxiv.org/abs/2210.03629) - The foundational ReAct paper
- [Anthropic Tool Use Documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) - Official guide for Claude function calling
- [Building Effective Agents (Anthropic)](https://www.anthropic.com/research/building-effective-agents) - Anthropic's guide to agent design patterns
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/) - Framework for building stateful agents
- [OpenAI Function Calling Guide](https://platform.openai.com/docs/guides/function-calling) - OpenAI's approach to tool use