~learn.

Day 0528 minPreface+60 XP

Cost, Caching and Operations.

6 stages4 code

watch it first · newThe whole lesson in a 24-minute animated lecture5 quiz checkpoints · ask the AI about any moment · captions, chapters and speed

Preface · 2 min

Preface#

Sale week at MegaMart. MegaMart Assist has been live for a month, answering return and refund questions with Day 3's retrieval behind Day 4's guardrails. The answers are good. The bill and the pager are not. (An illustrative scene: the times and amounts are made up; each failure is a common one.)

SALE WEEK AT MEGAMART ASSIST (ILLUSTRATIVE)
  • Mon 10:30 —
    Prompt v14 ships. To quote delivery cut-offs, it adds one line at the very top of the system prompt: Current time: 10:30:07 IST
  • Mon 10:35 —
    The share of input tokens read from the provider's prompt cache drops from about 90% to almost zero. Nobody has a graph of it
  • Mon 20:30 —
    Peak hour. Counted input tokens per minute triple and the provider answers 429
  • Tue 02:00 —
    One customer's order-lookup tool keeps failing and the model keeps calling it: 40 model calls in one conversation, with no step limit
  • Thu 14:10 —
    The provider returns 529 "overloaded" for 40 minutes. Traffic fails over to a second provider all at once, and the hour costs triple
  • Fri 10:00 —
    Finance projects the month at 2.8× the forecast and asks which feature spent it. The logs say only model and tokens

None of these is a model problem. They're old distributed-systems problems: a cache key that changes on every request, a loop with no limit, a shared quota with no owner, a failover nobody costed, a bill with no labels. The new part is that the dependency is metered by the token, so every mistake lands on the invoice.

Yesterday (Day 4) we measured quality and fenced the assistant in, and those judges, screens and grounding checks are model calls too. Today we make the whole system affordable and operable: what a conversation costs, three kinds of cache, routing easy turns to a cheaper model, batch work at half price, budgets that degrade before they cut off, failover that doesn't blow the budget, the telemetry that says who spent what, and the runbooks for the incidents above.

ExplainerThe whole lesson: cost, caching and operations

Sale week at MegaMart Assist: a clock on the prompt's first line, 429s at the peak, a 40-call loop, a 529 failover and a bill 2.8× the forecast. Tokens are billed on four meters (Sonnet 5.5: $2 input, $2.50 cache write, $0.10 cache read, $10 output), so a 6-turn conversation is 23,010 input and 1,500 output tokens, $0.061, and a 15-turn one ~3.2× that. Ordering the prompt stable to volatile makes the prefix a cache read, cutting a conversation to $0.022 and counted input from ~3.45M to ~290,000 tokens a minute; a timestamp on line one breaks it. Semantic caches give confident wrong answers on near-misses, a cascade pays unless ~95% escalate, routing 60% to Haiku gives $0.0096 (~$17,200 a month), and nightly work goes to the Batch API at half price. A budget governor reserves the worst case and settles after, a spend ladder degrades at 80% and 95%, a step cap stops loops, retries live in one layer, and failover to a cold cache costs ~3×, so it shifts in steps. OpenTelemetry spans carry tokens, feature and cost, so 'who spent it' is a group-by.

Part I · Stage 1 of 6 · 2 min

Part I: What You Pay For#

Chapter 1: Token Economics#

1.1 The Simple Definition#

An LLM API bills per token, on four meters: fresh input, input written to the provider's cache, input read from that cache, and output. Each meter has its own price on each model. A request's cost is the sum of the four meters times their prices. Everything in this lesson either moves tokens to a cheaper meter or stops sending them.

1.2 The Price List#

Day 1 showed the pattern: output costs about 5× input, and models span 20× or more. Today we also need the cache-write column. Anthropic's list prices as of October 2026, per million tokens (MTok), used as this lesson's example prices:

Model (Anthropic, October 2026) Input 5-min cache write Cache read Output
Claude Sonnet 5.5 $2 $2.50 $0.10 $10
Claude Haiku 5.5 (prompts up to 100,000 tokens) $0.10 $0.125 $0.01 $0.50

Two warnings before using any price. Tokens aren't words: Anthropic says its newer tokenizer "produces approximately 30 percent more tokens for the same text than earlier models", and Day 1 showed counts differ by language. And reasoning is output: thinking counts toward max_tokens "even when the thinking content isn't returned" (Anthropic effort docs), and OpenTelemetry counts reasoning tokens inside output tokens. So compare models on cost per resolved conversation, measured from real usage blocks, and keep prices in a dated config table, not in code.

1.3 Per Request, Per Conversation, Per User#

pythonrequest_cost · 12 lines
# USD per million tokens: Anthropic list prices as of October 2026 (keep in dated config)
PRICES = {
    "claude-sonnet-5-5": {"in": 2.00, "write_5m": 2.50, "read": 0.10, "out": 10.00},
    "claude-haiku-5-5":  {"in": 0.10, "write_5m": 0.125, "read": 0.01, "out": 0.50},
}   # Haiku 5.5 prompts over 100,000 tokens are billed at higher rates

def request_cost(model: str, usage) -> float:
    p = PRICES[model]
    return (usage.input_tokens * p["in"]                    # only tokens after the last breakpoint
            + usage.cache_creation_input_tokens * p["write_5m"]
            + usage.cache_read_input_tokens * p["read"]
            + usage.output_tokens * p["out"]) / 1_000_000

