Building Your First AI Agent from Scratch
AI agents are one of the most exciting developments in software right now. But if you cut t through the hype, the core idea is surprisingly simple. In this post, I'll explain what agents actually are, how they work, and walk you through building one from scratch in Python.
What Is an AI Agent?
An agent is an AI system that can autonomously plan, reason, and take actions to accomplish a goal — going beyond simple Q&A to actually do things on your behalf.
The key difference from a chatbot: agents take actions and iterate. A chatbot answers your question once and stops. An agent keeps working — using the result of one action to decide what to do next — until the task is complete.
Every agent, no matter how complex, is built on four core concepts:
- Perceive — take in inputs: user messages, tool results, environment state - Reason — decide what to do next
- Act — call a tool, run code, hit an API - Loop — repeat until the task is done
The Agent Loop
User Input
↓
┌─────────────────────────────────┐
│ LLM thinks: "What should I do?" │
│ ↓ │
│ Call a tool? → Get result │
│ ↓ │
│ Call another tool? → Get result │
│ ↓ │
│ Done? → Return final answer │
└─────────────────────────────────┘
↓
Final Response
This loop is the heartbeat of every agent. Let's build one.
Building the Agent
Prerequisites
You'll need Python installed and an Anthropic API key from https://console.anthropic.com.
mkdir my-agent && cd my-agent
python3 -m venv venv
source venv/bin/activate
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-your-key-here"
Step 1 — Define the Tools
Tools are regular Python functions — the agent can call them to take actions. Here we'll give our agent two abilities: a calculator and a weather lookup.
def calculator(operation: str, a: float, b: float) -> float:
ops = {
"add": a + b,
"subtract": a - b,
"multiply": a * b,
"divide": a / b if b != 0 else "Error: division by zero",
}
return ops.get(operation, "Unknown operation")
def get_weather(city: str) -> str:
# In production, call a real weather API here
data = {
"london": "15°C, cloudy",
"new york": "22°C, sunny",
"tokyo": "28°C, humid",
}
return data.get(city.lower(), f"No weather data for {city}")
Step 2 — Describe the Tools to the LLM
The LLM never sees your Python functions directly. You give it a description — a menu of what's available, what each tool does, and what inputs it expects. The LLM reads this to decide when and how
to use each tool.
TOOLS = [
{
"name": "calculator",
"description": "Perform basic arithmetic. Use this for any math.",
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
},
"a": {"type": "number"},
"b": {"type": "number"},
},
"required": ["operation", "a", "b"],
},
},
{
"name": "get_weather",
"description": "Get the current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"},
},
"required": ["city"],
},
},
]
Step 3 — The Tool Dispatcher
When the LLM says "call calculator with these inputs", we need something to route that request to the right Python function. That's the dispatcher:
def run_tool(name: str, inputs: dict):
if name == "calculator":
return calculator(**inputs)
elif name == "get_weather":
return get_weather(**inputs)
else:
return f"Unknown tool: {name}"
Think of it as a switchboard — it takes the LLM's instruction and hands it off to the right function.
Step 4 — The Agent Loop
This is where everything comes together. The loop is the agent.
import anthropic
client = anthropic.Anthropic()
def run_agent(user_message: str):
print(f"\nUser: {user_message}")
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6", max_tokens=1024,
tools=TOOLS, messages=messages,
)
# Agent is done — return the final answer if response.stop_reason == "end_turn":
final = next(b.text for b in response.content if hasattr(b, "text"))
print(f"\nAgent: {final}") return final
# Agent wants to use tools
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f" [tool call] {block.name}({block.input})")
result = run_tool(block.name, block.input)
print(f" [tool result] {result}")
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
})
messages.append({"role": "user", "content": tool_results}) # Loop continues — agent reasons again with new information
run_agent("What's the weather in London and Tokyo? Also, what's 847 times 23?")
---
What Happens When You Run It
User: What's the weather in London and Tokyo? Also, what's 847 times 23?
[tool call] get_weather({'city': 'London'})
[tool result] 15°C, cloudy
[tool call] get_weather({'city': 'Tokyo'})
[tool result] 28°C, humid
[tool call] calculator({'operation': 'multiply', 'a': 847, 'b': 23})
[tool result] 19481.0
Agent: Here's what I found:
- London: 15°C, cloudy
- Tokyo: 28°C, humid
- 847 × 23 = 19,481
The agent autonomously decided which tools to call, called them in the right order, and synthesized the results into a final answer.
How the Four Agent Concepts Map to the Code
Perceive
messages = [{"role": "user", "content": user_message}]
# ...
messages.append({"role": "user", "content": tool_results})
The messages list is everything the agent knows — the user's question, tool results, conversation history. It grows every iteration. The agent perceives again after every action, which is what makes it an agent rather than a chatbot.
Reason response = client.messages.create(
model="claude-sonnet-4-6",
tools=TOOLS,
messages=messages, )
Claude looks at the full history and available tools and decides: Do I have enough to answer? (end_turn) or Do I need more information? (tool_use). The TOOLS list is the agent's awareness of its own
capabilities.
Act
result = run_tool(block.name, block.input)
This is the agent doing something in the world. Here it's calling a calculator. In a more powerful agent it could be browsing the web, writing and running code, sending an email, or updating a database. The pattern is identical regardless.
Loop
while True:
# reason
# act # perceive result
# repeat
The while True is the agent. A chatbot stops after one LLM call. An agent keeps looping until it decides it's done.
The One-Line Summary The while True loop is the agent. Everything else is just giving it eyes (perceive), a brain (reason), and hands (act).
The full code is available above. To run it, save it as agent.py, set your ANTHROPIC_API_KEY, and run python3 agent.py.