Semantic Caching: A Technical Guide for LLM Applications

Matt George

Fractional CTO · Founder of Injuria.ai

•11 min read•

Large Language Model (LLM) API calls are often slow and expensive. For many applications, a significant portion of user queries are repetitive, not in their exact wording, but in their underlying intent. A user might ask "How do I change my password?", while another asks "I forgot my password, what do I do?". A traditional cache, which relies on exact string matching, would see these as two distinct misses. This is where semantic caching provides a powerful optimization.

Semantic caching intercepts prompts, and instead of just checking for an identical past query, it searches for a semantically similar one. If a sufficiently similar prompt exists in the cache, the system returns the stored response without ever calling the LLM. This approach uses vector embeddings to understand the meaning behind the words. For technical leaders and engineers building with LLMs, understanding semantic caching is critical for creating applications that are cost-effective, fast, and reliable.

How Semantic Caching Works: The Mechanics

Semantic caching replaces the simple key-value lookup of traditional caching with a more sophisticated vector similarity search. The entire process can be broken down into a sequence of well-defined steps, which typically involve an embedding model and a vector database.

Here is the step-by-step flow for a request in a system using semantic caching:

  1. Prompt Ingestion: The application receives a user prompt, for example, "I can't get into my account and need to reset my password."
  2. Embedding Generation: Instead of using the raw text as a cache key, the system uses a specialized embedding model to convert the prompt into a numerical vector. This vector, or embedding[1], is a list of numbers (e.g., 768 or 1536 dimensions) that represents the prompt's position in a high-dimensional semantic space. Prompts with similar meanings will have vectors that are close to each other in this space.
  3. Vector Similarity Search: The newly generated vector is used to query a vector database[2] or index. This database stores the embeddings of previously seen prompts. The search doesn't look for an exact match; it looks for the nearest neighbors to the query vector, typically using a similarity metric like cosine similarity[3]. The database returns a list of the most similar stored embeddings and their associated responses.
  4. Threshold Evaluation: The system calculates the similarity score between the new prompt's embedding and the top search result. This score (e.g., 0.98 for a near-perfect match, 0.7 for a loosely related concept) is compared against a pre-defined threshold. This threshold is a critical tuning parameter that balances the risk of incorrect matches with the benefit of more frequent cache hits.
  5. Cache Hit: If the similarity score is above the threshold, the system declares a cache hit. It retrieves the cached response that corresponds to the found embedding and returns it directly to the user. The expensive, slow call to the LLM is completely avoided.
  6. Cache Miss: If the top similarity score is below the threshold, or if the cache is empty, it's a cache miss. The application proceeds as it would without a cache. The prompt is forwarded to the LLM (e.g., via the OpenAI API[4] or AWS Bedrock[5]).
  7. Cache Population: After the LLM generates a response, the system doesn't just return it to the user. It first stores the original prompt's embedding and the new LLM response in the vector cache. This new entry enriches the cache, making it capable of answering this question, and similar ones, in the future.

This entire mechanism relies on two key components: a high-quality embedding model that can accurately capture semantic meaning, and an efficient vector store that can perform nearest-neighbor searches over millions or billions of vectors in milliseconds.

A Concrete Example: Customer Support Chatbot

Let's illustrate this with a simplified Python example. Imagine we're building a chatbot. We'll use the sentence-transformers library to generate embeddings and a simple dictionary to act as our in-memory vector cache. In a production system, this dictionary would be replaced by a dedicated vector database like Redis, PostgreSQL with pgvector[6], or a managed service like Pinecone[7].

import numpy as np
from sentence_transformers import SentenceTransformer, util

# 1. Initialize components
embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
semantic_cache = {}
SIMILARITY_THRESHOLD = 0.95

def llm_call(prompt: str):
 """A mock function representing a call to a real LLM."""
 print(f"\n>>> LLM Cache Miss. Calling LLM for: '{prompt}'")
 # In a real app, this would be an API call to GPT-4, Claude, etc.
 if "password" in prompt.lower():
 return "To reset your password, please visit the 'Account Settings' page and click 'Reset Password'."
 return "I'm sorry, I don't have an answer for that."

