An AI agent is a program that can reason, make decisions, and call external tools to complete a task — not just answer a single question. In this tutorial you’ll build a working AI agent in Python using the Claude API, tool use (function calling), and a simple agent loop. By the end you’ll have an agent that can search the web, do math, and chain multiple actions on its own.
Prerequisites
- Python 3.9+
- An Anthropic API key — console.anthropic.com
- Basic Python knowledge (functions, dicts)
- Completed the Claude API Python guide (recommended)
What Makes an AI Agent Different From a Chatbot
A regular chatbot takes a message and returns a response. An AI agent goes further: it decides what actions to take, executes them by calling tools (Python functions), observes the results, and keeps going until the task is done.
The three core components of any agent:
- LLM brain — Claude decides what to do next
- Tools — Python functions the agent can call (search, calculator, file ops, APIs)
- Agent loop — runs until the task is complete or the model says it’s done
Step 1: Install the SDK
pip install anthropic
Set your API key as an environment variable:
export ANTHROPIC_API_KEY="sk-ant-..."
Step 2: Define Your Tools
Tools are regular Python functions. You describe them to Claude using a JSON schema — Claude reads the description and decides when to call them.
import anthropicimport mathclient = anthropic.Anthropic()def calculate(expression: str) -> str: """Evaluate a math expression.""" try: result = eval(expression, {"__builtins__": {}}, {"math": math}) return str(result) except Exception as e: return f"Error: {e}"def search_web(query: str) -> str: """Return search results for a query.""" # Replace with SerpAPI, Brave Search, or Tavily return f"Top result for '{query}': AI agents use LLMs to reason and act autonomously."TOOLS = [ { "name": "calculate", "description": "Evaluate a mathematical expression. Use for any arithmetic or math.", "input_schema": { "type": "object", "properties": { "expression": { "type": "string", "description": "A valid Python math expression, e.g. '2 ** 10' or 'math.sqrt(144)'", } }, "required": ["expression"], }, }, { "name": "search_web", "description": "Search the web for up-to-date information on any topic.", "input_schema": { "type": "object", "properties": {"query": {"type": "string", "description": "The search query"}}, "required": ["query"], }, },]TOOL_MAP = {"calculate": calculate, "search_web": search_web}
Step 3: Build the Agent Loop
The agent loop sends a message to Claude, checks whether it wants to call a tool, executes the tool, feeds the result back, and repeats — until Claude returns a final answer.
def run_agent(user_message: str, max_iterations: int = 10) -> str: messages = [{"role": "user", "content": user_message}] for i in range(max_iterations): response = client.messages.create( model="claude-opus-4-7", max_tokens=4096, tools=TOOLS, messages=messages, ) tool_uses = [b for b in response.content if b.type == "tool_use"] if response.stop_reason == "end_turn" or not tool_uses: for block in response.content: if hasattr(block, "text"): return block.text return "(no text response)" # Add Claude's response to history messages.append({"role": "assistant", "content": response.content}) # Execute each tool and collect results tool_results = [] for tool_use in tool_uses: fn = TOOL_MAP.get(tool_use.name) result = fn(**tool_use.input) if fn else f"Unknown tool: {tool_use.name}" tool_results.append({ "type": "tool_result", "tool_use_id": tool_use.id, "content": result, }) # Feed results back to Claude messages.append({"role": "user", "content": tool_results}) return "Max iterations reached."
Step 4: Run Your Agent
if __name__ == "__main__": answer = run_agent("What is 2 to the power of 16?") print("Answer:", answer) answer = run_agent( "Search for what an AI agent is, then calculate how many seconds are in 7 days." ) print("Answer:", answer)
Sample output:
[iter 1] stop_reason=tool_use → calling calculate(2 ** 16) ← 65536[iter 2] stop_reason=end_turnAnswer: 2 to the power of 16 is 65,536.[iter 1] stop_reason=tool_use → search_web + calculate[iter 2] stop_reason=end_turnAnswer: An AI agent is a program that uses LLMs to reason autonomously.There are 604,800 seconds in 7 days.
Complete Agent — Full Code
Here’s the entire agent in one file you can copy and run:
import anthropicimport mathclient = anthropic.Anthropic()def calculate(expression: str) -> str: try: return str(eval(expression, {"__builtins__": {}}, {"math": math})) except Exception as e: return f"Error: {e}"def search_web(query: str) -> str: # Replace with Tavily / Brave Search / SerpAPI return f"Search result for '{query}': [connect your real API here]"TOOLS = [ {"name": "calculate", "description": "Evaluate a math expression (Python syntax).", "input_schema": {"type": "object", "properties": {"expression": {"type": "string"}}, "required": ["expression"]}}, {"name": "search_web", "description": "Search the web for current information.", "input_schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}},]TOOL_MAP = {"calculate": calculate, "search_web": search_web}def run_agent(user_message: str, max_iterations: int = 10) -> str: messages = [{"role": "user", "content": user_message}] for _ in range(max_iterations): response = client.messages.create( model="claude-opus-4-7", max_tokens=4096, tools=TOOLS, messages=messages ) tool_uses = [b for b in response.content if b.type == "tool_use"] if response.stop_reason == "end_turn" or not tool_uses: return next((b.text for b in response.content if hasattr(b, "text")), "") messages.append({"role": "assistant", "content": response.content}) results = [ {"type": "tool_result", "tool_use_id": tu.id, "content": (TOOL_MAP[tu.name](**tu.input) if tu.name in TOOL_MAP else "Unknown tool")} for tu in tool_uses ] messages.append({"role": "user", "content": results}) return "Max iterations reached."if __name__ == "__main__": task = input("Task: ") print("\nAgent:", run_agent(task))
How to Extend Your Agent
Add Real Search
Replace the search_web stub with a real API. Good options: Tavily (built for AI agents, free tier), Brave Search API (1,000 free calls/month), or SerpAPI.
Add File Operations
def read_file(path: str) -> str: with open(path) as f: return f.read()def write_file(path: str, content: str) -> str: with open(path, "w") as f: f.write(content) return f"Written {len(content)} chars to {path}"
Add Memory
Pass the full messages list between sessions by saving it to a JSON file or SQLite database. The agent will remember previous interactions.
Multi-Agent Systems
One agent can call another as a tool. A planner agent breaks a task into subtasks; worker agents execute each one. This pattern scales to complex workflows and is the foundation of MCP.
Key Concepts Recap
- Tool use — Claude decides when to call a function; you define what it does
-
Agent loop — keep calling the API until
stop_reason == "end_turn"with no pending tool calls -
Tool results — always return results as a
tool_resultblock with the matchingtool_use_id - Max iterations — always set a cap to prevent infinite loops
-
Model choice —
claude-opus-4-7for complex reasoning;claude-haiku-4-5-20251001for fast, cheap subtasks
What’s Next?
You’ve built a working AI agent from scratch. From here you can:
- Connect a real search API (Tavily, Brave) for live information
- Add persistent memory — store conversation history in SQLite between sessions
- Wrap this agent in a Telegram bot or REST API
- Read the Anthropic tool use docs for parallel tool calls and advanced patterns
- Explore Model Context Protocol (MCP) — a standard for connecting agents to external services
Originally published at kalyna.pro
Top comments (0)