Lab 1: Environment Setup and First API Calls

Section 1 Lab | Agent Engineering Duration: ~2 hours Prerequisites: Modules 1-2 (Lectures 1.1 through 2.3)


Overview

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.


What You'll Build

By the end of this lab, you'll have:

  1. A working Python environment with the Anthropic SDK installed
  2. A script that makes API calls and displays responses
  3. A complete chat loop that maintains conversation history — your first step toward building an agent

Part 1: Environment Setup (~30 minutes)

1.1 Install Python

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.

1.2 Create a Project Folder

Pick a location for your course work. Create a folder and navigate to it:

mkdir agent-engineering
cd agent-engineering

1.3 Create a Virtual Environment

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 CurrentUser

Then 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.

1.4 Install the Anthropic SDK

With your virtual environment active:

pip install anthropic python-dotenv

This installs two packages:

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.

1.5 Set Up Your API Key

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.

1.6 Set a Spending Limit

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.


Part 2: Your First API Calls (~45 minutes)

2.1 Hello, Claude

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:

2.2 Understanding Model Selection

That 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.

2.3 Exploring the Response Object

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:

Write 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.

2.4 Adding a System Prompt

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:

  1. Defines a system prompt — something like: "You are a grumpy pirate. Answer all questions in character, but still be accurate."
  2. Asks Claude a factual question (like "What is photosynthesis?")
  3. Prints the response

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.

2.5 Temperature Experiments

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:

  1. Sends the same prompt to Claude three times with temperature=0.0
  2. Sends the same prompt three times with temperature=1.0
  3. Prints all six responses so you can compare

Use 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?

2.6 Multi-Turn Conversations

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.


Part 3: Build a Chat Loop (~45 minutes)

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.

3.1 The Basic Loop

Create a file called chat.py. Build a program that:

  1. Creates an empty message list
  2. Enters a loop:
    • Gets input from the user (use Python's input() function)
    • Appends the user's message to the message list
    • Calls the API with the full message list
    • Extracts and prints the assistant's response
    • Appends the assistant's response to the message list
    • Repeats

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.

3.2 Add Token Tracking

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.

3.3 Choose a Better System Prompt

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.

3.4 Stretch Goal: Conversation Save/Load

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:


Deliverable

A working chat.py that:

Submit your chat.py file.


Troubleshooting

"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

What's Next

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.