Skip to main content
友田 陽大
本番LLMアプリ実装(信頼できるAI)
生成AI
LLM
キャッシュ
コスト最適化
パフォーマンス
RAG
pgvector
AIエージェント
TypeScript
アーキテクチャ設計

Production LLM App Caching Strategy 2026 | Prompt Caching + Semantic Caching to Cut Latency & Cost

An implementation guide to two kinds of caching that cut latency and cost for generative-AI/LLM apps: provider prompt caching (Anthropic cache_control, OpenAI automatic, Google context caching) and application-layer semantic caching (embedding similarity, pgvector) — with when to use which, false-hit mitigation, TTL design, and real TypeScript code.

Published
Reading time
7 min read
Author
友田 陽大
Share

Each LLM call is expensive and high-latency. Re-processing the same system prompt and long context in full every time, and generating from scratch for every similar question — leave that unchecked in production and your bill and p95 latency quietly balloon. Caching is the cheapest lever for cutting an LLM app's cost and latency.

But "caching" comes in two kinds with different properties, and the key is using each appropriately.

  1. Prompt caching (a provider feature) — reuses the prompt's stable prefix (system instructions, long context) and discounts those input tokens. Exact-match based.
  2. Semantic caching (app layer) — returns a past answer to a "paraphrased, similar question" via embedding similarity, skipping the LLM call itself. Threshold-based (= it carries a false-hit risk).

This article applies the production RAG with pgvector know-how to caching, and — after verifying each provider's official docs — designs both in real TypeScript. It's a move that complements reliability & resilience from the latency/cost side of "production AI."

⚠️ The prices and token thresholds here are 2026 official values tied to specific model names (Opus 4.8 / GPT-5 family / Gemini 3.x). They change frequently, so re-verify each source before implementing. Discount rates are model-dependent.

The two kinds of caching

Prompt cachingSemantic caching
LayerProvider (API feature)App (DIY / GPTCache, etc.)
MatchExact-match of the prefixSemantic similarity of embeddings (catches paraphrases)
EffectDiscounts input tokensSkips the LLM call entirely
RiskVirtually none (safe)False hits (needs threshold/TTL/eval)
Good forLarge fixed context (RAG, system instructions, few-shot)FAQ, canned questions, paraphrase-heavy inquiries

Start with safe prompt caching, then carefully add semantic caching — that's the practical order.

Prompt caching: put static content at the "front"

The effect of prompt caching is decided by the structure of your prompt. The shared principle: put the large unchanging part (system instructions, long context, few-shot examples) at the front, and the changing part (user input) at the back. The more the prefix matches, the better the cache works.

  • OpenAI is automatic. No code changes; it works on prompts above a length (1,024 tokens), then hits in 128-token increments. Just put static content at the front to benefit. The cached-read discount is model-dependent (~50% for the GPT-4o generation, up to 90% for the GPT-5 family).
  • Anthropic is explicit via cache_control (ephemeral). Up to 4 breakpoints; default TTL 5 minutes (1-hour option). Pricing is cache write 1.25× (5-min) / 2× (1-hour), read 0.1× (relative to base input, as of 2026).
  • Google Gemini has both an explicit CachedContent API and, from 2.5 onward, implicit caching on by default (note that keeping caches alive incurs a per-hour storage cost).

With the Vercel AI SDK, the simplest is the AI Gateway's automatic caching.

import { generateText } from "ai";

// 最も簡単:AI Gateway の自動キャッシュ。プロバイダに応じ最適な戦略を適用する
// (Anthropic系は cache_control を注入、OpenAI/Google は暗黙キャッシュを利用)。
const { text } = await generateText({
  model: "anthropic/claude-opus-4-8",
  messages,
  providerOptions: { gateway: { caching: "auto" } },
});

For explicit control, place cacheControl on the stable prefix.

// 明示制御:静的コンテンツを"前"に置き、そこに cacheControl を付ける。
const { usage } = await generateText({
  model: "anthropic/claude-opus-4-8",
  messages: [
    {
      role: "system",
      content: largeStaticContext, // 大きく・変わらない部分(RAG文脈やfew-shot)
      providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
    },
    { role: "user", content: userQuestion }, // 変わる部分は後ろ
  ],
});
// usage.inputTokenDetails.cacheReadTokens でヒットを計測(読み取りは入力の約0.1×課金)

Prompt caching is virtually no-risk (exact match, so no wrong answers). Start here as a rule.

Semantic caching: skip the call itself

Go one step further: "don't call the LLM at all for similar questions." Embed the question, match it against past questions by cosine similarity, and if it exceeds a threshold, return the past answer. Where prompt caching is "exact-prefix reuse," this catches paraphrases — its strength.

Separate the decision logic (threshold, freshness) into a pure function for testability.

// セマンティックキャッシュ:埋め込み類似で「言い換え」まで拾う。判定は純粋関数で分離。
interface CacheHit {
  readonly answer: string;
  readonly similarity: number; // 0..1(コサイン類似)
  readonly ageMs: number;
}

// 純粋・テスト容易:閾値と鮮度(TTL)の両方を満たすヒットだけ採用する(SRP)。
function acceptCacheHit(hit: CacheHit | null, minSimilarity: number, ttlMs: number): boolean {
  return hit !== null && hit.similarity >= minSimilarity && hit.ageMs <= ttlMs;
}

