Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

AI Caching Strategies

Cut LLM cost and latency with caching: exact-match caching, semantic caching by embedding similarity, provider prompt caching, and invalidation — with Redis and Java examples.

Advanced4 min readUpdated
On this page

Model calls are the dominant cost and latency in an AI application, and a lot of traffic is repetitive. Caching — from simple exact-match to embedding-based semantic caching — can cut both dramatically. This tutorial covers the strategies, with the honest caveat that semantic caching trades a little accuracy for the savings.

Key Takeaways

  • Exact-match caching is simple and safe for identical requests — use it freely.
  • Semantic caching catches paraphrased queries by embedding similarity — big savings, some risk.
  • Provider prompt caching makes repeated prompt prefixes cheaper — structure prompts for it.
  • Set semantic-cache thresholds conservatively; do not cache where a near-miss is harmful.

Exact-match caching

The simplest and safest: cache the response for an identical request. Same prompt, same parameters, same answer.

Exact-match cache
public String ask(String question, ChatOptions options) {
    // Key on the full request — prompt and parameters — so a cached answer is
    // genuinely for the same call.
    String key = cacheKey(question, options);
    String cached = redis.get(key);
    if (cached != null) {
        return cached;   // no model call, no cost, sub-millisecond
    }
    String answer = chatClient.prompt().user(question).options(options).call().content();
    redis.set(key, answer, Duration.ofHours(24));
    return answer;
}

Exact-match is safe because an identical request genuinely has the same answer (at temperature 0). It only misses when wording differs — which is where semantic caching comes in.

Semantic caching

Users phrase the same question many ways. Semantic caching matches by meaning:

Semantic cache
public String askCached(String question) {
    float[] queryVector = embeddingModel.embed(question);
 
    // Look for a cached query similar enough to reuse its answer.
    var hit = semanticCache.search(SearchRequest.builder()
            .query(question)
            .topK(1)
            // Conservative threshold — a near-miss returns a wrong answer.
            .similarityThreshold(0.95)
            .build());
 
    if (!hit.isEmpty()) {
        return hit.get(0).getMetadata().get("answer").toString();   // cache hit
    }
 
    String answer = chatClient.prompt().user(question).call().content();
    // Store the question (embedded) and its answer for future similar queries.
    semanticCache.add(question, answer);
    return answer;
}

Provider prompt caching

Distinct from caching responses: some providers cache the processing of a repeated prompt prefix. If every request shares a long system prompt or a large fixed context, structuring the prompt so the stable part comes first lets the provider reuse its processing:

// Put the stable, reusable content first (system prompt, fixed context), and
// the variable content last. Providers that support prefix caching then make
// repeated calls cheaper and faster on the shared prefix.
String prompt = STABLE_SYSTEM_PROMPT + STABLE_CONTEXT + variableUserQuery;

This is especially valuable for RAG or agents with a large fixed instruction set repeated on every call. It reduces cost without any risk of a wrong answer, because it caches computation, not results.

What to cache and what not to

Match the caching strategy to the request: exact-match for identical, semantic for paraphrased, none for personalized or time-sensitive.

Do not cache:

  • Personalized responses — a cache keyed only on the question serves one user's answer to another. Include the user or tenant in the key, or do not cache.
  • Time-sensitive answers — "what is today's status?" cached for a day is wrong for 23 hours.
  • Anything with tenant-scoped data — a cross-tenant cache hit is a data leak.

Invalidation

The hard part of caching, doubly so here. When the underlying knowledge changes, cached answers based on it are stale:

// When source documents change, invalidate cached answers derived from them.
// Tag cache entries with the sources they used, and invalidate by source.
public void onDocumentUpdated(String sourceId) {
    cache.invalidateByTag("source:" + sourceId);
}

For RAG answers, tag each cached answer with the sources it drew on, so updating a document invalidates exactly the answers that depended on it.

Measuring cache effectiveness

Track hit rate and savings, and watch for quality problems:

// Hit rate tells you the savings; sampled quality checks on cache hits tell you
// whether semantic caching is serving near-misses.
metrics.counter("ai.cache", "result", hit ? "hit" : "miss").increment();

A high hit rate is only good if the hits are correct. For semantic caching, sample cache hits and verify the returned answer actually fits the query — a hit rate that comes from serving near-misses is worse than no cache. See LLM evaluation & benchmarks.

Next

Frequently Asked Questions

What is a semantic cache?
A cache keyed by embedding similarity rather than exact string match, so "how do I reset my password" hits the entry stored for "password reset steps". It embeds each query, searches for a sufficiently similar cached query, and returns its answer if one is close enough. It cuts cost and latency on repetitive traffic that an exact-match cache would miss because the wording differs.
When should I use exact-match caching versus semantic caching?
Exact-match caching is simple and safe for identical requests — same prompt, same parameters. Semantic caching catches paraphrased queries an exact match misses, but risks returning a near-miss answer for a query that is similar but not the same. Use exact-match freely; use semantic caching where repetitive paraphrased queries are common and the risk of a close-but-wrong answer is acceptable.
What is provider prompt caching?
Some model providers cache the processing of a repeated prompt prefix — a long system prompt or shared context — so subsequent calls reusing that prefix are cheaper and faster. You enable it by structuring prompts with the stable, reusable part first. It is distinct from caching whole responses; it caches the expensive prompt processing.
What is the risk of semantic caching?
Returning a cached answer for a query that is similar but meaningfully different — the classic near-miss. "How do I cancel my subscription" and "how do I pause my subscription" may be close in embedding space but need different answers. Set the similarity threshold conservatively, and do not semantically cache queries where a close-but-wrong answer is harmful.

Related tutorials