LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 158 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/8
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned Classifier
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/15
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning & Test-Time Compute
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
Back to Topics
LearnApplied LLM EngineeringSemantic Caching & Cost Optimization
🚀MediumInference Optimization

Semantic Caching & Cost Optimization

Reuse stable policy answers across paraphrased questions without crossing release, access, or freshness boundaries; then prove the cache is both safe and worth serving.

19 min read
Learning path
Step 76 of 158 in the full curriculum
Model Versioning & DeploymentLLM Cost Engineering & Token Economics

A cached answer is behavior produced by one particular release, prompt policy, corpus, and access scope. Semantic caching only works when that contract is explicit.

Suppose the deployed developer-docs assistant repeatedly answers the public question, "How long are revoked API keys retained?" Developers phrase that question many ways. Reusing a verified answer could save generation work and respond faster. Reusing the answer after an API-key retention policy update, across a tenant boundary, or for a live incident-status question could be plainly wrong.

The safe version starts with one semantic cache for public policy answers. It will serve nothing until a shadow replay shows safe hits and worthwhile savings.

An answer is reusable only inside its contract

A response cache isn't a memory of generally true sentences. It's a store of outputs generated under a particular contract. The release bundle from the previous chapter gives us most of that contract already.

RequestCan an answer be reused?Why
"How long are revoked API keys retained?"CandidatePublic docs-policy answer can remain stable within one policy release.
"How long do revoked keys stay in audit logs?"CandidateParaphrase of the same public docs-policy question, after evaluation.
"What is incident INC-48192's current status?"NoAnswer depends on live incident state.
"Revoke API key KEY-48192."NoThe request asks for a side effect, not a reusable answer.

For this system, a reusable answer must match all of these fields:

FieldWhy it matters
release_idPins model, prompt, policy logic, and serving behavior.
corpus_versionPrevents old policy evidence from surviving a document update.
tenant_id and access_scopePrevents one tenant's restricted information from leaking into another response.
locale and response_schemaPrevents the right content from appearing in the wrong language or output contract.

Semantic lookup has a second version boundary. The embedding model, vector preprocessing, and index build determine the score distribution. Record them as embedding_model_id and embedding_index_version in the cache policy. If either changes, rebuild the vectors and recalibrate the threshold in shadow mode before serving semantic hits. The admitted answer may still be valid, but an old score threshold no longer has evidence behind it.

Eligibility matrix for four developer-docs requests before semantic similarity runs: an API-key retention question and its paraphrase are public-policy, read-only, non-live requests that enter evaluation; an incident INC-48192 status question requires live state and bypasses; a request to revoke API key KEY-48192 writes state and bypasses. Eligibility matrix for four developer-docs requests before semantic similarity runs: an API-key retention question and its paraphrase are public-policy, read-only, non-live requests that enter evaluation; an incident INC-48192 status question requires live state and bypasses; a request to revoke API key KEY-48192 writes state and bypasses.
Rows A and B may proceed to semantic evaluation because they are public, read-only policy questions. Live incident state and key-revocation writes bypass before vector lookup, regardless of any future similarity score.

Start the lab with the exact release scope and one response generated by the promoted release.

define-the-reuse-contract.py
1from dataclasses import asdict, dataclass, replace 2from hashlib import sha256 3import json 4import math 5 6@dataclass(frozen=True) 7class ReleaseScope: 8 release_id: str 9 corpus_version: str 10 tenant_id: str 11 access_scope: str 12 locale: str 13 response_schema: str 14 15@dataclass(frozen=True) 16class Request: 17 text: str 18 tenant_id: str = "docs-public" 19 access_scope: str = "public-policy" 20 locale: str = "en-US" 21 response_schema: str = "cited-answer-v2" 22 requires_live_data: bool = False 23 writes_state: bool = False 24 25@dataclass(frozen=True) 26class CachedAnswer: 27 answer_id: str 28 source_query: str 29 response: str 30 scope: ReleaseScope 31 response_class: str 32 admission_evidence_id: str 33 34stable_scope = ReleaseScope( 35 release_id="docs-evidence-answerer@sha256:df2d4fe7b0c5", 36 corpus_version="api-key-policy-2026-04", 37 tenant_id="docs-public", 38 access_scope="public-policy", 39 locale="en-US", 40 response_schema="cited-answer-v2", 41) 42 43seed_answer = CachedAnswer( 44 answer_id="ans_api_key_retention_30d", 45 source_query="How long are revoked API keys retained?", 46 response="Revoked API keys remain visible in audit logs for 30 days.", 47 scope=stable_scope, 48 response_class="public-policy", 49 admission_evidence_id="eval-public-policy-api-keys-v4", 50) 51 52approved_admission_evidence_ids = { 53 "eval-public-policy-api-keys-v4", 54 "eval-public-policy-rate-limits-v1", 55} 56 57print(f"release_id={stable_scope.release_id}") 58print(f"corpus_version={stable_scope.corpus_version}") 59print(f"seed_answer={seed_answer.answer_id}")
Output
1release_id=docs-evidence-answerer@sha256:df2d4fe7b0c5 2corpus_version=api-key-policy-2026-04 3seed_answer=ans_api_key_retention_30d