Day 1's numbers: a ~3,000-token system prompt, ~60-token messages, ~250-token replies and 6 turns give 23,010 input and 1,500 output tokens per conversation, ~$0.061 on Sonnet 5.5, three quarters of it input, because each turn resends everything before it. A user's cost is the sum over their conversations; a feature's is the sum over its calls. Both need labels on every call (Chapter 8).

Your turn · checkpoint 1not answered

Estimate first: the seller who never stops typing

A marketplace seller has 40 conversations a month with MegaMart Assist, each 15 turns long, with the usual message and reply sizes. With no caching, on Sonnet 5.5, what does one of their conversations cost, and what do they cost a month? Why isn't a 15-turn conversation simply 2.5× a 6-turn one?

Think it through, or jot it down. Then compare.

One strong answer

  • Input: the system prompt is sent 15 times (45,000 tokens), messages pile up (60 × (1 + … + 15) = 7,200) and so do earlier replies (250 × (0 + … + 14) = 26,250): ~78,500 tokens → $0.157. Output: 15 × 250 = 3,750 → $0.0375. ~$0.19 a conversation, ~$7.80 a month.
  • ~3.2×, not 2.5×: history is resent every turn, so the history part grows with the square of the turn count.
  • So budget per user and per conversation, not only per request: a few long conversations can cost more than hundreds of short ones.

Part II · Stage 2 of 6 · 6 min

Part II: Caching#

Chapter 2: Provider Prompt Caching#

2.1 How It Works#

Day 1 explained that prefill turns every input token into key/value states, the KV cache. Prompt caching keeps those states for a prefix between requests, so the next request that starts with exactly the same prefix skips reprocessing it. From Anthropic's documentation, as of October 2026:

  • Order: "Cache prefixes are created in the following order: tools, system, then messages." You mark where a prefix ends with cache_control (up to 4 breakpoints), or use automatic caching, where "the cache point moves forward automatically as conversations grow".
  • Exact match: "Cache hits require 100 percent identical prompt segments" up to the marked block, and the prefix must be at least 512 tokens on the 5.5 models.
  • Lifetime: 5 minutes, "refreshed for no additional cost each time the cached content is used"; a 1-hour lifetime costs more.
  • Price: a 5-minute write is 1.25× input, a 1-hour write 2×, a read 0.1× on most models (0.05× on Opus 5.5 and Sonnet 5.5, 0.025× on Fable 5.1). Caching "pays off after one cache read" at 5 minutes.
  • No quality cost: "The response you receive is identical to what you would get if prompt caching were not used."
  • Isolation: per workspace; "Different organizations never share caches, even if they use identical prompts."
  • Bursts miss: "a cache entry only becomes available after the first response starts", so a thousand identical requests sent at once all pay full price.
DiagramOrder the prompt from most stable to most volatile
%% title: Order the prompt from most stable to most volatile
flowchart TD
  T["1 · Tool definitions<br/>change only on deploys"]:::ok
  SP["2 · System prompt and policies<br/>~3,000 tokens, change only on deploys"]:::ok
  H["3 · Earlier turns of this conversation<br/>written to cache by the previous turn"]:::ok
  BP{{"Breakpoint: everything above is a cache read"}}:::core
  N["4 · This turn only<br/>retrieved passages, current time, the new message"]:::bad
  T --> SP --> H --> BP --> N

The response reports three input fields: cache_read_input_tokens, cache_creation_input_tokens, and input_tokens, which counts only tokens after the last breakpoint. Total input is their sum. A chart of input_tokens alone shows a working cache as a mysterious 90% drop in traffic.

Caching is also capacity. Day 1's calc found that ITPM binds at MegaMart's peak, and for most Claude models "only uncached input tokens count toward your ITPM rate limits". That's why Monday's cache-busting deploy at 10:30 became a rate-limit incident at 20:30.

Your turn · checkpoint 2not answered

What breaks if the first line is the clock?

Prompt v14 puts Current time: 10:30:07 IST as the first line of the system prompt, above 3,000 tokens of policy. What happens to cache hits, the bill and the ITPM limit? Where should the time go?

Think it through, or jot it down. Then compare.

One strong answer

  • Every request has a new prefix, because matching is exact and the clock changes every second. Nothing is read from cache any more, not even the conversation's own history: the system prompt is written at 1.25× on every call and never read.
  • Bill: the 18,000 system-prompt tokens per conversation move from $0.10/MTok (read) to $2.50/MTok (write). ITPM: writes count and reads don't, so counted input jumps several-fold and the evening peak hits 429s.
  • Fix: put the time in the newest turn, below the breakpoint, rounded to what the model needs. Then alert on cache-read share per prompt version.

2.2 The Same Idea at Three Providers#

(as of October 2026) Anthropic OpenAI Google Gemini
Turned on by cache_control, explicit or automatic "enabled by default" Implicit: automatic on 2.5 and newer; explicit: you create a cache
Minimum prefix 512 tokens on the 5.5 models 1,024 tokens for GPT-5.6 and later Implicit: 4,096 tokens on newer models, 2,048 on 2.5
Lifetime 5 min (refreshed on use) or 1 h GPT-5.6+: 30 min default or 12 h (Responses API); earlier models "around 5 to 10 minutes of inactivity, up to one hour", or 24 h Explicit: your TTL; "If not set, the TTL defaults to 1 hour"
Write price 1.25× (5 min), 2× (1 h) GPT-5.6+: 1.25× (30 min), 2.5× (12 h); earlier models: no write charge Explicit: storage billed for the TTL
Read price 0.1× on most models (0.05× on Opus and Sonnet 5.5, 0.025× on Fable 5.1) 0.1× on most GPT-5.6+ models Reduced rate; implicit has "no cost saving guarantee"
Usage field cache_read_input_tokens usage.input_tokens_details.cached_tokens usage_metadata

