What Is Semantic Caching? A Technical Deep Dive
Semantic caching is a request-side cache that embeds an incoming prompt into a vector, searches previously stored prompt vectors, and returns a stored response when the cosine similarity exceeds a configured threshold. It differs from a conventional cache in one respect that determines everything else about its behavior: the lookup is approximate, so the cache can return a response to a question nobody has asked before. Bifrost, the open-source AI gateway built in Go by Maxim AI, is the best choice for enterprise teams running it in production, where the cache has to be governed and measured rather than switched on and trusted. This deep dive covers the lookup pipeline, the real cost arithmetic, how to choose a similarity threshold, and the workloads where it loses money.
What Is Semantic Caching?
It stores LLM responses indexed by the embedding vector of the prompt that produced them, then serves a stored response when a new prompt's vector is close enough to a stored one. Closeness is measured by cosine similarity against a threshold you configure, typically between 0.75 and 0.95.
Three caching layers get conflated, and the distinction matters:
- Exact-match (hash) caching: the request is normalized and hashed, and an identical request replays instantly. Deterministic, no embeddings, no false positives.
- Prefix or prompt caching: a provider-side feature that reuses the model's internal computation over a repeated prompt prefix. It reduces the cost of tokens you still send, and it is exact rather than approximate.
- Semantic (embedding) caching: approximate lookup that matches paraphrases. "What are your business hours?" and "when do you open?" resolve to the same stored answer.
They stack rather than compete. A production configuration runs exact-match first and semantic on miss, because exact matching is cheaper and carries no correctness risk. Bifrost implements that ordering, covered further in semantic caching for LLMs: how it works and the tools that do it.
How Does Semantic Caching Work?
A lookup runs five steps between the request arriving and the response returning. Where the cost sits in that sequence separates a cache that saves money from one that quietly adds latency.
- Normalize and key the request. The prompt, model, provider, and parameters are normalized into a deterministic representation. Cache partitioning (per tenant, per session, per application) is applied here.
- Attempt an exact match. The normalized request is hashed and looked up. A hit at this stage costs one store round-trip and returns immediately.
- Embed the request. On an exact miss, the prompt is sent to an embedding model. This is a network call to a provider, typically tens to a few hundred milliseconds, and it is paid whether or not the search that follows succeeds.
- Search and threshold. The resulting vector is searched against the stored vectors in a vector database. The nearest neighbor's cosine similarity is compared to the threshold. Above it, the stored response is returned; below it, the request proceeds to the model.
- Write asynchronously. On a miss, the provider response is returned to the caller first and stored in the background, so the first request never waits on the cache write.
Step 3 is the step most cost models omit. The embedding call happens on every exact miss, including the misses that end up going to the model anyway. That makes a semantic miss strictly more expensive and slower than no cache at all, which is the central trade-off in the entire design. Gateways differ mainly in how they handle that step, as compared in this roundup of the tools that implement it.
Semantic Cache vs Prompt Cache: What Is the Difference?
Prompt caching, offered by model providers, reuses computation over an identical prompt prefix and is billed at a reduced rate for the cached portion. It is exact, provider-side, and cannot return a wrong answer. A semantic cache is approximate, gateway-side, and skips the model call entirely on a hit. They operate at different layers, so a team running both gets prefix savings on cache misses and full call elimination on semantic hits.
How Much Does Semantic Caching Actually Save on LLM Costs?
The saving is not the hit rate. The saving is the hit rate multiplied by the cost of the calls avoided, minus the embedding cost paid on every exact miss. Written as arithmetic:
net saving = (hit rate × avg cost per LLM call) − (exact-miss rate × cost per embedding call)
Two properties of that equation drive most real deployments:
- The break-even hit rate is very low. Embedding models are priced orders of magnitude below chat models per token, and the embedding runs only over the request, not the generated response. When an embedding lookup costs well under one percent of the LLM call it might replace, a low single-digit hit rate already covers the overhead.
- Hit rate varies enormously by workload. Research on customer-service style traffic reports substantial hit rates on repetitive query sets; the GPT Semantic Cache study measured between 61.6% and 68.8% across four query categories on an 8,000-pair question-answer dataset. Open-ended conversational agents with long, personalized context land far lower.
The measurement error to avoid is reporting hit rate as a cost result. A 40% hit rate concentrated on the cheapest, shortest requests in your traffic saves very little, because the expensive calls are the long-context ones that rarely repeat. Attribute the saving per request, not per hit, and reconcile it against the provider bill. Teams building that picture can start from how to cut LLM API and token costs in 2026.
What Is the Right Similarity Threshold for a Semantic Cache?
The threshold is the single parameter that determines whether a semantic cache is useful or dangerous, and there is no universally correct value. Set it too low and the cache returns responses to questions that were never asked (false hits). Set it too high and near-identical paraphrases miss, and the cache stops earning its overhead.
Academic work makes the shape of this trade-off concrete. The MeanCache paper evaluates precision, recall, and F-score across the full threshold range and finds an optimal point that is embedding-model-specific: for one model in its evaluation the peak F-score of 0.88 falls at a threshold of 0.78. Precision improves as the threshold rises, but past the optimum, accuracy and recall decline as false cache misses accumulate.
More recent work argues that a single static threshold is the wrong abstraction entirely. The vCache research learns per-embedding threshold regions rather than applying one global value, reporting up to 26-fold increases in cache hit rate and error-rate reductions of up to 74% against static-threshold baselines.
The practical implications for a production deployment:
- Tune per workload, not globally. A factual FAQ endpoint tolerates a lower threshold than a code-generation endpoint where a near-miss produces plausible but wrong output.
- Start conservative and measure. Begin high, log the similarity score of every hit, and lower the threshold only where sampled hits are genuinely correct.
- Treat false hits as correctness incidents. A wrong cached answer is indistinguishable from a model error to the user and much harder to reproduce.
- Re-tune when the embedding model changes. The optimum is a property of the embedding space, so a model swap invalidates the previous value.
Threshold tuning is easier when the cache sits behind a gateway that already records per-request cost, which is the argument made in cutting token spend with AI gateways.
When Does a Semantic Cache Hurt More Than It Helps?
The technique is a poor fit for several common workloads, and recognizing them early avoids a rollout that adds latency without reducing spend:
- Long multi-turn conversations. Context accumulates, prompts stop repeating, and the vector of turn twelve resembles nothing in the store. Caching these mostly buys embedding calls.
- Personalized or tenant-specific answers. Two users can ask a semantically identical question and require different responses. Without strict partitioning, the cache leaks one tenant's answer to another.
- Time-sensitive content. Anything backed by changing data goes stale within the TTL window, and TTL alone is a blunt instrument for freshness.
- Tool-calling and agentic loops. The decision the model makes depends on live state, so replaying a prior decision is usually wrong. Token pressure in these workloads is better addressed at the tool layer, as in Code Mode for multi-tool MCP workflows.
- Low-volume endpoints. Below a certain request rate the store never warms up, and every request pays lookup overhead for a near-zero hit rate.
For these, exact-match caching alone often delivers most of the available saving at none of the correctness risk.
How Bifrost Implements Semantic Caching
Bifrost runs caching as a single plugin with two complementary lookup paths that can run together or independently. Direct mode hashes the normalized request for exact-match replay and needs no embedding provider at all. Semantic mode adds embedding-based similarity search on top, and runs only after a direct miss.
Several design decisions in the implementation map directly to the failure modes above:
- Caching is opt-in per request. A request caches only when it carries an
x-bf-cache-keyheader (or a configureddefault_cache_key). That key is also the partition boundary, so tenant and session isolation is explicit rather than assumed. - Direct-only mode is a first-class option. Setting
dimension: 1and omitting the embedding provider gives exact-match deduplication with zero embedding cost, which is the right configuration for stable, repeated prompts. - Multi-turn conversations are skipped by default. A
conversation_history_threshold(default 3) stops caching conversations longer than the configured message count, rather than accumulating entries that will never match. - Thresholds and TTL are overridable per request.
x-bf-cache-threshold,x-bf-cache-ttl,x-bf-cache-type, andx-bf-cache-no-storelet a single deployment run different policies per endpoint without separate cache instances. - Hits are observable. Every response carries cache debug metadata including
cache_hit,hit_type(direct or semantic), the actual cosinesimilarityon a semantic hit, and the tokens consumed computing the embedding. That is what makes threshold tuning empirical rather than guesswork.
Storage is a vector store you run: Redis or Valkey, Weaviate, Qdrant, or Pinecone. Entries carry a per-entry expiry and persist across restarts, so a redeploy keeps serving a warm cache. Writes are asynchronous, and caching covers chat completions, text completions, the Responses API, embeddings, transcriptions, speech, and image generation, including streaming variants. Because the cache sits in the gateway rather than in application code, savings are attributable per virtual key, which is how a hit rate becomes a line on a team's budget.
Semantic Caching FAQ
Does a semantic cache reduce latency?
On a hit, substantially: a stored response replaces a multi-second generation. But a semantic hit still costs an embedding round-trip, so it is slower than an exact-match hit, and a semantic miss is slower than no cache at all because the embedding call is paid on top of the full model call.
Can a semantic cache return a wrong answer?
Yes, and this is inherent rather than a bug. Any threshold below 1.0 accepts approximate matches, so some fraction of hits will be responses to a different question. The threshold controls that rate; it cannot eliminate it.
What hit rate should you expect?
It depends entirely on query repetition in your traffic. Repetitive support and FAQ workloads report high rates in published research; open-ended agents and long-context applications are much lower. Measure on a sample of your own traffic before modeling savings.
Do you need a vector database for semantic caching?
Yes for semantic mode, and in Bifrost also for direct mode, since entries are stored there either way. Redis or Valkey is the practical choice for exact-match-only deployments because metadata-only entries need no vector.
How do you keep one tenant's cached response from reaching another?
Partition on the cache key and include tenant identity in it. In Bifrost this is the x-bf-cache-key value, and it should never be a shared constant in a multi-tenant application. Pairing the cache key with a virtual key keeps partitioning and cost attribution on the same boundary.
Getting Started with Semantic Caching on Bifrost
Semantic caching is worth deploying where traffic repeats, where the threshold has been tuned against real hits, and where the saving is checked against the provider bill rather than a hit-rate dashboard. The open-source Bifrost gateway gives you both lookup paths, per-request threshold and TTL overrides, explicit cache partitioning, and per-hit similarity metadata, and it runs inside your own VPC or air-gapped environment when prompts and responses cannot leave your network.
Teams comparing approaches can review how to optimize LLM cost and latency with semantic caching and the wider Bifrost resources hub.
To see how caching fits alongside routing, governance, and observability in one gateway, book a demo with the Bifrost team.