Case Studies Basics
8 examples for a first end-to-end agent case study - 5 basic and 3 intermediate.
You will frame a problem, build a single-tool agent, expose it as an HTTP endpoint, record metrics, and write a one-page case study note.
The running story is a timezone answer agent: one tool, clear done_when, no multi-agent complexity.
Prerequisites
- Python 3.11+ recommended
- Comfort with a simple agent loop (model proposes tool → host runs tool → observe)
- Optional: FastAPI or any ASGI server for the deploy sketch
- Treat API keys as secrets even in demos
python -m venv .venv && source .venv/bin/activate
pip install pydantic fastapi uvicorn openaiBasic Examples
1. Write the Case Study Header Before Code
Lock the frame so you do not rebuild a chatbot by accident.
Title: Timezone Answer Agent v0
User: support ops who need "what time is it in X?"
Goal: return local time for a city or IANA zone
done_when: ISO local time string + zone name, or clear "unknown city"
Non-goals: scheduling meetings, multi-turn travel planning
Success: ≥90% correct on a 20-item golden list; p95 < 3s; <$0.01/call
Bounds: max_turns=4, no write tools- A case study without
done_whenis a demo diary. - Non-goals stop scope creep mid-build.
- Cost and latency targets make "it works" measurable.
Related: What a Case Study Should Capture for Future Reference
2. Define One Tool With a Strict Schema
Single-tool agents still need validation.
from datetime import datetime
from zoneinfo import ZoneInfo
from pydantic import BaseModel, Field
class GetLocalTimeArgs(BaseModel):
tz: str = Field(description="IANA timezone, e.g. America/New_York")
def get_local_time(tz: str) -> dict:
try:
now = datetime.now(ZoneInfo(tz))
except Exception as e:
return {"ok": False, "error": f"unknown_tz: {e}"}
return {
"ok": True,
"tz": tz,
"local_iso": now.isoformat(timespec="seconds"),
"utc_offset": now.strftime("%z"),
}- Prefer IANA zones over free-text cities in v0 (map cities later).
- Return structured errors the model can read as observations.
- No shell, no network - tool risk stays tiny.
3. Run a Bounded Host Loop
The host owns stop conditions, not the model.
from dataclasses import dataclass, field
@dataclass
class RunState:
goal: str
turns: list = field(default_factory=list)
max_turns: int = 4
def run_agent(goal: str, decide) -> dict:
state = RunState(goal=goal)
tools = {"get_local_time": get_local_time}
for i in range(state.max_turns):
decision = decide(state) # model: final answer or tool call
if decision.get("type") == "final":
return {"status": "ok", "answer": decision["text"], "turns": i + 1}
name = decision["tool"]
args = GetLocalTimeArgs.model_validate(decision.get("args") or {})
obs = tools[name](**args.model_dump())
state.turns.append({"tool": name, "args": args.model_dump(), "obs": obs})
return {"status": "stopped", "reason": "max_turns", "trace": state.turns}- Four turns is enough for "normalize zone → call tool → answer."
- Validate args before execution.
- Persist the trace for the case study evidence section.
4. Expose a Minimal HTTP Endpoint
Deployment is part of the case study story.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Ask(BaseModel):
goal: str
@app.post("/v1/timezone-agent")
def timezone_agent(body: Ask):
# wire decide() to your model client in a real app
result = run_agent(body.goal, decide=lambda s: {
"type": "final",
"text": "stub - plug model here",
})
return result- Version the path (
/v1/...) so clients survive prompt changes. - Keep auth off-host for the learning exercise; add a key in intermediate steps.
- Log
turns,status, and latency for metrics.
Related: Deploying Agents Basics
5. Fill a One-Page Case Study After the Spike
Write while numbers are fresh.
## Timezone Answer Agent - case study (YYYY-MM-DD)
**Problem:** Ops chat needs reliable local times without browsers.
**Architecture:** Single agent loop + `get_local_time` + FastAPI.
**Tools:** one read-only tool; no memory store.
**Bounds:** max_turns=4; request timeout 8s.
**Eval:** 20 golden goals; 18/20 correct; 2 city→zone misses.
**Cost:** ~$X / 100 calls (model slug: ...).
**Latency:** p50 0.9s, p95 2.1s (N=50).
**Chose single tool over multi-agent:** complexity not justified.
**Next:** city alias table; then optional OpenRouter routing.
**Links:** ADR-001, eval JSON, sample traces (redacted).- This is the artifact future you will trust.
- Record rejects (what you did not build).
- Link evidence instead of pasting full logs.
Intermediate Examples
6. Golden Set Runner for Honest Metrics
Automate the quality claim in the case study.
GOLDEN = [
{"goal": "Time in America/Los_Angeles", "must_include": "America/Los_Angeles"},
{"goal": "UTC now as Europe/London", "must_include": "Europe/London"},
]
def score_run(answer: str, must_include: str) -> bool:
return must_include in answer
def run_eval(agent_fn) -> dict:
hits = 0
for row in GOLDEN:
out = agent_fn(row["goal"])
hits += int(score_run(str(out), row["must_include"]))
return {"n": len(GOLDEN), "pass": hits, "rate": hits / len(GOLDEN)}- Tiny sets lie less than zero sets.
- Grow to 20+ before calling a pilot "ready."
- Store failures as regression cases when you fix them.
7. Log Cost and Turns Per Request
Case studies need operational numbers, not only accuracy.
import time
from contextlib import contextmanager
@contextmanager
def timed():
t0 = time.perf_counter()
meta = {}
yield meta
meta["latency_ms"] = int((time.perf_counter() - t0) * 1000)
def handle(goal: str, decide, price_per_1k: float = 0.0) -> dict:
with timed() as meta:
result = run_agent(goal, decide)
# plug real token usage from the provider response when available
result["latency_ms"] = meta["latency_ms"]
result["approx_cost_usd"] = price_per_1k # replace with usage-based calc
return result- Log
turns,latency_ms,status, and cost estimate every call. - Case study tables come from these fields aggregated over a week.
- Cap spend with a per-key limit on the provider or gateway.
8. Capture a Trade-Off Line Before Expanding Scope
When someone asks for "also book meetings," write the reject.
DECISIONS = [
{
"date": "2026-07-01",
"fork": "add calendar write tool",
"chose": "defer",
"because": "no HITL gate design yet; timezone read path still failing cities",
"revisit_when": "golden city alias accuracy ≥95% and approval queue exists",
}
]- Scope expansion without a trade-off log is how mini agents become sprawl.
revisit_whenmakes deferrals actionable.- Promote these lines into the case study's trade-off section.
FAQs
Is a single-tool agent "really" an agent?
If a model chooses when to call the tool and when to stop under host bounds, yes. If the path is a fixed script, document it as automation instead of forcing the agent label.
Do I need a framework for this basics case study?
No. A small host loop teaches the case study shape. Adopt LangGraph, the OpenAI Agents SDK, or similar when state or tools grow.
What if I only have a notebook, not a deploy?
Still write the case study, but mark deploy as incomplete. The endpoint step matters because production failure modes appear at the boundary.
How polished must the first case study be?
Honest and short beats polished and empty. Golden-set numbers and one trade-off line are enough for a first artifact.
Should I publish internal case studies externally?
Only with redaction and legal review. The internal engineering version is the priority.
Related
- What a Case Study Should Capture for Future Reference - full capture checklist
- Case Study: Building a Customer-Support Agent with RAG and Escalation - next complexity step
- Deploying Agents Basics - host and target choices
- Stopping Condition Rules Every Agent Must Implement - bounds discipline
- Case Studies Best Practices - habits after the first write-up
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.