// cache-aside:ヒットなら即返す(LLM呼び出しを省く)、ミスなら生成して保存。
async function cachedGenerate(query: string, scope: string): Promise<string> {
  const embedding = await embed(query);
  // pgvector で最近傍を1件(scope でユーザー/地域を隔離し、他者の応答を返さない)
  const hit = await cache.nearest(embedding, scope);
  if (acceptCacheHit(hit, 0.95, 3_600_000)) return hit!.answer; // 高閾値で偽ヒットを抑制
  const answer = await callLlm(query);
  await cache.put({ query, embedding, answer, scope }); // ミス:保存して次回に備える
  return answer;
}

Semantic caching: risks and controls

The danger of semantic caching is false hits. "What were 2024 sales?" and "What were 2025 sales?" have near-identical embeddings, yet the correct answers differ. A loose threshold mass-produces wrong answers. Four controls:

  • Threshold — start high (0.90–0.95) and tune while monitoring the false-hit rate (AWS, too, states "the threshold controls the trade-off between hit rate and answer quality").
  • TTL — by data volatility. Real-time (prices/inventory) 5–15 min, static facts 24 hours. Longer TTL raises hit rate but also staleness risk.
  • Scope — filter by user, region, product ID, so a personalized response isn't served to someone else.
  • Invalidation — invalidate the affected entries content-triggered when source data updates.

And measure hit quality with evaluation. Include "is this cache hit correct?" in offline evaluation and gate on the false-hit rate (LLM evaluation & testing). The go-to OSS is GPTCache, where you can swap the threshold and vector store.

Be equally clear about when NOT to cache. Always needs freshness, heavily personalized, audit requires fresh generation — for these, the cost of false hits/staleness outweighs the benefit, so just call every time.

Observability: measure the effect and the side effects

Caching is only safe once you measure it. Record hit rate, tokens/cost saved, and p95 latency, plus the false-hit rate (from evaluation). Metrics guide tuning — "hit rate is high but false hits rose = threshold too loose." For the foundation, see production observability with OpenTelemetry. Note that caching is a different layer of optimization from model/infra economics (self-host vs API); for that, see generative-AI cost design.

Adoption checklist

  • Prompt caching (the safe first move) — static content at the front, OpenAI automatic, Anthropic cacheControl (or Gateway caching:'auto') enabled?
  • Structure — the large unchanging part at the front, the variable part at the back?
  • Semantic caching (carefully) — starting from a high similarity threshold (0.90+)?
  • TTL — set by data volatility, and invalidated on source updates?
  • Scope — hits isolated by user/region, not leaking personalization?
  • False-hit monitoring — is cache-hit correctness in evaluation and gated?
  • Non-cache decision — excluded queries that need freshness / heavy personalization / audit?
  • Observability — monitoring hit rate, cost saved, latency, and false-hit rate?

Summary

Caching for LLM apps is, by convention, "a foundation of safe prompt caching, with semantic caching carefully layered on."

  • Prompt caching is exact-match and virtually no-risk. Just putting static content at the front lowers input cost (Anthropic reads ≈0.1×, OpenAI automatic).
  • Semantic caching is a powerful way to skip whole calls, but false hits are its intrinsic risk. Manage it with a high threshold, TTL, scope, and evaluation.
  • Operate by measuring. Monitor not just hit rate but the false-hit rate, and include the decision not to cache in your design.

Fast, cheap, and accurate — you can have all three at once when you design caching correctly.

Frequently asked questions

What's the difference between prompt caching and semantic caching?
Prompt caching is a provider feature that reuses a stable 'prefix' of the prompt (system instructions, long context) by exact match and discounts those input tokens (Anthropic reads are ≈0.1× of base input). Semantic caching is an app-layer mechanism that returns a past answer to a 'paraphrased, similar question' via embedding similarity, skipping the LLM call itself. You can use both together.
Do Anthropic/OpenAI prompt caching need code?
OpenAI is automatic — it works without code changes on prompts above a length (1024 tokens). Anthropic is explicit via cache_control (ephemeral) (with the Vercel AI SDK, providerOptions.anthropic.cacheControl; with the AI Gateway, caching: 'auto'). Both hit more when static content is at the front. Discount rates are model-dependent (up to 90% for the GPT-5 family; Anthropic reads ≈0.1×), and prices are 2026 values, so re-verify the primary sources before implementing.
Isn't semantic caching dangerous?
The biggest risk is false hits. A question that's semantically similar but different in intent (different time period, polarity, or subject) can be served a wrong past answer. Manage it with a high similarity threshold (start at 0.90–0.95 and tune while monitoring), TTL, scope (user/region), and hit-quality monitoring via evaluation. Be cautious with time-sensitive or heavily personalized responses.
How do you set the cache TTL?
By data volatility. Rules of thumb: real-time data (prices/inventory) 5–15 minutes; static facts ~24 hours. Longer TTLs raise the hit rate but also the risk of stale answers. When the source data updates, invalidate the affected entries explicitly.
When should you NOT cache?
When freshness is always required, responses are heavily personalized, or legal/audit requirements demand fresh generation every time. If the cost of false hits or staleness outweighs the cost/latency savings, the right call is to not cache that query.
How do you measure the effect of caching?
Monitor hit rate, tokens/cost saved, and p95 latency. Anthropic exposes cacheReadTokens/cacheWriteTokens in usage to measure reads/writes. Measure the 'quality' of hits (whether false hits crept in) continuously via evaluation (eval).

References

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Also worth reading