Build an AI Agent with Claude API
Build a CLI AI agent that autonomously calls tools using the Claude API.
Introduction
30 Second Summary
AI assistants like Siri and Alexa decide which actions to take based on your questions. Behind the scenes, they use a pattern called "tool use" to pick the right action and execute it.
In this project, you will build your own AI agent in Python that autonomously decides which tools to call to answer complex questions. You will use the Claude API to power your agent and learn the core pattern behind every production AI system.
What You'll Build
A command-line AI agent powered by Claude that answers questions by calling custom tools you define, including a calculator and a dictionary lookup.
By the end of this project, you'll have:
- A Python script with custom tool definitions using JSON Schema.
- An agent loop that handles multi-step tool calling automatically.
- A working CLI interface where you can chat with your agent and watch it reason.
- Secret Mission: Add a real web search tool so your agent can answer questions about current events.
Are there any prerequisites?
You need basic Python knowledge (variables, functions, loops). No prior AI or API experience is needed.
Before We Start
Before We Start...
Set Up Your Development Environment
Before you can build an AI agent, you need three things in place: Python to run your code, a code editor to write it in, and access to the Claude API.
In this step, you will install Python, set up VS Code, and create your Anthropic Console account with an API key.
In this step, get ready to:
- Install and verify Python 3.9+.
- Install VS Code.
- Create an Anthropic Console account and set up your API key.
Install and verify Python
Your AI agent will be a Python script. You need Python version 3.9 or higher installed on your machine.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.9 or higher
You are good to go. Move on to the next substep.
ⓧ I see an older version
You have Python installed but it is too old for this project. You need version 3.9 or higher.
- Go to python.org/downloads and download the latest Python installer.
- Run the installer. On Windows, check the box that says Add python.exe to PATH before clicking Install Now.
- Close and reopen your terminal, then run the version check again to confirm.
macOS users with an older default Python
macOS sometimes ships with an older Python. After installing a newer version from python.org, you may need to use python3.12 or python3.11 explicitly if python3 --version still shows the old version.
ⓧ Command not found
Python is not installed yet. Follow these steps to install it.
- Go to python.org/downloads and download the latest Python installer for your operating system.
- Run the installer. On Windows, check the box that says Add python.exe to PATH before clicking Install Now.
- Close and reopen your terminal after installation completes.
- Run the version check command again to confirm Python is installed.
Still seeing command not found?
On macOS, try python3 instead of python. On Windows, make sure you checked the PATH checkbox during installation. If you missed it, run the installer again and select Modify to add Python to your PATH.
help me troubleshoot my Python installation
Install VS Code
You need a code editor to write your Python script. VS Code is a free, lightweight editor that works on all platforms.
- Go to code.visualstudio.com and download the installer for your operating system.
- Run the installer and follow the prompts to complete setup.
- Once installed, open VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows), typing VS Code, and pressing Enter.
Already have VS Code?
If VS Code is already installed, skip ahead to the next substep. Any recent version works for this project.
Create an Anthropic Console account and set up your API key
Your agent needs to communicate with Claude through an API. To do that, you need an account on the Anthropic Console and an API key that authenticates your requests.
- Open your browser and go to console.anthropic.com.
- Sign up for a new account (or log in if you already have one).
- Once logged in, navigate to your API keys settings. You can find this under Account Settings.
- Click the button to create a new API key.
- Give your key a name (for example, ai-agent-project) and confirm.
- Copy the key immediately. You will not be able to see it again after you close the dialog.
Copy your key right now
The full API key is shown only once at the moment of creation. Copy it to a safe place (like a temporary text file or password manager) before closing the dialog. If you lose it, you will need to create a new one.
What is an API key?
An API key is like a password that lets your Python script talk to Claude. Every request your agent sends to Claude includes this key so the server knows it is you.
The Anthropic API uses a prepaid credits model. You will need to add a small amount of credit to your account. This entire project costs well under $1 in API usage.
Now set the API key as an environment variable in your terminal so Python can read it automatically.
macOS
- Set the environment variable by running this command (replace your-api-key-here with the key you just copied):
export ANTHROPIC_API_KEY='your-api-key-here'
Windows
- Set the environment variable by running this command in PowerShell (replace your-api-key-here with the key you just copied):
$env:ANTHROPIC_API_KEY="your-api-key-here"
Why an environment variable?
Storing your key in an environment variable keeps it out of your code. The Anthropic Python SDK reads the ANTHROPIC_API_KEY variable automatically, so you never need to paste the key directly in your script.
This variable lasts only for your current terminal session. If you close the terminal, you will need to set it again next time.
Not sure if the variable was set?
On macOS, run echo $ANTHROPIC_API_KEY in your terminal. On Windows PowerShell, run echo $env:ANTHROPIC_API_KEY. You should see your key printed back. If it is blank, try running the export command again in the same terminal window.
help me set up my ANTHROPIC_API_KEY environment variable
Your environment is ready. Next up, you will create your project folder, install the Anthropic SDK, and make your first API call to Claude.
Create Your Project and Install the SDK
Your environment is locked and loaded. Python is installed, VS Code is ready, and your API key is set.
Now you need a place for your agent code to live. In this step, you'll create a project folder, install the Anthropic Python SDK, and make your first API call to confirm everything is wired up.
In this step, get ready to:
- Create your project folder and a Python file.
- Install the Anthropic Python SDK.
- Write and run a hello world API call to verify your setup.
Create your project folder and file
Every project starts with a dedicated folder. This keeps your code, dependencies, and any future files neatly contained.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir ai-agent
cd ai-agent
- Confirm the folder exists by running:
ls
You should see an empty output because the folder is brand new.
- Open VS Code. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the ai-agent folder, then click Open.
VS Code now shows your empty project folder in the Explorer sidebar on the left.
- In the Explorer sidebar, hover over the AI-AGENT heading and click the New File icon (a page with a plus sign).
- Type agent.py and press Enter.
Your new file opens in the editor. This is where all your agent code will live.
Don't see the Explorer sidebar?
Press Cmd+Shift+E (macOS) or Ctrl+Shift+E (Windows) to toggle it open.
If you see a Get Started tab instead of your folder, you may not have opened the folder correctly. Go back to File > Open Folder and select ai-agent from your Desktop.
Install the Anthropic Python SDK
The Anthropic Python SDK is a library that handles all the complexity of talking to Claude's API. It manages authentication, request formatting, and response parsing for you.
- Open VS Code's integrated terminal by pressing Ctrl+` (backtick key, top-left of your keyboard). This works on both macOS and Windows.
- Install the SDK by running this command:
pip install anthropic
What does this command do?
pip is Python's package manager. It downloads the anthropic library from PyPI (Python's package registry) and installs it so your code can import and use it.
- After the install finishes, you should see a line that says Successfully installed anthropic followed by a version number.
Seeing 'pip not found'?
Try pip3 install anthropic instead. Some systems use pip3 for Python 3 packages.
If you still get an error, make sure the terminal session has the same Python you verified in Step 1. help me fix my pip install
Write a hello world API call
Time to confirm your API key and SDK are working together. You will write a short script that sends a message to Claude and prints the response.
- Click on agent.py in the Explorer sidebar to open it in the editor.
- Copy the following code into agent.py:
import anthropic
# Create a client that reads your ANTHROPIC_API_KEY from the environment
client = anthropic.Anthropic()
# Send a simple message to Claude and store the response
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello! Are you working?"}]
)
# Print Claude's reply
print(response.content[0].text)
What does this code do?
anthropic.Anthropic() creates a client object that automatically reads your ANTHROPIC_API_KEY environment variable. No need to paste your key into the code.
client.messages.create() sends a request to the Claude Messages API. You specify the model, a token limit, and an array of messages. Claude responds with content blocks, and you print the text from the first one.
- Save agent.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Run your script in the VS Code terminal:
python3 agent.py
Windows users
If python3 is not recognized, try python agent.py instead. Use whichever command worked when you checked your Python version in Step 1.
You should see a friendly response from Claude printed in your terminal, something like "Hello! Yes, I'm working perfectly."
Seeing an authentication error?
This means your ANTHROPIC_API_KEY environment variable is not reaching the script. Make sure you set the variable in the same terminal session where you are running the script.
If you set it in a different terminal window or tab, it will not carry over. Re-export or re-set the variable in VS Code's integrated terminal, then try again. help me fix my API key authentication
✔️ Awesome, I've got everything!
Great. Double check you have saved agent.py and that Claude's response printed in your terminal.
ⓧ I'd like to double check the full code
Here is the complete agent.py at this point:
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello! Are you working?"}]
)
print(response.content[0].text)
Your SDK is installed and your API connection is live. Next up, you will define the tools that give your agent its superpowers.
Define Your Tools with JSON Schema
In the previous step, you proved your API connection works by sending a message to Claude and getting a response back.
Now it's time to give Claude abilities beyond just chatting. In the Claude API, you do this by defining tools. Each tool is a JSON object that tells Claude what the tool does and what inputs it expects. Claude reads these definitions and autonomously decides when to call them.
In this step, get ready to:
- Define a calculator tool using JSON Schema.
- Define a dictionary lookup tool with a local definitions database.
- Understand how Claude decides which tool to call.
Create the calculator tool definition
Tools in Claude's API are defined as JSON Schema objects. Each tool has three parts: a name Claude uses to identify it, a description that explains when to use it, and an input_schema that defines what parameters the tool accepts.
Before adding your first tool, you need to update the imports and remove the hello-world test code from the previous step.
- In agent.py, find the import line at the top of your file:
import anthropic
- Add two more imports directly below it, so your imports look like this:
import anthropic
import json
import math
- Now find and delete the hello-world test code below client = anthropic.Anthropic(). Delete these lines:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello! Are you working?"}]
)
print(response.content[0].text)
Your file should now have only the three imports and the client line. The next chunk adds your first tool definition below it.
- Add the calculator tool definition below client = anthropic.Anthropic() by pasting this code:
tools = [
{
"name": "calculator",
"description": "Performs mathematical calculations. Use this tool when the user asks to compute, calculate, or evaluate a mathematical expression. Supports basic arithmetic, exponents, square roots, and common math operations.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate, e.g. '2 + 2', 'sqrt(144)', '15 * 7'"
}
},
"required": ["expression"]
}
}
]
What does this code do?
The tools list holds all tool definitions your agent can use. Each tool is a Python dictionary that Claude reads before responding.
- name is the identifier Claude uses when it decides to call this tool.
- description tells Claude exactly when to reach for this tool. The more specific the description, the better Claude's decisions.
- input_schema follows the JSON Schema standard and defines what parameters the tool accepts. Here, the calculator takes a single expression string.
- Save agent.py.
You won't see any output yet. The next chunk adds a second tool to the list.
Create the dictionary lookup tool definition
Your agent needs more than one tool to demonstrate autonomous decision-making. A dictionary lookup tool gives Claude a completely different capability to choose between.
- In agent.py, find the closing ] of the tools list. Replace it by adding the dictionary lookup tool, so your tools list now contains both tools:
tools = [
{
"name": "calculator",
"description": "Performs mathematical calculations. Use this tool when the user asks to compute, calculate, or evaluate a mathematical expression. Supports basic arithmetic, exponents, square roots, and common math operations.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate, e.g. '2 + 2', 'sqrt(144)', '15 * 7'"
}
},
"required": ["expression"]
}
},
{
"name": "dictionary_lookup",
"description": "Looks up the definition of a word. Use this tool when the user asks what a word means, asks for a definition, or wants to understand vocabulary.",
"input_schema": {
"type": "object",
"properties": {
"word": {
"type": "string",
"description": "The word to look up the definition for"
}
},
"required": ["word"]
}
}
]
What does this code do?
The dictionary_lookup tool follows the same structure as the calculator. It takes a single word parameter as input.
Notice how each tool's description uses natural language to explain when Claude should use it. Claude compares the user's question against all tool descriptions to decide which one fits.
- Save agent.py.
The dictionary tool needs data to look up. The next chunk adds a local definitions database.
- Add the definitions dictionary below the tools list by pasting this code:
definitions = {
"algorithm": "A step-by-step procedure or set of rules for solving a problem or accomplishing a task, especially by a computer.",
"recursion": "The process of defining something in terms of itself. In programming, a function that calls itself.",
"api": "Application Programming Interface. A set of rules and protocols that allows different software applications to communicate with each other.",
"latency": "The time delay between a request and its response. In computing, often measured in milliseconds.",
"token": "In natural language processing, a unit of text (word, subword, or character) that a language model processes.",
}
What does this code do?
The definitions dictionary is a simple key-value store that maps words to their definitions. In a production system, this could be a real dictionary API or a database query.
For this project, a hardcoded dictionary keeps things simple so you can focus on the agent loop pattern itself.
- Save agent.py and run it to confirm there are no syntax errors:
python agent.py
You should see no output and no errors. This confirms your Python syntax is correct across the tool definitions and the definitions dictionary.
Seeing a syntax error?
- Check that each tool dictionary inside the tools list is separated by a comma.
- Make sure all opening braces { have a matching closing brace }. Nested dictionaries in JSON Schema make it easy to miss one.
- Verify the definitions dictionary has a trailing comma after the last entry (Python allows this).
Still stuck? Help me debug a syntax error in my agent.py tool definitions
Understand how Claude reads tool descriptions
How does Claude decide which tool to call?
When you send a message to Claude along with your tools list, Claude reads every tool's description field. It compares the user's question against those descriptions to decide which tool (if any) is relevant.
This is why writing clear, specific descriptions matters. A vague description like "does math" is less reliable than "Performs mathematical calculations. Use this tool when the user asks to compute, calculate, or evaluate a mathematical expression."
Claude can also decide to call no tools at all if the question doesn't match any description. The LLM is making a judgment call every time.
✔️ Awesome, I've got everything!
Great. Double check you've saved agent.py and confirmed no errors when running the file.
ⓧ I'd like to double check the full code
import anthropic
import json
import math
client = anthropic.Anthropic()
tools = [
{
"name": "calculator",
"description": "Performs mathematical calculations. Use this tool when the user asks to compute, calculate, or evaluate a mathematical expression. Supports basic arithmetic, exponents, square roots, and common math operations.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate, e.g. '2 + 2', 'sqrt(144)', '15 * 7'"
}
},
"required": ["expression"]
}
},
{
"name": "dictionary_lookup",
"description": "Looks up the definition of a word. Use this tool when the user asks what a word means, asks for a definition, or wants to understand vocabulary.",
"input_schema": {
"type": "object",
"properties": {
"word": {
"type": "string",
"description": "The word to look up the definition for"
}
},
"required": ["word"]
}
}
]
definitions = {
"algorithm": "A step-by-step procedure or set of rules for solving a problem or accomplishing a task, especially by a computer.",
"recursion": "The process of defining something in terms of itself. In programming, a function that calls itself.",
"api": "Application Programming Interface. A set of rules and protocols that allows different software applications to communicate with each other.",
"latency": "The time delay between a request and its response. In computing, often measured in milliseconds.",
"token": "In natural language processing, a unit of text (word, subword, or character) that a language model processes.",
}
Your tools are defined and ready for Claude to use. Next up, you'll build the agent loop that sends these tools to Claude, executes the tool calls Claude makes, and feeds the results back.
Build the Agent Loop
Your tools are defined, but Claude can only describe them right now. It cannot actually execute them.
In this step, you will write the functions that run each tool and build the agent loop. The agent loop is the core pattern behind every production AI system. It sends a message to Claude, checks if Claude wants to call a tool, executes that tool, and feeds the result back. This cycle repeats until Claude has a final answer.
In this step, get ready to:
- Write functions that execute each tool when called.
- Build the agent loop that sends messages, checks for tool calls, and stops when done.
- Test your agent with a real question.
Write the tool execution functions
Claude will tell your code which tool to call and what inputs to pass. You need functions that actually perform the work and return results.
- In agent.py, add this function below your definitions dictionary:
def execute_calculator(expression):
# Only allow safe math functions (no file access or imports)
safe_dict = {
"sqrt": math.sqrt,
"pow": pow,
"abs": abs,
"round": round,
"pi": math.pi,
"e": math.e,
}
try:
result = eval(expression, {"__builtins__": {}}, safe_dict)
return json.dumps({"result": str(result)})
except Exception as e:
return json.dumps({"error": f"Could not evaluate expression: {e}"})
What does this code do?
This function receives a math expression as a string (like sqrt(144)) and evaluates it.
The safe_dict restricts what eval() can access. By passing {"__builtins__": {}}, you block access to dangerous built-in functions like file operations. Only the math functions you explicitly list are available.
- Below execute_calculator, add the dictionary lookup function:
def execute_dictionary_lookup(word):
# Normalize the input to match our dictionary keys
word_lower = word.lower().strip()
if word_lower in definitions:
return json.dumps({"word": word, "definition": definitions[word_lower]})
else:
return json.dumps({"word": word, "definition": f"No definition found for '{word}'. This is a demo dictionary with limited entries."})
What does this code do?
This function takes a word, normalizes it to lowercase, and looks it up in your definitions dictionary from the previous step. If the word exists, it returns the definition as JSON. If not, it returns a helpful message.
- Below execute_dictionary_lookup, add a dispatcher function that routes tool calls to the right function:
def execute_tool(tool_name, tool_input):
# Route each tool call to the correct function
if tool_name == "calculator":
return execute_calculator(tool_input["expression"])
elif tool_name == "dictionary_lookup":
return execute_dictionary_lookup(tool_input["word"])
else:
return json.dumps({"error": f"Unknown tool: {tool_name}"})
Why a dispatcher function?
When Claude calls a tool, it returns the tool name and inputs as data. The dispatcher maps that tool name to the correct Python function. This keeps your agent loop clean. It only needs to call execute_tool() regardless of which tool Claude chose.
- Save agent.py.
- You will not see a visible change yet. The agent loop in the next substep uses these functions.
Seeing a syntax error on save?
Make sure each function starts at column 0 (no indentation). They should be at the same level as definitions, not indented inside it.
Also check that you have a blank line between the definitions dictionary and your first function.
help me fix a syntax error in my tool execution functions
Build the agent loop
The agent loop is the pattern that every AI agent system uses in production. It sends a user message to the LLM, checks whether the LLM wants to call a tool, executes that tool, and sends the result back. This cycle repeats until the LLM decides it has enough information to give a final answer.
- Below execute_tool, add the run_agent function:
def run_agent(user_message):
# Start a conversation with the user's question
messages = [{"role": "user", "content": user_message}]
print(f"\nYou: {user_message}")
print("-" * 40)
while True:
# Send the conversation to Claude with our tool definitions
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=tools,
messages=messages,
)
# Check if Claude wants to use any tools
tool_use_blocks = [block for block in response.content if block.type == "tool_use"]
# If Claude is done (no more tool calls), print the final answer and exit
if response.stop_reason == "end_turn" or not tool_use_blocks:
for block in response.content:
if hasattr(block, "text"):
print(f"\nAgent: {block.text}")
break
# Add Claude's response (with tool calls) to the conversation
messages.append({"role": "assistant", "content": response.content})
# Execute each tool Claude requested and collect results
tool_results = []
for tool_use in tool_use_blocks:
print(f" [Calling tool: {tool_use.name} with input: {tool_use.input}]")
result = execute_tool(tool_use.name, tool_use.input)
print(f" [Result: {result}]")
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result,
})
# Send tool results back to Claude as a user message
messages.append({"role": "user", "content": tool_results})
What does this code do?
This is the heart of your AI agent. Here is the cycle it follows:
- It sends the user's message plus your tool definitions to Claude via client.messages.create().
- Claude responds with either a final text answer OR tool_use blocks requesting tool calls.
- If stop_reason is end_turn and there are no tool calls, the loop breaks and prints the answer.
- If Claude requested tools, the loop executes each one, packages the results, and sends them back as a new user message.
- The loop repeats until Claude has all the information it needs.
Why send tool results as a user message?
The Claude API requires strict message alternation: user, assistant, user, assistant. Tool results are sent in a user-role message so Claude can read them and decide what to do next.
- Save agent.py.
- You will not see a change yet. The next chunk adds the entry point so you can run your agent.
Seeing an indentation error?
The entire while True: block must be indented inside run_agent. Each level of nesting adds 4 spaces. The deepest lines (inside the for tool_use loop) should be at 12 spaces.
help me fix indentation in my run_agent function
Test your agent with a question
Your agent loop is complete. Now you need an entry point that lets you type questions and see the agent work.
- At the bottom of agent.py, add this code to create a command-line interface:
if __name__ == "__main__":
print("AI Agent Ready! Type 'quit' to exit.")
print("Try asking: 'What is the square root of 144?' or 'What does algorithm mean?'")
print("=" * 50)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
if not user_input:
continue
run_agent(user_input)
What does this code do?
The if __name__ == "__main__": block only runs when you execute the file directly (not when importing it). It creates a simple chat loop that reads your input, passes it to run_agent(), and repeats until you type quit.
- Save agent.py.
- Run your agent by entering this command in your terminal:
python3 agent.py
- When you see AI Agent Ready!, type this question and press Enter:
What is the square root of 144?
You should see output showing the agent calling the calculator tool, receiving the result, and then printing a final answer that includes 12.
- Type quit to exit the agent when you are done testing.
Seeing an API error?
If you see an authentication error, your ANTHROPIC_API_KEY environment variable may not be set in this terminal session. Re-export it in the same terminal window where you run the script.
If you see a model-not-found error, double-check that the model string in your code is exactly claude-sonnet-4-5-20250929.
help me fix an API error when running my AI agent
✔️ Awesome, I've got everything!
Great. Double check you have saved agent.py.
ⓧ I'd like to double check the full code
import anthropic
import json
import math
client = anthropic.Anthropic()
tools = [
{
"name": "calculator",
"description": "Performs mathematical calculations. Use this tool when the user asks to compute, calculate, or evaluate a mathematical expression. Supports basic arithmetic, exponents, square roots, and common math operations.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate, e.g. '2 + 2', 'sqrt(144)', '15 * 7'"
}
},
"required": ["expression"]
}
},
{
"name": "dictionary_lookup",
"description": "Looks up the definition of a word. Use this tool when the user asks what a word means, asks for a definition, or wants to understand vocabulary.",
"input_schema": {
"type": "object",
"properties": {
"word": {
"type": "string",
"description": "The word to look up the definition for"
}
},
"required": ["word"]
}
}
]
definitions = {
"algorithm": "A step-by-step procedure or set of rules for solving a problem or accomplishing a task, especially by a computer.",
"recursion": "The process of defining something in terms of itself. In programming, a function that calls itself.",
"api": "Application Programming Interface. A set of rules and protocols that allows different software applications to communicate with each other.",
"latency": "The time delay between a request and its response. In computing, often measured in milliseconds.",
"token": "In natural language processing, a unit of text (word, subword, or character) that a language model processes.",
}
def execute_calculator(expression):
safe_dict = {
"sqrt": math.sqrt,
"pow": pow,
"abs": abs,
"round": round,
"pi": math.pi,
"e": math.e,
}
try:
result = eval(expression, {"__builtins__": {}}, safe_dict)
return json.dumps({"result": str(result)})
except Exception as e:
return json.dumps({"error": f"Could not evaluate expression: {e}"})
def execute_dictionary_lookup(word):
word_lower = word.lower().strip()
if word_lower in definitions:
return json.dumps({"word": word, "definition": definitions[word_lower]})
else:
return json.dumps({"word": word, "definition": f"No definition found for '{word}'. This is a demo dictionary with limited entries."})
def execute_tool(tool_name, tool_input):
if tool_name == "calculator":
return execute_calculator(tool_input["expression"])
elif tool_name == "dictionary_lookup":
return execute_dictionary_lookup(tool_input["word"])
else:
return json.dumps({"error": f"Unknown tool: {tool_name}"})
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
print(f"\nYou: {user_message}")
print("-" * 40)
while True:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=tools,
messages=messages,
)
tool_use_blocks = [block for block in response.content if block.type == "tool_use"]
if response.stop_reason == "end_turn" or not tool_use_blocks:
for block in response.content:
if hasattr(block, "text"):
print(f"\nAgent: {block.text}")
break
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for tool_use in tool_use_blocks:
print(f" [Calling tool: {tool_use.name} with input: {tool_use.input}]")
result = execute_tool(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})
if __name__ == "__main__":
print("AI Agent Ready! Type 'quit' to exit.")
print("Try asking: 'What is the square root of 144?' or 'What does algorithm mean?'")
print("=" * 50)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
if not user_input:
continue
run_agent(user_input)
Your agent is alive. It can autonomously decide which tools to call and synthesize answers from the results. Next up, you will push it further by asking questions that require multiple tools in a single conversation.
Test Multi-Tool Conversations
Your agent can already answer single-tool questions. The real power of an AI agent shows up when it needs to call multiple tools in a single conversation to build a complete answer.
In this step, you'll push your agent with complex questions that require both the calculator and dictionary tools. You'll also verify that error handling works gracefully when the agent encounters inputs it can't process.
In this step, get ready to:
- Ask questions that require multiple tool calls.
- Observe how Claude chains tool calls autonomously.
- Verify error handling for edge cases.
Ask questions that require multiple tool calls
Until now, you've only tested questions that use one tool at a time. A multi-tool question forces your agent loop to call both tools before composing a final answer.
- Run your agent by running this command:
python3 agent.py
- When you see the You: prompt, type this multi-tool question:
What is the square root of 144, and what does the word algorithm mean?
You should see the agent call both tools in the terminal output. Look for two separate [Calling tool: ...] lines followed by their [Result: ...] lines, then a final synthesized answer from the agent.
What should I see?
Your terminal will show the agent calling calculator with an expression like sqrt(144) and dictionary_lookup with the word algorithm. After both results come back, Claude composes a natural-language answer that combines both pieces of information.
- Try another multi-tool question to see the pattern repeat:
What is 15 * 7, and what does the word recursion mean?
You should again see both tools called and a combined final answer.
Only seeing one tool call?
Claude sometimes decides to call tools one at a time across multiple loop iterations instead of both at once. This is still correct behavior. Your agent loop handles both patterns. Watch for two separate rounds of [Calling tool: ...] output before the final answer appears.
If you see no tool calls at all and just a text response, make sure your question clearly asks for both a calculation and a definition.
Help me debug why my agent only calls one tool
Observe how the agent loop drives multi-tool calls
The terminal output you just saw reveals exactly how the agent loop works under the hood. Two values control the entire flow.
How does the loop know when to stop?
Every response from Claude includes a stop_reason field. When Claude wants to call a tool, the stop reason is tool_use. When Claude is done and ready to give a final answer, the stop reason is end_turn.
Your run_agent function checks tool_use_blocks on each iteration. If the list is empty or the stop reason is end_turn, the loop breaks and prints the final text. Otherwise, it executes the tools and sends results back for another round.
Here is what a multi-tool conversation looks like step by step:
- Loop iteration 1: Claude sees both questions, returns one or more tool_use blocks. Your code executes each tool and sends the results back.
- Loop iteration 2 (if needed): Claude may call additional tools based on earlier results. Your code handles these the same way.
- Final iteration: Claude has all the information it needs. It responds with stop_reason of end_turn and a text block with the synthesized answer.
Why is this called the agent loop pattern?
The word "agent" means the LLM is making autonomous decisions about which tools to call and when. You did not hardcode "call calculator then dictionary." Claude figured that out from the question and the tool descriptions you provided.
This same loop pattern scales to any number of tools. Production AI systems use it with dozens of tools (databases, APIs, file systems) and Claude picks the right ones each time.
Verify error handling for edge cases
Your agent needs to handle gracefully when a tool cannot produce a valid result. The code already includes error handling in both tool functions. Let's confirm it works.
- In your running agent (or restart it with python3 agent.py if you exited), type this question to test the dictionary with an unknown word:
What does the word florbinate mean?
You should see the agent call dictionary_lookup and receive a result indicating no definition was found. Claude then relays this to you in a natural response instead of crashing.
- Now test the calculator with an expression it cannot evaluate:
Calculate the value of hello + world
The calculator's try/except block catches the error and returns a JSON error message. Claude receives this and explains the issue to you without the program crashing.
Why does error handling matter in an agent?
In a traditional script, an unhandled error crashes the program. In an agent loop, a crashed tool breaks the entire conversation. By returning error information as a JSON result instead of raising an exception, your tools let Claude explain the problem to the user and continue the conversation.
- Try one more edge case that combines both a valid and invalid request:
What is 2 + 2, and what does the word xyloquartz mean?
The agent should successfully calculate 2 + 2 and gracefully report that xyloquartz is not in the dictionary. Claude handles partial success naturally.
- Type quit to exit the agent when you're done testing.
Agent crashing instead of handling errors?
If you see a Python traceback instead of a graceful error message, check that your execute_calculator function wraps the eval() call in a try/except block. The except branch should return a JSON string with an error key, not re-raise the exception.
Help me fix my agent's error handling
✔️ Awesome, I've got everything!
Great. Double check you've saved your file and your agent is working for multi-tool questions.
ⓧ I'd like to double check the full code
Here is the complete agent.py file. Your code should match this exactly.
import anthropic
import json
import math
client = anthropic.Anthropic()
tools = [
{
"name": "calculator",
"description": "Performs mathematical calculations. Use this tool when the user asks to compute, calculate, or evaluate a mathematical expression. Supports basic arithmetic, exponents, square roots, and common math operations.",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate, e.g. '2 + 2', 'sqrt(144)', '15 * 7'"
}
},
"required": ["expression"]
}
},
{
"name": "dictionary_lookup",
"description": "Looks up the definition of a word. Use this tool when the user asks what a word means, asks for a definition, or wants to understand vocabulary.",
"input_schema": {
"type": "object",
"properties": {
"word": {
"type": "string",
"description": "The word to look up the definition for"
}
},
"required": ["word"]
}
}
]
definitions = {
"algorithm": "A step-by-step procedure or set of rules for solving a problem or accomplishing a task, especially by a computer.",
"recursion": "The process of defining something in terms of itself. In programming, a function that calls itself.",
"api": "Application Programming Interface. A set of rules and protocols that allows different software applications to communicate with each other.",
"latency": "The time delay between a request and its response. In computing, often measured in milliseconds.",
"token": "In natural language processing, a unit of text (word, subword, or character) that a language model processes.",
}
def execute_calculator(expression):
safe_dict = {
"sqrt": math.sqrt,
"pow": pow,
"abs": abs,
"round": round,
"pi": math.pi,
"e": math.e,
}
try:
result = eval(expression, {"__builtins__": {}}, safe_dict)
return json.dumps({"result": str(result)})
except Exception as e:
return json.dumps({"error": f"Could not evaluate expression: {e}"})
def execute_dictionary_lookup(word):
word_lower = word.lower().strip()
if word_lower in definitions:
return json.dumps({"word": word, "definition": definitions[word_lower]})
else:
return json.dumps({"word": word, "definition": f"No definition found for '{word}'. This is a demo dictionary with limited entries."})
def execute_tool(tool_name, tool_input):
if tool_name == "calculator":
return execute_calculator(tool_input["expression"])
elif tool_name == "dictionary_lookup":
return execute_dictionary_lookup(tool_input["word"])
else:
return json.dumps({"error": f"Unknown tool: {tool_name}"})
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
print(f"\nYou: {user_message}")
print("-" * 40)
while True:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=tools,
messages=messages,
)
tool_use_blocks = [block for block in response.content if block.type == "tool_use"]
if response.stop_reason == "end_turn" or not tool_use_blocks:
for block in response.content:
if hasattr(block, "text"):
print(f"\nAgent: {block.text}")
break
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for tool_use in tool_use_blocks:
print(f" [Calling tool: {tool_use.name} with input: {tool_use.input}]")
result = execute_tool(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})
if __name__ == "__main__":
print("AI Agent Ready! Type 'quit' to exit.")
print("Try asking: 'What is the square root of 144?' or 'What does algorithm mean?'")
print("=" * 50)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
if not user_input:
continue
run_agent(user_input)
You've just seen the agent loop in action across multiple tools and error conditions. Your agent autonomously decides what to call, handles failures, and synthesizes a final answer. Next up, you'll extend this agent with a real-world tool in the Secret Mission.
Secret mission
Add a Web Search Tool
Your agent can do math and look up definitions. But what about answering questions about things happening right now? In this secret mission, you'll connect your agent to the Brave Search API so it can search the web in real time.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project runs entirely locally and uses prepaid API credits, so there are no ongoing costs. You only spend money when you actively make API calls.
Resources you used:
- Project folder containing agent.py.
- The anthropic Python package.
- The ANTHROPIC_API_KEY environment variable.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your agent or want to add more tools.
- Your project folder stays on your Desktop.
- The anthropic package remains installed and available for future projects.
- Your API key stays configured so you can run the agent anytime.
Pause - I'll come back to this later
Since this project runs locally with prepaid credits, there is nothing to pause. Your files will be waiting for you when you come back.
- Close VS Code and any open terminal windows.
- Your project folder, installed packages, and API key all remain in place for next time.
Delete - I don't want to use this again
Remove all project resources and start fresh.
- Delete the project folder by running this command in your terminal (macOS/Linux):
rm -rf ~/Desktop/ai-agent
On Windows, run this in PowerShell instead:
Remove-Item -Recurse -Force ~\Desktop\ai-agent
- Uninstall the Anthropic Python package by running:
pip uninstall anthropic -y
- Remove the ANTHROPIC_API_KEY environment variable. If you added it to your shell profile (~/.zshrc or ~/.bashrc), open that file and delete the export ANTHROPIC_API_KEY=... line.
- On Windows, if you set it via System Environment Variables, open Settings, search for environment variables, find ANTHROPIC_API_KEY in the list, and click Delete.
- To revoke the API key itself, log in to the Anthropic Console and delete the key from your API keys list.
Nice Work!
Nice Work!
You just built your first AI agent from scratch. Welcome to AI engineering.
You've learned how to:
- Built a working AI agent that autonomously decides which tools to use and chains multiple calls to answer complex questions.
- Implemented the agent loop, the core pattern behind every production AI system.
- Wrote reusable tool definitions using JSON Schema that Claude reads to decide when and how to call your functions.
- Secret Mission: Added a real web search tool using the Brave Search API so your agent can answer questions about current events.
Ready to quiz yourself?