The advice is the same everywhere: OpenAI says "put stable developer instructions and shared reference material first", Gemini says put "large and common contents at the beginning of your prompt". Two portability traps: on OpenAI models before GPT-5.6, routing to a cache uses a prompt_cache_key, at "about 15 requests per minute" per key; and Gemini's explicit caching is "not supported for Gemini 4 Argon and onward".

Chapter 3: Application-Level Caches#

A prompt cache still runs the model on every request. An application cache skips the model and returns a stored answer: Week 4's cache-aside with a new kind of key.

3.1 Exact Caching#

The key is a hash of everything that determines the answer: model, parameters, prompt version, messages and retrieved passages. Cloudflare's AI Gateway works this way, serving cached responses "for identical requests", with a TTL, a skip-cache header and a custom-key header (Cloudflare docs). It's safe, but customers rarely phrase things identically, so it hits mostly on machine-made prompts: a classification, a summary of a shared policy page, a re-run.

3.2 Semantic Caching#

A semantic cache embeds the question (Day 2), finds the nearest past question, and returns its stored answer if it's similar enough. GPTCache's modules spell out the parts: an embedding generator, cache storage, a vector store that finds "the K most similar requests", and a similarity evaluator that decides whether it's a hit. Its README names the catch: "In a semantic cache, you may encounter false positives during cache hits and false negatives during cache misses" (GPTCache).

A false hit in a support assistant is a confident, wrong answer that skipped the model and Day 4's guardrails:

Risk Example Why similarity misses it
One small word "Can I return opened earphones?" vs "unopened earphones?" The answer flips; the embeddings barely move
Entities "Refund for order 4411?" vs "order 4471?" Numbers hardly change an embedding
Personal answers "When does my refund arrive?" The right answer depends on who asks
Staleness The return window changes from 10 to 7 days The stored answer cites the old policy
Leaks A stored answer containing a customer's name or order A key without user scope hands it to the next person

The rules follow. Scope the key by tenant, language and prompt version. Cache only non-personal answers: general policy, never "my order". Invalidate from the sources: cached answers are derived data (Day 3), so when a policy page changes, or a customer's data must be erased (Week 9, Day 4), purge every answer built from it (Week 4, Day 2 · Cache Invalidation). Set the threshold from data: grade a sample of hits with Day 4's judge or a human, and raise the threshold until the false-hit rate is acceptable.

Your turn · checkpoint 3not answered

Which questions may skip the model?

You can put a semantic cache in front of MegaMart Assist. Would you let it answer (a) "what is the return window for electronics", (b) "where is my order 88213", (c) "can I return a used pressure cooker", (d) the nightly job's "summarise this conversation" calls? What do the others use instead?

Think it through, or jot it down. Then compare.

One strong answer

  • (a) Yes: general, non-personal, asked in many phrasings. Scope by language and prompt version; purge when the returns page changes.
  • (b) No: personal and live, answered by the order-lookup tool. A hit could show one customer another's order.
  • (c) Risky: "used" vs "unused" is exactly the flip similarity misses. Leave it to the model, or cache only above a threshold you've measured.
  • (d) Exact, not semantic: every conversation differs, so a semantic hit is a wrong summary. An exact cache only helps re-runs.

3.3 Three Caches Compared#

Three caches for one assistant

Provider prompt cache

  • Skips: reprocessing a stable prefix; the model still runs
  • Risk: none, output unchanged
  • Hits depend on: prefix stability, traffic inside the TTL
  • Use for: system prompts, tools, history

Exact answer cache

  • Skips: the model call
  • Risk: low, if the key covers every input
  • Hits: rare in chat, common for repeated machine prompts
  • Use for: classification, shared summaries, re-runs

Semantic answer cache

  • Skips: the model call for a similar question
  • Risk: false hits, stale and leaked answers
  • Needs: scoped keys, measured threshold, source-driven invalidation
  • Use for: general, non-personal, frequent questions only

Part III · Stage 3 of 6 · 3 min

Part III: Spending Less Per Answer#

Chapter 4: Routing and Cascades#

Most support turns are easy, and "where is my refund?" doesn't need the large model. Two designs use that (Day 4 owns fallback when a model fails; this is choosing a model by difficulty):

  • A router decides before the call. A cheap classifier or rules pick the model. RouteLLM, for example, trains routers on human preference data to choose between a stronger and a weaker model (Ong et al., 2024).
  • A cascade decides after. Ask the cheap model, score its answer, escalate only if the score is low. FrugalGPT's cascade "sends a query to a list of LLM APIs sequentially. If one LLM API's response is reliable, then its response is returned"; it accepts an answer "if the score is higher than a threshold" and queries the next model otherwise (Chen, Zaharia and Zou, 2023).
DiagramA two-step cascade for one turn
%% title: A two-step cascade for one turn
flowchart TD
  Q["Customer turn<br/>with retrieved passages"]:::core
  SM@{ icon: "sd:ml", label: "Small model<br/>Haiku 5.5" }
  SC{"Scorer<br/>grounded? confident?<br/>score ≥ threshold"}
  OK(["Send the small model's answer"]):::ok
  LM@{ icon: "sd:ml", label: "Large model<br/>Sonnet 5.5" }
  OUT(["Send the large model's answer"]):::ok
  Q --> SM --> SC
  SC -->|"yes"| OK
  SC -->|"no: escalate"| LM --> OUT
  class SM,LM external
