Cutting Prompt Token Cost Without Losing Output Quality
Cache the stable prefix, compact the variable part, route to the right model, and measure what a token actually costs you. Where prompt engineering saves money and where it does not.
InnovProduct Team
Library Team
Prompt cost is a design constraint, not a bill that arrives later. Two things drive it: how many tokens you send, and what you pay per token. Most teams optimize neither, then wonder why the bill moved. The useful move is to understand which part of your prompt is actually costing money, because in most applications the answer is not the instruction. It is the context you re-send on every call.
Cache the stable prefix
Most prompt caching works on a prefix basis: the leading portion of the prompt is cached and only the trailing portion is re-read. This inverts the usual instinct to put the variable part first, because the variable part now sits at the end by design. System instructions, rubrics, schemas, and few-shot examples are stable across calls and belong at the front, where they get cached. The user input, the retrieved documents, and the current date go at the end.
# Cached: everything here is identical across calls, and is read once
# rather than re-billed on every request.
SYSTEM INSTRUCTIONS
OUTPUT SCHEMA (JSON, v3)
EVALUATION RUBRIC
EXAMPLE 1 ... EXAMPLE 5
# Uncached: this part changes per request.
CURRENT DATE: 2026-09-29
USER INPUT: {{user_input}}
# Wrong order, and expensive: the variable part breaks the cache immediately.
USER INPUT: {{user_input}}
CURRENT DATE: 2026-09-29
SYSTEM INSTRUCTIONS
...Rearranging an existing prompt so the stable prefix leads is often the single largest cost reduction available, and it costs no quality at all.
Compact the variable part
The part that is not cached is the part worth squeezing. Retrieved documents should be chunked to the smallest span that still answers the question, deduplicated, and ordered so the most relevant content is nearest the instruction the model is meant to follow. History should be truncated to what the task needs, and clearly marked as truncated so the model does not treat an abrupt cut as a complete record.
| Technique | Effect | Watch for |
|---|---|---|
| Prefix caching | Removes most of the repeated system cost | Breaks if any stable text changes between calls |
| Narrower retrieval chunks | Cuts the uncached tail | Too narrow and recall drops; measure it |
| Deduplicating context | Removes repeated spans | Do not drop the only copy of a constraint |
| Summarizing old turns | Bounds long-running conversations | Summaries drift; keep the originals reachable |
| Model routing by difficulty | Sends easy cases to cheaper models | Needs a difficulty signal you trust |
Route by difficulty, not by hope
Using a small model for everything wastes money on hard cases, and using a large model for everything wastes more. Routing pays when you have a signal. Cheap and surprisingly effective signals exist: input length, whether retrieval returned confident matches, how many steps the task needs, and a short classifier pass that labels the request as simple, moderate, or hard. Route on the signal, and measure the escape rate, because a router that sends hard work to a small model pays twice: once for the bad answer, once for the retry.
def route(request, signal):
if signal.input_tokens > 8_000 or signal.steps_needed > 5:
return "large" # long context or multi-step: do not economise here
if signal.retrieval_confidence < 0.4:
return "large" # weak retrieval usually means a genuinely hard case
if signal.label == "simple":
return "small"
return "medium"
# Cost per successful task, not cost per call. A cheap call that needs
# three retries is the expensive call.
def cost_per_success(spend, successes):
return spend / successes if successes else float("inf")That last function is the metric that actually matters. Cost per call is a vanity number; cost per successful task is the one that reflects whether your efficiency work helped, and it makes retries and escapes impossible to hide.
Cheap is not the same as efficient. Measure what you spend per task that worked.
Where the tokens actually go
Before optimizing anything, measure the split. Most unexpected bills come from a handful of causes, and they look different from each other in the usage numbers, so one measurement tells you which fix applies. Divide usage into the stable system prompt, the variable input, the conversation history, retrieved documents, tool definitions, and the output. Anything that grows with conversation length deserves attention first.
| Component | Typical growth | First fix to try |
|---|---|---|
| System prompt | Constant per call | Cache it; keep it out of the variable part |
| Variable input | Scales with request size | Deduplicate, then truncate the least useful input |
| Conversation history | Unbounded over a session | Summarize older turns, keep recent ones verbatim |
| Retrieved documents | Scales with corpus size | Cut chunk count, then lower chunk size |
| Tool definitions | Scales with tool count | Expose only the tools this request needs |
| Output | Whatever you asked for | Cap max tokens and ask for shorter answers |
Two numbers are worth watching on every dashboard: input tokens per request and output tokens per request. A rising input with flat output usually means a leaking conversation or an over-broad retrieval query. A rising output with flat input usually means the prompt stopped constraining length, and it is the cheapest of these to fix because the fix is one instruction.
A weekly review catches what daily numbers hide. Compare the same metric against the same weekday a week earlier rather than against yesterday, because usage is usually tied to traffic rather than to your prompts, and a Monday comparison will convince you that your fix failed.
Cache, shorten, or route: one decision per component
Each part of a prompt has exactly one correct cost strategy, and picking the wrong one is why optimization efforts stall. Caching pays off for a prefix that is byte-identical across requests. Shortening pays off for a prefix that changes every time. Routing pays off when the variable part is what actually determines difficulty. Applying all three to the same segment means paying the maintenance cost of each while getting the benefit of only one.
| Segment | Correct strategy | Wrong move |
|---|---|---|
| Long invariant system prompt | Cache, and keep it byte-identical | Editing it once a day, which throws away the cache |
| Per-request user input | Shorten and truncate the least useful part | Trying to cache it, which cannot work |
| Retrieved documents | Reduce count, then reduce size | Caching per-user documents you never re-request |
| Tool definitions | Route: send only what this call needs | Sending every tool on every turn |
| Chat history | Summarize old turns, keep recent ones | Truncating the most recent turns first |
Caching requires the prefix to be byte-identical, not merely similar. A single changed character, a timestamp, or a reordered list in the first block can invalidate the entire prefix, and the effect is invisible in a dashboard that only shows total tokens.
Where this backfires
- Compressing instructions below the point where behavior changes. Shortening a rubric is easy; shortening it past its clarity is a regression.
- Caching a prefix that changes for one user. One dynamic character in the stable block disables caching for every call.
- Routing on a signal you have not validated. An unmeasured difficulty classifier is a guess with extra infrastructure.
- Optimizing tokens while ignoring retries, because a rejected structured response is billed three times.
Every saving must clear the same frozen evaluation set as any other prompt change. A cheaper prompt that scores worse is not an optimization, it is a trade you did not mean to make.
How do I reduce prompt token cost without hurting quality?
Reorder the prompt so stable content is a cacheable prefix, then compact only the variable part: narrower retrieval chunks, deduplicated context, and bounded history. Verify on a frozen evaluation set, because a cheaper prompt that scores worse is a regression rather than a saving.
Does prompt caching change how I should structure a prompt?
Yes, materially. Caching applies to a prefix, so stable instructions, schemas, rubrics, and examples belong at the front, and the changing input belongs at the end. A single variable character inside the stable block disables the cache for that request.
When is a smaller model the right choice?
When you have a reliable difficulty signal: short inputs, confident retrieval, few steps, or a validated classifier. Route easy work to the smaller model and keep the large one for long context, weak retrieval, and multi-step tasks. Track the escape rate, since a misrouted case costs the retry too.
What is the right cost metric to track?
Cost per successful task. Cost per call hides retries, validation failures, and routing escapes, all of which are billed. Dividing total spend by the number of successful outcomes is the number that reflects whether your optimization actually helped.
Measure before and after you optimize
Cost work without a frozen evaluation set is just a guess with a lower number attached.
- How to evaluate LLM prompts with frozen test setsThe harness that lets you confirm a cost reduction did not quietly trade away quality.
- Best practices for training AI models with promptsWhy a frozen eval set and one change per iteration come before optimization work.
- LLM output formats: forcing reliable structured JSONRepair loops are billed three times per failure, so format reliability is also a cost lever.
Prompts that are efficient by construction
Well-built bundles separate the stable instruction block from the variable input, so caching works and the prompt stays maintainable. Browse a library organized for reuse rather than for one-off copy-paste.
Browse the library
