How to Implement Semantic Caching for LLM Responses in ASP.NET Core 10 Using Redis and C# (2026)
A chatbot can answer “When can I return this item?” and “What is the return window?” with the same public policy. Reusing that answer can avoid another model call. Reusing “returns for international orders” against a domestic-order answer is a correctness failure, even when the embeddings are close.
This guide builds a Redis semantic cache for LLM responses in ASP.NET Core 10. It checks exact keys first, searches a Redis vector index on misses, and accepts a nearest neighbor only within an explicit distance threshold.
What makes a semantic cache different?
An exact cache requires an identical key. A semantic cache compares embeddings to find a related prompt. The nearest vector can still be the wrong answer, so the cache has a miss threshold and an eligibility policy. This fixture uses supplied three-component vectors to make results reproducible. A production caller should generate both vectors using the same trusted embedding model and never accept an unverified embedding alongside a private prompt.
| Match | Example | When safe |
|---|---|---|
| Exact | Same prompt and compatible context | Scope and policy versions match |
| Semantic | Two public-policy paraphrases | Evaluation demonstrates acceptable false-positive rate |
| Miss | Different topic or unsupported scope | Send to the model only if authorized |
Create the Redis index and C# app
Install .NET 10 and a Redis deployment with vector search commands. Redis 8 supports the Query Engine. For an older server, verify that Redis Search is installed. A plain server without FT.CREATE and FT.SEARCH cannot run this sample. See Redis vector search.
The fields must match the C# hash schema. Each vector contains three little-endian FLOAT32 values. The FLAT index favors simple, exact nearest-neighbor search for the fixture. Evaluate HNSW separately when the index is large. The cache index projects only the answer and distance. Redis’s cosine distance is smaller for closer vectors. Here a distance of 0.02 is a demonstration threshold, not a production recommendation.
The API is a single-scope loopback fixture. The hash key includes a version and SHA-256 digest of the prompt. The KNN query filters on scope and version before choosing the nearest answer. The hash and its TTL are set in one Redis transaction. Changing CacheVersion causes a logical invalidation, while old entries expire. A raw prompt hash is still susceptible to guessing if prompts are predictable. Avoid storing sensitive data, and consider a scoped keyed digest where appropriate.
Verify exact, semantic and miss behavior
Expect 204 for storage and a semantic hit for the near vector. Use the original prompt to verify an exact hit, then try a distinct prompt with [0,1,0] and expect 404. Repeat after expiration or after changing the version. Check that invalid vectors and oversized answers are rejected. An indexing lag after writing can briefly affect vector search. Test that behavior for your deployment.
The C# project compiled on .NET 10. The Redis Query Engine and the example hit and miss requests were not executed here.
Prevent false positives and leakage
The real cache scope should include authorized tenant, retrieval-policy version, prompt template version, model and output contract, locale, and any other context that changes the answer. Use safe encoded scope identifiers in Redis queries. Never share private answers through the public cache. A matching embedding does not authorize a data read. Avoid caching time-dependent answers, private account results or actions.
Evaluate threshold precision on positive paraphrases and difficult negative pairs. Include negation, different dates, geographic scope and almost identical product names. Choose an acceptable false-hit rate before optimizing hit rate. Measure latency of embedding plus lookup against a fresh model call. Provide a safe miss when the cache is unavailable. Do not allow a lookup cancellation to imply a submitted Redis mutation was rolled back.
| Symptom | Check |
|---|---|
| Unknown FT command | Redis Query Engine availability |
| Vector mismatch | DIM, FLOAT32 layout and model version |
| Wrong tenant answer | Scope filter and authenticated identity |
| Outdated answer | TTL and policy-version invalidation |
| Similar text gives a bad hit | Negative examples and threshold policy |
FAQ and references
Can I cache every model answer? Restrict reuse to eligible tasks with stable inputs and public or properly isolated data.
Does a higher similarity threshold always help? It trades fewer false hits for more misses. Measure using domain-specific pairs.
Is exact cache invalidation enough? Both exact keys and vector queries must use the new scope and version.
Start with stable public FAQs. Add semantic reuse only after the negative fixture shows the wrong answer is reliably rejected.
Comments
Post a Comment