n4nAI

How to cut agent API costs by 60% with prompt caching

Practical steps to implement LLM prompt caching in agent loops, cutting repeated token costs by up to 60% with provider cache controls and gateways.

n4n Team3 min read745 words

Audio narration

Coming soon — every post will get a voice note here.

Engineers building LLM agents burn tokens replaying the same system prompt, tool schemas, and few-shot examples on every turn. Prompt caching cost reduction is the fastest win available: store the static prefix once and pay a fraction for subsequent reads. Here is how to implement it end to end in a production agent.

Step 1: Audit your agent’s token flow

Before changing code, measure where tokens go. Most agents send an identical block of text—system instructions, tool definitions, retrieval context—on every request, then append a small dynamic user message. That repeated block is your caching target.

Count tokens with the provider’s tokenizer. For OpenAI models, tiktoken works:

import tiktoken

enc = tiktoken.encoding_for_model("gpt-4o")
system_prompt = open("system_prompt.txt").read()
tool_schema = open("tools.json").read()

static_tokens = len(enc.encode(system_prompt + tool_schema))
print(f"Static prefix: {static_tokens} tokens")

If your static prefix exceeds 1,000 tokens, you are leaving money on the table. Prompt caching cost reduction starts by isolating that prefix so the provider can cache it.

Step 2: Restructure prompts to maximize cacheable prefix

Providers cache from the start of the prompt up to a marked breakpoint. Anything after the breakpoint is dynamic and bypasses the cache. Reorder your messages so the static content is first and contiguous.

A correct layout for a tool-using agent:

[
  {"role": "system", "content": "You are a DevOps assistant. Use tools to query logs and deploy."},
  {"role": "system", "content": "[TOOL DEFINITIONS]\n{...large JSON schema...}"},
  {"role": "user", "content": "What is the error rate on prod-1?"}
]

Do not interleave static and dynamic content. If you inject a timestamp into the system prompt, you invalidate the cache every call. Generate timestamps inside the user turn instead.

Step 3: Enable cache control on supported providers

Anthropic: explicit breakpoints

Anthropic requires a cache_control marker on the last block of the static prefix. The SDK accepts it as an extra field:

import anthropic

client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    system=[
        {"type": "text", "text": SYSTEM_PROMPT},
        {"type": "text", "text": TOOL_SCHEMA, "cache_control": {"type": "ephemeral"}}
    ],
    messages=[{"role": "user", "content": user_query}]
)

The cache_control tells Anthropic to cache everything up to that text block. Subsequent calls with the same prefix return cache_read_input_tokens in the usage.

OpenAI: implicit prefix caching

OpenAI applies caching automatically for prompts longer than 1,024 tokens on supported models (e.g., gpt-4o, gpt-4o-mini). No API change is needed beyond keeping the prefix identical. You still benefit from the restructuring in Step 2.

Step 4: Route through a gateway that honors cache directives

If you serve multiple models or need fallback, use a gateway that forwards cache hints without stripping them. n4n.ai exposes an OpenAI-compatible endpoint across 240+ models and forwards provider cache-control hints, so the same client code works whether the backend is Anthropic or OpenAI. It also meters per-token usage, which makes verification trivial.

Point your OpenAI client at the gateway:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.n4n.ai/v1",
    api_key="YOUR_KEY"
)
# Same chat.completions call as OpenAI; cache hints pass through

Automatic fallback kicks in when a provider is rate-limited, but cached prefixes only apply to the original provider—expect a cold cache on fallback.

Step 5: Verify cache hits and measure savings

Never assume caching works. Check the usage object on every response.

For Anthropic:

print(response.usage)
# {'input_tokens': 120, 'output_tokens': 80, 'cache_read_input_tokens': 5400, 'cache_creation_input_tokens': 0}

For OpenAI via the gateway, inspect response_headers for x-cache-read-tokens if exposed, or rely on the prompt_tokens_details field:

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=messages
)
print(resp.usage.prompt_tokens_details)
# {'cached_tokens': 5400, 'audio_tokens': 0}

A successful rollout shows cache_read_input_tokens (or cached_tokens) growing on the second turn of a session. To verify prompt caching cost reduction, compare total input tokens over a day before and after. If your static prefix is 5,000 tokens and you process 1,000 agent turns, uncached cost is ~5M input tokens; cached, you pay creation once plus ~5K reads per turn at a 90% discount. That math yields the 60%+ cut.

Step 6: Handle multi-turn conversations and invalidation

Caching is prefix-based. In a multi-turn agent loop, append new assistant and user messages after the static block. Never mutate the system prompt mid-session.

messages = [
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "system", "content": TOOL_SCHEMA}
]
while True:
    user_msg = get_user_input()
    messages.append({"role": "user", "content": user_msg})
    resp = client.chat.completions.create(model="gpt-4o", messages=messages)
    messages.append({"role": "assistant", "content": resp.choices[0].message.content})

Cache TTLs are short—Anthropic ephemeral caches last 5 minutes, OpenAI’s cached prefix persists based on recent usage. If your agent idles beyond the TTL, the next call pays creation cost again. For long-running workers, send a keepalive request with the static prefix every few minutes.

Step 7: Monitor cache miss rate in production

Log the ratio of cache_read_input_tokens to total input tokens per request. A miss rate above 10% on steady sessions signals prefix drift or TTL expiry.

import logging

def log_cache_ratio(usage):
    read = getattr(usage, "cache_read_input_tokens", 0)
    total = usage.input_tokens
    if total > 0:
        logging.info("cache_hit_ratio=%.2f", read / total)

Alert when the rolling average drops below 0.8. The usual culprits: a dynamic variable leaked into the system prompt, or a gateway routing to a model that doesn’t support caching for that prefix length.

What to watch out for

Cache creation is not free. Providers charge a small premium (often 1.25×) for writing the prefix. If your agent turns are sparse—say one call per hour—the cache expires before reuse and you lose money. Cache only when you expect at least two hits within the TTL.

Also, not all models cache equally. Smaller models may have tighter minimum prefix lengths. Test with your actual system prompt, not a toy string.

Prompt caching cost reduction is a structural change, not a toggle. Get the prefix right, verify the hits, and your agent bill drops without touching model quality.

Tagsprompt-cachingcost-optimizationapi-costsllm-agents

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All ai agent cost & latency optimization posts →