For requests already eligible for answer reuse, an ordinary key-value cache can safely reuse normalized exact repeats as long as its key contains the full scope. It can't see through paraphrasing.

The scope fields must come from application-owned route policy and authenticated context, not from raw user text or a model's guess. Unknown response classes should bypass answer reuse. The lab also checks that the caller-selected scope agrees with the request before deriving a key.

exact-cache-respects-release-scope.py
1def normalized_text(text: str) -> str: 2 return " ".join(text.lower().split()) 3 4def request_matches_scope(request: Request, scope: ReleaseScope) -> bool: 5 return ( 6 request.tenant_id == scope.tenant_id 7 and request.access_scope == scope.access_scope 8 and request.locale == scope.locale 9 and request.response_schema == scope.response_schema 10 ) 11 12def exact_key(scope: ReleaseScope, request: Request) -> str: 13 if not request_matches_scope(request, scope): 14 raise ValueError("request does not match cache scope") 15 payload = { 16 "scope": asdict(scope), 17 "text": normalized_text(request.text), 18 } 19 encoded = json.dumps(payload, sort_keys=True).encode("utf-8") 20 return sha256(encoded).hexdigest() 21 22same_words = Request("How long are revoked API keys retained?") 23paraphrase = Request("How long do revoked keys stay in audit logs?") 24cross_tenant = replace(same_words, tenant_id="internal-admin") 25updated_scope = replace( 26 stable_scope, 27 release_id="docs-evidence-answerer@sha256:new-policy", 28 corpus_version="api-key-policy-2026-05", 29) 30 31seed_key = exact_key(stable_scope, Request(seed_answer.source_query)) 32print(f"exact_repeat_hit={exact_key(stable_scope, same_words) == seed_key}") 33print(f"paraphrase_exact_hit={exact_key(stable_scope, paraphrase) == seed_key}") 34print(f"updated_policy_hit={exact_key(updated_scope, same_words) == seed_key}") 35try: 36 exact_key(stable_scope, cross_tenant) 37except ValueError as error: 38 print(f"cross_tenant_rejected={error}")
Output
1exact_repeat_hit=True 2paraphrase_exact_hit=False 3updated_policy_hit=False 4cross_tenant_rejected=request does not match cache scope

The exact cache does the correct thing: it refuses a paraphrase and refuses an old answer under a new policy release. Semantic caching adds only the first capability. It must not weaken the second.

Similarity retrieves a candidate, not a truth

A semantic cache embeds a new question, searches stored question embeddings, and proposes a nearby saved answer. In a larger store, that retrieval step is commonly an approximate nearest-neighbor (ANN) search. Systems such as GPTCache apply candidate retrieval before deciding whether to call the LLM at all.[1]Reference 1GPTCache: An Open-Source Semantic Cache for LLM Applications Enabling Faster Answers and Cost Savings.https://aclanthology.org/2023.nlposs-1.24/ Sentence-BERT showed why this shape works: sentence embeddings can be compared efficiently with cosine similarity for semantic matching tasks.[2]Reference 2Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks.https://arxiv.org/abs/1908.10084

For two vectors aaa and bbb, cosine similarity is:

cosine⁡(a,b)=a⋅b∥a∥2∥b∥2\operatorname{cosine}(a, b) = \frac{a \cdot b}{\lVert a \rVert_2 \lVert b \rVert_2}cosine(a,b)=∥a∥2​∥b∥2​a⋅b​

The numerator measures their aligned components. Dividing by both lengths makes the result compare direction rather than vector magnitude. A high score says two encoded questions are close under this embedding model. It does not say their answers are interchangeable.

The tiny vectors below are an instructional fixture, not scores from a commercial embedding model. They let us see the failure mode without downloading a model: a restore-key exception can sit near a general retention question while still needing a different answer.

