Index Ai Academy
Phase 1 · Building with LLMs · Quality & production

Cost and latency

Prompt caching, batching and routing to cheaper models.

About 30 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

An AI feature that costs $0.02 a request is a rounding error in a demo. At 10,000 requests a day, it's $6,000 a month. The same few levers decide both the bill and the wait: how much text you send, how much you get back, which model reads it, and whether repeated text is cached. Teams often cut costs by more than half with no loss in quality, just by working through these levers in order.

What you'll be able to do

  • Work out what a request costs from its usage fields, including cache reads and writes
  • Structure prompts so prompt caching works, and know when it pays off
  • Rank the other cost levers: model choice, output length, effort, batching, trimming input
  • Explain what makes a reply slow and how to make it feel fast

What it covers

  1. Where the money goes
  2. Prompt caching
  3. Worked example: see the cache in the usage fields
  4. The other cost levers, in order
  5. The number finance asks for

Where the money goes

Every reply includes a usage section. It tells you exactly what you were charged for:

FieldWhat it countsPrice on Claude Opus 5.5 (per million tokens)
input_tokensInput that wasn't read from or written to the cache$4.00
cache_creation_input_tokensInput written to the cache this time$5.00 (1.25×) for the 5-minute cache
cache_read_input_tokensInput read from the cache$0.20
output_tokensEverything the model wrote, including thinking$20.00

Two things stand out:

  • Output costs five times as much as input.
  • Cached input costs a twentieth of normal input on this model (around a tenth on most others).

Picture a support bot that sends the same 3,000-token help center with every question. It pays full price for the same text, over and over:

def cost(input_tokens, output_tokens, cache_read=0, cache_write=0):
    return (input_tokens * 4 + output_tokens * 20 + cache_read * 0.20 + cache_write * 5) / 1_000_000

per_day = 10_000
no_cache = cost(input_tokens=3_050, output_tokens=200)
cached = cost(input_tokens=50, output_tokens=200, cache_read=3_000)
print(f"without caching: ${no_cache:.4f} per request, ${no_cache * per_day:,.0f} per day")
print(f"with caching:    ${cached:.4f} per request, ${cached * per_day:,.0f} per day")
It runs right here in your browser, with nothing to install.

It prints $0.0162 per request ($162 a day) without caching, and $0.0048 per request ($48 a day) with it. The cost function multiplies each kind of token by its price per million, then divides by a million.

Quick check
What does this print?
def cost(input_t, output_t):
    return (input_t * 4 + output_t * 20) / 1_000_000
print(cost(1000, 100))
  1. 0.024
  2. 6.0
  3. 0.004
  4. 0.006
Show the answer

0.006

1000 × 4 + 100 × 20 is 4,000 + 2,000 = 6,000, and 6,000 / 1,000,000 is 0.006: $0.004 for input plus $0.002 for output. Note how output costs more per token.

Prompt caching

Think of a teacher who photocopies the same worksheet for every class. It's quicker to keep a stack of copies ready than to write it out each time.

When the start of your prompt is the same across requests, the API can keep its processed form for a while and reuse it. That shared start might be instructions, a style guide, documents, tool definitions or the earlier turns of a conversation. You mark where the reusable part ends with cache_control:

"system": [{"type": "text", "text": HELP_CENTER_AND_RULES,
            "cache_control": {"type": "ephemeral"}}],

The rules that make it work:

  • Stable first, changing last. The cache matches your prompt from the very beginning up to the marker, exactly, in the order tools, then system, then messages. A timestamp or user name near the top changes the text, so the cache misses every time.
  • Writes cost a bit more, reads much less. A write costs 1.25× normal input for the default 5-minute lifetime (2× for the optional 1-hour lifetime). Each read restarts the timer.
  • A minimum size. Starts shorter than the model's minimum (512 tokens on current models) aren't cached, and nothing warns you. Check cache_creation_input_tokens to confirm.
  • Up to 4 markers per request, for parts that change at different rates. Tools might never change, a document might change daily, and the conversation changes every turn.

Caching also makes replies start sooner, because the model skips re-reading the cached part.

Try it now

One extra write already costs more than it saves if nothing reads it. By two requests it has paid for itself, and 20 requests save about 84%.

Quick check
Which prompt layout lets caching work?
  1. 4,000 tokens of policies first, then today's date and the customer's question
  2. The customer's question first, then the policies
  3. Today's date first, then the policies, then the question
  4. A random order each time, so the model doesn't get bored
Show the answer

4,000 tokens of policies first, then today's date and the customer's question