def get_response(prompt: str) -> str:
 print(f"---\nProcessing prompt: '{prompt}'")
 
 # 2. Generate embedding for the new prompt
 prompt_embedding = embedding_model.encode(prompt, convert_to_tensor=True)
 
 # 3. Search the cache
 best_match_score = -1
 best_match_prompt = None

 # In a real DB, this is a single `search` call.
 # Here we iterate for demonstration.
 for cached_prompt, data in semantic_cache.items():
 cached_embedding = data['embedding']
 similarity = util.cos_sim(prompt_embedding, cached_embedding).item()
 
 if similarity > best_match_score:
 best_match_score = similarity
 best_match_prompt = cached_prompt

 # 4. Evaluate threshold and decide on hit or miss
 if best_match_prompt and best_match_score > SIMILARITY_THRESHOLD:
 # 5. Cache Hit
 print(f"<<< Semantic Cache Hit! Score: {best_match_score:.4f}")
 print(f" (Matches cached prompt: '{best_match_prompt}')")
 return semantic_cache[best_match_prompt]['response']
 else:
 # 6. Cache Miss
 response = llm_call(prompt)
 
 # 7. Populate cache
 print(f" Populating cache with new entry.")
 semantic_cache[prompt] = {
 'embedding': prompt_embedding,
 'response': response
 }
 return response

# --- Simulation ---

# First query is always a miss
response1 = get_response("How can I reset my password?")
print(f"Response: {response1}")

# Second, semantically similar query is a hit
response2 = get_response("I forgot my login and need to change my password")
print(f"Response: {response2}")

# Third, unrelated query is a miss
response3 = get_response("What are your business hours?")
print(f"Response: {response3}")

When you run this, you'll see the first query results in an LLM call. The second, despite being worded differently, achieves a high similarity score and results in a cache hit, instantly returning the stored answer. The third query is correctly identified as a miss, triggering another LLM call and populating the cache with a new, distinct entry.

The Tradeoffs: A Decision Lens for Leaders

Semantic caching is not a silver bullet. A technical leader must weigh its significant benefits against its operational costs and failure modes.

Benefits and When to Use It

Semantic caching is most effective in applications with a high volume of recurring user intents, even if the phrasing varies. This is common in customer support, FAQ bots, and internal knowledge base search.

  • Cost Reduction: This is the most direct benefit. Every cache hit saves a full round-trip to a paid LLM API. With models like GPT-4 costing dollars per million tokens, an effective cache with a 30% to 50% hit rate can translate directly to a 30% to 50% reduction in LLM operational expenses.
  • Latency Improvement: A vector search in a well-architected system (e.g., using an optimized index like Faiss[8]) can take 20 to 100 milliseconds. An LLM call can take 2 to 10 seconds. For user-facing applications, this 100x difference in response time dramatically improves the user experience.
  • Rate Limit Mitigation: High-traffic applications often run into API rate limits imposed by LLM providers. By handling a large fraction of requests from the cache, you reduce the overall call volume, making it easier to operate within these limits.
  • Response Consistency: LLMs can be non-deterministic. Asking the same question twice might yield slightly different answers. Caching ensures that for a given semantic query, the response is always the same pre-vetted, high-quality answer. This is crucial for applications that must provide factual and consistent information.

Costs and Failure Modes

Implementing semantic caching introduces new components and new potential points of failure into your system.

  • Semantic Collisions (False Positives): This is the most dangerous failure mode. The system incorrectly determines that two semantically different prompts are similar, causing it to return a wrong and irrelevant answer. For example, a cache might confuse "Get sales report for Apple Inc." with "Get sales report for Applebee's". This is not just a bad user experience; it can be a critical error. Mitigating this requires careful selection of the embedding model and rigorous tuning of the similarity threshold.
  • Semantic Misses (False Negatives): This is a less severe but more common issue where the system fails to recognize two prompts are semantically identical. For example, it might not match "where is your office?" with "what's your company's address?". This results in a missed opportunity to save cost and time, reducing the cache's efficiency. This is often a sign that your embedding model is not a good fit for your domain or your threshold is too high.
  • Cache Invalidation and Staleness: What happens when a correct answer changes? If your company's return policy is updated, all cached responses related to the old policy are now dangerously incorrect. This is a classic, hard problem in computer science. Simple solutions like a Time-to-Live (TTL)[9] can help, but more robust systems require event-driven invalidation, where an update to a source document (e.g., in a RAG[10] system) triggers the removal of related entries from the semantic cache.
  • Infrastructure Overhead: You are trading LLM API calls for your own infrastructure. This means managing, scaling, and paying for an embedding model service and a vector database. While often cheaper than the LLM calls they replace, this adds architectural complexity and requires engineering expertise.
  • The Long Tail Problem: Caching works best on popular items. In many applications, the distribution of user queries follows a long tail, with a small number of very common queries and a vast number of unique, rare ones. A semantic cache will be very effective for the "head" of this distribution but will have a near-zero hit rate for the long tail. You must analyze your expected query patterns to determine if the hit rate will be high enough to justify the implementation cost.

Correcting Common Misconceptions

As with any emerging technology, several misconceptions have formed around semantic caching.

Misconception 1: "It's just a key-value cache."
This is incorrect. A traditional key-value cache, like standard HTTP caching[11], relies on an exact match of the key. cache.get("How do I reset my password?") will only work if that exact string was previously stored. Semantic caching works on meaning. It effectively allows a lookup like cache.find_similar("I forgot my login"), which is a fundamentally different and more powerful operation based on vector math, not string equality.