similarity-only-proposes-a-candidate.py
1fixture_vectors = { 2 seed_answer.source_query: (1.00, 0.00, 0.00), 3 "How long do revoked keys stay in audit logs?": (0.99, 0.04, 0.00), 4 "Can I restore a revoked API key?": (0.94, 0.10, 0.00), 5 "What is incident INC-48192's current status?": (0.00, 0.05, 1.00), 6} 7 8def cosine(left: tuple[float, ...], right: tuple[float, ...]) -> float: 9 dot = sum(a * b for a, b in zip(left, right)) 10 left_norm = math.sqrt(sum(value * value for value in left)) 11 right_norm = math.sqrt(sum(value * value for value in right)) 12 return dot / (left_norm * right_norm) 13 14seed_vector = fixture_vectors[seed_answer.source_query] 15for question in [ 16 "How long do revoked keys stay in audit logs?", 17 "Can I restore a revoked API key?", 18 "What is incident INC-48192's current status?", 19]: 20 score = cosine(seed_vector, fixture_vectors[question]) 21 print(f"{question} | score={score:.3f}")
Output
1How long do revoked keys stay in audit logs? | score=0.999 2Can I restore a revoked API key? | score=0.994 3What is incident INC-48192's current status? | score=0.000
Cosine candidate plot for the lesson fixture: the API-key retention seed lies on the policy axis; the paraphrase scores 0.999 and the restore-key exception scores 0.994, so both fall inside the 0.98 candidate cone, while live incident state scores 0.000 far outside it; a separate gate shows that the paraphrase becomes a semantic hit only after release, corpus, tenant, access, eligibility, and admission checks all pass. Cosine candidate plot for the lesson fixture: the API-key retention seed lies on the policy axis; the paraphrase scores 0.999 and the restore-key exception scores 0.994, so both fall inside the 0.98 candidate cone, while live incident state scores 0.000 far outside it; a separate gate shows that the paraphrase becomes a semantic hit only after release, corpus, tenant, access, eligibility, and admission checks all pass.
Both the valid paraphrase and the restore-key exception clear the 0.98 score threshold, proving that similarity only retrieves candidates. The paraphrase becomes servable only after every release, scope, eligibility, and admission check passes.

Eligibility rules run before the score threshold

The assistant shouldn't response-cache live incident state or actions at any threshold. Even for a public-policy question, a cached answer must be from the same release scope.

This decision procedure checks the non-negotiable rules first. Only an eligible, same-scope request reaches the similarity threshold.

gate-semantic-hits-by-contract.py
1def same_scope(request: Request, record: CachedAnswer, active: ReleaseScope) -> bool: 2 return ( 3 record.scope == active 4 and request_matches_scope(request, active) 5 ) 6 7def record_is_admitted(record: CachedAnswer) -> bool: 8 return ( 9 record.response_class == "public-policy" 10 and record.admission_evidence_id in approved_admission_evidence_ids 11 ) 12 13def decide_candidate( 14 request: Request, 15 record: CachedAnswer, 16 active: ReleaseScope, 17 score: float, 18 threshold: float, 19) -> str: 20 if request.requires_live_data or request.writes_state: 21 return "BYPASS_DYNAMIC_OR_WRITE" 22 if not same_scope(request, record, active): 23 return "MISS_SCOPE_CHANGED" 24 if not record_is_admitted(record): 25 return "BYPASS_UNVALIDATED_RECORD" 26 if score < threshold: 27 return "MISS_BELOW_THRESHOLD" 28 return "SEMANTIC_HIT" 29 30policy_paraphrase = Request("How long do revoked keys stay in audit logs?") 31live_incident = Request( 32 "What is incident INC-48192's current status?", 33 access_scope="incident-state", 34 requires_live_data=True, 35) 36revoke_action = Request( 37 "Revoke API key KEY-48192.", 38 access_scope="key-admin", 39 writes_state=True, 40) 41 42policy_score = cosine(seed_vector, fixture_vectors[policy_paraphrase.text]) 43print(decide_candidate(policy_paraphrase, seed_answer, stable_scope, policy_score, 0.98)) 44print(decide_candidate(live_incident, seed_answer, stable_scope, 1.00, 0.98)) 45print(decide_candidate(revoke_action, seed_answer, stable_scope, 1.00, 0.98))
Output
1SEMANTIC_HIT 2BYPASS_DYNAMIC_OR_WRITE 3BYPASS_DYNAMIC_OR_WRITE

Version changes invalidate answers without guessing

A time-to-live (TTL) can expire old entries after a period. It can't know that an API-key retention policy changed five minutes after an answer was stored. The release bundle provides a stronger invalidation hook: if policy evidence or answer behavior changes, the release or corpus version changes and old entries are no longer in scope.

invalidate-on-policy-release.py
1policy_update = replace( 2 stable_scope, 3 release_id="docs-evidence-answerer@sha256:7a12policy", 4 corpus_version="api-key-policy-2026-05", 5) 6 7same_question = Request(seed_answer.source_query) 8old_release_decision = decide_candidate( 9 same_question, seed_answer, stable_scope, 1.00, 0.98 10) 11new_release_decision = decide_candidate( 12 same_question, seed_answer, policy_update, 1.00, 0.98 13) 14 15print(f"old_release={old_release_decision}") 16print(f"new_policy_release={new_release_decision}") 17print(f"new_release_must_generate={new_release_decision != 'SEMANTIC_HIT'}")
Output
1old_release=SEMANTIC_HIT 2new_policy_release=MISS_SCOPE_CHANGED 3new_release_must_generate=True

