Learn agentic AI in Python with 10 small exercises
Building agentic AI? I co-run a 6-week cohort where you ship a production-ready agent, not another API wrapper.
Most "build an AI agent" tutorials hand you a framework and skip the part where you actually understand what it's doing under the hood. When the abstraction breaks, you can't debug it because you never built the layer underneath. Juanjo and I think that gap is worth closing.
Yesterday we shipped 10 small browser-based exercises that walk through that layer one pattern at a time (more on how we run them in the browser with Pyodide here).
This article is the conceptual journey behind them: how you get from "I can call Claude" to a complete agent loop with a testable architecture and a human-in-the-loop workflow. Each stage builds on the previous one.
Stage 1: make a model reply (exercise 1)
Every agent app starts with the same 3-line skeleton. Build a client, call messages.create, read content[0].text. The shape doesn't change much. Only what wraps around it does.
import anthropic
client = anthropic.Anthropic()
msg = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": "Say hi"}],
)
print(msg.content[0].text)
Why content[0].text and not .text? Because content is a list of blocks (text, tool_use, and others). That list is how tool use plugs in later without breaking the response shape. Get this mental model before anything else.
Stage 2: make the reply machine-readable (exercises 2, 3)
Raw LLM strings are unreliable. The fix is two paired habits: a specific system prompt that locks the output shape, and a Pydantic model that validates it on the way back in.
from pydantic import BaseModel
class ExpenseResult(BaseModel):
category: str
confidence: float
result = ExpenseResult.model_validate_json(msg.content[0].text)
Treat the system prompt like an API contract. Say "JSON only", show the literal shape, forbid improvisation ("no punctuation, no explanation, nothing else"). The phrase "nothing else" is doing real work; without it, models love to append a friendly sentence that breaks your parser.
Stage 3: make it remember (exercise 4)
LLMs don't remember anything. They have no state, no memory, no context beyond the current call. The "conversation" is a fiction we create by sending the whole message history every time.
To get a continuous conversation, you keep the list of {"role": ..., "content": ...} dicts and send the whole thing every turn. Append the user message before the call, the assistant reply after. Roles must alternate.
history = []
def ask(user_msg):
history.append({"role": "user", "content": user_msg})
reply = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=history,
).content[0].text
history.append({"role": "assistant", "content": reply})
return reply
State lives in your code, not the model. That single realization clears up most of the confusion students have about context windows and "memory."
Stage 4: give the model hands (exercise 5)
Tool use turns a chatbot into something that can act. The loop is dumber than people think:
while True:
response = client.messages.create(..., tools=TOOLS, messages=messages)
if response.stop_reason == "end_turn":
return response.content[0].text
# else: run the tool the model asked for, append the result, loop again
Two gotchas: append the full response.content as the assistant turn (it contains the tool_use blocks the model needs to see), and tool results come back wrapped in a user message, not assistant.
Stage 5: make it swappable and testable (exercises 6, 7, 8)
By exercise 6 the chatbot works, but it's also often a highly coupled mess importing external dependencies like anthropic and sqlite3 into the business logic. Time for three common patterns, applied to LLM apps:
- A
Protocolfor the LLM provider, so tests can pass aMockProviderwith a.callslist instead of an API key. - A Repository pattern for the persistence layer, so an in-memory dict satisfies the same interface as a database backend.
- A service layer that accepts both via
__init__and orchestrates: call provider, parse, save, return.
That's the four-layer agent architecture, built piece by piece instead of dumped on you all at once.
Stage 6: keep a human in the loop (exercise 9)
When the model returns a confidence score, use it. Above the threshold: auto-accept. Below: show the suggestion and let the user confirm or override.
def process(result, threshold=0.8):
if result.confidence >= threshold:
return result.category
answer = input(f"Accept '{result.category}'? (Enter to confirm): ").strip()
return answer or result.category
Make the accept path the cheapest action (empty input or y). Users pay the manual handling cost only when overriding. This is what separates a trusted assistant from one that quietly mislabels things, and it's the gap between "AI demo" and production-ready workflow.
Stage 7: generalize the loop (exercise 10)
The agent is exercise 5 with one change: replace the hardcoded function call with a TOOL_FUNCTIONS[name] lookup.
TOOL_FUNCTIONS = {
"add": lambda a, b: a + b,
"multiply": lambda a, b: a * b,
}
# inside the loop:
content = str(TOOL_FUNCTIONS[block.name](**block.input))
Now adding a tool is one schema entry plus one dict entry. Swap add/multiply for search_web, query_db, send_email and the loop is identical. Look at agent frameworks under the hood (LangChain, OpenAI Assistants) and you'll see this same pattern.
What the journey teaches
Frameworks make sense once you can write the layer underneath. Skip that, and you are stuck the first time the abstraction leaks. After coaching many developers through this, the dividing line is clear: have they ever written the loop themselves?
The 10 exercises are deliberately small. The arc matters more than any single one. Once you've done them, "agentic AI" stops being "magic" and starts being a loop, schema, and some patterns you might already know.
Try them out:
- In the browser: pythonagenticai.com/exercises. No install, no API key, no dependencies. Loads fast.
- Locally: clone the repo and work through them in your IDE.
Keep reading
Most AI tutorials end at "call the API." This cohort ends with a deployed agent: function calling, structured outputs, three interfaces, Docker, 95%+ test coverage. Six weeks of real engineering, not notebooks. Join the next Agentic AI cohort →