DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Prompt Caching: Overriding Tokenization for Faster and More Cost-Effective AI
  • Improving Repeated Analytics Workloads With Databricks Disk Cache
  • Ampere PMU Profiler: A Guide to Microarchitecture Profiling
  • Stop Paying Your AI Agent to Do the Same Job Twice

Trending

  • Dynamic Arrays, Spill, and LET: What Changed in Excel and Why It Matters for Java Applications
  • Distributing Massive AI Models With Network-Layer Multicast
  • Part 2: Securing and Scaling Goose-to-Java Agent Traffic With agentgateway
  • Why Continuous Application Security Testing Is No Longer Optional
  1. DZone
  2. Data Engineering
  3. Data
  4. Prompt Caching Doesn't Save Money on Turn One

Prompt Caching Doesn't Save Money on Turn One

Cache writes cost 1.25x the input price, and reads cost 0.1x. So a fresh conversation's first turn is more expensive with caching on.

By 
Ninaad Rao user avatar
Ninaad Rao
DZone Core CORE ·
Sep. 25, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
73 Views

Join the DZone community and get the full member experience.

Join For Free

I went looking for a clean way to show what prompt caching actually saves an agent, and the first thing I found was a fact that's easy to miss if you only read the "up to 90% savings" headline. Caching a fresh conversation's first turn costs more than not caching it. There's no cache to read from yet, so you pay the input price on the content plus a 25% premium to write to the cache, and get nothing back. The savings arrive starting on turn two, once there's something to read.

Where This Lives in deepagents

Deep Agents ships AnthropicPromptCachingMiddleware from langchain-anthropic in its default middleware stack. It doesn't decide what to cache by guessing. It tags exactly two things on every model call:

Python
 
# langchain_anthropic/middleware/prompt_caching.py (trimmed)
def _apply_caching(self, request: ModelRequest) -> ModelRequest:
    overrides: dict[str, Any] = {}
    cache_control = self._cache_control  # {"type": "ephemeral", "ttl": self.ttl}

    overrides["model_settings"] = {**request.model_settings, "cache_control": cache_control}

    system_message = _tag_system_message(request.system_message, cache_control)
    if system_message is not request.system_message:
        overrides["system_message"] = system_message

    tools = _tag_tools(request.tools, cache_control)
    if tools is not request.tools:
        overrides["tools"] = tools

    return request.override(**overrides)


The system prompt gets one breakpoint on its last content block, and the tool list gets one breakpoint on its last tool. Tool definitions are sent as one contiguous block; a single trailing breakpoint caches the entire tool set. On top of that, the top-level cache_control on model_settings gets translated by Anthropic's own auto-caching behavior into a breakpoint at the end of whatever's cacheable in the request. This is what lets the cached prefix grow to cover prior conversation turns as an agent session gets longer, not just the fixed system+tools prefix.

That's the whole mechanism. No decision logic, no cost estimation, no adaptive behavior. It tags the stable parts and lets the API's own prefix-match caching do the rest.

The Actual Cost Shape, Measured Live

Here's the part that isn't obvious from "caching saves money": it saves money on a schedule, not uniformly. Prompt caching is a prefix match. Turn 2 can only read from cache what turn 1 wrote, turn 3 reads what's accumulated through turn 2, and so on. That gives every agent conversation the same two-phase cost curve: write-only first turn, then reads that get proportionally cheaper as the stable prefix grows relative to what's new each turn. 

To measure this for real rather than project it, I ran the same 8-turn engineering conversation (a realistic back-and-forth about refactoring a blocking-I/O call inside an async FastAPI handler) through claude-opus-5 twice - once with no caching, once with top-level auto-caching (cache_control={"type": "ephemeral"} on every request, the exact mechanism AnthropicPromptCachingMiddleware uses via model_settings["cache_control"]). The system prompt was a real ~1,345-token engineering-assistant prompt, counted with count_tokens before running anything billed:

Python
 
"""python run_experiment.py (needs ANTHROPIC_API_KEY)"""

import anthropic

MODEL = "claude-opus-5"
INPUT_PRICE = 5.00 / 1_000_000
OUTPUT_PRICE = 25.00 / 1_000_000
CACHE_WRITE_MULT = 1.25  # 5-minute TTL
CACHE_READ_MULT = 0.10

SYSTEM_PROMPT = open("system_prompt.txt").read()  # a real ~1,345-token prompt
TURNS = [...]  # 8 real follow-up questions in one coherent conversation

client = anthropic.Anthropic()

