Cost, Latency & Evals Across Frameworks
Lesson 12 · Module 6 · Choosing & Shipping · ~12 min read
Three pillars of operating an agent
Building an agent that runs once is one thing; operating it at scale is another. The difference between a demo and a production system isn’t the model or the framework — it’s the discipline of measuring what you spend, what you wait for, and whether the answer is right. Three pillars matter:
- Cost. You have a budget per task, per user, per day, per quarter. Without per-task caps and per-user-per-hour limits, a single runaway loop can torch a month’s budget in an afternoon.
- Latency. Your user (or your downstream code) has patience. UX patience is ~3 seconds for chat; downstream-job patience is ~30 seconds for a research task. Anything longer needs streaming, batching, or expectation-setting.
- Evals. Did the agent give the right answer? Did this commit’s change make any task worse than the last commit’s version? You can’t ship safely without a regression signal.
Lesson 11 covered the cross-framework comparison of cost + latency + observability with the scorecard. This lesson zooms into each pillar in turn, plus an explicit eval rig sketch that’s framework-agnostic. The MCP-bridge sidebar at the end is the bridge between this course’s agents and the Creating MCP Servers course’s tool ecosystem — the moment where a tool stops belonging to one agent and starts belonging to your whole stack.
Cost accounting in practice
Three layers of cost stack up in every agent run:
-
Token cost (the majority). Input tokens + output tokens × per-million
rates. Every framework returns usage data; the shape differs:
- Claude SDK:
result.usage.input_tokens,output_tokens,total_cost_usd(the SDK does the multiplication for you). - OpenAI Agents SDK:
RunResult.usageis adictwith per-model breakdown — you compute USD yourself from the published pricing. - LangGraph: subscribe to
on_llm_endcallbacks; aggregate per node yourself. - CrewAI: the
Crew.kickoff()returned object hasusage_metrics; you sum across the role-played sub-agents.
agent.pyexposes a singlecost_usdfield in its return dict, so the scorecard treats all four frameworks the same way regardless of the underlying SDK’s shape. - Claude SDK:
- Tool execution cost. Usually zero (your code, your CPU). Non-zero when a tool calls a paid API: Tavily ($0.005/request), Serper (free tier then paid), a vector DB on a SaaS plan, etc. A tool-heavy agent making 10 paid API calls per task is sometimes more expensive in tool cost than in tokens — check both.
- Framework overhead. Approximately zero in dollars; non-zero in engineer-time. The “cost” of LangGraph is the hour you spent learning the DAG mental model — paid once. Don’t double-count this against running costs.
Three caps every production agent should have on day one:
- Per-task cap.
max_cost_usd=arg or equivalent. Kills runaway loops before they finish. - Per-user-per-hour cap. App-level. The framework can’t see this — you maintain a counter keyed by user ID.
- Global cost alarm. Your billing dashboard or CloudWatch alarm. The framework can’t see this either, and you don’t want it to: this is a business-level safeguard.
Latency budgeting
Latency = LLM round-trip × turns × (tool wait factor). A useful back-of-envelope formula:
budget_s = max_turns × per_turn_s × (1 + tool_serial_factor)
For the L04 research assistant, plausible defaults:
max_turns = 15(cap, not typical — typical is 6).per_turn_s = 2.5(Sonnet round-trip + small framework overhead).tool_serial_factor = 0.2(some tools run serially after their args are resolved).
That’s ~45s for a worst-case run, ~18s for a typical run. If your UX needs <10s,
cut max_turns first, then drop to a faster model (Haiku,
GPT-4o-mini). Don’t switch frameworks for latency — L11’s
scorecard shows frameworks vary by ~10–30% on latency, but the agent
design (how many turns, how many tools per turn) varies by 200%+. The wrong knob to
turn is the framework choice; the right knob is the loop budget.
Streaming changes the feel of latency without changing the wall-clock. Time-to-first-token drops to ~500ms on most frameworks. For chat UX, streaming is the single biggest perceived-performance lever. For batch jobs, streaming buys you nothing — the consumer is waiting for the final answer anyway, not the prefix.
Per use case, not per framework
Latency budgets should be set per use case, not per framework. A back-office summariser can take 60s; a customer-support reply can’t. Negotiate the number with whoever owns the UX, then build backwards from it — not the other way around.
Evals — beyond “did it crash?”
Four eval flavours, increasing in complexity and signal:
-
Smoke evals. Does the agent return something (non-empty
final_text) on a representative input? The L11 eval harness does this — useful for “did this commit break the basic happy path?” Cheap, fast, and the floor below which nothing else matters. -
Structural evals. Does the output have the expected shape?
E.g., does
LAST_REPORThave non-emptyintro,bullets, andsources? The “final-answer tool” pattern from L04 makes structural evals easy — parse the tool call args, assert the fields. Catches most regressions caused by prompt drift. -
Quality evals. Is the answer good? Two approaches:
- Substring matching. Does each expected fact appear (substring-wise) in the bullets? Crude but reproducible — the same input gives the same score every time.
- LLM-as-judge. Ask Claude or GPT to rate the agent’s answer against a rubric or a reference answer. Higher signal, higher cost (1.5–3× of the agent’s own cost per eval), and the judge itself can drift between model versions.
-
Regression evals. Run the suite on every PR. Track
cost,median_latency, andquality_scoreover time. Any of these drifting by >10% relative to the last green commit is worth a manual review. This is the eval that catches the slow-rot you don’t see in a single CI run.
A minimum rig for the L04 research assistant:
def eval_one(q, expected_bullets):
out = run_research_agent(q)
bullets = out["report"].get("bullets", [])
hits = sum(
1 for expected in expected_bullets
if any(expected.lower() in b.lower() for b in bullets)
)
return {
"q": q,
"score": hits / len(expected_bullets),
"cost_usd": out["cost_usd"],
"n_bullets": len(bullets),
}
Run this across 5–10 known questions. Track the average score over time. When it drops, investigate — don’t just bump the threshold.
Evals are a forcing function, not a substitute
Don’t let “we have an eval suite” become a substitute for “we know what right looks like.” Evals are a forcing function for clarity, not a replacement for judgment. A 90% quality score on a question set that doesn’t cover the real production traffic is worse than no eval — it gives false confidence.
Observability sketch
Every framework emits per-step events you can log:
- Claude SDK:
agent.stream(...)events;on_stephook. - OpenAI SDK:
Runner.run(..., trace=...)plusRunResult.events. - LangGraph: subscribe to node-enter / node-exit events on the compiled graph.
- CrewAI:
Crew(verbose=True)for stdout traces; for structured logs, wrap each task with your own logger.
Pipe the events to a structured logger (structlog, loguru)
keyed by agent_id, turn, tool_name, and
cost_so_far. That’s the minimum production setup — you can query
it in any log aggregator without further investment. Step up to OpenTelemetry / Datadog /
Honeycomb when the agent volume justifies it (typically >10k runs/day or
multi-tenant traffic).
MCP-bridge sidebar
The Model Context Protocol (MCP) lets an agent call tools hosted in a separate process — possibly written in a different language, possibly running on a different host. The pitch: write a tool once as an MCP server; any MCP-aware agent (Claude SDK, OpenAI SDK, LangGraph via adapter, CrewAI via adapter) can call it without re-implementation.
In 2026 Q2 the wins are:
- Reuse across frameworks. The same
pkb_searchtool serves all four of our sweet-spot agents if we package it as an MCP server — no per-framework rewrite of the tool body. - Process isolation. A misbehaving tool can crash without taking the agent down. Memory leaks in a parser stay confined to the tool process.
- Auth at the server boundary. The MCP server owns “who can call what.” The agent process doesn’t need to hold the tool’s credentials.
The course bundles a stub MCP server at
agents/_eval/mcp_stub/server.py that exposes pkb_search over
stdio (the simplest MCP transport). The L12 lab connects to it from a Claude SDK agent
— the same agent shape as L04, but with the tool now hosted in a separate process.
You launch the server as a subprocess, the agent speaks MCP over the subprocess’s
stdin/stdout, and the rest of the loop is unchanged.
What’s not covered here:
- Hosting MCP servers in production (TLS, scaling, monitoring, rollouts).
- Resources / prompts / sampling — the MCP primitives beyond
tools. - HTTP / WebSocket transports — stdio is the easiest to demo, but production usually uses one of the others.
For all of the above, see the Creating MCP Servers course (mcp-servers/).
This lesson’s sidebar is the bridge; that course is the destination.
Production checklist
A reader-takeaway list. Treat it as the floor, not the ceiling — if any item is missing in your deployment, fix it before adding new features:
- Cost cap per task.
max_cost_usd=or equivalent. Always. - Token + cost logging per turn. Structured, indexable, queryable.
- Latency budget documented. Agreed with whoever owns the UX.
- Smoke + structural evals in CI. Every PR runs them; red blocks merge.
- Quality evals in a slower lane. Nightly run on a fixed question set; trend the average.
- Streaming on user-facing endpoints. Buys you perceived-latency for free.
- MCP for any tool used by >1 agent. Reuse beats re-implementation.
- An obvious kill switch. Env var or feature flag that disables the agent on the inevitable bad day — no code change, no deploy.
References
agents/lesson11-choosing-the-right-framework/theory.html— the scorecard’s harness and the rank-order of frameworks on cost/latency/observability.agents/_eval/mcp_stub/server.py— the stub MCP server the L12 lab connects to over stdio.- Creating MCP Servers course (
mcp-servers/) — the deep dive on MCP transports, resources, prompts, and production hosting. - Anthropic MCP adapter docs and OpenAI MCP adapter docs — the per-SDK glue for speaking MCP from the agent side.
LAST-REVIEWED: 2026-05-25