Foundations of AI Agents
Hands-on lab with starter code you can copy + run, and guided steps to produce the artifact for this module.
Setup
- Open a fresh terminal in a new folder, e.g.
mkdir lab-1 && cd lab-1. - Create a Python 3.11 venv:
python -m venv .venv && source .venv/bin/activate. - Install the dependencies noted on each starter snippet (the small pip install … line above the code).
- Copy each starter into a file with the suggested name, then run it and capture the output.
Starter code
import json, time
from openai import OpenAI
client = OpenAI()
TOOLS = {
"get_weather": lambda city: {"temp_c": 18, "city": city},
}
SYSTEM = """You are a weather agent. Loop: think, then either call a tool
or emit FINAL. Always respond as JSON: {"thought": str, "action": str,
"args": object} where action is a tool name or "FINAL"."""
def run(goal: str, max_steps: int = 6, max_seconds: int = 30):
messages = [{"role": "system", "content": SYSTEM},
{"role": "user", "content": goal}]
started = time.time()
for step in range(max_steps):
if time.time() - started > max_seconds:
return {"status": "budget_exhausted", "reason": "time"}
resp = client.chat.completions.create(
model="gpt-4o-mini", messages=messages,
response_format={"type": "json_object"})
out = json.loads(resp.choices[0].message.content)
messages.append({"role": "assistant", "content": json.dumps(out)})
if out["action"] == "FINAL":
return {"status": "success", "answer": out.get("args", {})}
tool = TOOLS.get(out["action"])
if not tool:
obs = {"error": "unknown_tool", "name": out["action"]}
else:
obs = tool(**out["args"])
messages.append({"role": "user",
"content": f"Observation: {json.dumps(obs)}"})
return {"status": "budget_exhausted", "reason": "steps"}
print(run("What is the weather in Paris right now?"))python 01_01_lab.py{
"name": "query_invoices",
"description": "Search invoices in the billing system. Use this when the user asks about a specific invoice, payment status, or overdue amounts.",
"parameters": {
"type": "object",
"required": ["customer_id"],
"properties": {
"customer_id": {
"type": "string",
"description": "Stripe customer ID, format cus_XXXX. NOT the email."
},
"status": {
"type": "string",
"enum": ["draft", "open", "paid", "void", "uncollectible"],
"description": "Filter by invoice status. Omit to return all statuses."
},
"limit": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}
}
}
}cat 01_02_lab.jsonexport type AgentResult =
| { status: "success"; answer: unknown; steps: number; cost_usd: number; trace_id: string }
| { status: "budget_exhausted"; budget: "steps" | "time" | "tokens" | "dollars"; partial?: unknown; trace_id: string }
| { status: "guardrail_tripped"; rule: string; details: unknown; trace_id: string }
| { status: "human_handoff"; reason: string; context: unknown; trace_id: string }
| { status: "error"; error: { code: string; message: string }; trace_id: string };bun 01_03_lab.ts{
"trace_id": "run_01HZ...",
"step": 3,
"thought": "I need yesterday's revenue to answer the question.",
"action": "query_db",
"args": {"sql": "SELECT SUM(amount) FROM orders WHERE day = '2026-05-27'"},
"observation": {"rows": [{"sum": 12431}], "latency_ms": 84},
"budgets_remaining": {"steps": 22, "tokens": 184213, "dollars": 0.94},
"ts": "2026-05-28T09:14:22.118Z"
}cat 01_04_lab.jsonfrom dataclasses import dataclass
import time
@dataclass
class Budget:
max_steps: int = 25
max_seconds: float = 300
max_tokens: int = 200_000
max_dollars: float = 1.00
used_steps: int = 0
used_tokens: int = 0
used_dollars: float = 0.0
started_at: float = 0.0
def tick(self, tokens: int, dollars: float) -> str | None:
self.used_steps += 1
self.used_tokens += tokens
self.used_dollars += dollars
if self.used_steps > self.max_steps: return "steps"
if time.time() - self.started_at > self.max_seconds: return "time"
if self.used_tokens > self.max_tokens: return "tokens"
if self.used_dollars > self.max_dollars: return "dollars"
return Nonepython 01_05_lab.pyGuided steps
Work through each step in order. Stop when your output matches the expected artifact — then push your work to your lab folder.
- 1Exercise 1.1 — Classify five real systems
Do — Pick five software products you use weekly (e.g. Gmail Smart Compose, GitHub Copilot, Stripe billing automation, a customer-support widget, a recommendation feed). For each, decide whether it is a chatbot, a workflow, a copilot, or an agent, and justify the call in two sentences using the section 1.3 test.
Expected artifact — A markdown table with columns: product | category | runtime decisions count | justification.
Hint: If you cannot draw the full control flow ahead of time, it is an agent. Otherwise it is one of the other three.
Run free: - 2Exercise 1.2 — Map the five anatomical components
Do — Pick one open-source agent (suggested: smol-agents, OpenAI swarm sample, LangGraph quickstart). Read its source long enough to point at the file/line where each of the five components (system prompt, memory, tools, reasoning loop, execution environment) lives.
Expected artifact — A 5-row table: component | file:line | one-sentence summary of what's there.
Hint: Start at the entry point and follow the function that runs the loop. The system prompt is usually a constant near the top.
Run free: - 3Exercise 1.3 — Implement the budget guard
Do — Take the Budget class from Code Example 5 and wire it into the minimal ReAct loop in Code Example 1. Force three different terminations by deliberately tuning the budgets: one by steps, one by time, one by dollars (mock the cost).
Expected artifact — Three trace logs, one per termination class, each ending in a structured AgentResult with the right status and budget field.
Hint: You'll need to mock the cost since the example doesn't track real token usage. Add it to the loop where you'd compute it from the response object.
Run free: - 4Exercise 1.4 — Trace a known failure mode
Do — Modify the minimal loop so it deliberately exhibits one of the six failure modes from section 1.13 (tool-call loop is easiest: make the tool always return the same observation). Capture the trace and write one paragraph explaining how you would detect this failure in production without reading the trace by hand.
Expected artifact — A trace file and a short detection-strategy note (e.g. 'same args N times in a row → trip').
Hint: Detection ideas: argument-hash repetition, no change in observed state, monotonic step count without budget delta.
Run free: - 5Exercise 1.5 — Write your own definition
Do — After reading the module, write a 75–125 word definition of 'AI agent' in your own words, suitable for explaining to a senior engineer who has built backend systems for ten years but never touched an LLM. Include at least: goal-directed behavior, the loop, tools, and a budget/guardrail.
Expected artifact — 75–125 word paragraph.
Hint: Lead with the goal-directed framing. The loop is the mechanism. Tools are the interface to the world. Budgets are how you keep your job.
Run free:
Authoritative sources
Independent primary sources used to check and corroborate the guidance on this page.
- NIST National Initiative for Cybersecurity Education (NICE)
US national framework for cybersecurity work roles and competencies.
- Financial Action Task Force (FATF)
Global AML/CFT standards referenced by financial-crime curricula.
- FBI Internet Crime Complaint Center — Annual Reports
Primary loss and typology data used in fraud-investigation training.
- US Federal Trade Commission — Consumer Sentinel Data Book
Official complaint statistics for teaching fraud trend analysis.