def run(*, use_caching: bool) -> list[dict]:
    messages: list[dict] = []
    turn_records = []
    for user_text in TURNS:
        messages.append({"role": "user", "content": user_text})
        kwargs = {
            "model": MODEL, "max_tokens": 400,
            "system": SYSTEM_PROMPT, "messages": messages,
        }
        if use_caching:
            kwargs["cache_control"] = {"type": "ephemeral"}
        resp = client.messages.create(**kwargs)
        usage = resp.usage
        messages.append({
            "role": "assistant",
            "content": "".join(b.text for b in resp.content if b.type == "text"),
        })
        turn_records.append({
            "input_tokens": usage.input_tokens,
            "output_tokens": usage.output_tokens,
            "cache_creation_input_tokens": getattr(usage, "cache_creation_input_tokens", 0) or 0,
            "cache_read_input_tokens": getattr(usage, "cache_read_input_tokens", 0) or 0,
        })
    return turn_records

def cost_for_turn(r: dict) -> float:
    return (
        r["input_tokens"] * INPUT_PRICE
        + r["cache_creation_input_tokens"] * INPUT_PRICE * CACHE_WRITE_MULT
        + r["cache_read_input_tokens"] * INPUT_PRICE * CACHE_READ_MULT
        + r["output_tokens"] * OUTPUT_PRICE
    )


The real output, 16 live API calls, $0.24 total:

Turn No-cache $ Cached $ Savings cache_read/creation (cached run)
1 0.0170$ 0.0187$ -10.2% read=0 write=1389
2 0.0173$ 0.0111$ 35.7% read=1389 write=64
3 0.0175$ 0.0110$ 37.1% read=1453 write=40
4 0.0177$ 0.0110$ 37.7% read=1493 write=40
5 0.0179$ 0.0110$ 38.4% read=1533 write=37
6 0.0182$ 0.0112$ 38.5% read=1570 write=61
7 0.0184$ 0.0111$ 39.6% read=1631 write=46
8 0.0185$ 0.0110$ 40.5% read=1677 write=27
Total 0.1423$ 0.0961$ 32.5%


Turn 1 really is negative: 10.2% more expensive than no caching at all, exactly as the documented economics predict. A 5-minute-TTL cache write needs at least two requests to break even (1.25x write + 0.1x read ≈ 1.35x, against 2x for two uncached requests). After that, savings climb every single turn, because the thing growing is the cached portion of the prompt (the cache_read_input_tokens column climbing from 1,389 to 1,677 across the run), while the thing staying flat is the new portion (the write column). By turn 8, caching is saving 40.5% on that turn alone.

Savings climb every single turn, because the thing growing is the cached portion of the prompt

This is also why a one-shot script or a short-lived Lambda almost never benefits from caching. If the conversation ends after one or two turns, you're stuck in the loss zone the math shows on turn 1. Caching pays for itself on sustained, multi-turn agent sessions, which is exactly the shape of a deepagents CLI session or a long-running subagent loop, not a single classification call.

How Much the Stable Prefix Matters

The 32.5% overall figure here is real, but it's specific to this run's cacheable prefix being a fairly modest ~1,345-token system prompt. That number moves with the size of the stable part of the prompt relative to what's genuinely new each turn. Swapping SYSTEM_PROMPT in the script above for a larger, more tool-heavy prefix (closer to what a deepagents agent with 15+ MCP tool schemas actually carries) and rerunning would show the savings percentage climb further. The shape of the curve doesn't change, only how much of the bill it's discounting. If you want to know your own number, this script is a five-minute run against your own agent's actual system prompt.

Two Levers, and They Stack

Caching cuts the price per token of everything that repeats turn over turn. It says nothing about how many tokens you're sending in the first place. That's a second, independent lever: tool-selection filtering, which cuts the token count itself by not sending schemas irrelevant to the current turn. Measured earlier on a 61-tool registry, a lexical-overlap scorer at top_k=10 gets 67% recall at an 84% reduction in tool-schema payload per turn.

These aren't competing techniques — they compose. Caching makes the tokens you do send cheaper turn over turn; tool selection reduces how many tokens are in the first place, in the one part of the prompt (the tool list) caching's own breakpoint sits right in front of. A 6,000-token stable prefix that's 40% tool schemas doesn't stay 6,000 tokens once you're only sending the top-10 relevant tools instead of all 61: it shrinks, and then gets cached at the smaller size. Neither technique substitutes for the other: one is about the price of a token, the other is about whether you send it at all.

Takeaways

The counterintuitive part is the one worth remembering: caching a conversation that only runs one or two turns can cost more than not caching it, because the write premium has nothing to amortize against. It only pays off on sessions that actually run long enough to read back what turn one wrote. Second, if you're trying to actually cut what an agent costs, price-per-token and token-count are separate dials: caching turns one, tool selection turns the other, and a real cost reduction effort turns both.

Cache (computing)

Opinions expressed by DZone contributors are their own.

Related

  • Prompt Caching: Overriding Tokenization for Faster and More Cost-Effective AI
  • Improving Repeated Analytics Workloads With Databricks Disk Cache
  • Ampere PMU Profiler: A Guide to Microarchitecture Profiling
  • Stop Paying Your AI Agent to Do the Same Job Twice

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook