Case Studies Basics
8 examples for a first end-to-end agent case study - 5 basic and 3 intermediate.
Search across all documentation pages
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.
python -m venv .venv && source .venv/bin/activate
pip install pydantic fastapi uvicorn openaiLock 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 toolsdone_when is a demo diary.Related: What a Case Study Should Capture for Future Reference
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"),
}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}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/v1/...) so clients survive prompt changes.turns, status, and latency for metrics.Related: Deploying Agents Basics
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).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)}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 resultturns, latency_ms, status, and cost estimate every call.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",
}
]revisit_when makes deferrals actionable.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.
No. A small host loop teaches the case study shape. Adopt LangGraph, the OpenAI Agents SDK, or similar when state or tools grow.
Still write the case study, but mark deploy as incomplete. The endpoint step matters because production failure modes appear at the boundary.
Honest and short beats polished and empty. Golden-set numbers and one trade-off line are enough for a first artifact.
Only with redaction and legal review. The internal engineering version is the priority.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026