pythonanswer_turn · 6 lines
def answer_turn(convo, threshold: float):
    draft = llm("claude-haiku-5-5", convo, effort="low")
    score = scorer(convo, draft)        # trained and calibrated on Day 4's graded set
    if score >= threshold:
        return draft, "small"
    return llm("claude-sonnet-5-5", convo), "escalated"   # this turn pays for both calls

FrugalGPT reported matching GPT-4 "with up to 98% cost reduction" on its benchmarks in 2023: the paper's result on its tasks, not a promise for yours. Its scorer needs graded examples "from the same or similar distribution as the test examples", which is what Day 4's golden set provides.

Your turn · checkpoint 4not answered

Estimate first: when does the cascade stop paying?

With caching, a MegaMart conversation costs about $0.0012 on Haiku 5.5 and $0.022 on Sonnet 5.5 (this lesson's calc). In a cascade every conversation pays for Haiku and escalated ones pay for Sonnet too. Above what escalation rate does the cascade cost more than all-Sonnet? At 40% escalation, what do you save, and what do escalated customers pay?

Think it through, or jot it down. Then compare.

One strong answer

  • Break-even: $0.0012 + p × $0.022 = $0.022 → p ≈ 95%. With the small model ~18× cheaper, the cascade wins unless almost everything escalates.
  • At 40%: $0.0012 + 0.4 × $0.022 ≈ $0.010, about 55% less than all-Sonnet.
  • The price is latency: escalated turns wait for two models in series. A router avoids that but decides blind. A common mix: route the obvious cases, cascade the uncertain middle.

Three practical points:

  • Keep a conversation on one model. The cache holds a model's own processed prefix, so a turn escalated to another model reprocesses the whole history at full price.
  • Lower effort is a third option. Anthropic's effort parameter trades "response thoroughness and token efficiency with a single model"; low gives "significant token savings with some capability reduction". Kept fixed for a conversation, it keeps the cache warm (changing effort mid-conversation invalidates cached messages, unless you use the beta per-message effort setting); test it on the same eval set.
  • Alert on the escalation rate. If it creeps from 30% to 70%, the questions or the small model changed, and so did your bill.

Chapter 5: Batch APIs for Offline Work#

MegaMart's nightly job tags every closed conversation (reason, resolved or not, sentiment) for the support team: 60,000 calls nobody is waiting for. Batch APIs take a file of requests and return results later, at half price (as of October 2026):

Anthropic Message Batches OpenAI Batch API
Price "50 percent of the standard API prices" "50% cost discount compared to synchronous APIs"
Size 100,000 requests or 256 MB per batch 50,000 requests; input file up to 200 MB
Turnaround "most batches completing within 1 hour"; expire after 24 hours "within 24 hours (and often more quickly)"
Unfinished work errored, canceled and expired requests aren't billed Unfinished requests cancelled; completed ones charged
Results kept 29 days Output file deleted after 30 days

Batch limits are separate from the real-time ones on both. On Anthropic, batch and caching discounts stack, but cache hits are "best-effort", typically "30 percent to 98 percent", and the docs suggest the 1-hour cache lifetime for shared context because batches can outlast 5 minutes.

DiagramThe nightly tagging job on the Batch API
%% title: The nightly tagging job on the Batch API
sequenceDiagram
  participant C as Nightly job (02:00)
  participant DB as Conversation store
  participant B as Batch API
  participant T as Tags table
  C->>DB: Yesterday's closed conversations (about 60,000)
  C->>B: Create batch, custom_id = conversation id
  B-->>C: batch id, in progress
  loop every few minutes
    C->>B: Poll status
  end
  B-->>C: Ended, results file
  C->>T: Upsert tags by custom_id (idempotent)
  Note over C,T: errored or expired rows join tomorrow's batch

custom_id is the idempotency key (Week 2, Day 2 · Idempotency): results come back in any order and a re-run must not double-write. Good candidates: tagging and summaries, Day 4's offline eval runs, backfills after a prompt change, and re-embedding (Day 2; OpenAI's batch endpoint also takes embeddings). Bad ones: anything a person is waiting for.

Part IV · Stage 4 of 6 · 4 min

Part IV: Staying Inside Limits#

Chapter 6: Budgets and a Budget Governor#

6.1 Provider Limits Aren't a Plan#

Day 1 covered the provider's limits, including monthly spend caps whose 429 has no retry-after. Anthropic also lets you set your own lower spend limit (requests then fail with a 400) and give each workspace its own spend and rate limits, a useful outer wall between features. But provider limits know nothing about tenants or users, and they fail by stopping everything at once. You want limits that know who is spending and degrade gradually.

6.2 Reserve, Then Settle#

A budget governor is a rate limiter whose unit is money, keyed by feature, tenant, user and conversation (Week 1, Day 3 · Rate Limiting has the counters). A call's cost is unknown until it ends, so reserve the worst case before the call and settle the real cost after. max_tokens bounds the worst case:

