05 Β· AI & LLM Systems
LLM Ops & Evals
Anyone can call an API. AI-engineering roles are about making LLM features production-grade β fast enough, cheap enough, observable, regression-tested, and injection-aware. You run an LLM gateway and an agentic pipeline in production, so most of this is vocabulary for things you already do.
01The production frame
An LLM in production is a dependency with unusual properties: non-deterministic, slow (seconds), expensive (per token), rate-limited, and silently version-shifting under you. LLM-ops is ordinary production discipline β retries, caching, observability, testing β re-derived for a component with those properties.
02Serving: streaming and token budgeting
TTFT vs decode β the two halves of latency
Total latency β prefill (prompt processing) + decode (output generation, serial per token). A 500-token answer at ~50 tok/s is ~10s β unacceptable as a blank spinner, fine as a stream. Streaming doesn't reduce total latency; it collapses perceived latency to TTFT.
TTFT β time to first token
# perceived responsiveness
dominated by:
prefill (prompt processing)
+ queueing at the provider
improve with:
prefix caching (skip re-prefill)
smaller context
faster model tier
less queue contention
Decode β tokens/sec
# sustained generation speed
serial: one token at a time
500 tok @ ~50 tok/s β 10s
improve with:
shorter outputs (max_tokens,
concise-output instructions)
faster model tier
streaming (hides it, doesn't
shrink it)
Engineering realities worth naming:
- Transport: SSE (typical for provider APIs) or WebSockets; a FastAPI layer proxies with
StreamingResponse/generators. Buffering proxies (nginx defaults) silently un-stream you β a classic gotcha. - Streaming + structured output conflict: you can't
json.loadshalf a document. Options: stream to the UI but parse-on-complete for machine consumers; incremental/partial-JSON parsing; or stream only designated free-text fields. - Downstream effects: mid-stream failures need re-request logic (you can't "resume" a completion); moderation on streams must be incremental; non-user-facing pipeline calls don't need streaming at all β knowing when it doesn't matter is also signal.
Token budgeting
Context windows and wallets are both finite; budget explicitly per request class:
budget = system prompt + tools + history + retrieved context + input + max_output
- Set
max_tokensdeliberately per call type β it caps cost, bounds worst-case latency, and (for agents) prevents a runaway generation from eating the loop's budget. - Budget the inputs: cap retrieved-context size (top-k Γ chunk size), truncate/summarize history and tool outputs, keep tool definitions lean β in agent loops, tool results are usually the context hog (trim at the source).
- Count, don't estimate, where it matters: tokenizer libraries / count-tokens endpoints for pre-flight checks on user-supplied content.
- Per-tenant/per-task ceilings: an agent run gets a total token allowance; exceeding it is a handled failure (escalate/truncate), not a surprise invoice.
03Caching: exact, semantic, prefix
Three different mechanisms β being precise about which is which reads very well:
| Type | Mechanism | Saves | Risk / gotcha |
|---|---|---|---|
| Exact-match | hash(model + params + full prompt) β stored response | Whole call | Hit rate collapses with any variation β timestamps in prompts kill it. Safe for temperature-0 extraction/classification. |
| Semantic | Embed the query; serve cached response if a similar-enough past query exists | Whole call | Threshold is a correctness dial: "reset password on iOS" vs "on Android" embed nearly identically β wrong answer served. Scope per-tenant/per-version, TTL aggressively. |
| Prefix (prompt) | KV cache (attention state) for a shared prompt prefix is stored and reused β skips re-prefilling the shared part | Prefill compute / cost / TTFT | Not a response cache β output is still generated fresh. Requires stable content first; one prompt reordering can kill it. |
04Cost engineering
Cost = Ξ£ over calls of (input_tokens Γ input_price + output_tokens Γ output_price), with output typically severalΓ the input price. Levers in rough order of leverage:
- Model routing β the biggest single lever: route each task to the cheapest model that passes evals (small model for triage/extraction, workhorse for drafting, frontier for hard reasoning). Order-of-magnitude price gaps between tiers mean routing 70% of traffic down-tier transforms the bill. Evals are the referee β routing without evals is just hoping.
- Prompt & context diet β trim boilerplate, cap retrieved context, summarize history, compress tool outputs. Token reduction is a permanent annuity.
- Prefix caching β restructure prompts for cacheability (Β§03).
- Output discipline β concise-output instructions, structured formats, tight
max_tokens(output is the expensive direction). - Batch APIs β ~50% off for async batch (results in minutesβhours). Perfect for non-interactive volume: nightly evals, backfills, bulk classification.
- Fine-tune small to replace big β distill a frontier-prompted behavior into a cheap model once volume justifies the fixed cost.
- Measure per unit of value β cost per resolved ticket / per fix PR / per tenant, and alert on anomalies. A looping agent shows up as a cost spike before anyone notices functionally; budget alarms are incident detection.
05Reliability: retries, fallbacks, circuit breakers
The dependency fails in more ways than a normal API: 429s (rate limits), 5xx/overloaded, timeouts, and semantic failures β malformed JSON, refusals, empty or off-contract output. Handle all of them:
- Retries with exponential backoff + jitter, honoring
Retry-After; retry 429/5xx/timeouts; don't blind-retry 400s (deterministic failures). Distinguish transport retries (same request) from semantic retries (re-prompt with the validation error appended β "your output failed schema X, fix it" β which fixes a surprising fraction of malformed outputs). Cap both; then fall back or fail cleanly. - Fallback chains across models/providers: primary model β same-provider sibling β second provider. Requires a gateway abstraction (yours!): one internal request/response schema, per-provider adapters normalizing message formats, tool-call formats, error taxonomies, token accounting.
- Circuit breakers: provider erroring above threshold β open the circuit, route around it for a cooldown, probe before restoring. Prevents retry storms against a struggling provider (which worsens their brownout and burns your latency budget).
- Timeouts everywhere, sized per call class (a reasoning call legitimately runs minutes; a router call should die in seconds); hedging (fire a second request if p95 is breached) for latency-critical paths if budget allows.
- Graceful degradation as product design: if all LLM paths fail, what happens? Queue-and-retry-later (fine for async pipelines like Sentinel's), serve a cached/template answer, or surface honest failure β decided per feature, in advance.
06Observability for LLM apps
Classic observability tells you the request was 200-in-3s. LLM observability must also answer: what did the model see, what did it say, what did it cost, and was it any good?
Tracing
Treat each user request / agent run as a trace; each LLM call, tool call, and retrieval as spans. Per LLM span, record: model + version, full rendered prompt (system + messages), parameters, completion, token counts (in/out/cached), latency (TTFT + total), finish reason, error/retry/fallback path taken. For agent runs the trace is the debugging artifact β "which tool result poisoned the context?" is answerable only with full transcripts. Tooling names to drop: LangSmith, Langfuse, Braintrust, Arize Phoenix, or OTel GenAI semantic conventions on your existing stack β the concept matters more than the vendor.
Metrics to dashboard
- Token throughput and cost β per feature, per tenant, per model
- Latency β TTFT and total, per model
- Error taxonomy β rate-limit vs timeout vs malformed-output vs refusal
- Cache hit rates (exact / semantic / prefix), fallback activation rate
- Agent-specific: loop iterations, tool-failure rate, tokens per completed task, human-gate approval/rejection rate
Online quality signal: you can't assert correctness per-request, but you can monitor proxies β schema-validation failure rate, refusal rate, output-length drift, judge-scored samples, user feedback/regeneration rate. Drift in these catches silent model updates and prompt regressions between eval runs.
07Evals β testing a probabilistic system
The core inversion: normal tests assert exact outputs; LLM outputs are distributions. So you test properties, scores, and rates over a dataset, with thresholds. "Do you eval, and how" is the question that separates production AI engineers from API tinkerers.
The eval pyramid: golden set β judge β human
A curated dataset of real inputs + expected outputs/criteria β mined from prod traffic (especially failures and human-gate rejections), edge cases, adversarial cases. Even 50β100 well-chosen cases transforms development; grow it every time prod surprises you β every incident becomes a test case. Graded first by deterministic, free checks: schema validity, required fields, length bounds, contains/excludes, does-the-patch-apply / do-tests-pass β for code-generating systems, execution is the best grader there is. Similarity/statistical checks (embedding similarity, exact-match) sit here too: cheap, fuzzy.
A (usually stronger) model scores outputs against a rubric β faithfulness, relevance, tone, correctness-vs-reference. Scales human-like judgment to thousands of cases. Runs on everything the code graders can't decide.
The scarce, expensive apex β used to calibrate the judge on a sample. Judgeβhuman agreement is itself a metric; an uncalibrated judge is a random number generator with gravitas. Human-gate approvals/rejections in prod feed this layer for free.
Regression testing prompts (evals in CI)
Prompts, model versions, retrieval configs, and agent scaffolds are all behavior-defining artifacts β version them, and run the eval suite in CI on any change to them: "prompt change β eval run" exactly as "code change β test run". Also re-run on provider model updates β the dependency shifts under you silently. Tiering mirrors the test pyramid: fast cheap checks per-commit; full judged suite pre-merge/nightly; expensive end-to-end agent evals on schedule. Track score trends, not just pass/fail β slow degradation is the common failure shape.
A/B testing and shadow mode
Offline evals rank candidates; online A/B confirms on real traffic with product metrics (task completion, user acceptance/gate-approval rate, regeneration rate, downstream conversions). LLM specifics: high output variance β longer runs or paired/counterfactual designs; log the variant into traces so quality metrics segment cleanly; and for risky changes, shadow mode first β the new variant runs silently, outputs logged and judged, users see the old one. Shadow mode is the natural pattern for systems with human gates: run the new prompt through the gate pipeline and compare approval rates before switching.
08Safety basics: prompt injection
The vulnerability class every AI engineer must explain fluently in 2026. Root cause: the model has one input channel β the context β in which trusted instructions and untrusted data are concatenated as indistinguishable tokens. Any text the model reads can act like instructions. It's SQL-injection-shaped, but there is no reliable equivalent of parameterized queries β delimiters and "ignore instructions in the data" prompts lower, never eliminate, the success rate. Saying "unsolved; managed architecturally" is the correct expert position.
Mitigations, layered
| # | Layer | What it does |
|---|---|---|
| 1 | Least-privilege tools | What the model can't do can't be injected into happening. Read-only where possible; scoped tokens; no general egress. |
| 2 | Break the lethal trifecta | Private data + untrusted input + exfiltration channel (cite Willison) β an agent holding all three is exploitable; remove or gate one leg. Sentinel's answer: untrusted input and repo access coexist, so every write action funnels through adversarial review + human approval β the exfiltration/action leg is gated. |
| 3 | Privilege separation | A quarantined model processes untrusted content and returns structured, constrained data (never free-text instructions) to the privileged orchestrator β the dual-LLM idea; CaMeL-style designs formalize it. |
| 4 | Mark & delimit untrusted content | Helps, not sufficient; strip/flag instruction-like content at ingestion. |
| 5 | Detect | Injection classifiers on inputs; output anomaly checks (why does this PR description contain a base64 blob?); log and red-team β put known injections in the eval set and track resistance as a metric. |
| 6 | Human gates on consequential actions | The backstop when everything above fails. |
Adjacent basics worth one line each: secrets/PII redaction in prompts and traces; output moderation for user-facing text; supply-chain trust for third-party MCP servers/tools (their descriptions and outputs enter your context); insecure-output-handling β never eval/render/execute model output without the same sanitization you'd give user input. The model is a user.
09Rapid-fire interview Q&A
Your LLM feature is too slow β walk through fixes.
Your LLM bill doubled β what do you do?
How do you test a prompt change?
Exact vs semantic vs prefix caching?
How would you attack your own system with prompt injection?
10Self-test
What do TTFT and tokens/sec measure, and which does streaming improve?
TTFT = time to first token (perceived responsiveness; dominated by prefill + queueing). Tokens/sec = sustained decode speed. Streaming improves neither number β it collapses perceived latency to TTFT; total latency is unchanged.
Why can a single prompt reordering change your bill dramatically?
Prefix caching reuses the KV cache only for the shared prefix. Moving volatile content (timestamps, user input) above stable content (system prompt, tools, few-shot examples) breaks the shared prefix, so every request re-pays full prefill β cached reads at ~90% discount vanish.
Name the two kinds of retry and when each applies.
Transport retries β same request, for 429/5xx/timeouts, with exponential backoff + jitter, honoring Retry-After. Semantic retries β re-prompt with the validation error appended ("your output failed schema X, fix it") for malformed/off-contract outputs. Never blind-retry 400s; cap both, then fall back or fail cleanly.
Three biases of LLM-as-judge, plus mitigations?
Position bias (swap order and average in pairwise comparisons), verbosity bias (longer scored better), self-preference (models favor their own family). Mitigate: rubric + reference answers, forced reasoning-before-score, low temperature, pairwise where possible β and calibrate against human labels; judgeβhuman agreement is itself a metric.
What is the lethal trifecta, and how does a human-gated pipeline break it?
Private data + untrusted input + an exfiltration channel β an agent holding all three is exploitable (Willison). Remove or gate one leg: when untrusted input and repo access must coexist, funnel every write action through adversarial review + human approval, gating the exfiltration/action leg.
Rank the cost levers and justify the top one.
1 model routing, 2 prompt/context diet, 3 prefix caching, 4 output discipline, 5 batch APIs, 6 fine-tune small to replace big, 7 measure cost per unit of value + budget alarms. Routing wins because price gaps between tiers are order-of-magnitude β sending 70% of traffic down-tier transforms the bill; everything else shaves percentages. But routing without evals is just hoping β evals are the referee.
Why are prompt/response logs a security problem?
They contain user content plus whatever secrets wandered into context β observability tooling is a favorite exfiltration target. Apply redaction, access control, and retention policies; sample intelligently (full transcripts only for errors, gate-rejections, and a random slice).