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 177 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/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠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/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued 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
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
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.

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

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

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 and hard-gate semantic hits on those fields. If either changes, rebuild the vectors and recalibrate the threshold in shadow mode before serving semantic hits. An old τ under a new index is not a safe score, even when the admitted answer text still looks right.

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. The same rule applies to eligibility flags: requires_live_data and writes_state must come from a route or intent-class policy map (or a deterministic classifier behind an admission gate), never from free-form model labels on user text alone. Unknown routes and 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.
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, and the serving index must match the embedding identity that calibrated the threshold.

This decision procedure checks the non-negotiable rules first. Route policy owns live/write classification. Only an eligible, same-scope request with a matching embedding policy reaches the similarity threshold.

gate-semantic-hits-by-contract.py
1@dataclass(frozen=True) 2class CachePolicy: 3 embedding_model_id: str 4 embedding_index_version: str 5 selected_threshold: float 6 7# Pins the embedding identity that produced selected_threshold evidence. 8ACTIVE_CACHE_POLICY = CachePolicy( 9 embedding_model_id="docs-query-embedder-v3", 10 embedding_index_version="api-key-policy-index-2026-04-v2", 11 selected_threshold=0.98, 12) 13 14# Route or intent-class map owns eligibility. Do not trust free-form model labels alone. 15ROUTE_ELIGIBILITY = { 16 "public-policy": {"requires_live_data": False, "writes_state": False}, 17 "incident-state": {"requires_live_data": True, "writes_state": False}, 18 "key-admin": {"requires_live_data": False, "writes_state": True}, 19} 20 21def request_from_route(text: str, access_scope: str) -> Request: 22 route = ROUTE_ELIGIBILITY.get(access_scope) 23 if route is None: 24 # Unknown routes fail closed: treat as live so answer reuse can't apply. 25 return Request(text, access_scope=access_scope, requires_live_data=True) 26 return Request(text, access_scope=access_scope, **route) 27 28def same_scope(request: Request, record: CachedAnswer, active: ReleaseScope) -> bool: 29 return ( 30 record.scope == active 31 and request_matches_scope(request, active) 32 ) 33 34def record_is_admitted(record: CachedAnswer) -> bool: 35 return ( 36 record.response_class == "public-policy" 37 and record.admission_evidence_id in approved_admission_evidence_ids 38 ) 39 40def decide_candidate( 41 request: Request, 42 record: CachedAnswer, 43 active: ReleaseScope, 44 score: float, 45 threshold: float, 46 cache_policy: CachePolicy = ACTIVE_CACHE_POLICY, 47) -> str: 48 if request.requires_live_data or request.writes_state: 49 return "BYPASS_DYNAMIC_OR_WRITE" 50 if ( 51 cache_policy.embedding_model_id != ACTIVE_CACHE_POLICY.embedding_model_id 52 or cache_policy.embedding_index_version != ACTIVE_CACHE_POLICY.embedding_index_version 53 ): 54 return "MISS_EMBEDDING_VERSION" 55 if not same_scope(request, record, active): 56 return "MISS_SCOPE_CHANGED" 57 if not record_is_admitted(record): 58 return "BYPASS_UNVALIDATED_RECORD" 59 if score < threshold: 60 return "MISS_BELOW_THRESHOLD" 61 return "SEMANTIC_HIT" 62 63policy_paraphrase = request_from_route( 64 "How long do revoked keys stay in audit logs?", 65 "public-policy", 66) 67live_incident = request_from_route( 68 "What is incident INC-48192's current status?", 69 "incident-state", 70) 71revoke_action = request_from_route( 72 "Revoke API key KEY-48192.", 73 "key-admin", 74) 75stale_index_policy = CachePolicy( 76 embedding_model_id="docs-query-embedder-v3", 77 embedding_index_version="api-key-policy-index-2026-03-v1", 78 selected_threshold=0.98, 79) 80 81policy_score = cosine(seed_vector, fixture_vectors[policy_paraphrase.text]) 82print(decide_candidate(policy_paraphrase, seed_answer, stable_scope, policy_score, 0.98)) 83print(decide_candidate(live_incident, seed_answer, stable_scope, 1.00, 0.98)) 84print(decide_candidate(revoke_action, seed_answer, stable_scope, 1.00, 0.98)) 85print(decide_candidate( 86 policy_paraphrase, seed_answer, stable_scope, policy_score, 0.98, stale_index_policy, 87))
Output
1SEMANTIC_HIT 2BYPASS_DYNAMIC_OR_WRITE 3BYPASS_DYNAMIC_OR_WRITE 4MISS_EMBEDDING_VERSION