pythonBudgetGovernor · 20 lines
class BudgetGovernor:
    """Daily USD budgets for every key a call touches (feature, tenant, user, conversation)."""
    def __init__(self, store, limits: dict[str, float]):
        self.store, self.limits = store, limits          # store: atomic counters with a TTL

    def reserve(self, keys: list[str], worst_case: float) -> bool:
        taken = []
        for k in keys:
            if self.store.incrbyfloat(f"spend:{today()}:{k}", worst_case) > self.limits[k]:
                for t in taken + [k]:                     # roll back every key we touched
                    self.store.incrbyfloat(f"spend:{today()}:{t}", -worst_case)
                return False
            taken.append(k)
        return True

    def settle(self, keys: list[str], reserved: float, actual: float):
        for k in keys:                                    # actual = request_cost(model, usage)
            self.store.incrbyfloat(f"spend:{today()}:{k}", actual - reserved)

# worst case for one call: all input at the full input price + max_tokens at the output price

A sensible max_tokens keeps reservations honest; a huge one makes every reservation huge and starves the budget.

6.3 Degrade Before You Cut Off#

DiagramA spend ladder for one feature's daily budget (illustrative thresholds)
%% title: A spend ladder for one feature's daily budget (illustrative thresholds)
stateDiagram-v2
  [*] --> Normal
  Normal --> Thrifty: 80 percent of budget spent
  Thrifty --> Minimal: 95 percent spent
  Minimal --> Handoff: budget spent
  Thrifty --> Normal: new day
  Minimal --> Normal: new day
  Handoff --> Normal: new day
  note right of Thrifty
    small model for every turn
    lower effort, shorter replies
  end note
  note right of Minimal
    pause optional features
    move deferrable work to batch
  end note
  note right of Handoff
    polite message, human handoff
    never a raw error
  end note

The ladder is per feature: when the nightly tagging budget runs out, the chat keeps going. Alert at the first step, not the last.

Your turn · checkpoint 5not answered

The 40-call conversation

Tuesday 02:00: an order-lookup tool fails and the model keeps calling it. One conversation makes 40 model calls, each resending a growing history. Which controls stop it, in what order, and which would you rely on?

Think it through, or jot it down. Then compare.

One strong answer

  • A step cap per turn (say, 5 tool calls, then hand off) stops it first, at the loop. Rely on this one: cheap, local, immediate.
  • A per-conversation budget stops it next, and catches loops you didn't predict.
  • Per-user and per-feature budgets stop it much later, after it has eaten other customers' share; the provider's cap is last and stops everyone.
  • Also: detect repetition (same tool, same arguments, three times) and make the tool's error tell the model not to retry.

Chapter 7: Retries and Failover, Costed#

Day 1 set the retry rules for 429 and 529 (honour retry-after, jitter, a budget, never retry a spend-cap 429), and Day 4 built the fallback ladder. Two operational details remain.

Retry in one layer. The official SDK "automatically retries transient failures ... with exponential backoff, twice by default". Wrap it in your own 5-retry loop and a failing turn makes up to 6 × 3 = 18 calls; a retrying gateway in front multiplies again. Retries amplify load (Week 2, Day 3 · Circuit Breakers), so retry only in the gateway and build its clients with max_retries=0:

pythoncall_with_policy · 18 lines
RETRYABLE = {429, 500, 504, 529}

def call_with_policy(req, providers, deadline: float):
    for p in providers:                                    # primary, then Day 4's tested fallbacks
        if p.breaker.is_open() or not p.ramp.admit():      # shift traffic a share at a time
            continue
        for attempt in range(3):
            try:
                return p.send(req)                         # SDK client built with max_retries=0
            except ProviderError as e:
                if e.code == "enforced_spend_limit_reached" or e.status not in RETRYABLE:
                    break                                  # retrying here can't succeed
                wait = e.retry_after or random.uniform(0, min(8.0, 0.5 * 2 ** attempt))
                if not retry_budget.take() or time.monotonic() + wait > deadline:
                    break
                time.sleep(wait)
        p.breaker.record_failure()
    raise AllProvidersFailed()                             # caller takes Day 4's handoff path

Failover has a price. The fallback provider has never seen your prefix, so its cache is cold: every input token is fresh, billed at the full input price and counted against the fallback's input-token limit. The fallback also runs a separately tested prompt variant (Day 4), with its own tokenizer and price list.

DiagramOne turn during Thursday's 529s
%% title: One turn during Thursday's 529s
sequenceDiagram
  participant A as Assist service
  participant G as LLM gateway
  participant P as Primary provider
  participant F as Fallback provider
  A->>G: Turn, deadline 8 s
  G->>P: Request
  P-->>G: 529 overloaded
  Note over G: Jittered backoff, retry budget OK
  G->>P: Retry 1
  P-->>G: 529 overloaded
  Note over G: Breaker opens on the 529 rate
  G->>F: Same turn, fallback prompt variant
  F-->>G: 200, cold cache, every input token at full price
  G-->>A: Reply, tagged provider = fallback
Your turn · checkpoint 6not answered

Thursday 14:10: failing over all at once

The primary returns 529 for 40 minutes, and MegaMart moves 100% of traffic to a second provider in one step. Using this lesson's numbers (about 91% of input normally read from cache), what happens to cost per conversation and to the second provider's limits? What should the shift look like?

Think it through, or jot it down. Then compare.

One strong answer

  • Cost: with a cold cache, all ~23,000 input tokens are fresh, so a conversation costs about the no-cache $0.061 (at similar prices) instead of ~$0.022: roughly 3×, until the new cache warms.
  • Limits: every input token now counts against the fallback's input limit, and a jump from zero to full traffic is the "sharp increase in usage" that triggers acceleration limits. Expect 429s from the fallback too.
  • Shift in steps (10%, 25%, 50%), cache-friendly prompt order on the fallback too, optional features paused by the spend ladder, and a spend alert for the incident.