Cache identity should inherit the release identity from deployment. Eviction can clean up storage later; correctness shouldn't depend on eviction finishing first. Keep a bounded TTL as a cleanup policy and backstop, but don't treat it as the authoritative invalidation signal.

Choose a threshold in shadow mode

Serving a semantic hit immediately turns a retrieval mistake into a user-visible wrong answer. Shadow mode runs the lookup decision but still serves the normal fresh path. Reviewers then label whether each proposed reuse would have been acceptable.

A good cache metric separates two questions:

  • Proposal rate: how often would the cache return something?
  • Hit precision: among proposed hits, how often is answer reuse acceptable?

High proposal rate without high precision is a cheaper system that's wrong more often. A raw cache hit rate can't distinguish those outcomes.

The labeled fixture below contains public-policy paraphrases, a subtle opened-item exception, and ineligible requests. Thresholds are specific to this fixture and embedding setup; don't copy them into production. Seven probes are enough to explain the tradeoff, but not enough to authorize a 99% production precision claim.

calibrate-with-shadow-replay.py
1@dataclass(frozen=True) 2class ShadowProbe: 3 name: str 4 score: float 5 eligible: bool 6 acceptable_reuse: bool 7 8shadow_probes = [ 9 ShadowProbe("key retention paraphrase", 0.995, True, True), 10 ShadowProbe("audit log retention wording", 0.989, True, True), 11 ShadowProbe("revoked key wording", 0.982, True, True), 12 ShadowProbe("policy FAQ reworded", 0.981, True, True), 13 ShadowProbe("restore-key exception", 0.965, True, False), 14 ShadowProbe("live incident state", 0.999, False, False), 15 ShadowProbe("revoke key action", 0.997, False, False), 16] 17 18def replay_at(threshold: float) -> dict[str, float | int]: 19 proposed = [ 20 probe for probe in shadow_probes 21 if probe.eligible and probe.score >= threshold 22 ] 23 accepted = [probe for probe in proposed if probe.acceptable_reuse] 24 precision = len(accepted) / len(proposed) if proposed else 1.0 25 return { 26 "proposed": len(proposed), 27 "accepted": len(accepted), 28 "precision": precision, 29 "proposal_rate": len(proposed) / len(shadow_probes), 30 } 31 32for threshold in [0.960, 0.980, 0.990]: 33 metrics = replay_at(threshold) 34 print( 35 f"threshold={threshold:.3f} " 36 f"proposed={metrics['proposed']} " 37 f"precision={metrics['precision']:.1%} " 38 f"proposal_rate={metrics['proposal_rate']:.1%}" 39 ) 40 41selected_threshold = 0.980 42 43def wilson_lower_bound(successes: int, trials: int, z: float = 1.96) -> float: 44 if trials == 0: 45 return 0.0 46 observed = successes / trials 47 denominator = 1 + z**2 / trials 48 center = observed + z**2 / (2 * trials) 49 margin = z * math.sqrt( 50 observed * (1 - observed) / trials + z**2 / (4 * trials**2) 51 ) 52 return (center - margin) / denominator 53 54# Synthetic full-window fixture at the selected threshold. In production, 55# populate these counts from representative labeled shadow traffic. 56full_shadow_requests = 10_000 57full_shadow_proposed = 5_714 58full_shadow_accepted = 5_714 59full_shadow_precision = full_shadow_accepted / full_shadow_proposed 60precision_lower_bound = wilson_lower_bound( 61 full_shadow_accepted, 62 full_shadow_proposed, 63) 64safe_hit_fraction = full_shadow_accepted / full_shadow_requests 65 66print( 67 f"full_shadow proposed={full_shadow_proposed} " 68 f"precision={full_shadow_precision:.1%} " 69 f"precision_lower_bound={precision_lower_bound:.2%}" 70)
Output
1threshold=0.960 proposed=5 precision=80.0% proposal_rate=71.4% 2threshold=0.980 proposed=4 precision=100.0% proposal_rate=57.1% 3threshold=0.990 proposed=1 precision=100.0% proposal_rate=14.3% 4full_shadow proposed=5714 precision=100.0% precision_lower_bound=99.93%
Seven-probe shadow replay matrix across semantic-cache thresholds 0.960, 0.980, and 0.990: four acceptable public-policy paraphrases score from 0.981 to 0.995, a restore-key exception scores 0.965 and becomes a wrong hit only at 0.960, and live-incident plus revoke-key requests bypass every threshold as ineligible; summaries show 80 percent precision with five proposals at 0.960, 100 percent with four proposals at selected threshold 0.980, and 100 percent with one proposal at 0.990. Seven-probe shadow replay matrix across semantic-cache thresholds 0.960, 0.980, and 0.990: four acceptable public-policy paraphrases score from 0.981 to 0.995, a restore-key exception scores 0.965 and becomes a wrong hit only at 0.960, and live-incident plus revoke-key requests bypass every threshold as ineligible; summaries show 80 percent precision with five proposals at 0.960, 100 percent with four proposals at selected threshold 0.980, and 100 percent with one proposal at 0.990.
The 0.960 threshold admits the restore-key exception and fails precision. The selected 0.980 threshold keeps all four valid paraphrases, while live-state and write requests bypass every threshold before scoring. A larger representative shadow window must still confirm the result before promotion.

The larger fixture keeps the same 0.980 policy but evaluates 5,714 proposed hits from 10,000 requests. All are accepted, so observed precision is 100%. More importantly, the 95% Wilson lower bound is 99.93%, above the 99% gate. A confidence bound prevents a tiny perfect sample from looking stronger than it's. The traffic still needs to represent the production mix; statistical confidence can't repair a biased replay.

A safe cache still has to pay for itself

Every semantic lookup incurs work, even on a miss: embedding the request, searching an index, and recording metrics. Evaluate cost only after the precision gate passes.

Let:

  • NNN be requests in a measured period.
  • CgC_gCg​ be average fresh-generation cost per request.
  • ClC_lCl​ be semantic-lookup cost per request.
  • hhh be the observed safe-hit fraction.

If a hit skips fresh generation, expected period savings are:

savings=N(hCg−Cl)\text{savings} = N \left(h C_g - C_l\right)savings=N(hCg​−Cl​)

These quantities must come from the workload and model you plan to operate. The next example uses labeled measurement fixtures, not provider prices.

measure-break-even-savings.py
1requests_per_day = full_shadow_requests 2fresh_generation_usd = 0.0040 # measured fixture: average full answer cost 3semantic_lookup_usd = 0.00008 # measured fixture: embed + index lookup 4 5without_cache = requests_per_day * fresh_generation_usd 6with_cache = requests_per_day * ( 7 semantic_lookup_usd + (1 - safe_hit_fraction) * fresh_generation_usd 8) 9savings = without_cache - with_cache 10break_even_hit_fraction = semantic_lookup_usd / fresh_generation_usd 11 12print(f"safe_hit_fraction={safe_hit_fraction:.1%}") 13print(f"break_even_hit_fraction={break_even_hit_fraction:.1%}") 14print(f"daily_savings_fixture_usd={savings:.2f}") 15print(f"savings_positive={savings > 0}")
Output
1safe_hit_fraction=57.1% 2break_even_hit_fraction=2.0% 3daily_savings_fixture_usd=22.06 4savings_positive=True

Don't guess from list price. Measure generation and lookup cost for the real release and traffic mix, then rerun the gate when either changes.

Authorize writes before they become reusable

The read path now rejects records without approved admission evidence. The write path must enforce the same rule before adding a record to the servable index. A freshly generated answer isn't automatically safe to repeat across paraphrases.

Treat admission as write authorization. Keep unreviewed answers in a quarantine store, attach the evaluation artifact that approved a response class, and admit only records inside the tested release scope.

authorize-cache-writes.py
1def admission_decision(answer: CachedAnswer) -> str: 2 if answer.response_class != "public-policy": 3 return "QUARANTINE_RESPONSE_CLASS" 4 if answer.scope != stable_scope: 5 return "QUARANTINE_SCOPE_CHANGED" 6 if answer.admission_evidence_id not in approved_admission_evidence_ids: 7 return "QUARANTINE_MISSING_EVIDENCE" 8 return "ADMIT_SERVABLE" 9 10unreviewed_answer = replace( 11 seed_answer, 12 answer_id="ans_restore_key_review", 13 admission_evidence_id="", 14) 15validated_policy_answer = replace( 16 seed_answer, 17 answer_id="ans_rate_limit_public", 18 source_query="What is the default API rate limit?", 19 response="Default API keys allow 600 requests per minute unless the account policy says otherwise.", 20 admission_evidence_id="eval-public-policy-rate-limits-v1", 21) 22dynamic_incident_answer = replace( 23 seed_answer, 24 answer_id="ans_live_incident_status", 25 response_class="live-incident-status", 26) 27 28for label, answer in [ 29 ("unreviewed", unreviewed_answer), 30 ("validated_policy", validated_policy_answer), 31 ("dynamic_incident", dynamic_incident_answer), 32]: 33 print(f"{label}={admission_decision(answer)}")
Output
1unreviewed=QUARANTINE_MISSING_EVIDENCE 2validated_policy=ADMIT_SERVABLE 3dynamic_incident=QUARANTINE_RESPONSE_CLASS

The admitted record still isn't a universal truth. Reads must match its release and access scope, then pass the calibrated semantic threshold. Admission prevents one bad fresh generation from silently becoming a high-fanout cached answer.

Promote only the narrow policy you tested

Don't turn on semantic caching for all support. Promote only the public-policy scope that passed shadow evidence. Account state and write actions still bypass.

make-the-cache-promotion-decision.py
1@dataclass(frozen=True) 2class CachePromotionGate: 3 minimum_precision_lower_bound: float 4 minimum_daily_savings_usd: float 5 required_scope: ReleaseScope 6 7gate = CachePromotionGate( 8 minimum_precision_lower_bound=0.99, 9 minimum_daily_savings_usd=5.00, 10 required_scope=stable_scope, 11) 12 13passes_quality = ( 14 precision_lower_bound >= gate.minimum_precision_lower_bound 15) 16passes_economics = savings >= gate.minimum_daily_savings_usd 17passes_scope = seed_answer.scope == gate.required_scope 18decision = ( 19 "PROMOTE_PUBLIC_POLICY_SEMANTIC_CACHE" 20 if passes_quality and passes_economics and passes_scope 21 else "KEEP_SHADOW_ONLY" 22) 23 24print(f"quality_gate={passes_quality}") 25print(f"economics_gate={passes_economics}") 26print(f"scope_gate={passes_scope}") 27print(f"cache_decision={decision}")
Output
1quality_gate=True 2economics_gate=True 3scope_gate=True 4cache_decision=PROMOTE_PUBLIC_POLICY_SEMANTIC_CACHE
Semantic-cache promotion flow for 10,000 public-policy requests: 57.1 percent become safe cache hits, 42.9 percent still generate fresh answers, measured daily cost falls from 40 dollars to 17.94 dollars, and promotion proceeds only after quality, savings, and scope gates pass. Semantic-cache promotion flow for 10,000 public-policy requests: 57.1 percent become safe cache hits, 42.9 percent still generate fresh answers, measured daily cost falls from 40 dollars to 17.94 dollars, and promotion proceeds only after quality, savings, and scope gates pass.
Safe hits cut this fixture from $40.00 to $17.94 per day, but promotion still requires quality, savings, and exact scope before reuse turns on.

Record why each request hit or bypassed

Once the cache is serving, traces must answer: which release generated the cached response, which cache policy reused it, and why a request bypassed reuse? Without those fields, wrong-hit incidents become difficult to reconstruct.

emit-cache-decision-traces.py
1def trace_decision(request: Request, score: float) -> dict[str, str | float]: 2 cache_decision = decide_candidate( 3 request, seed_answer, stable_scope, score, selected_threshold 4 ) 5 return { 6 "request": request.text, 7 "release_id": stable_scope.release_id, 8 "corpus_version": stable_scope.corpus_version, 9 "tenant_id": request.tenant_id, 10 "access_scope": request.access_scope, 11 "cache_policy": "public-policy-semantic-v1", 12 "embedding_model_id": "docs-query-embedder-v3", 13 "embedding_index_version": "api-key-policy-index-2026-04-v2", 14 "answer_id": seed_answer.answer_id if cache_decision == "SEMANTIC_HIT" else "", 15 "admission_evidence_id": seed_answer.admission_evidence_id, 16 "decision": cache_decision, 17 "score": score, 18 } 19 20hit_trace = trace_decision(policy_paraphrase, policy_score) 21bypass_trace = trace_decision(live_incident, 1.00) 22 23print(f"hit_decision={hit_trace['decision']} answer_id={hit_trace['answer_id']}") 24print(f"bypass_decision={bypass_trace['decision']}") 25print(f"traced_release={hit_trace['release_id'] == stable_scope.release_id}") 26print(f"traced_scope={hit_trace['corpus_version'] == stable_scope.corpus_version and hit_trace['access_scope'] == stable_scope.access_scope}") 27print(f"traced_index={hit_trace['embedding_index_version'] == 'api-key-policy-index-2026-04-v2'}") 28print(f"traced_admission={hit_trace['admission_evidence_id'] == seed_answer.admission_evidence_id}")
Output
1hit_decision=SEMANTIC_HIT answer_id=ans_api_key_retention_30d 2bypass_decision=BYPASS_DYNAMIC_OR_WRITE 3traced_release=True 4traced_scope=True 5traced_index=True 6traced_admission=True

Watch production for accepted-hit review failures, user retries after cache hits, scope bypass volume, p95 latency, and realized saved generation. An incident should be able to disable this cache policy pointer without changing the production release that generates fresh responses.

Semantic response caching isn't prompt-prefix caching

The cache in this lab can return a stored answer for a paraphrase and skip generation. Provider prompt caching operates at a different layer. OpenAI's documented prompt caching applies automatically to prompts of at least 1,024 tokens, requires an exact prefix match, and reports how many input tokens were cached. The model still computes a new output.[3]Reference 3Prompt cachinghttps://developers.openai.com/api/docs/guides/prompt-caching

LayerMatchesResult on hitMain correctness risk
Exact response cacheSame scoped request keyReturn stored answer, skip generationStale or incomplete scope key
Semantic response cacheSimilar eligible question under same contractReturn stored answer, skip generationFalse semantic reuse
Provider prompt cacheMatching input prefix under provider rulesCompute a new answer with cheaper/faster repeated input workMissed cost opportunity, not stored-answer substitution

The distinction determines the evaluation: semantic answer caching needs labeled reuse precision; prompt-prefix caching needs token and latency accounting. The next chapter expands that economics.

separate-answer-reuse-from-prefix-reuse.py
1@dataclass(frozen=True) 2class ReuseCase: 3 name: str 4 semantic_answer_hit: bool 5 repeated_prefix_hit: bool 6 7cases = [ 8 ReuseCase( 9 name="paraphrased public API-key question", 10 semantic_answer_hit=True, 11 repeated_prefix_hit=False, 12 ), 13 ReuseCase( 14 name="new live incident question after same long instructions", 15 semantic_answer_hit=False, 16 repeated_prefix_hit=True, 17 ), 18] 19 20for case in cases: 21 print( 22 f"{case.name}: " 23 f"skip_generation={case.semantic_answer_hit}, " 24 f"reuse_input_work={case.repeated_prefix_hit}" 25 ) 26 27print("next_measure_token_economics=True")
Output
1paraphrased public API-key question: skip_generation=True, reuse_input_work=False 2new live incident question after same long instructions: skip_generation=False, reuse_input_work=True 3next_measure_token_economics=True

Mastery check

Key concepts

  • A cached answer belongs to an immutable release scope, not question text alone.
  • Exact response caches catch identical scoped requests; semantic caches retrieve answer candidates across paraphrases.
  • Similarity is evidence for candidate retrieval, never permission to ignore access, freshness, or side-effect boundaries.
  • New corpus or release identity naturally invalidates old answer reuse.
  • Embedding-model or index changes require re-embedding and threshold recalibration.
  • Shadow-mode precision confidence and measured break-even savings are promotion gates.
  • Servable cache writes need a validated admission path; don't let unreviewed answers become reusable records.
  • Provider prompt caching reuses repeated input processing, while semantic response caching can skip generation.

Practice tasks

  1. Add response_schema="json-citations-v3" to a new scope and prove an old natural-language answer can't hit it.
  2. Add one public-policy question that's almost similar but requires a different answer. Re-run the threshold sweep and explain the new selected threshold.
  3. Reduce the full shadow window to four accepted proposals. Compute the Wilson lower bound and explain why observed 100% precision no longer passes the gate.
  4. Replace the fixture costs with measured numbers for a workload you control. Compute the hit fraction required to break even.
  5. Add a cache-policy rollback event that turns semantic hits back into misses while keeping fresh generation on the same release.
  6. Change embedding_model_id, rebuild the index under a new version, and keep it shadow-only until its threshold passes calibration.
  7. Add a new generated public-policy answer without an approved evidence ID. Prove that it remains quarantined until admission evidence is attached.

Evaluation rubric

  • Foundational: Explains why a paraphrase misses an exact cache and why similarity can propose reuse.
  • Foundational: Identifies live data and write actions as ineligible for response reuse before considering score.
  • Intermediate: Builds a cache key or metadata filter that includes release, corpus, tenant, access, locale, and schema scope.
  • Intermediate: Separates generated answers from admitted cache records and quarantines writes without approved evidence.
  • Intermediate: Calibrates a threshold in shadow mode using accepted-hit precision and a sample-size-aware lower bound rather than raw hit rate.
  • Advanced: Computes measured break-even savings and promotes only the response class proved safe and worthwhile.
  • Advanced: Distinguishes semantic stored-answer reuse from provider prefix-computation reuse and chooses evidence for each.

Self-check questions

Common pitfalls

Optimizing hit count instead of correct reuse

  • Symptom: Hit rate rises while users report answers for a nearby but different policy case.
  • Cause: Threshold was loosened without accepted-reuse labels.
  • Fix: Run shadow replay, gate on precision, and exclude classes where a near match is unsafe.

