Lab Guide — Claude API Setup & First Calls
Three hands-on exercises to go from zero to a working multi-turn chatbot.
📥 Demo Files
⬇ first_call.pyPrerequisites & Setup
Install dependencies
pip install anthropic python-dotenv
Create your .env file in the same folder as your notebook:
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
Get your key from console.anthropic.com → API Keys.
Open lab.pdf in Jupyter and run the setup cell to verify your environment.
.env using load_dotenv(). Add .env to your .gitignore
before pushing anything to a repository.
In this exercise you will load your API key, create an Anthropic client, make a basic
messages.create() call, and print both the response text and the token usage statistics.
Load environment and create the client
from dotenv import load_dotenv
import os
import anthropic
load_dotenv()
client = anthropic.Anthropic()
# The SDK automatically reads ANTHROPIC_API_KEY from the environment
Make the API call
response = client.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=256,
messages=[
{"role": "user", "content": "Tell me a fun fact about large language models."}
]
)
Print the response text and usage
print("Response:")
print(response.content[0].text)
print()
print("Token usage:")
print(f" Input tokens: {response.usage.input_tokens}")
print(f" Output tokens: {response.usage.output_tokens}")
print(f" Stop reason: {response.stop_reason}")
Expected output:
stop_reason: end_turn,
which means Claude finished naturally rather than hitting the max_tokens cap.
Implement stream_response(prompt: str) -> None that streams Claude's reply to the terminal,
printing each chunk as it arrives. Then test it with a long prompt to see the difference in perceived latency
compared to a non-streaming call.
Implement the function
def stream_response(prompt: str) -> None:
"""Stream a response from Claude, printing each chunk as it arrives."""
with client.messages.stream(
model="claude-3-5-haiku-20241022",
max_tokens=512,
messages=[{"role": "user", "content": prompt}]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print() # newline after stream ends
Test it with a longer prompt
stream_response("Explain quantum entanglement in simple terms.")
Compare streaming vs non-streaming latency
import time
prompt = "Write a haiku about APIs."
# Non-streaming: waits for full response
start = time.time()
response = client.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=64,
messages=[{"role": "user", "content": prompt}]
)
non_stream_time = time.time() - start
print(f"Non-streaming: got full response in {non_stream_time:.2f}s")
print(response.content[0].text)
print()
# Streaming: first token arrives quickly
print("Streaming (notice first token arrives faster):")
start = time.time()
stream_response(prompt)
stream_total_time = time.time() - start
print(f"Streaming: total time {stream_total_time:.2f}s")
Build chat_loop() — an interactive chatbot that maintains the full conversation history.
Claude will remember everything said in the session because you send the complete history with each API call.
Implement the chat loop
def chat_loop() -> None:
"""Run an interactive multi-turn conversation with Claude."""
history = []
print("Chat with Claude (type 'quit' to exit)\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() == "quit":
print("Goodbye!")
break
# Append user message to history
history.append({"role": "user", "content": user_input})
# Call the API with the full history
response = client.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=512,
messages=history
)
# Extract the reply and append it to history
assistant_reply = response.content[0].text
history.append({"role": "assistant", "content": assistant_reply})
# Show the reply
print(f"Claude: {assistant_reply}\n")
chat_loop()
Test that Claude remembers context
Try this conversation to verify memory works:
You: My name is Alex and I love Python.
Claude: Nice to meet you, Alex! Python is a fantastic language...
You: What is my name?
Claude: Your name is Alex, as you mentioned earlier!
You: What programming language do I love?
Claude: You mentioned that you love Python!
Optional: add a system prompt to give Claude a persona
response = client.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=512,
system="You are a friendly Python tutor. Keep answers concise and practical.",
messages=history
)
history list you maintain and pass on every API call. The API is stateless —
your code is responsible for managing the conversation state. This is why very long conversations become
expensive: you send more tokens with each turn.
Self-check Checklist
Before moving on, verify that you can answer "yes" to each item:
- ✓ My
.envfile is loaded andclientis created without errors - ✓ Exercise 1:
response.content[0].textprints a coherent reply - ✓ Exercise 1: I can see
input_tokensandoutput_tokensin the usage - ✓ Exercise 2:
stream_response()prints text incrementally (not all at once) - ✓ Exercise 2: I observed that streaming shows output before non-streaming does
- ✓ Exercise 3:
chat_loop()correctly remembers my name across turns - ✓ Exercise 3: Typing "quit" exits the loop cleanly
lab_answer.pdf for the complete working solutions with inline
comments explaining every step. Try to work through it yourself first — struggling with the code is how
the concepts stick.