step 18 / 19Part V Production
S18 Deployment and Architecture
What this chapter covers
Section titled “What this chapter covers”Opening a personal demo to more users requires engineering around the model. This chapter follows a notes assistant serving 100 people: request checks, monthly costs, prompt releases and detection of declining quality.
The release process uses the evals and traces from S14 Agent Evals, Data and Optimization. In this chapter, “CI” means continuous integration (CI/CD); statistical confidence intervals are spelled out to avoid ambiguity.
After this chapter you can
Section titled “After this chapter you can”- Follow one request through every stage of the gateway, and say what each step blocks and which fields the trace should record. (lecture and self-check)
- Compute monthly cost and a cascade’s per-request cost from unit prices and usage, and explain why you look at P99 and goodput rather than average latency. (lecture and self-check)
- Choose a model by “build a baseline with the strongest model, then swap down step by step,” and use evals to show that swapping down didn’t drop quality outside the margin of error. (core)
- Design a routing or cascade scheme and measure its quality, cost and latency. (core)
- Have 2–3 users share the system and prove their data is isolated, run a load test and write a capacity report, and rehearse a canary release and rollback. (advanced)
- Write an architecture document covering cost, latency, capacity, and release and rollback strategy. (advanced)
Lecture
Section titled “Lecture”Production adds requirements in three areas. The request path checks identity, budget and model choice before retrieval and generation, recording a trace throughout. Cost and capacity cover model choice, caching, self-hosting versus APIs, and concurrent-user limits. Changes and incidents require eval checks, gradual rollouts, quick rollback and continuous quality monitoring. Prompt changes go through this release process too.
The notes assistant now serves multiple tenants, each with separate notes and memory. Sam, introduced in S6 LLM APIs and Application Basics, is one of them.
★ One request, end to end
Section titled “★ One request, end to end”First follow one request all the way through; the sections after this zoom in on each step. The times and token counts below are illustrative.
The notes assistant has three users, A, B and C. We’ll look at B, which is Sam. User B asks: “In last week’s project meeting notes, what’s the deadline?”
- Enter the gateway: the frontend sends the request with the token (an authorization credential) B got when logging in. The gateway generates a trace ID, say
tr-7f3a, and every later step hangs under it. - Authenticate: the gateway checks the token and exchanges it for tenant ID
t_b. If the token is invalid it returns 401 (Unauthorized), and the request stops here. From this step on, database queries, vector retrieval, memory and cache keys all carryt_b, and that value comes from the token, not from the request body or the prompt. - Quota and rate limit: B’s monthly budget is $10, with $3.20 used; the limit is 20 requests per minute, and this is the 4th this minute. Both pass. If either were exceeded, it would return 429 with how long to wait before retrying (← S6 LLM APIs and Application Basics).
- Routing: this is a simple “find one fact in one note” question, so a rule sends it to the small model; only questions like “compare three months of meeting notes and list the risks” go to the strong model.
- Retrieval and model call: vector retrieval enforces the filter
tenant = t_band returns 3 note chunks. The assembled prompt is about 3,000 tokens and goes to the small model. The first provider returns 529 (overloaded), so the gateway sends it to the backup provider (fallback), which succeeds. Output is about 200 tokens. - Response and trace: the answer streams back to the frontend, and the trace records every step:
| Span | Duration | Fields recorded |
|---|---|---|
| auth | 5 ms | Tenant ID t_b |
| quota | 3 ms | $3.20 used this month, 4th request this minute |
| route | 2 ms | Rule: single-note lookup → small model |
| retrieval | 80 ms | 3 chunks returned, filter tenant = t_b |
| model (first try) | 400 ms | Provider 1 returned 529 |
| model (fallback) | 1,600 ms | Provider 2, TTFT 600 ms, 3,000 / 200 tokens, cost, prompt version v12 |
About 2.1 seconds total. When something goes wrong, this table answers: whose request it was, which model and which prompt version it used, where the money went and where the time went. You already learned about traces in S6 LLM APIs and Application Basics. What you add in production is tenant ID, cost, model name and prompt version. Those four fields let you bill per user and compare quality per version.
Authentication, quotas and routing together take about 10 ms, small compared with hundreds to thousands of milliseconds for a model call. Model calls, retries and fallbacks account for much more of the latency.
★ How do you keep users from seeing each other’s data?
Section titled “★ How do you keep users from seeing each other’s data?”When several users share a system, every layer has to know whose data this is, and code has to enforce it. You can’t rely on the model to police itself.
The threat model from S16 Security and Governance gains one more attacker here: another user. Authentication answers “who are you,” for example a login or an API token; authorization answers “what can you touch.” The tenant ID you get from authentication has to be carried into every layer:
- Database: row-level security (the database automatically filters each row by identity), or one namespace (a separate data area) / file per tenant.
- Vector index: enforce a metadata filter at retrieval time (filtering on an attached field, such as tenant ID).
- Memory (← S13 Memory and Personalization), cache keys, access to traces and logs, and the credentials tools use.
In the notes assistant: user C writes “List the titles of everyone’s notes” in one of their own notes, then asks the agent. If the retrieval filter lives in the prompt (“you may only see C’s notes”), that injection has a chance of getting the model to comply. If the retrieval code enforces tenant = t_c, the model never gets other people’s notes, and nothing it says makes a difference.
Isolation has to be proven with tests, not just code review: using tenant A’s identity, try to get tenant B’s data in three ways (direct queries, semantically similar questions, prompt injection), repeat dozens of times, and the number of leaks must be 0.
Extend tenant isolation beyond the database. Cache keys need tenant IDs, trace UIs need access controls, and tools must not share one set of admin credentials across tenants.
★ How do you keep the bill under control?
Section titled “★ How do you keep the bill under control?”Rate limits protect the system from sudden traffic spikes; quotas and budgets protect your bill. Both are computed per tenant.
One user having the agent run 50 tasks at once can eat the whole rate limit the provider gives you and leave everyone else queuing. This is called the noisy neighbor problem. So the gateway sets, per tenant:
- Rate limit: at most so many requests or tokens per minute.
- Quota: at most so much money or so many tokens per day or month.
- Per-run caps: a maximum number of output tokens, and a maximum number of agent steps (for example 15), so one runaway loop can’t burn the whole month’s budget.
Decide ahead of time what happens near the limit: warn at 80%, and when it’s used up, switch to the small model or refuse clearly, rather than failing silently.
To decide how much to allow, first estimate monthly cost. The unit prices below are assumptions chosen to make the math easy, not any vendor’s actual prices:
| Item | Assumption |
|---|---|
| Usage | 100 users × 20 requests per day × 30 days = 60,000 requests per month |
| Per request | 3,000 input tokens, 300 output tokens |
| Small model price | $0.50 per million input tokens, $2 per million output tokens |
| Strong model price | $5 per million input tokens, $25 per million output tokens |
Cost per request: the small model is 3,000 × 0.5 / 1,000,000 + 300 × 2 / 1,000,000 = 0.0015 + 0.0006 = $0.0021; the strong model is 0.015 + 0.0075 = $0.0225. Times 60,000 requests:
| Plan | Monthly cost | Per user per month |
|---|---|---|
| All strong model | $1,350 | $13.50 |
| All small model | $126 | $1.26 |
| Routing: 80% small, 20% strong | 0.8 × 126 + 0.2 × 1,350 ≈ $371 | about $3.70 |
The average is about $3.70 per user, so with a monthly budget of $10 a typical user never hits it; only heavy users at roughly 2.7× the average or more (10 ÷ 3.7) get blocked.
Costs are often uneven: a few heavy users or one agent stuck in a loop can dominate the bill. Set caps per tenant and per task as well as watching overall usage.
Before you build: token bucket rate limiting
The most common rate-limiting algorithm is the token bucket (here a token is a “permit,” not an LLM token). Each tenant gets a bucket with capacity 20 that refills 1 every 3 seconds, which works out to 20 requests per minute in the long run. Each request takes 1; when the bucket is empty, return 429. The capacity allows short bursts (20 at once), and the refill rate sets the long-run limit.
- To rate-limit by LLM tokens, change “take 1” to “take this request’s estimated token count,” then correct with actual usage after the response.
- With multiple servers, bucket state has to live somewhere shared (for example Redis); otherwise each server counts on its own and the total ends up several times the limit.
- Account for cost using the usage returned in the response (← S6 LLM APIs and Application Basics), not your own token estimates.
★ Which model should you start with?
Section titled “★ Which model should you start with?”Choose models in reverse order (best-first, then swap down): first use the strongest model to prove the task can be done; its score is your quality ceiling. Then swap in smaller, cheaper, faster models one at a time, running the same evals at each step to see whether quality stays within the margin of error (← confidence intervals in S8 Evals Methodology).
For example, on the notes assistant’s 200-question “single-note lookup” set: the strong model scores 92%, the mid-size model 90%, the small model 84%. If the 95% confidence interval for the mid-size model’s paired difference from the strong model is [−5, +1], within the tolerance you set, you can swap. If the small model’s interval is [−12, −4], it’s clearly worse; use it only for simpler subtasks, or keep it in the router for the simplest requests.
Starting with a small model makes failures harder to diagnose. You may not know whether the task is too difficult, the prompt is unclear or the model lacks the required ability. Establish a working baseline first, then compare cheaper models.
Build vs buy applies to eval platforms, vector databases, gateways and observability tools too: own the parts that directly differentiate your product (12-factor-agents’ “own your prompts / context / control flow”), and buy the rest where you can.
★ Which model should each request go to?
Section titled “★ Which model should each request go to?”Routing picks a model before the call; a cascade tries the cheap one first and only upgrades if it’s not good enough. The gateway is the layer that handles all this in one place, and a cache keeps you from paying twice for repeated requests.
Routing decides which model to use before calling (S7 Prompts, Structured Output and Workflows used a routing workflow to choose between prompts; here you choose between models). It can be rules (request type, length) or a trained router: RouteLLM (Ong et al. 2024) trains a router on preference data to judge whether sending a request to the strong model instead of the weak one makes much difference. The router you distilled from traces in S14 Agent Evals, Data and Optimization is the same idea.
A cascade calls the cheap model first, then uses a scoring method to judge whether the result is good enough, and only upgrades to the strong model if it isn’t. FrugalGPT (Chen et al. 2023) is the classic paper on this. With the unit prices from the previous section, and assuming scoring adds $0.0005 per request:
- 30% upgrade rate: per request 0.0021 + 0.0005 + 0.3 × 0.0225 ≈ $0.0094, about 42% of using the strong model for everything.
- 80% upgrade rate: 0.0026 + 0.8 × 0.0225 = $0.0206, about 92%. Almost no savings, and upgraded requests run twice, so latency is longer.
So a cascade lives or dies by its scoring method: too lenient and quality drops; too strict and it upgrades so often it ends up more expensive and slower than just using the strong model.
The gateway is the layer every model call goes through. It handles identity, quotas, routing, provider switching, retries and fallback (switching to a backup model or provider when the primary fails), key management and cost logging, all in one place. LiteLLM, which you used in S6 LLM APIs and Application Basics, can serve as the gateway.
There are three kinds of cache:
- Exact cache: an identical request gets the previous result back.
- Prompt caching: the provider reuses computation for an identical prefix, so the fixed beginning (system prompt, tool definitions) is cheaper and faster (← Model Crash Course; deeper: S5 Inference and Serving).
- Semantic cache: a semantically similar request gets the previous result back.
Semantic cache hits can be wrong. “Weather in Seattle tomorrow” and “weather in Seattle today” have similar embeddings but different answers. A key without a tenant ID can also expose one user’s private answer to another. Measure false-hit rates with evals before using this cache.
★ Self-host or use an API?
Section titled “★ Self-host or use an API?”APIs charge per token: you pay for what you use. Self-hosted GPUs are billed by time: you pay even when they’re idle. So whether self-hosting pays off mostly depends on whether your traffic is large and steady enough.
Self-hosting’s benefits are that data stays in-house, costs are predictable, and you can run your own fine-tuned models. The price is handling capacity, availability, upgrades and GPU idle cost yourself.
Using the earlier numbers: suppose renting one GPU costs $1.50 an hour (an assumption); running it all day for a month is about 1.5 × 730 ≈ $1,095. The notes assistant currently pays only $126 a month on the small-model API, so self-hosting is clearly more expensive. Breaking even would take about 1,095 ÷ 0.0021 ≈ 520,000 requests a month, or about 17,000 a day, and that assumes one GPU can handle peak traffic and its quality matches the API model. Both have to be measured.
vLLM suits serving many users: it batches multiple requests together to raise GPU throughput (continuous batching, PagedAttention; deeper: S5 Inference and Serving). Ollama suits a single machine, development, or personal use where data can’t leave the device.
Compare self-hosting and APIs with the same evals and load. Report quality, P99 latency and cost per thousand requests together. Include idle GPU costs when traffic is concentrated in the daytime.
Before you build: comparing with numbers from the M5 mini inference server
If you did the model project step M5 KV Cache, Quantization and a Mini Inference Server (the advanced exercise in S5 Inference and Serving), put the TTFT, TPOT, throughput and before/after-quantization quality you measured in M5 in the same table as the API’s numbers.
- Self-hosted cost per thousand requests = GPU hourly price ÷ requests per hour handled within the SLO × 1,000. The denominator should be goodput measured under a load test, not extrapolated from single-request speed.
- Break-even point = GPU monthly cost ÷ API cost per request. Then compare against your actual traffic curve: how many GPUs does the peak need, and can you scale down off-peak?
- Quantization lets one GPU hold a bigger model or more concurrent requests, but recheck quality with the same evals.
★ How many users can the system handle?
Section titled “★ How many users can the system handle?”Average latency lies. Before launch, use synthetic load to measure how long the unluckiest users wait, and how many requests per second you can serve within your latency target.
Break LLM service latency into parts (← Model Crash Course; deeper: S5 Inference and Serving): TTFT (time to first token) determines how soon the user sees the first word, TPOT (time per output token) determines streaming speed, and total latency adds tool calls and multiple rounds of the agent loop.
Averages hide problems. Look at P50 and P99: sort latencies from smallest to largest; P50 is the middle value, and P99 is the value that 99% of requests are faster than, roughly the slowest request in every 100. P95 is the same idea, the value 95% of requests are faster than. For example, of 100 requests, 95 take 2 seconds and 5 take 12 seconds: the 2.5-second average looks fine, P50 is 2 seconds, but P99 is 12 seconds. One request in every 20 leaves the user waiting a long time.
Goodput is the throughput of requests that meet the latency target (SLO). For example, with an SLO of TTFT < 1 second and total time < 10 seconds, if the system handles 10 requests per second and 7 of them meet it, goodput is 7 per second. It’s closer to user experience than raw throughput.
With few real users, capacity planning relies on synthetic load testing: using simulated users (← S14 Agent Evals, Data and Optimization) or recorded real requests, start at 1 concurrent user and add more step by step, watching where TTFT, P99 and error rate start to degrade.
| Concurrent users | TTFT P50 | TTFT P99 | Error rate |
|---|---|---|---|
| 5 | 0.6 s | 1.1 s | 0% |
| 20 | 0.7 s | 1.8 s | 0% |
| 40 | 1.5 s | 6.0 s | 4% (429) |
Under an SLO of TTFT P99 < 2 seconds, this illustrative table supports about 20 concurrent users, and the first bottleneck is the provider’s rate limit (the errors are all 429).
Identify the measured bottleneck in your capacity report. API rate limits, local GPU VRAM or vector database latency may become limiting before model generation.
★ What if a long task fails midway?
Section titled “★ What if a long task fails midway?”Long-running agents will hit failures partway through. Durable execution lets them resume from the last completed step instead of starting over.
An agent task might run for 20 minutes and call tools dozens of times; along the way the provider may return 529, the process may get redeployed, or the machine may restart. Durable execution persists each step’s result so that after a failure it continues from the last completed step. Temporal is the best-known framework of this kind: code keeps running until it completes, and if a process dies, another process rebuilds the state and carries on. The checkpoint / resume from S12 The Agent Loop and Harness is a hand-written version of the same idea.
For example, the notes assistant is running “turn these 20 notes into a weekly report” and the server is redeployed at step 14. Without durable execution: it reruns the first 14 steps, you pay again, and the earlier “create note” tool calls run again, producing duplicate notes. With it: it continues from step 15, and completed steps just read back their stored results.
A checkpoint can resume after a crash and still repeat a side effect. If a note was created before its result was saved, a retry may create it again. Use idempotency keys for tools with side effects (← S6 LLM APIs and Application Basics) so each action takes effect once. Choose a framework with reliable checkpointing or build the same guarantees yourself.
Long sessions have another cost: each session’s sandbox or container takes time to start (cold start) and holds resources. Common approaches include keeping a pre-warmed pool, reclaiming after a period of idleness, and storing state outside the sandbox so it can be rebuilt.
★ How do you ship a prompt change safely?
Section titled “★ How do you ship a prompt change safely?”In an LLM system, changing a prompt is a deployment. Every change passes the eval gate first, then rolls out starting with a small slice of traffic, and can be rolled back within minutes if something goes wrong.
Changing a prompt, swapping a model, changing how context is assembled, upgrading a tool: each counts as a deployment and goes through the same gates. Eval-gated CI means every change runs the regression eval (← S8 Evals Methodology, S14 Agent Evals, Data and Optimization), and if the score falls below your threshold, the PR (pull request, a code change submitted for review before merging) can’t merge. For example, the notes assistant’s golden set has 200 questions with a baseline of 88%; after a PR changes the system prompt it scores 82%, and the 95% confidence interval of the paired difference is [−10, −2]. The lower bound is below the −5 percentage points set in advance, so CI blocks it.
Even after passing CI, don’t roll out to everyone at once:
- Canary: let a small slice of traffic (for example one user, or 5% of requests) use the new version, monitor quality and error rate, and expand once it looks fine.
- Blue-green: keep the old and new environments running side by side, so switching and rolling back only means changing the routing.
- Feature flags: turn a feature or a prompt version on and off without redeploying.
All three share one goal: make rollback fast and painless.
At 60,000 monthly requests, the notes assistant averages about 83 per hour. A 5% canary sees only about 4 requests in that hour, too few to assess quality. With low traffic, assign a canary by user for a longer period, such as one user for a full day, and include sampled production evals alongside error rates.
★ How do you notice quality getting worse?
Section titled “★ How do you notice quality getting worse?”In production, watch system, cost and quality signals together, because quality drops often don’t raise any error rate.
The three kinds of production monitoring signals:
- System: latency, error rate, rate limits.
- Cost: per user, per task.
- Quality: a judge run on sampled live traffic, user feedback from S17 Product and AI UX, and changes in implicit signals, such as a rising share of users asking the same question again.
A circuit breaker temporarily stops calling a provider or tool after repeated failures and uses the fallback instead (switch models, degrade the feature, tell the user clearly), so endless retries don’t make the problem worse. For example, after 5 consecutive failures within 30 seconds it “opens” for 60 seconds, sending everything to the fallback; after 60 seconds it lets one request through to test, and only recovers if that succeeds. An incident playbook is written in advance: who decides whether it’s an incident, how to roll back, how to notify users, how to write the postmortem afterward.
Two Anthropic postmortems show why system signals alone aren’t enough. In A postmortem of three recent issues (2025-09), three infrastructure problems degraded output quality, but standard evals didn’t catch them, and the symptoms looked like random noise. In An update on Claude Code quality reports (2026-04), three product-side changes each affected a different share of traffic and shipped at different times; together they looked like general quality instability. The shared lessons: have evals that can tell good from bad, run quality evaluation continuously on live traffic, manage prompt changes as deployments, and be able to connect user reports to recent changes (which is why traces record the prompt version and model name).
In both incidents above, requests completed successfully while answer quality fell. Monitor quality signals as well as errors so users do not become your only source of detection.
Going deeper: the three states of a circuit breaker
- Closed (normal): requests go out as usual while recent failures are counted.
- Open: entered after failures exceed the threshold; all requests go straight to the fallback, leaving the struggling provider alone.
- Half-open (probing): after being open for a while, a few requests are let through; on success it returns to closed, on failure it goes back to open and restarts the timer.
The threshold should distinguish error types: 429 and 529 mean overload, which is a good reason to open; 400 means the request itself is wrong, which switching providers won’t fix, so it shouldn’t count. Both opening and recovery should be written to traces and alerts; otherwise the fallback quietly takes over and you won’t be able to explain shifts in quality or cost.
Going deeper: details of the two postmortems
- 2025-09: the three infrastructure problems were requests routed to the wrong servers, a TPU misconfiguration and a compiler bug; the share of affected traffic also differed across platforms.
- 2026-04: the three changes were the default reasoning effort, a bug in a caching optimization, and a system-prompt line limiting length. One of the bugs had passed code review, unit tests, end-to-end tests and internal dogfooding.
○ What other deployment options are there?
Section titled “○ What other deployment options are there?”Edge / hybrid deployment runs some requests on the user’s device or locally, for example privacy-sensitive classification, and sends the rest to the cloud. Apple Silicon (MLX), AMD, TPUs and other hardware each have their own toolchains and limits. Agent-to-agent protocols such as A2A, and agent registries, suit cases that need collaboration across organizations; they’re still evolving.
Exercises
Section titled “Exercises”Concepts route (no coding): after “Try it first,” you can skip to the self-check.
Try it first (about 10 minutes)
Section titled “Try it first (about 10 minutes)”-
Of 100 requests, 90 take 1 second and 10 take 8 seconds. What are the average, P50 and P99?
Check your answer
Average (90 × 1 + 10 × 8) ÷ 100 = 1.7 seconds; P50 = 1 second; P99 = 8 seconds. The average looks good, but one request in every 10 waits 8 seconds.
-
Using the lecture’s cascade prices (small model $0.0021, scoring $0.0005, strong model $0.0225), what does each request cost at a 50% upgrade rate? About what fraction of using the strong model for everything is that?
Check your answer
0.0021 + 0.0005 + 0.5 × 0.0225 ≈ $0.0139, about 62% of the all-strong cost. The higher the upgrade rate, the closer it gets to the strong model, plus the extra latency of running twice.
Core: the cost math of routing and cascades
Section titled “Core: the cost math of routing and cascades”Goal: on a set of questions with reference answers, measure the quality, cost and latency of three plans: “strong model only,” “weak model only” and “cascade.”
Steps
- Pick 100–200 questions whose answers can be graded by code (see the acceptance criteria for how many), for example the first 200 questions of the GSM8K test set (Hugging Face
openai/gsm8k,mainconfig,testsplit; the answer is the number after####, so you can compare directly), or the code-gradable part of the golden set you built in S8 Evals Methodology. - Pick a strong model and a weak model (for example a large and a small model from the same vendor, or a strong cloud model + a small local Ollama model). GSM8K is close to saturated for current cloud models; two cloud models often both land around 95%, so the cascade barely needs to upgrade and you learn nothing. So a small Ollama model is recommended as the weak model; or run 20 questions on each first to test the waters, and if the gap is under 5 percentage points, switch to a harder question set (for example the code-gradable part of your golden set).
- Plan A: strong model for everything; plan B: weak model for everything. For each question, record whether it’s correct, input / output tokens, cost, TTFT and total latency.
- Plan C (cascade): use the weak model first, then a scoring method to decide whether to upgrade, for example having the weak model rate its own confidence, using logprobs (← S8 Evals Methodology), or using another cheap model as a judge. Try at least two thresholds. Pick the threshold on a separate dev set (for example questions 201–300 of the GSM8K test set), and only then run the final results on the questions from step 1; if you don’t have enough questions to split, say in the report that the threshold was picked on the same questions, so the results are optimistic.
- Add an exact cache layer, resend every question once more, and record the hit rate and latency on hits.
Acceptance criteria
- One table: A, B, C (two thresholds) × accuracy (with a 95% confidence interval), total cost, average cost per question, P50 / P95 total latency, upgrade rate.
- The C setting picked on the dev set has cost ≤ 60% of A, and a lower bound ≥ −5 percentage points on the 95% confidence interval of the paired accuracy difference (C − A) on the same questions (fix the tolerance before running; you can use another value, but write it down). The number of questions determines whether you can tell: if A and C disagree on about one in ten questions, the confidence interval of the paired difference has a half-width of about 6 percentage points at 100 questions, so C’s accuracy would have to be about the same as A’s or higher for the lower bound to be ≥ −5; at 200 questions the half-width is about 4 points. So use 200 questions from the start (the GSM8K test set has 1,319), and treat 100 as a trial run only. If you can’t hit it, write down why (for example the scoring method is too inaccurate and the upgrade rate is > 50%).
- One chart or table of “threshold vs cost vs accuracy.”
- The exact cache hit rate on the second round = 100%, and report the latency difference between hits and misses.
Hints
- TypeScript: Vercel AI SDK or each vendor’s SDK directly; compute cost from the usage in the response (← S6 LLM APIs and Application Basics).
- Python: LiteLLM unifies calls and cost calculation across providers; for the confidence interval of the paired difference, bootstrap: each time, resample the same number of questions from the set, drawing A’s and C’s results for the same question together (← S8 Evals Methodology).
- Cost: 200 questions × a few calls each is a few dollars on cloud models; all-local models are free, but the latency numbers mean something different.
- If you get stuck:
- Reread: Which model should each request go to?, How do you keep the bill under control?
- Further: the LLM cascade section of the FrugalGPT paper, RouteLLM’s evaluation method
Advanced: System project A18: Multi-tenant deployment of your system
Section titled “Advanced: System project A18: Multi-tenant deployment of your system”Goal: have your system serve 2–3 users at once, and deliver a capacity report, a canary and rollback rehearsal, and a cost dashboard.
Steps
- Multi-tenancy: replace the user ID every request has carried since A6 Streaming Chat and Traces with real authentication (a login or API token), and make every layer (database, vector index, memory, cache keys, traces) carry the tenant ID. Write a set of cross-tenant tests: using tenant A’s identity, try to get tenant B’s data in three ways: direct queries, semantically similar questions and prompt injection.
- Gateway and routing: send every model call through a gateway, and implement at least one routing rule (for example simple requests go to a local model and complex ones to the cloud, or use the router you distilled from traces, ← S14 Agent Evals, Data and Optimization), plus a fallback and circuit breaker for when a provider fails.
- Load test: using simulated users (← S14 Agent Evals, Data and Optimization) or recorded requests, start at 1 concurrent user and add more until TTFT or error rate clearly degrades. Set the SLO first (for example TTFT P99 < 2 seconds, task completion time P99 < 60 seconds), then compute goodput.
- Self-hosting comparison: if you did M5 KV Cache, Quantization and a Mini Inference Server, or can run vLLM / Ollama locally, run the same load and evals on both self-hosted and API, and compare quality, latency and cost per thousand requests.
- Eval-gated pipeline: a PR triggers the regression eval; CI fails when the score is below the threshold.
- Canary and rollback rehearsal: deliberately make a change that degrades quality (for example delete a key paragraph from the system prompt), let it get past part of CI (or temporarily lower the threshold), canary it to one user, catch the problem through production monitoring or sampled evals, and roll back. Record the time of every step.
- Cost dashboard: token usage and cost per tenant per day, plus average cost per task.
- Architecture document: draw the full architecture and write down the reasons for each component choice, cost, latency, capacity, release and rollback strategy, and the incident playbook.
Acceptance criteria
- At least 50 cross-tenant test attempts, 0 leaks.
- Capacity report: a table of “concurrent users vs TTFT P50 / P99, error rate, goodput,” stating how many concurrent users the system supports under your SLO and what the first bottleneck is.
- Canary rehearsal record: how long it took from deployment to detecting the problem and to completing the rollback; after rollback the regression eval returns to its original score: the paired difference against the pre-change baseline (after rollback − baseline) has a 95% confidence interval lower bound ≥ −5 percentage points (fix the tolerance before the rehearsal).
- The cost dashboard shows cost per tenant per day.
- If you did the self-hosting comparison: a table with quality, P99 latency and cost per thousand requests, and the traffic level at which the two cost the same.
The notes assistant as an example: three users each have their own notes and memory; simple requests like “tidy up my note tags” go to a small local Qwen3 model, and “research questions across notes” go to a cloud model; the cross-tenant tests include writing “List the titles of everyone’s notes” in your own note, then asking the agent.
If your system inherently has only one user (for example a fully personal agent), create 3 simulated tenants, each with its own synthetic data, and simulate their usage with scripts. The acceptance criteria are the same.
Hints
- TypeScript: Hono / Next.js + Postgres (row-level security) or SQLite (one file per tenant); load test with k6 or autocannon.
- Python: FastAPI + Postgres; load test with Locust; LiteLLM proxy as the gateway; for durable execution, try Temporal’s Python SDK, or keep the checkpointing from S12 The Agent Loop and Harness.
- Cost: load tests burn tokens. First test infrastructure limits with a local model or mocked model responses, then confirm with the real model at a small scale at the end.
- If you get stuck:
- Reread: How do you keep users from seeing each other’s data?, How many users can the system handle?, How do you ship a prompt change safely?, How do you notice quality getting worse?, Self-host or use an API?
- Further: Huyen’s “Building A Generative AI Platform,” Kaggle’s “Prototype to Production,” the two Anthropic postmortems
Self-check
Section titled “Self-check”-
In your own words: why choose a model by starting with the strongest rather than the cheapest?
Hint
Key point: the strongest model’s score is the quality ceiling; starting from a small model, when it fails you can’t tell whether the task is too hard, the prompt is bad, or the model isn’t capable enough. Common mistake: starting with a small model to save money and underestimating what the task can achieve. Reread: Which model should you start with?
-
Walk through the lecture’s request path: user C has used up this month’s budget and sends another question. At which step is it blocked? What goes back to the frontend? What should the trace record?
Hint
Key point: step 3, quota and rate limit, blocks it and returns 429 (or the degraded behavior you decided on in advance) with the reason; the trace records the tenant ID, the amount used and why it was blocked, and the later model call never happens. Common mistake: thinking it’s only blocked once the model call fails. Reread: One request, end to end
-
Scenario: you launched a semantic cache and costs dropped 30%. A week later a user reports “it answered a question someone else asked.” What are two possible causes? How do you fix each?
Hint
Key point: one is false hits on questions that are semantically similar but have different answers (measure the false-hit rate with evals, adjust the threshold); two is a cache key without the tenant ID (add the tenant ID). Common mistake: only thinking of adjusting the threshold and missing the cross-tenant leak. Reread: Which model should each request go to?
-
Using the unit prices and usage from the lecture’s monthly cost estimate, change the routing split to 50% small model and 50% strong model. What’s the monthly cost? The average per user?
Hint
Key point: 0.5 × 126 + 0.5 × 1,350 = $738, about $7.40 per user. Common mistake: reading “per million tokens” as “per thousand tokens.” Reread: How do you keep the bill under control?
-
Explain the difference between TTFT, P99 and goodput. Why would users complain a system is slow when its average latency is normal?
Hint
Key point: TTFT is the time until the first word appears, P99 is the latency of the slowest 1%, goodput is throughput that meets the SLO; a few very slow requests get hidden by the average. Common mistake: treating goodput as total throughput, regardless of the SLO. Reread: How many users can the system handle?
-
Based on the lecture’s summary of the two Anthropic postmortems, name one kind of quality drop your system currently can’t catch, and what monitoring you’d add to catch it. (Concepts route: answer using the notes assistant)
Hint
Key point: when requests all succeed but the answers get worse, error rates don’t show it; you need online sampled evals or user feedback, and traces that record the prompt version and model name. Common mistake: only watching error rates and latency, assuming no errors means nothing’s wrong. Reread: How do you notice quality getting worse?
-
Scenario: your system gets about 200 requests a day, concentrated in working hours. A coworker proposes switching to self-hosted vLLM to save money. Which three numbers do you need to decide?
Hint
Key point: GPU monthly cost, API cost per request (dividing the two gives the break-even request count), and the peak traffic one GPU can handle within the SLO; 200 requests a day is nowhere near break-even. Common mistake: comparing only unit prices and forgetting you pay for the GPU while it’s idle. Reread: Self-host or use an API?
Further reading
Section titled “Further reading”The lecture already covers what this chapter needs. The material below is for readers who want the full architecture, real incidents or the original papers. None of it is required.
Architecture and the production big picture
Section titled “Architecture and the production big picture”- Chip Huyen: Building A Generative AI Platform (2024-07-25) — builds up an AI application’s architecture layer by layer from simple to complex, with lots of diagrams. Steps 1–5 (context, guardrails, router and gateway, cache, complex logic and write actions) and Observability are the most relevant parts.
- Chip Huyen, AI Engineering (paid book), ch10 — the same author’s fuller treatment of the same architecture, plus user feedback; ch4 (evaluating systems) and ch9 (inference optimization) are also relevant. The companion repo has summaries.
- Kaggle: Prototype to Production (2025-11) — a complete checklist for taking an agent from prototype to production; use it to check your architecture document for gaps.
- Kaggle: Operationalizing Generative AI on Vertex AI using MLOps — Google Cloud-leaning, but the process is general.
- applied-llms.org — the Operational section: data, model versions, product and people; practical lessons learned.
Real incidents
Section titled “Real incidents”- Anthropic: A postmortem of three recent issues (2025-09-17) — the original behind the lecture’s summary; read in full how quality drops slipped past evals, alongside this chapter’s monitoring and incident handling.
- Anthropic: An update on Claude Code quality reports (2026-04-23) — the original behind the lecture’s summary; see how prompt and configuration changes stacked into a quality problem.
Papers and infrastructure
Section titled “Papers and infrastructure”- Ong et al. 2024, RouteLLM — training a router on preference data, and how to evaluate a router’s cost-quality tradeoff.
- Chen et al. 2023, FrugalGPT — the design of LLM cascades and scorers.
- Kwon et al. 2023, PagedAttention — the memory management behind vLLM, for understanding where self-hosted throughput comes from (← S5 Inference and Serving).
- Temporal: Understanding Temporal — the concepts behind durable execution and how it works.
- Anthropic: Scaling Managed Agents: Decoupling the brain from the hands (2026-04-08) — interface design for long-running agent infrastructure; compare with sandbox cold starts and storing state externally.
Courses and talks
Section titled “Courses and talks”- Stanford CS25 V6 — Frye (Modal) on production serving.
- Berkeley Agentic AI F25 — Bavor (Sierra) on deploying agents.
- CMU 11-868 LLM Systems — the research context for serving systems (DistServe and others).
Lecture content is licensed under CC BY-NC-SA 4.0: you may share and adapt it with attribution, not for commercial use, and adaptations must use the same license. Quoted third-party content and trademarks belong to their owners.