Caching dynamic or write requests

  • Symptom: User sees stale incident data or a workflow appears completed when no action ran.
  • Cause: Cache eligibility was treated as a similarity decision.
  • Fix: Bypass live-state and side-effect requests before vector lookup can produce a servable hit.

Keeping entries across a policy release

  • Symptom: A new API-key retention rule is live, but responses still quote the previous rule.
  • Cause: Cache records weren't scoped to release and evidence version.
  • Fix: Include release and corpus identifiers in lookup filters; treat a version change as an immediate miss.

Reusing a threshold after an embedding change

  • Symptom: A previously safe threshold suddenly admits unrelated questions or misses valid paraphrases.
  • Cause: The embedding model, preprocessing, or index build changed, so the old score distribution no longer applies.
  • Fix: Version the embedding model and index, rebuild vectors, and rerun labeled shadow calibration before serving hits.

Proving safety but not value

  • Symptom: Quality remains stable, yet overall request cost or latency gets worse.
  • Cause: Embedding and index lookup costs exceed saved generations.
  • Fix: Measure lookup overhead and safe-hit fraction for the target traffic before promoting.

Unreviewed answers enter the reusable store

  • Symptom: One incorrect fresh answer gets repeated across several paraphrased questions.
  • Cause: The write path admitted every generated answer directly into the servable cache.
  • Fix: Treat cache admission as write authorization. Admit only validated response classes with recorded evidence; quarantine or review new records before reuse.
Complete the lesson

Mastery Check

Answer every question, then check your score. Score above 75% to mark this lesson complete.

1.A cached answer says revoked API keys remain visible in audit logs for 30 days. It was written under release_id R1 and corpus_version api-key-policy-2026-04. Five minutes later, the API-key policy corpus is promoted to api-key-policy-2026-05 under release_id R2, but the cache entry has a 24-hour TTL remaining. What should the semantic cache do for the same public question under the new active scope?
2.A shadow replay for the target traffic shows safe-hit fraction h = 1%. Fresh generation costs 0.0040perrequest,andsemanticlookupcosts0.0040 per request, and semantic lookup costs 0.0040perrequest,andsemanticlookupcosts0.00008 on every request. Using savings = N(h C_g - C_l), what should the promotion decision conclude about cost savings?
3.A new request uses the same long system instructions and policy context as earlier requests, but it asks, 'What is incident INC-48192's current status?' Provider prompt caching reports a repeated-prefix hit. How should this be handled relative to the semantic answer cache?
4.A semantic response cache rejects requests that require live data or write state before checking similarity thresholds. It retrieves a saved public-policy API-key answer for 'Revoke API key KEY-48192' with score 0.999; the request has access_scope='key-admin' and writes_state=True. Which decision follows this gate order?
5.Shadow replay for eligible public-policy probes gives these results: 0.960 proposes 5 hits with 80% precision, 0.980 proposes 4 hits with 100% precision, and 0.990 proposes 1 hit with 100% precision. If the precision gate is 99%, which threshold should advance to the economics evaluation while retaining the largest proposal rate?
6.A fresh generation produces a public-policy answer under the current release scope, but the record has an empty admission_evidence_id. What should the cache write path do before the answer can be served from the semantic index?
7.A response-cache key includes normalized text, release_id, and corpus_version, but omits tenant_id, access_scope, locale, and response_schema. Those values are available from authenticated route context. What correction makes the lookup contract complete?
8.An exact scoped cache misses the paraphrase 'Can I restore a revoked API key?' Semantic search retrieves a general API-key retention answer with cosine similarity 0.994, but review labels show that key restoration requires a different answer. What should the system conclude?
9.Shadow replay for admitted public-policy records shows a 99.93% precision lower bound, USD 22.06 in daily savings, and a matching active scope. The required gates are a 99% precision lower bound and USD 5.00 in daily savings. Live incident status and write actions were not approved for reuse. What should be promoted?
10.A cache keeps the same release_id and corpus_version but replaces its embedding model and rebuilds the vector index. Can it keep serving semantic hits with the old cosine threshold?

10 questions remaining.

Next Step
Continue to LLM Cost Engineering & Token Economics

You can now decide whether an answer is safe to reuse under one evaluated release. Next you'll measure the token, model, prefix-cache, and routing costs of requests that still require generation.

PreviousModel Versioning & Deployment
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

GPTCache: An Open-Source Semantic Cache for LLM Applications Enabling Faster Answers and Cost Savings.

Bang, Fu · 2023 · NLP-OSS 2023

Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks.

Reimers, N., & Gurevych, I. · 2019 · EMNLP 2019

Prompt caching

OpenAI · 2026

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.