The cache matches from the very start. Put everything that never changes first, and anything that changes, like the date or the question, after it.

Worked example

Worked example: see the cache in the usage fields

Put the whole help center in a cached system prompt, and ask two different questions. The second request reads the shared start from the cache.

import requests

docs = requests.get("https://api.gradient.test/v1/docs").json()["data"]
help_center = "\n\n".join(f"## {d['title']}\n{d['text']}" for d in docs)
system = [{"type": "text",
           "text": "Answer customer questions using only this help center.\n\n" + help_center,
           "cache_control": {"type": "ephemeral"}}]

for question in ["How long is the return window?", "Do gift cards expire?"]:
    response = requests.post("https://api.anthropic.com/v1/messages",
        headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"},
        json={"model": "claude-opus-5-5", "max_tokens": 200, "system": system,
              "messages": [{"role": "user", "content": question}]},
        timeout=60).json()
    u = response["usage"]
    print(question, "| written:", u["cache_creation_input_tokens"], "| read:", u["cache_read_input_tokens"], "| uncached:", u["input_tokens"])
In the lesson you can change this and run it.

The first request writes the help center to the cache. The second reads it, and only the question is billed at the full input price. If someone sent the same start in the last few minutes, even your first request is a read. (The academy's simulator estimates tokens at about four characters each, so its counts are approximate.)

The other cost levers, in order

  1. Pick the cheapest model that passes your eval. Haiku 5.5 is 40 times cheaper than Opus 5.5 per token. Classifying, tagging and pulling out fields often work just as well on it. Route by task (the routing pattern), and prove it with an eval.
  2. Cap and shrink the output. Set max_tokens sensibly. Ask for the format you need, such as "three bullets" or JSON, instead of essays. Use structured output.
  3. Lower the effort for simple tasks. Less thinking means fewer output tokens and faster replies.
  4. Batch what can wait. The Message Batches API halves the price of every token, and the discount stacks with caching.
  5. Send less. Retrieve the five relevant chunks instead of the whole manual. Return small tool results. Drop old conversation turns you no longer need.

Measure before and after each change. A saving that costs quality will show up in the eval.

What makes replies slow

Two numbers matter:

  • time to first token: how long before anything appears,
  • total time: how long until the reply is finished.

The first grows with the length of the input and with thinking. The second depends mostly on how much the model writes, because output is produced one token at a time. So:

  • Stream the reply, so people see text straight away (the streaming lesson). It doesn't make the answer finish sooner, but it makes it feel faster.
  • Write less. Shorter outputs finish sooner.
  • Use a smaller model or lower effort where quality allows.
  • Cache long, repeated starts.
  • Run independent calls at the same time instead of one after another.

For marketers: cost per request × requests per day × 30 is the number your finance team will ask for. Work it out before launch, with caching and model choice already applied.

Worked example

The number finance asks for

With caching, the support bot costs $0.0048 per request. At 10,000 requests a day, over a 30-day month:

per_request = 0.0048
print(round(per_request * 10_000 * 30))
print(round(0.0162 * 10_000 * 30))
In the lesson you can change this and run it.

It prints 1440, then 4860: about $1,440 a month with caching, against $4,860 without. Same help center, same answers, roughly a third of the bill.

Quick check
Your bot's system prompt starts with "Today is {date} {time}", followed by 4,000 tokens of policies. Caching never hits. Why?
  1. Caching only works on output
  2. The time changes the first bytes of the prompt, so the cached start never matches
  3. 4,000 tokens is too long to cache
  4. System prompts can't be cached
Show the answer

The time changes the first bytes of the prompt, so the cached start never matches

Move the changing date after the cached policies, or into the user message.

Quick check
Replies take 8 seconds and users complain it feels broken. What changes the experience most for the least work?
  1. Stream the reply, so text starts appearing straight away
  2. Add more instructions to the system prompt
  3. Switch to the largest model
  4. Raise max_tokens
Show the answer

Stream the reply, so text starts appearing straight away

Streaming doesn't make the reply finish sooner, but people see text within a second or so instead of staring at a blank screen. A bigger model or more tokens would make it slower.

Practice what you just read

  • 4 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 5 review cards for spaced repetition

Create a free account

Try a quiz question

Which part of a prompt should come first so caching works?

  1. The parts that never change: instructions, documents, tools
  2. The user's question, so the model reads what matters most before anything else
  3. The current date and time, so every request starts from an accurate timestamp
  4. The user's name, so each person's replies get stored in a cache of their own

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson