Module 7: Project — Build a Mini Coding Agent
Setup: API and Tool Calling
Description
Before building the agent, you need three things: the dependencies installed, an API key configured, and a first successful API call. This capsule takes you from zero to "the LLM responds to me and can call tools."
Step 1: Create the Project
# Create the project directory
mkdir mini-agent
cd mini-agent
# Create a virtual environment
python3 -m venv venv
source venv/bin/activate # On macOS/Linux
# venv\Scripts\activate # On Windows
# Create the initial files
touch mini_agent.py
touch .env
touch requirements.txt
requirements.txt
Choose ONE option based on your provider:
# Option A: Anthropic (Claude)
anthropic>=0.40.0
python-dotenv>=1.0.0
# Option B: OpenAI (GPT)
openai>=1.50.0
python-dotenv>=1.0.0
# Install dependencies
pip install -r requirements.txt
Step 2: Configure the API Key
Get the API key
ANTHROPIC:
1. Go to https://console.anthropic.com/
2. Create an account if you don't have one
3. Go to "API Keys"
4. Create a new key
5. Copy it (it's only shown once)
OPENAI:
1. Go to https://platform.openai.com/
2. Create an account if you don't have one
3. Go to "API Keys"
4. Create a new key
5. Copy it
Save the key in .env
# .env (NEVER commit this file)
# Option A: Anthropic
ANTHROPIC_API_KEY=sk-ant-api03-...your-key-here...
# Option B: OpenAI
OPENAI_API_KEY=sk-...your-key-here...
Add .env to .gitignore
echo ".env" >> .gitignore
echo "venv/" >> .gitignore
Step 3: First API Call (without tools)
Before adding tools, verify that the API works. Create this in mini_agent.py:
Option A: With Anthropic (Claude)
import os
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": "Say 'Hello, I'm a mini agent' and nothing else."}
]
)
print(response.content[0].text)
print(f"\nTokens used - Input: {response.usage.input_tokens}, Output: {response.usage.output_tokens}")
Option B: With OpenAI (GPT)
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
max_tokens=1024,
messages=[
{"role": "user", "content": "Say 'Hello, I'm a mini agent' and nothing else."}
]
)
print(response.choices[0].message.content)
print(f"\nTokens used - Input: {response.usage.prompt_tokens}, Output: {response.usage.completion_tokens}")
Run and verify
python mini_agent.py
EXPECTED RESULT:
Hello, I'm a mini agent
Tokens used - Input: 22, Output: 10
IF YOU SEE THIS: ✅ The API works. Continue.
IF THERE'S AN ERROR: Check your API key in .env
Step 4: Understand Tool Definitions
What tool definitions are
Tool definitions tell the LLM which tools it has available. They're JSON objects that describe each tool: name, description, and parameters.
THE LLM RECEIVES THIS IN EACH REQUEST:
"You have these tools available:
1. read_file
- Description: Reads the content of a file
- Parameters: path (string, required)
2. list_directory
- Description: Lists files in a directory
- Parameters: path (string, optional, default '.')
And the LLM can RESPOND with:
→ Normal text (if it doesn't need tools)
→ A tool call: {name: 'read_file', args: {path: 'example.py'}}"
The JSON Schema format
LLM APIs use JSON Schema to define tools. Here's the format for Anthropic:
TOOLS = [
{
"name": "read_file",
"description": "Read the contents of a file at the given path. Returns the file content as a string.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to the file to read"
}
},
"required": ["path"]
}
},
{
"name": "list_directory",
"description": "List all files and directories at the given path.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The directory path to list. Defaults to current directory.",
"default": "."
}
},
"required": []
}
}
]
For OpenAI the format is slightly different (it uses "parameters" instead of "input_schema" and wraps it in {"type": "function", "function": {...}}), but the idea is identical.
Why the description matters
THE DESCRIPTION IS WHAT THE LLM "READS"
TO DECIDE WHETHER TO USE THE TOOL.
VAGUE DESCRIPTION:
"read_file: Reads a file"
→ The LLM doesn't know when to use it vs list_directory
PRECISE DESCRIPTION:
"read_file: Read the contents of a file at the given path.
Returns the file content as a string. Use this when you
need to see what's inside a specific file."
→ The LLM knows exactly when it's appropriate
REMEMBER FROM MODULE 03:
The LLM chooses the tool by PATTERN MATCHING.
Better descriptions = better decisions.
Step 5: First API Call with Tools
Now let's make it so the LLM can request tools (without executing them yet):
With Anthropic
import os
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
client = Anthropic()
TOOLS = [
{
"name": "read_file",
"description": "Read the contents of a file at the given path.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to the file to read"
}
},
"required": ["path"]
}
},
{
"name": "list_directory",
"description": "List files and directories at the given path.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Directory path. Defaults to '.'",
"default": "."
}
},
"required": []
}
}
]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=TOOLS,
messages=[
{"role": "user", "content": "What files are in the current directory?"}
]
)
for block in response.content:
if block.type == "text":
print(f"TEXT: {block.text}")
elif block.type == "tool_use":
print(f"TOOL CALL: {block.name}")
print(f"ARGUMENTS: {block.input}")
print(f"TOOL ID: {block.id}")
print(f"\nStop reason: {response.stop_reason}")
Expected result
TOOL CALL: list_directory
ARGUMENTS: {'path': '.'}
TOOL ID: toolu_01ABC123...
Stop reason: tool_use
WHAT HAPPENED?
→ The LLM received the question: "What files are there?"
→ It saw that it has the "list_directory" tool
→ Instead of generating text, it generated a TOOL CALL
→ The stop_reason is "tool_use" (not "end_turn")
→ The LLM is REQUESTING that we execute the tool
→ WE have to execute it and send the result back
THIS IS THE START OF THE AGENTIC LOOP:
→ The LLM requested an action
→ We execute it
→ We send it the result
→ The LLM keeps reasoning
Troubleshooting
Problem 1: AuthenticationError or Invalid API key
Cause: The API key isn't in .env or is copied incorrectly.
Solution:
# Check that .env exists and has content
cat .env
# Check that python-dotenv loads it
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print(os.getenv('ANTHROPIC_API_KEY', 'NOT FOUND')[:10])"
If it prints "NOT FOUND": your .env doesn't have the correct variable or load_dotenv() doesn't find it (check that you're running from the project directory).
Problem 2: ModuleNotFoundError: No module named 'anthropic'
Cause: The dependencies aren't installed or you're not in the virtual environment.
Solution:
# Check that you're in the venv
which python
# It should show something like: /path/mini-agent/venv/bin/python
# If not, activate the venv
source venv/bin/activate # macOS/Linux
# Reinstall
pip install -r requirements.txt
Problem 3: RateLimitError or 429 Too Many Requests
Cause: You exceeded your plan's request limit.
Solution:
→ Wait 60 seconds and try again
→ If it persists, check your plan in the provider's console
→ For development, use cheaper models:
- Anthropic: claude-haiku (cheaper than sonnet)
- OpenAI: gpt-4o-mini (cheaper than gpt-4o)
Problem 4: Connection Error or Timeout
Cause: Network problems or the service is down.
Solution:
→ Check your internet connection
→ Check the provider's status page:
- Anthropic: status.anthropic.com
- OpenAI: status.openai.com
→ If you use a proxy or VPN, check that it doesn't block the API
What You Learned in This Capsule
1. SETUP:
→ Project created with venv and dependencies
→ API key configured in .env
→ .gitignore protects secrets
2. BASIC API CALL:
→ The LLM is an API that returns text
→ Each call has input_tokens and output_tokens
→ It's a function: input → output
3. TOOL DEFINITIONS:
→ They're JSON Schema that describe which tools there are
→ Name + description + parameters
→ The description guides the LLM's selection
4. TOOL CALLING:
→ The LLM can return tool_use instead of text
→ stop_reason: "tool_use" means "execute this tool"
→ We execute, the LLM doesn't execute anything
5. THE AGENTIC LOOP STARTS HERE:
→ The LLM requests → we execute → we give it the result
→ Capsule 03 implements this complete loop
Next capsule: 03 - Implement the agentic loop — the heart of the agent.