Part V · Stage 5 of 6 · 4 min

Part V: Seeing It and Running It#

Chapter 8: Observability and Cost Attribution#

8.1 One Span Per Model Call#

Traces, metrics and logs in general are Week 10, Day 2 · Observability. What's specific is what each model-call span carries. OpenTelemetry's GenAI semantic conventions (status: Development, so names may change) define it:

  • Name: {gen_ai.operation.name} {gen_ai.request.model}, e.g. chat claude-sonnet-5-5.
  • Required: gen_ai.operation.name, gen_ai.provider.name; gen_ai.request.model when available; error.type on failure.
  • Recommended: gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reasons, and when non-zero gen_ai.usage.cache_read.input_tokens, gen_ai.usage.cache_write.input_tokens and gen_ai.usage.reasoning.output_tokens.
  • Opt-in: prompt and reply text (gen_ai.input.messages, gen_ai.output.messages), because they hold customer data; Day 4's PII rules decide.
  • Metrics: token counters such as gen_ai.client.inference.usage.input_tokens (input tokens "including cached tokens"), with cache and reasoning subsets; gen_ai.client.inference.time_to_first_chunk for streams.

One trap: OpenTelemetry's input count includes cached tokens, while Anthropic's input_tokens excludes them. The Anthropic mapping says: gen_ai.usage.input_tokens = input_tokens + cache_read_input_tokens + cache_write_input_tokens, where the last term is the response's cache_creation_input_tokens. Get it wrong and your token or cost totals are off by the size of your cache.

8.2 Who Spent It?#

Add your own attributes to every model-call span: feature (assist.chat, assist.router, guard.screen, guard.judge, qa.nightly_tags), tenant, prompt version, route (small, escalated, fallback) and cost in USD from the dated price table. Then Friday's question is a group-by. Day 4's screens and judges get their own feature names, so you can see what safety costs. Uber's GenAI Gateway (Day 1) does the same job with audit logs "for comprehensive cost attribution, security audit purposes, quality evaluation" (Uber Engineering, July 2024).

DiagramFrom one model call to a cost-per-feature chart
%% title: From one model call to a cost-per-feature chart
flowchart TD
  GW["LLM gateway<br/>one span per call: tokens, model,<br/>feature, tenant, prompt version, cost"]:::core
  P@{ icon: "sd:file", label: "Price table<br/>dated, per model" }
  C@{ icon: "sd:log", label: "OTel collector" }
  TS@{ icon: "tech:clickhouse", label: "ClickHouse<br/>spans and cost rows" }
  D@{ icon: "sd:dashboard", label: "Cost per feature, tenant,<br/>resolved conversation" }
  I@{ icon: "sd:file", label: "Provider invoice<br/>monthly" }
  AL@{ icon: "sd:alert", label: "Alerts<br/>cache-read share, spend rate, 529s" }
  P --> GW --> C --> TS --> D
  TS --> AL
  I -.->|"reconcile monthly"| TS
  class P,TS store
  class I external
  class C,AL,D service

Reconcile computed cost with the invoice monthly. A gap means a missing label, an unlogged path (batch jobs and retries are usual suspects) or a stale price.

MegaMart Assist operations dashboard, 20:45 on a sale evening (illustrative)
Metric Now
Spend rate / cost per conversation $88 an hour / $0.0098
Cache-read share of input tokens, prompt v15 91%
Escalation rate to the large model 36%
Spend ladder qa.nightly_tags: Thrifty; all others Normal
429 / 529 rate · retries as a share of calls 0.2% / 0.0% · 1.1% (budget 10%)
Turns ending in human handoff 6.5%

The two lines that would have caught the preface early: cache-read share per prompt version (it fell to zero at 10:35) and spend rate per feature (the loop at 02:00).

Chapter 9: Incident Patterns and Runbooks#

9.1 Provider Outage#

Providers go down completely. Between 3:16 PM and 7:38 PM PST on 11 December 2024, "all OpenAI services experienced significant degradation or complete unavailability". A new telemetry service made every node run Kubernetes API operations "whose cost scaled with the size of the cluster", overwhelming the control plane, and the fix needed that control plane, which they "could not do due to the increased load" (OpenAI status page write-up). For hours, retrying the primary helped nobody.

Runbook: breaker opens on the error rate; shift to the tested fallback in steps (Chapter 7); pause optional features and defer work to batch; tell users replies are slower; staff up the handoff queue.

9.2 The 429 Storm After a Deploy#

Runbook: check cache-read share by prompt version first (did the prefix change?), then retries as a share of calls; roll back the prompt; shed optional features down the spend ladder; ramp back gradually.

9.3 The Runaway Agent Loop#

Runbook: step caps per turn and budgets per conversation (Chapter 6); repetition detection; a kill switch per feature; fix the tool's error so it tells the model to stop.

9.4 The Quiet Quality Regression#

Day 4 gates prompt changes on evals. Regressions also come from below. Between August and early September 2025, "three infrastructure bugs intermittently degraded Claude's response quality"; one misrouted Sonnet 4 requests, from 0.8% at first to 16% in the worst hour on August 31, and because routing is "sticky", some users were hit far harder than the average. Anthropic wrote that its validation "ordinarily relies on benchmarks alongside safety evaluations and performance metrics", and responded with "more sensitive evaluations" and "quality evaluations in more places" (Anthropic, September 2025).