Misconception 2: "Any embedding model will do."
This is a costly mistake. The choice of embedding model is the single most important factor in a semantic cache's performance. Models are trained for different purposes. Some are best for symmetric tasks (comparing two short, similar texts like prompts), while others excel at asymmetric tasks (comparing a short query to a long document, as in RAG). Using the wrong model will lead to a high rate of false positives and negatives[12]. Leaders should consult resources like the MTEB (Massive Text Embedding Benchmark) leaderboard[13] to select a top-performing model appropriate for their specific use case.

Misconception 3: "The similarity threshold is a set-it-and-forget-it number."
The similarity threshold is a dynamic tuning parameter, not a universal constant. An ideal threshold of 0.95 for one application might be too restrictive or too permissive for another. It depends on the embedding model used, the nature of the expected queries, and the business's tolerance for false positives versus false negatives. The threshold should be determined empirically using a validation set of query pairs and adjusted as the application and its data evolve.

History and Current State

The ideas behind semantic caching are not new, but their practical application at scale is a recent development. The core concept, the vector space model[14], was developed by Gerard Salton[15] and his colleagues at Cornell in the 1970s for information retrieval systems. For decades, however, creating high-quality vector representations of language was a major challenge.

A significant breakthrough came in 2018 with the release of BERT (Bidirectional Encoder Representations from Transformers)[16]. BERT and subsequent Transformer-based models provided a way to generate nuanced, context-aware embeddings that captured semantic meaning far more effectively than previous methods. This innovation in modeling, combined with the parallel development of scalable vector databases, laid the necessary groundwork.

The final catalyst was the explosion of generative LLMs in the early 2020s. The high cost and latency of these models created a powerful economic incentive to avoid calling them whenever possible. Semantic caching emerged as the perfect solution, applying decades-old information retrieval theory with modern machine learning infrastructure to solve a pressing new problem.

Today, semantic caching is rapidly moving from a niche optimization to a standard component of the LLM application stack. Major frameworks like LangChain[17] and LlamaIndex[18] offer built-in support for semantic caching. Cloud providers are also integrating these capabilities into their AI platforms, such as Google Vertex AI Search and Amazon's offerings. There are no formal IETF[19] or W3C[20] standards governing semantic caching; it is a design pattern. However, de facto standards are coalescing around the APIs of popular vector databases and the formats of leading open-source embedding models from sources like Hugging Face[21]. For any team building serious LLM applications, implementing a semantic caching strategy is now a key consideration for achieving performance, reliability, and cost control.

Sources (21)
  1. Google Developers - Embeddings
  2. Wikipedia - Vector database
  3. Wikipedia - Cosine similarity
  4. OpenAI API Documentation
  5. AWS Bedrock
  6. pgvector on GitHub
  7. Pinecone
  8. Facebook Engineering - Faiss
  9. Wikipedia - Time to live
  10. Wikipedia - Retrieval-augmented generation
  11. MDN Web Docs - HTTP Caching
  12. Wikipedia - False positives and false negatives
  13. Hugging Face - MTEB Leaderboard
  14. Wikipedia - Vector space model
  15. Wikipedia - Gerard Salton
  16. arXiv - BERT Paper
  17. LangChain LLM Caching
  18. LlamaIndex
  19. IETF
  20. W3C
  21. Hugging Face

Recommended reading

Structured Data Extraction With LLMs: When It Beats Hand-Written Parsers12 min readLarge Language Models can extract structured data like JSON from unstructured text with surprising reliability, but the mechanism and tradeoffs determine when this approach is superior to traditional parsers.Speculative Decoding: How LLMs Generate Faster12 min readSpeculative decoding accelerates large language model inference by using a smaller, faster model to draft text that a larger, more powerful model then verifies in parallel, significantly reducing generation latency without compromising output quality.LLM Observability: Tracing, Logging, and Cost Tracking11 min readA comprehensive guide to LLM observability, explaining the technical mechanisms of tracing, logging, evaluation, and cost analysis for production AI systems.Context Windows: What They Cost and How to Use Them Well13 min readA context window is the measure of text an AI model can consider at once to process a request and generate a response. Understanding its technical mechanisms, costs, and failure modes is critical for making sound engineering decisions about when to use large contexts versus alternative architectures like RAG.How Attention Works in Large Language Models13 min readA technical explanation of the attention mechanism in LLMs, from its conceptual origins to the mathematical details of Scaled Dot-Product Attention and its practical implications for engineering leaders.Can I Just Use ChatGPT to Scrape a Website?7 min readCan ChatGPT scrape a website? For one page maybe, but here is why it breaks at real scale and where AI genuinely earns its keep in a data pipeline.