Semantic Caching
Two users ask "what's your refund policy?" and "how do refunds work?" — semantically identical, lexically different. A normal cache misses both. This module doesn't.
Learning objectives
- Beginner: A semantic cache in front of a single, high-traffic FAQ endpoint, with a conservative threshold and manual monitoring of hit quality.
- Intermediate: Redis-backed semantic cache with a TTL matching how quickly the underlying answers actually change (e.g. daily for promotions, indefinite for policy text).
- Advanced: Combine with observability (Module 17) to track cache hit rate and false-positive rate over time, adjusting the threshold based on real production query patterns rather than a value chosen once at launch.
◆ The problem
A traditional cache keys on exact string match — "what's your refund policy?" and "how do refunds work?" are different cache keys entirely, even though a correct answer to one is a correct answer to the other. Every rephrased-but-identical question re-triggers a full (possibly RAG-augmented) LLM call: the latency and cost of Module 09's whole pipeline, paid again for a question you've effectively already answered.
A semantic cache keys on meaning instead of exact text: embed the incoming question, and if a sufficiently similar question (above a similarity threshold) exists in the cache, return its stored answer without calling the model at all.
This directly reuses the vector-search machinery from Module 08: the "cache" is itself a small vector store. Cache lookup is a similarity search; a cache write is inserting a new embedded (question, answer) pair.
@Service public class SemanticCacheService { private final VectorStore cacheStore; // a dedicated collection, separate from RAG's own store private static final double SIMILARITY_THRESHOLD = 0.85; // sweet spot is ~0.7-0.9 — see pitfall below public Optional<String> lookup(String question) { return cacheStore.similaritySearch( SearchRequest.builder().query(question).topK(1).similarityThreshold(SIMILARITY_THRESHOLD).build()) .stream() .findFirst() .map(doc -> (String) doc.getMetadata().get("answer")); } public void store(String question, String answer) { cacheStore.add(List.of(new Document(question, Map.of("answer", answer)))); } }
💻 Code example
@Service public class SemanticCacheService { private final VectorStore cacheStore; // a dedicated collection, separate from RAG's own store private static final double SIMILARITY_THRESHOLD = 0.85; // sweet spot is ~0.7-0.9 — see pitfall below public Optional<String> lookup(String question) { return cacheStore.similaritySearch( SearchRequest.builder().query(question).topK(1).similarityThreshold(SIMILARITY_THRESHOLD).build()) .stream() .findFirst() .map(doc -> (String) doc.getMetadata().get("answer")); } public void store(String question, String answer) { cacheStore.add(List.of(new Document(question, Map.of("answer", answer)))); } }
▲ Pitfall
This is the entire design risk of semantic caching: set the threshold too low, and genuinely different questions ("what's your refund policy?" vs. "what's your exchange policy?") can be similar enough to incorrectly return the wrong cached answer — a false cache hit is worse than a cache miss, because it's silently, confidently wrong. Set it too high (approaching 1.0), and matching becomes too strict — you lose most of the benefit, rarely matching anything except near-exact rephrasings. The practical sweet spot most teams land on is 0.7–0.9, tuned against real query logs rather than guessed at — not the near-1.0 value intuition suggests "safe" would be.
◆ Under the hood — Redis vs. a dedicated vector store for caching
Redis, with its vector search module, is a common choice specifically for semantic caching (as distinct from the main RAG knowledge store) because cache entries are typically short-lived, high-throughput, and benefit from Redis's existing TTL/eviction semantics — a cached answer to a question about "today's promotions" should naturally expire, which a general-purpose RAG document store isn't optimized to do.
✓ Quick recap
What does a semantic cache key on, instead of exact string match? Embedding similarity — a rephrased but semantically identical question can still hit the cache. Why is a false cache hit worse than a cache miss? It returns a wrong answer confidently and silently, rather than simply costing an extra (correct) model call.
Want a visual for this concept?
Generate a diagram tailored to “Semantic Caching” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →