Section 1 Lab | Agent Engineering Duration: ~2 hours Prerequisites: Modules 1-2 (Lectures 1.1 through 2.3)
In this lab you'll set up your Python development environment, connect to the Anthropic API, and build your first interactive program that talks to an LLM. By the end, you'll have a working chat loop — a simple program where you type messages, the LLM responds, and the conversation maintains history across turns.
This is the foundation for everything we build in this course. Every agent starts with an API call.
By the end of this lab, you'll have:
You need Python 3.10 or later. Let's check if you already have it.
Open a terminal (Terminal on Mac, PowerShell on Windows) and run:
python3 --version
If you see Python 3.10 or higher, you're good — skip to Section 1.2.
If you need to install Python:
Mac:
The easiest path is Homebrew. If you don't have Homebrew installed, visit brew.sh and follow the one-line install command. Then:
brew install [email protected]
After installation, verify:
python3 --version
Windows:
Download the installer from python.org/downloads. During installation, check the box that says "Add Python to PATH" — this is important. If you miss it, you'll need to add it manually later and it's a pain.
After installation, open a new PowerShell window (existing windows won't see the new PATH) and verify:
python --version
Note: On Windows, the command is usually python (not python3). Throughout this lab, substitute python for python3 if that's what your system uses.
Pick a location for your course work. Create a folder and navigate to it:
mkdir agent-engineering
cd agent-engineering
A virtual environment keeps your project's packages isolated from everything else on your system. This is a standard Python practice — get in the habit of using one for every project.
python3 -m venv venv
This creates a venv folder in your project directory. Now activate it:
Mac/Linux:
source venv/bin/activate
Windows (PowerShell):
.\venv\Scripts\Activate.ps1
Windows note: If you get an error about execution policies, run this first:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserThen try activating again.
You should see (venv) appear at the beginning of your terminal prompt. This tells you the virtual environment is active. Any packages you install now will go into this environment, not your system Python.
Important: You'll need to activate the virtual environment every time you open a new terminal to work on this project. If you don't see (venv) in your prompt, your packages won't be found.
With your virtual environment active:
pip install anthropic python-dotenv
This installs two packages:
anthropic — the official Python SDK for calling the Claude APIpython-dotenv — a small library that loads environment variables from a .env file (we'll use this to keep your API key safe)Verify the install:
python3 -c "import anthropic; print(anthropic.__version__)"
You should see a version number printed. If you get an ImportError, make sure your virtual environment is active.
You'll need an Anthropic API key. If you don't have one yet, go to console.anthropic.com, create an account, and generate an API key.
Never put API keys directly in your code. If you push code to GitHub with a key in it, someone will find it and use it (this happens constantly — bots scan for exposed keys). Instead, we'll use environment variables.
Create a file called .env in your project folder:
ANTHROPIC_API_KEY=sk-ant-your-key-here
Replace sk-ant-your-key-here with your actual key.
Now create a .gitignore file so this never gets committed:
.env
venv/
__pycache__/
The Anthropic SDK automatically looks for the ANTHROPIC_API_KEY environment variable, and python-dotenv will load it from your .env file. You'll see how this works in the next section.
API calls cost money. Over the course of the semester, you should expect to spend roughly $50 in API credits — some labs use more than others, and your final project will likely be the biggest chunk.
Before you start making calls, go to console.anthropic.com and set a monthly spending limit under your account settings. This is a hard cap — the API will reject requests once you hit it, so you won't accidentally run up a surprise bill. Setting it to something like $20/month is a reasonable starting point. You can always raise it later if needed.
A few things to be aware of:
Check your usage regularly at console.anthropic.com. The token tracking you'll add in Part 3 of this lab will help you build intuition for how costs accumulate.
Create a file called hello.py:
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv() # Load API key from .env file
client = Anthropic() # Automatically uses ANTHROPIC_API_KEY
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=256,
messages=[
{"role": "user", "content": "What is the capital of France?"}
]
)
print(response.content[0].text)
Run it:
python3 hello.py
You should see Claude's response printed to your terminal. If you get an authentication error, double-check your API key in .env.
Take a moment to look at what's happening here:
client.messages.create() sends a request to the Anthropic APImodel specifies which Claude model to usemax_tokens sets the maximum response lengthmessages is a list of conversation messages — right now just one user messageresponse.content[0].text extracts the textThat model parameter is important. You can't just write "claude" — you need the exact model identifier string. In our example we used "claude-sonnet-4-5-20250929", which is Claude Sonnet 4.5 (the version released on September 29, 2025).
Where to find model names: The authoritative source is the Anthropic docs at docs.anthropic.com/en/docs/about-claude/models. This page lists every available model with its exact identifier string, context window size, and pricing. Bookmark it — you'll reference it throughout this course.
As of this writing, the models you'll see most often are:
| Model | Identifier | Good For | Relative Cost |
|---|---|---|---|
| Claude Haiku 3.5 | claude-3-5-haiku-20241022 |
Fast, cheap tasks — good for experimentation | Lowest |
| Claude Sonnet 4.5 | claude-sonnet-4-5-20250929 |
Best balance of quality and cost | Medium |
| Claude Opus 4.5 | claude-opus-4-5-20251101 |
Highest capability, complex reasoning | Highest |
For this course, use Sonnet as your default. It's capable enough for everything we'll build and won't burn through your credits too fast. Switch to Haiku when you're testing and iterating quickly (e.g., running the same prompt 20 times to test temperature) — it's significantly cheaper. Save Opus for when you're working on something that genuinely needs the extra reasoning capability.
The model names include dates because Anthropic releases updated versions over time. The models page will always show you the current options. If a model identifier from this lab doesn't work, check the docs — a newer version may have been released.
Tip: A common beginner mistake is hardcoding the model string everywhere. Instead, define it once at the top of your script —
MODEL = "claude-sonnet-4-5-20250929"— and reference that variable. When you want to switch models, you change one line.
The API returns more than just text. Modify your script to print the full response:
print(response)
Run it again and look at the output. You'll see fields like:
id — a unique ID for this requestmodel — the model that was usedrole — always "assistant" for responsescontent — a list of content blocks (text, tool calls, etc.)stop_reason — why the model stopped generating (usually "end_stop")usage — token counts for input and outputWrite a few lines that print the token usage in a readable format. Something like:
Input tokens: 14
Output tokens: 28
Total tokens: 42
The usage information is in response.usage.input_tokens and response.usage.output_tokens. Tracking token usage matters — it's how you'll understand cost and context budget as we build agents.
In Lecture 2.3, you learned that system prompts shape agent behavior and override training defaults. Let's try it.
Create a new file called system_prompt.py. Write a script that:
"You are a grumpy pirate. Answer all questions in character, but still be accurate."The system prompt goes in the system parameter of client.messages.create() — it's a string, not part of the messages list:
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=512,
system="Your system prompt here",
messages=[
{"role": "user", "content": "Your question here"}
]
)
Try a few different system prompts and see how they change the response. This is what it means to shape behavior through context rather than training — same model, completely different outputs.
In Lecture 3.3, you'll learn about temperature in detail. For now, let's see it in action.
Create a file called temperature.py. Write a script that:
temperature=0.0temperature=1.0Use a creative prompt — something like "Give me a name for a coffee shop that's run by robots." Temperature is passed as a parameter alongside model and max_tokens. Since you're making six calls for comparison purposes, this is a good time to use Haiku instead of Sonnet — cheaper and faster for quick experiments like this.
What do you notice? At temperature 0, the responses should be nearly identical. At temperature 1, they should vary significantly. Think about why this matters for agents — when your agent decides which tool to call, do you want variety or consistency?
So far, every call has been a single exchange. But real conversations have history. The key insight: the API itself is stateless. It has no memory of previous calls. To have a conversation, you maintain the message history and send it every time.
Create a file called multi_turn.py. Here's the pattern:
messages = [
{"role": "user", "content": "My name is Alex."}
]
# First call
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=256,
messages=messages
)
assistant_message = response.content[0].text
print(f"Claude: {assistant_message}")
# Add the assistant's response to history
messages.append({"role": "assistant", "content": assistant_message})
# Add a follow-up question
messages.append({"role": "user", "content": "What's my name?"})
# Second call — includes full history
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=256,
messages=messages
)
print(f"Claude: {response.content[0].text}")
Run this and verify that Claude remembers the name from the first message. Then try removing the message history — start a fresh messages list for the second call. Claude won't remember anything.
This is the core mechanic. The conversation history is the context. The LLM doesn't remember — you give it everything it needs, every time. This is what "context engineering" means at its most basic level.
Now you're going to put it all together into something that actually feels like a conversation. This is the core deliverable for Lab 1.
Create a file called chat.py. Build a program that:
input() function)The user should be able to exit by typing quit or exit.
Here's the skeleton to get you started:
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
client = Anthropic()
conversation_history = []
# Your system prompt
system_prompt = "You are a helpful assistant."
def chat(user_message):
"""Send a message and get a response, maintaining conversation history."""
# TODO:
# 1. Append the user message to conversation_history
# 2. Call client.messages.create() with the system prompt and full history
# 3. Extract the assistant's response text
# 4. Append the assistant's response to conversation_history
# 5. Return the response text
pass
def main():
"""Main chat loop."""
print("Chat with Claude! (type 'quit' to exit)\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ['quit', 'exit']:
print("Goodbye!")
break
if not user_input:
continue
response = chat(user_input)
print(f"\nClaude: {response}\n")
if __name__ == "__main__":
main()
Fill in the chat() function. This is the part you need to write — it's about 8-10 lines of code. Refer back to the patterns from Part 2 if you get stuck.
Once your basic chat loop works, add token tracking. After each response, print the token usage for that turn and a running total. Something like:
Claude: [response text]
[This turn: 45 in / 128 out | Total: 89 in / 256 out]
Watch those numbers as the conversation grows. The input token count increases every turn because you're sending the entire conversation history each time. This is the context growth problem you'll be solving throughout this course.
Replace the generic "You are a helpful assistant" system prompt with something more interesting. Try making Claude act as:
Pick one, write a system prompt that's at least 2-3 sentences, and have a conversation. Notice how the system prompt shapes every response. The system prompt is your most powerful tool for controlling agent behavior — we'll build on this extensively.
If you finish early, add the ability to save and load conversations. When the user types /save, write the conversation history to a JSON file. When the user types /load, read it back and resume the conversation.
Hints:
json moduleconversation_history is already a list of dictionaries — that's JSON-friendlyjson.dump() writes to a file, json.load() reads from oneA working chat.py that:
Submit your chat.py file.
"ModuleNotFoundError: No module named 'anthropic'"
Your virtual environment probably isn't active. Run source venv/bin/activate (Mac) or .\venv\Scripts\Activate.ps1 (Windows) and try again.
"AuthenticationError" or 401
Your API key isn't being loaded. Check that your .env file is in the same directory where you're running the script, and that the key is correct. You can verify by adding print(os.environ.get('ANTHROPIC_API_KEY', 'NOT SET')) after load_dotenv().
"RateLimitError" or 429 You're making too many requests too quickly. Add a small delay between calls, or just wait a minute and try again. This is normal — production code handles this with retry logic (you'll implement this in a later lab).
Responses are getting cut off
Increase max_tokens. The default in our examples is 256 or 512 — for longer responses, try 1024 or higher. Remember: you're only charged for tokens actually generated, not the maximum.
Windows: "running scripts is disabled on this system" Run this in PowerShell as described in Section 1.3:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
In Lab 2, you'll use these same API skills to explore generation parameters in depth and start working with structured outputs. The chat loop you built today is the skeleton of every agent — an LLM in a loop, receiving context, producing responses. The rest of this course is about making that loop smarter.