A live incident-status request has a similarity score of 1.00 against a saved response. Can it be a semantic answer-cache hit?

Answer

No. Its answer depends on live operational state. Eligibility is checked before similarity, so the request bypasses response reuse regardless of score.

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.

Why should changing the model, prompt, evidence snapshot, or authorization policy invalidate cached answers even when query similarity stays high?

Answer

Similarity says the request resembles an old request; it doesn't prove the old answer was produced under the current behavior and access contract. Version those dependencies in cache eligibility.

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.
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.

Why doesn't a high hit rate prove that semantic caching is helping?

Answer

A hit is only beneficial when the reused answer is acceptable under the active release and scope. A loose threshold can raise hit rate by serving nearby but incorrect answers.

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.
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": ACTIVE_CACHE_POLICY.embedding_model_id, 13 "embedding_index_version": ACTIVE_CACHE_POLICY.embedding_index_version, 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'] == ACTIVE_CACHE_POLICY.embedding_index_version}") 28print(f"traced_admission={hit_trace['admission_evidence_id'] == seed_answer.admission_evidence_id}") 29print(f"traced_model={hit_trace['embedding_model_id'] == ACTIVE_CACHE_POLICY.embedding_model_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 7traced_model=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
Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher 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?

Correct answer: Treat the old record as out of scope and generate a fresh answer; eviction can remove the stale record later.

TTL is a cleanup and backstop mechanism, not the authoritative freshness boundary. A release_id or corpus_version change moves the old answer outside the active release scope, so the lookup must miss even if the old entry has not been evicted.

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?

Correct answer: Do not promote for savings, because h C_g is 0.00004andisbelowthe0.00004 and is below the 0.00004andisbelowthe0.00008 lookup cost.

The lookup cost is paid on every request. Here the avoided generation value per request is 0.01 * 0.0040=0.0040 = 0.0040=0.00004, which is less than the 0.00008lookupcost.Thebreak−evenhitfractionis0.00008 lookup cost. The break-even hit fraction is 0.00008lookupcost.Thebreak−evenhitfractionis0.00008 / $0.0040 = 2%.

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?

Correct answer: Reuse input-prefix work if the provider offers it, but still generate a fresh live-state answer and do not return a stored answer.

Provider prompt caching and semantic response caching operate at different layers. A repeated prefix can reduce repeated input processing, but it does not make a new live incident-status answer reusable or allow stored-answer substitution.

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?

Correct answer: BYPASS_DYNAMIC_OR_WRITE before applying the threshold, because the request performs a side effect.

Eligibility rules run before score thresholds. Requests that require live state or write state are outside the semantic answer-cache contract, so a high similarity score cannot make the key-revocation action a servable cached answer.

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?

Correct answer: 0.980, because it clears the precision gate and retains more proposals than 0.990.

The 0.960 threshold fails the 99% precision gate. Both 0.980 and 0.990 pass, but 0.980 retains four proposals instead of one. It should therefore advance to the separate economics gate; passing precision alone does not yet authorize production serving.

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?

Correct answer: Quarantine it until approved admission evidence is attached for the tested response class and scope.

A freshly generated answer is not automatically safe to reuse across paraphrases. The write path must treat admission as authorization and only make records servable when the response class, scope, and approved evaluation evidence match the tested cache policy.

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?

Correct answer: Add all four fields from authenticated route context before any lookup.

Release and corpus identity alone do not prevent cross-tenant disclosure or the reuse of content under the wrong access, language, or output contract. Scope fields must be included in lookup identity and must come from application-owned policy and authenticated context, not untrusted text or model inference.

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?

Correct answer: Treat the result as a candidate and reject reuse using the acceptance evidence.

An exact cache correctly misses changed wording, while semantic search can retrieve a nearby candidate. Cosine similarity measures closeness under the embedding model, not whether policy answers are interchangeable. Labeled acceptance evidence therefore overrides the tempting high score.

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?

Correct answer: Promote semantic reuse only for admitted public-policy records in that scope.

The measured public-policy cache passes its sample-size-aware quality, economics, and scope gates, so it can be promoted for the response class actually tested. Those results do not authorize reuse for live state or side effects, which remain outside the answer-cache contract.

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?

Correct answer: No. Version the new embedding model and index, then recalibrate the threshold on labeled shadow traffic before serving hits.

Semantic thresholds are calibrated against one embedding model, preprocessing path, and index build. Changing that geometry invalidates the score evidence even when the admitted answer and generation release remain valid.

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

https://aclanthology.org/2023.nlposs-1.24/

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

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

https://arxiv.org/abs/1908.10084

Prompt caching

OpenAI · 2026

https://developers.openai.com/api/docs/guides/prompt-caching

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.