Runbook: prompt_version and gen_ai.response.model on every span; online quality signals (handoffs, thumbs-down, repeat contacts) per version and per model; sampled online judging (Day 4); one-click prompt rollback (Week 10, Day 3 · Deployment Strategies); a support path to report provider-side degradation with request IDs.

9.5 The Cache Key That Forgot the User#

Someone adds an answer cache and leaves the user out of the key; customer B sees customer A's refund. Runbook: turn the cache off by flag, purge it, find affected responses through the spans' cache-hit attribute, and run it as a privacy incident (Week 10, Day 5 · Incident Management). Prevention: one key-building function that refuses to run without a scope, with tests.

Part VI · Stage 6 of 6 · 7 min

Part VI: Putting It Together#

Chapter 10: The Monthly Cost Model#

Every lever, applied to Day 1's MegaMart numbers.

WorksheetMegaMart Assist, one month (Day 1's illustrative traffic; Anthropic list prices as of October 2026)
Value
Conversations (Day 1) 60,000/day × 30 = 1.8 million a month
Tokens per conversation (Day 1) 23,010 input, 1,500 output
Input split: system prompt sent 6 times / earlier turns / each turn's new tail 18,000 / 3,400 / 1,610
A. No caching, all Sonnet 5.5 ($2 in, $10 out per MTok) $0.046 + $0.015 = $0.061/conversation → ~$110,000/month
Assumed: system prompt always warm (a conversation starts every ~1.4 s on average) 18,000 read at $0.10/MTok = $0.0018
Assumed: 90% of follow-up turns arrive within the 5-minute lifetime 3,060 read ($0.0003) + 340 rewritten at $2.50/MTok ($0.00085)
Each turn's new tail written to cache at $2.50/MTok 1,610 → $0.0040
Output, unchanged by caching $0.015
B. Caching, all Sonnet 5.5 $0.022/conversation → ~$39,600/month (−64%)
Same conversation, cached, on Haiku 5.5 ($0.01 read, $0.125 write, $0.50 out) ~$0.0012
Router on the first message (Haiku, ~500 in, ~5 out) ~$0.00005
Conversations routed to Haiku (assumed; Day 4's evals must confirm) 60%
C. Caching + routing 0.6 × $0.0012 + 0.4 × $0.022 + $0.00005 ≈ $0.0096 → ~$17,200/month (−84%)
Counted input tokens per minute, peak hour (9,000 conversations, Day 1) A: ~3.45 million; B: ~290,000 (writes count, reads don't)

Conclusion: caching is worth about two thirds of the bill and over 10× of ITPM headroom at no cost in quality, so it's the first thing to protect with an alert. After caching, output is ~68% of a Sonnet conversation, so the next levers are reply length, effort and routing, and the 60% routing share is only real once Day 4's evals show quality holds. The nightly tagging job on the Batch API is half price on top.

Chapter 11: The Whole Design#

DiagramMegaMart Assist, run economically (illustrative design)
%% title: MegaMart Assist, run economically (illustrative design)
flowchart TD
  U@{ icon: "sd:users", label: "Customers<br/>web and app chat" }
  AS["Assist service<br/>conversation state, tools, RAG (Day 3), guardrails (Day 4)"]:::core
  GW["LLM gateway<br/>budget governor · answer cache ·<br/>router · one retry layer · breaker"]:::core
  R@{ icon: "tech:redis", label: "Redis<br/>spend counters, answer cache" }
  S@{ icon: "sd:ml", label: "Small model<br/>Haiku 5.5" }
  L@{ icon: "sd:ml", label: "Large model<br/>Sonnet 5.5" }
  F@{ icon: "sd:cloud", label: "Fallback provider<br/>tested prompt variant (Day 4)" }
  N@{ icon: "sd:cron", label: "Nightly QA tagging<br/>Batch API, 50% price" }
  O@{ icon: "sd:log", label: "OTel spans<br/>tokens, cost, feature, prompt version" }
  D@{ icon: "sd:dashboard", label: "Cost and quality dashboards,<br/>alerts" }
  U --> AS --> GW
  GW <--> R
  GW --> S
  GW --> L
  GW -.->|"breaker open, stepped shift"| F
  N --> GW
  GW --> O --> D
  class U client
  class R cache
  class S,L,F external
  class N,O,D service

The gateway sees every call, so budgets, caches, routing, retries and telemetry live there. No service calls a provider directly.

Chapter 12: Common Mistakes#

12.1 Volatile Content Above the Breakpoint#

Timestamps, names, request IDs or a reordered tool list early in the prompt make every prefix unique. Fix: stable first, volatile in the newest turn; alert on cache-read share per prompt version.

12.2 Counting Tokens Without the Cache Fields#

Charting only Anthropic's input_tokens hides most input once caching works, and makes cost and capacity charts lie. Fix: log all four meters and map them to OpenTelemetry's totals.

12.3 A Semantic Cache Without Scope or Measurement#

Keyed only by the question's meaning, it returns personal, stale or flipped answers with full confidence. Fix: scoped keys, non-personal intents only, invalidation from sources, a sampled false-hit rate.

12.4 One Budget for Everything#

One organisation-wide cap lets a runaway loop or a batch job take down the customer-facing assistant. Fix: budgets per feature, tenant, user and conversation, reserved before the call, with a ladder that degrades before it stops.

Chapter 13: Interview Section#

13.1 When to Bring It Up#

Any design with an LLM in the request path. Bring up cost as soon as you've estimated tokens, because it shapes the architecture: prompt order, model choice, what runs in batch, where the gateway sits.

13.2 Phrases That Work#

Key phrases
SIZING

"Each turn resends the history, so input grows with the square of the turns. At 23,000 input tokens a conversation, input is three quarters of the bill before caching."

CACHING

"I'll order the prompt stable-to-volatile with the breakpoint after the history. Cache reads cost a twentieth of input on this model and don't count toward the input rate limit, so it's our biggest cost and capacity lever. I'll alert on cache-read share per prompt version."

ROUTING AND SEMANTIC CACHE

"Easy turns go to a small model, uncertain ones cascade, and we watch the escalation rate. A semantic cache only for general, non-personal questions, with scoped keys, source-driven invalidation and a measured false-hit rate."

BUDGETS

"Budgets per feature, tenant and conversation: reserve the worst case from max_tokens, settle from usage, and degrade to a small model and then a handoff before anything hard-stops."

13.3 Follow-Up Questions#

Question Good answer
"How do you know what each feature costs?" "Every model call is a span with tokens per meter, model, feature, tenant, prompt version and computed cost; group by feature, reconcile with the invoice monthly."
"The cache hit rate dropped overnight. Why?" "The prefix changed: something volatile added, tools reordered, or a model switch. Split cache-read share by prompt version and diff the prompts."
"What stops a runaway agent?" "A step cap per turn, then a per-conversation budget, then feature budgets; the provider's cap is a last resort that stops everyone."
"When do you use the Batch API?" "For anything nobody waits for: tagging, evals, backfills. Half price, within 24 hours, separate limits, idempotent writes by custom_id."

Chapter 14: Practice Problems#

Practice · problem 1

Problem 1: The Long-Contract Assistant#

A legal team asks 10 to 30 questions about one 80,000-token contract per session, over two hours. Design the prompt layout, choose a cache lifetime, and estimate input cost per session with and without caching.

Hint: compare 5-minute writes (1.25×, refreshed on use) with 1-hour writes (2×) when gaps between questions often pass 5 minutes.

Practice · problem 2

Problem 2: Budgets for Three Plans#

A SaaS sells an AI feature on free, team and enterprise plans. Design budgets so free-tier abuse can't hurt paying tenants, and a paying tenant near its limit gets a degraded answer, not an error.

Hint: reserve-then-settle per tenant and plan, a ladder per plan, and provider workspace limits as the outer wall.

Practice · problem 3

Problem 3: A Week on Two Providers#

Your primary has a capacity problem for a week, and half the traffic moves to a second provider. What changes in cost, quality, limits and telemetry?

Hint: a cold cache, a different tokenizer and price list, a prompt variant with its own eval run, the second provider's ramp-up limits, and gen_ai.provider.name on every span.

Summary#

DAY 5 KEY TAKEAWAYS
WHAT YOU PAY FOR
  • Four meters: fresh input, cache write, cache read, output
  • History is resent every turn: cost grows with the square of the turns
  • Dated price config; compare models on cost per resolved conversation
CACHING
  • Prompt cache: exact prefix, stable content first, cheap reads that don't count toward ITPM
  • Exact answer cache: safe, best for machine-made prompts
  • Semantic cache: non-personal only, scoped, invalidated from sources, measured
SPENDING LESS
  • Route easy turns to small models; cascades pay unless nearly everything escalates
  • Batch APIs: half price for work nobody waits for
RUNNING IT
  • Reserve-then-settle budgets per feature, tenant, user and conversation; degrade before cut-off
  • One retry layer; failover in steps, expecting a cold cache
  • One span per model call with tokens, cost, feature and prompt version

Further Reading#

Official Documentation#

Papers and Libraries#

Engineering Blogs and Incident Reports#

this lesson in shorts · 4

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Day 05 · Cost, Caching and Operations
Finish Day 5Stages clear as you read them (0/6 so far). Skipping ahead doesn't count: marking complete unlocks after ~5 more min of reading.

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

AnthropicDocs · ~30 min

Prompt caching

What
Anthropic's reference for caching prompt prefixes between requests.
Key idea
Prefixes build in the order tools, system, messages and need an exact match; a 5-minute write costs 1.25x input, a read 0.1x, and the lifetime refreshes on use.
Why here
The mechanics behind this lesson's prompt layout, its cache-busting Monday and the cache-read share alert.
taught here: Prompt (prefix) caching
StanfordPaper · ~35 min

FrugalGPT: How to Use Large Language Models While Reducing Cost and Improving Performance

Lingjiao Chen, Matei Zaharia, James Zou · 2023
What
The Stanford paper on cutting LLM API costs through prompt adaptation, model approximation and cascades.
Key idea
A cascade asks cheaper models first and returns an answer only when a learned scorer trusts it, matching GPT-4 with up to 98% lower cost on its benchmarks.
Why here
The routing and cascade chapter of this lesson, including why the scorer needs graded examples from your own traffic.
taught here: Model cascades
AnthropicPostmortem · ~12 min

A postmortem of three recent issues

Sam McAllister · 2025
What
Anthropic's postmortem of three infrastructure bugs that intermittently degraded Claude's answers in August and September 2025.
Key idea
A routing bug grew from 0.8% to 16% of Sonnet 4 requests in the worst hour, sticky routing hit some users harder, and existing evaluations did not catch it.
Why here
This lesson's runbook for regressions that come from below: model and prompt version on every span, and online quality signals.
taught here: The quiet quality regression

The lesson links the older anthropic.com/engineering URL, which redirects here.

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…