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 Agent System Design
🎤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
LearnSystem Design CapstonesLLM-Powered Search Engine
🏗️HardSystem Design

LLM-Powered Search Engine

Master the architecture of an end-to-end AI search engine, covering freshness routing, hybrid retrieval, evidence packing, citation verification, and streaming synthesis.

35 min read
Learning path
Step 151 of 177 in the full curriculum
Multi-Tenant LLM PlatformVision-Language Models & CLIP

Personalize this lesson

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

Multi-tenant serving made tenant identity, cost, and visibility part of every request. Large language model (LLM)-powered search applies that discipline to evidence: retrieval, ranking, caches, citations, and generated claims must all respect freshness and authorization before the model writes an answer.

You're the Staff AI Engineer at CodeAtlas. Developers type questions such as "can service accounts rotate OAuth tokens every 90 days?" and "what happens if parser-v2 deprecation affects our SDK migration?" A generative search experience must retrieve allowed API docs, release notes, and workspace policies, synthesize a cited answer, and keep private roadmaps and incident notes outside unauthorized contexts.

An LLM-powered search engine combines retrieval, ranking, synthesis, and citation into a user-facing answer system. The core engineering challenge is balancing retrieval and generation latency with factual accuracy, freshness, and workspace isolation.

Building a search engine that generates answers rather than just lists of links requires orchestrating retrieval, reranking, and LLM synthesis inside an interactive latency budget. This CodeAtlas case study designs that system around evidence and measurable service objectives.

If the ideas of embeddings or chunking feel rusty, here's a quick reminder: embeddings turn sentences into dense vectors so similar meanings sit close together in math space, and chunking splits long documents into short, self-contained passages so retrieval can grab the right snippet. Both are prerequisites for everything that follows.

What makes LLM-powered search different from a chatbot over documents?

Answer

It must retrieve fresh, permission-safe evidence, rank it, synthesize only supported claims, attach citations, and stream an answer within an interactive search latency budget.

From keywords to reasoning

Traditional search points users to candidate documents. LLM-powered search retrieves API docs, policy pages, release notes, and incident records, then returns a cited answer with a support state. This relies on Retrieval-Augmented Generation (RAG)[1]Reference 1Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.https://arxiv.org/abs/2005.11401, which conditions generation on retrieved evidence rather than treating model memory as the source of truth.

Traditional search leaves synthesis to the user. Generative search performs that synthesis, which means it also owns citation binding, abstention, and authorization failures.

This capstone uses a practical answer-engine architecture: retrieve with keyword and embedding signals, rerank candidates, apply visibility and freshness policy, then synthesize a short cited answer whose claims can be checked.

Traditional vs LLM search

FeatureTraditional SearchGenerative Search
OutputRanked links or snippetsAnswer plus cited evidence
InteractionQuery and result navigationAnswer and optional follow-up
User validationUser opens sources directlyUser must be able to inspect cited sources
Source trackingSource page is the resultClaim-to-source binding is an application duty

What does the LLM add after traditional retrieval?

Answer

The LLM performs synthesis: it reads retrieved evidence, resolves conflicts, explains the answer in user language, and binds claims to citations. It should not replace retrieval or source verification.

What the system must deliver

Before selecting components, define performance and functional constraints. These are example SLOs for this interactive developer-product case study, not universal constants.

DimensionConstraintExample target
LatencyTime to first token (TTFT)~0.8-1.5 seconds
ThroughputPeak queries per second (QPS)1K-10K QPS per region
QualityAnswer accuracyHigh citation precision, low unsupported claims
CostAverage query costLow single-digit cents at scale
FreshnessInformation latencySeconds to minutes for hot updates, hours for stable docs

Why must "workspace isolation" be a first-class requirement in this design?

Answer

Search evidence can include private roadmaps, incident notes, contracts, and policy exceptions. Retrieval, caching, ranking, and citations must enforce tenant visibility before the LLM ever sees context.

How the pipeline works end to end

The architecture has four query-time layers: plan the query, retrieve candidate evidence, pack the best evidence into context, and generate an answer while verifying citations.

Separate from this online path, a background ingestion system crawls or receives source updates, cleans them, chunks them, and keeps sparse and dense indexes fresh. Without that ingest path, an "AI search engine" quickly becomes a prompt wrapper around stale data.

LLM search architecture with an offline build lane for source normalization and hybrid indexing, then an online query lane that retrieves through access control, reranks evidence, and returns a cited answer.
Build indexes offline. At query time, retrieve through access control, rerank evidence, and answer with citations.

Tracing a concrete query

Follow the same question through every stage of the pipeline: "Which deprecation policy applies to parser-v2 SDK migration for service accounts?"

StageWhat the system does
Query planThe router classifies the question as a comparison needing both API deprecation policy and service-account migration rules. It decomposes the query into sub-queries for "parser-v2 deprecation policy" and "service-account SDK migration exceptions."
RetrievalBM25 (Best Match 25, a keyword-scoring algorithm) pulls the exact "Parser v2 Deprecation" and "Service Account Migration" pages from the doc store. Dense retrieval finds a related FAQ about legacy SDK workflows.
RerankingThe cross-encoder scores the candidate passages and keeps the top 3 that mention both parser-v2 and service accounts.
SynthesisThe LLM receives the packed passages with source IDs and writes a two-sentence answer with inline citations like [1] and [2].
VerificationA verifier returns support labels for answer claims against cited text. Product policy decides whether pending or unsupported claims are hidden, marked, or regenerated.

This trace will reappear as we look at each component in detail.


Why separate offline ingest from the online query path?

Answer

Ingest normalizes, chunks, indexes, and refreshes source material before users ask questions. The online path must stay fast: plan, retrieve, rerank, pack evidence, synthesize, and verify.

Inside the pipeline

1. Query understanding

The first step is classifying intent and deciding whether retrieval needs rewriting. A large synthesis model can perform this task, but using it on every query adds measured latency and cost that many simple routes don't need.

A common approach uses specialized, smaller models or encoder-class classifiers for routing, rewriting, and decomposition. Benchmark the chosen planner on your hardware and traffic shape; its job is to distinguish a simple fact lookup from an exploratory or freshness-sensitive request and attach retrieval hints such as "prefer exact matches" or "fresh data required."

One useful trick is HyDE (Hypothetical Document Embeddings): generate a hypothetical relevant document, embed that document, and use its vector as an alternate dense-search query.[2]Reference 2Precise Zero-Shot Dense Retrieval without Relevance Labels.https://arxiv.org/abs/2212.10496 The generated text may contain false details, so HyDE is a retrieval technique, not answer evidence. Validate whether it improves recall for your query mix.

Routing targets

Not every query should hit the same retriever. Exact, structured questions often route to APIs, SQL, or an entity store first, then optionally use the LLM only for verbalization. Treating every request like unstructured RAG adds latency and often makes numeric answers less reliable.

Query patternBest first backendWhy
"What is deploy job build-1042 doing right now?"Live APIFreshness and exact values matter more than semantic similarity
"Find the parser-v2 deprecation policy"BM25 + doc storeExact title and keyword match dominate
"Compare OAuth rotation exceptions for service accounts"Hybrid sparse + denseNeeds multi-document synthesis and terminology overlap
"What changed in the latest SDK migration guide?"Fresh web / hot indexRecency matters, then reranking decides quality
ComponentPurposeExample
Intent ClassifierRoute to appropriate pipeline"build-1042 status" -> direct API call
Query DecomposerBreak complex queries into sub-queries"Compare service-account rotation exceptions" -> 3 sub-queries
Safety FilterBlock harmful queries earlyContent policy enforcement
Freshness DetectorDetermine if real-time data is needed"latest SDK migration guide" -> force fresh crawl

This Python code structures a low-latency query planner. It takes the raw user query as input and returns a structured execution plan, including classified intent and decomposed sub-queries. In production, the decision can come from a small classifier, rules plus embeddings, or a compact model rather than a frontier synthesis model.

routing-targets.py
1import json 2from dataclasses import asdict, dataclass, field 3 4@dataclass(frozen=True) 5class QueryPlan: 6 intent: str 7 route: str 8 sub_queries: list[str] = field(default_factory=list) 9 filters: dict[str, str | bool] = field(default_factory=dict) 10 needs_fresh_data: bool = False 11 complexity: str = "simple" 12 13def understand_query(user_query: str) -> QueryPlan: 14 """Plan with fast routing logic; swap rules for a small classifier in production.""" 15 text = user_query.lower() 16 17 if any(term in text for term in ("build-", "quota", "runner status")): 18 return QueryPlan( 19 intent="factual", 20 route="structured_api", 21 filters={"requires_live_data": True}, 22 needs_fresh_data=True, 23 ) 24 25 if "parser-v2" in text and "service account" in text: 26 return QueryPlan( 27 intent="comparison", 28 route="hybrid_search", 29 sub_queries=[ 30 "parser-v2 deprecation policy", 31 "service-account SDK migration exceptions", 32 ], 33 filters={"doc_type": "policy", "tenant_scoped": True}, 34 complexity="moderate", 35 ) 36 37 return QueryPlan( 38 intent="exploratory", 39 route="hybrid_search", 40 sub_queries=[user_query], 41 complexity="moderate", 42 ) 43 44plan = understand_query("Which deprecation policy applies to parser-v2 SDK migration for service accounts?") 45print(json.dumps(asdict(plan), indent=2))
Output
1{ 2 "intent": "comparison", 3 "route": "hybrid_search", 4 "sub_queries": [ 5 "parser-v2 deprecation policy", 6 "service-account SDK migration exceptions" 7 ], 8 "filters": { 9 "doc_type": "policy", 10 "tenant_scoped": true 11 }, 12 "needs_fresh_data": false, 13 "complexity": "moderate" 14}

When should a query planner route to a structured API instead of generic RAG?

Answer

Use a structured API for exact, live, or numeric facts such as deploy status, quota remaining, entitlement, or current feature-flag state. Generic semantic retrieval is weaker for those values and can return stale text.

2. Multi-stage retrieval

We use a hybrid retrieval strategy to broaden candidate recall before refining for precision. A single retrieval method is rarely sufficient for complex queries. Relying only on keyword search might miss documents that use synonyms, while relying entirely on dense vector embeddings (encoding data as points in a high-dimensional space so related concepts are near each other) can overlook exact matches for specific names, product IDs, or acronyms.

By designing a multi-stage pipeline, we capture the best of both approaches. The first stage runs sparse retrieval and dense retrieval in parallel, then fuses the ranked lists with Reciprocal Rank Fusion (RRF)[3]Reference 3Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods.https://dl.acm.org/doi/10.1145/1571941.1572114. The second stage applies a more expensive reranker (often a cross-encoder or late-interaction model[4]Reference 4ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT.https://arxiv.org/abs/2004.12832) to a much smaller candidate set, passing only the most relevant chunks or parent passages to the LLM.

This visual outlines the flow of this multi-stage retrieval process.

Hybrid retrieval funnel: BM25, dense, and fresh or API routes fan out for recall, access control runs before fusion, a reranker cuts to top 50, and packing keeps 8 cited chunks.
Retrieve wide, filter access before fusion, rerank a small set, then pack only cited evidence.

Why hybrid retrieval?

BM25 (Best Match 25, a reliable keyword search algorithm) excels at exact keyword matching; dense retrieval captures semantic similarity. Dense retrieval typically uses bi-encoders (which encode queries and documents separately for fast lookup), though more advanced systems may use architectures like ColBERT (Contextualized Late Interaction over BERT) for late interaction between query and document tokens.[4]Reference 4ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT.https://arxiv.org/abs/2004.12832 On a mixed workload, combining retrievers can surface candidates that either one would miss alone.

In practice, hybrid search isn't just "run two retrievers." You also need rank fusion, chunking, and evidence assembly. A common pattern is to embed short child chunks for recall, then retrieve the larger parent passage for synthesis so the model sees enough surrounding context to answer cleanly.

Why fuse on rank instead of raw score? BM25 and dense-similarity scores have different scales, so adding them without calibration is brittle. Reciprocal Rank Fusion scores documents from rank positions: score(d) = sum over lists of 1 / (k + rank(d)). Cormack et al. evaluated RRF with k = 60; treat that value as a starting point to validate, not a universal optimum.[3]Reference 3Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods.https://dl.acm.org/doi/10.1145/1571941.1572114 Apply authorization filtering before fusion so a high-ranked forbidden document never becomes generation context.

authorized-rrf-fusion.py
1from collections import defaultdict 2 3tenant = "workspace-a" 4acl = {"deprecation": {"workspace-a"}, "rotation": {"workspace-a"}, "private-roadmap": {"workspace-b"}} 5sparse_ranked = ["deprecation", "private-roadmap", "rotation"] 6dense_ranked = ["private-roadmap", "rotation", "deprecation"] 7 8def allowed_ranked(document_ids: list[str]) -> list[str]: 9 return [document_id for document_id in document_ids if tenant in acl[document_id]] 10 11def reciprocal_rank_fusion(rankings: list[list[str]], k: int = 60) -> list[str]: 12 scores: defaultdict[str, float] = defaultdict(float) 13 for ranking in rankings: 14 for rank, document_id in enumerate(ranking, start=1): 15 scores[document_id] += 1 / (k + rank) 16 return sorted(scores, key=scores.get, reverse=True) 17 18fused = reciprocal_rank_fusion([allowed_ranked(sparse_ranked), allowed_ranked(dense_ranked)]) 19assert "private-roadmap" not in fused 20print("authorized_fused_docs:", fused)
Output
1authorized_fused_docs: ['deprecation', 'rotation']

Freshness and index tiers

Real search products rarely use one monolithic index. A useful pattern is a hot-warm split:

TierTypical contentUpdate patternQuery behavior
HotNews, changelogs, live API snapshotsSeconds to minutesQueried when freshness detector fires
WarmProduct docs, internal wikis, help center contentMinutes to hoursDefault hybrid search tier
ColdStable archive and long-tail corpusBatch compactionBackfill recall when warm tier misses

Freshness should influence ranking, not replace relevance. A common pattern is to combine retrieval score with time decay, then let the reranker decide whether the newer document is better. If you rank purely by recency, a low-quality post from five minutes ago can outrank the canonical source.

Why does hybrid retrieval beat dense-only retrieval for developer-doc search?

Answer

Dense retrieval captures semantic paraphrases, but BM25 preserves exact identifiers such as API versions, issue IDs, feature flags, and policy titles. Rank fusion gives the reranker both kinds of candidates.

Latency budget

TTFT is a critical constraint of this system. The values below are an illustrative budget; validate them against product objectives and measured stage timings.

StageExample latencyMethodParallelism
Query planning10-50msSmall router / rewriterCan overlap with initial retrieval
Retrieval and fetch150-500msSparse, dense, API, or web callsFan-out within allowed routes
Reranking50-150msCross-encoder GPU batchBatch allowed candidates together
Evidence packing10-30msDedupe, trim, retain source IDsSequential after ranking
LLM prefill to first token300-800msStreaming generationSequential after context exists
Total to first token~0.8-1.5 secondsExample product objectiveDepends on overlap and tail latency
Time-to-first-token timeline where planning overlaps initial retrieval, then reranking, evidence packing, and model prefill run in sequence until the first token at 800 milliseconds.
Budget TTFT on one timeline. Overlap what you can, then measure the remaining serial path by route.

Note: If you support speculative retrieval, cheap lexical or dense search can begin before rewrite finishes. If you don't, planning and retrieval stack sequentially. Make that decision explicit when you budget TTFT.

ttft-critical-path.py
1planner_ms = 40 2initial_retrieval_ms = 260 3rerank_ms = 90 4pack_ms = 20 5prefill_to_first_token_ms = 430 6 7sequential_ttft_ms = planner_ms + initial_retrieval_ms + rerank_ms + pack_ms + prefill_to_first_token_ms 8overlapped_ttft_ms = max(planner_ms, initial_retrieval_ms) + rerank_ms + pack_ms + prefill_to_first_token_ms 9 10assert sequential_ttft_ms == 840 11assert overlapped_ttft_ms == 800 12print("sequential_ttft_ms:", sequential_ttft_ms) 13print("overlapped_ttft_ms:", overlapped_ttft_ms)
Output
1sequential_ttft_ms: 840 2overlapped_ttft_ms: 800

What is the main latency tradeoff in query planning?

Answer

A better rewrite can improve recall, but waiting for it serially delays retrieval. Speculative retrieval starts cheap searches from the raw query while the planner finishes, then merges or cancels results.

3. LLM synthesis with grounded citations

The synthesis prompt can require citations and abstention, but prompting can't prove grounding. The product must keep source IDs attached to evidence, check generated claims, and decide what may be shown while support is pending or missing.

Grounding isn't just a prompt problem. Evidence packing matters too. Put the strongest chunks first, leave room for the model to answer, and avoid stuffing dozens of mediocre passages into the middle of a huge prompt. Long-context models still show "lost in the middle" behavior, where evidence in the center of the context window gets used less reliably.[5]Reference 5Lost in the Middle: How Language Models Use Long Contextshttps://arxiv.org/abs/2307.03172 That's one more reason reranking and tight top-K beat dumping everything into a large context window.

The citations a user sees can be a subset of retrieved evidence, but each visible citation must bind to a specific claim and its source span. Don't treat a source list as proof that every generated sentence is supported.

Before generation, pack allowed passages into a bounded context while preserving source IDs:

evidence-packing-boundary.py
1from dataclasses import dataclass 2 3@dataclass(frozen=True) 4class Passage: 5 source_id: int 6 tenant: str 7 text: str 8 tokens: int 9 rerank_score: float 10 11def pack_allowed(passages: list[Passage], tenant: str, budget: int) -> list[Passage]: 12 selected: list[Passage] = [] 13 used = 0 14 for passage in sorted(passages, key=lambda item: item.rerank_score, reverse=True): 15 if passage.tenant != tenant or used + passage.tokens > budget: 16 continue 17 selected.append(passage) 18 used += passage.tokens 19 return selected 20 21candidates = [ 22 Passage(1, "workspace-a", "Parser-v2 migration starts within 30 days.", 7, 0.95), 23 Passage(2, "workspace-b", "Private roadmap milestone.", 4, 0.99), 24 Passage(3, "workspace-a", "Service-account exceptions require security approval.", 5, 0.82), 25] 26packed = pack_allowed(candidates, tenant="workspace-a", budget=12) 27assert [passage.source_id for passage in packed] == [1, 3] 28print("packed_source_ids:", [passage.source_id for passage in packed])
Output
1packed_source_ids: [1, 3]

Compression after selection, not instead of it

Sometimes the authorized, reranked evidence is still too large for the prefill budget. Query-aware prompt compression is another packing lever after access filtering, deduplication, and passage selection. It isn't permission filtering, retrieval, or a license to keep every weak result.

LLMLingua uses a smaller language model's cross-perplexity signal to estimate which prompt tokens carry less information for the target model, then applies coarse-to-fine pruning.[6]Reference 6LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models.https://arxiv.org/abs/2310.05736 Its paper reports compression ratios as high as 20× on evaluated tasks with limited performance loss, but that number isn't a service guarantee. Search evidence needs its own support and answer-quality evaluation at each target ratio.

Protect exact spans before compression: source IDs, document titles, code symbols, version numbers, dates, quantities, negation, and quoted policy language. Keep a mapping from compressed text back to original source offsets. The synthesizer may read compressed evidence, but citation verification should resolve claims against the original uncompressed passage.

Packing stepInputFailure if skipped
Authorize and rerankRaw retrieved candidatesCompression can retain forbidden or irrelevant text
Select and deduplicateAllowed ranked passagesRepeated weak evidence consumes the budget
Protect evidence spansIDs, numbers, quotations, and source anchorsCompression can change the fact the answer must preserve
Compress remaining textQuery-aware candidate contextPrefill stays larger than the latency and cost envelope
Verify against originalsDraft claims plus source-offset mapA fluent answer can cite wording that compression distorted

Track raw_tokens, selected_tokens, compressed_tokens, protected-span survival, retrieval recall, citation support, and TTFT together. A smaller prompt isn't a win if the correct exception or negation disappears.

Where does LLMLingua-class compression belong in evidence packing?

Answer

After authorization, reranking, deduplication, and passage selection. Protect exact evidence spans, compress only the remaining text, retain source-offset mappings, and verify generated claims against original passages.

Keeping temperature low reduces answer variance, but it doesn't create factuality. The next deterministic example represents a support gate around generated claims: a real synthesizer can draft text, while publication depends on evidence checks.

grounded-answer-contract.py
1from dataclasses import dataclass 2 3@dataclass(frozen=True) 4class Source: 5 source_id: int 6 title: str 7 content: str 8 9def contains_all(source: Source | None, required_terms: tuple[str, ...]) -> bool: 10 if source is None: 11 return False 12 text = source.content.lower() 13 return all(term in text for term in required_terms) 14 15def grounded_answer(sources: list[Source]) -> str: 16 by_id = {source.source_id: source for source in sources} 17 if not contains_all(by_id.get(1), ("parser-v2", "within 30 days")): 18 return "Insufficient cited evidence to answer." 19 if not contains_all(by_id.get(2), ("security approval", "service-account")): 20 return "Insufficient cited evidence to answer." 21 return ( 22 "Start the parser-v2 SDK migration within 30 days [1]. " 23 "Service-account OAuth rotation exceptions require security approval [2]." 24 ) 25 26sources = [ 27 Source( 28 source_id=1, 29 title="Parser v2 Deprecation Policy", 30 content="Parser-v2 SDK migrations must start within 30 days.", 31 ), 32 Source( 33 source_id=2, 34 title="Service Account Rotation Exceptions", 35 content="Service-account OAuth rotation exceptions require security approval.", 36 ), 37] 38 39answer = grounded_answer(sources) 40assert grounded_answer(sources[:1]) == "Insufficient cited evidence to answer." 41assert grounded_answer(sources[:1] + [Source(2, "Service Account Rotation Exceptions", "No approval rule.")]) == "Insufficient cited evidence to answer." 42print(answer)
Output
1Start the parser-v2 SDK migration within 30 days [1]. Service-account OAuth rotation exceptions require security approval [2].

Why is low temperature not enough to make generated search factual?

Answer

Low temperature reduces variance, but factuality comes from retrieved evidence, source IDs, evidence packing, and claim-level verification. A deterministic unsupported answer is still unsupported.

4. Streaming architecture

For one-way progressive delivery, Server-Sent Events (SSE) work through the browser EventSource API over HTTP and are straightforward to render[7]Reference 7Using server-sent eventshttps://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events. Streaming creates a product-policy choice: either hold factual claims until verification completes, or label streamed text as provisional and update or retract unsupported claims.

Streaming answer flow where citations and provisional text render first, a parallel claim-check branch verifies support, and the UI later attaches supported or blocked status.
Stream text early only when it's marked provisional. Support checks run in parallel and attach final status after verification.

Streaming endpoint

This event contract fits a low-risk answer path that permits provisional rendering. It emits the plan and sources, declares pending support, streams text, then attaches the result of claim checks. A higher-risk workflow can omit text frames until support becomes supported.

streaming-endpoint.py
1import json 2 3def sse(event_type: str, data: object) -> str: 4 payload = {"type": event_type, "data": data} 5 return f"data: {json.dumps(payload, separators=(',', ':'))}\n\n" 6 7def event_stream() -> list[str]: 8 return [ 9 sse("plan", {"intent": "comparison", "route": "hybrid_search"}), 10 sse("sources", [{"title": "Parser v2 Deprecation Policy", "source_id": 1}]), 11 sse("support_state", {"state": "pending", "display": "provisional"}), 12 sse("text", "For parser-v2 SDK migration"), 13 sse("text", ", start within 30 days [1]."), 14 sse( 15 "support_state", 16 {"state": "supported", "claim_ids": ["migration-window"]}, 17 ), 18 "data: [DONE]\n\n", 19 ] 20 21for frame in event_stream(): 22 print(frame, end="")
Output
1data: {"type":"plan","data":{"intent":"comparison","route":"hybrid_search"}} 2 3data: {"type":"sources","data":[{"title":"Parser v2 Deprecation Policy","source_id":1}]} 4 5data: {"type":"support_state","data":{"state":"pending","display":"provisional"}} 6 7data: {"type":"text","data":"For parser-v2 SDK migration"} 8 9data: {"type":"text","data":", start within 30 days [1]."} 10 11data: {"type":"support_state","data":{"state":"supported","claim_ids":["migration-window"]}} 12 13data: [DONE]

In production, add periodic heartbeat events and configure upstream proxies not to buffer the stream. Also log state transitions so provisional output that becomes blocked can be audited.


Why use SSE for search-style answer streaming?

Answer

SSE fits one-way progressive token delivery over HTTP when the browser needs plan, source, text, and support-state events from the server. It doesn't decide whether unverified facts may be displayed.

Why answers still hallucinate and how to catch them

One of the most critical challenges in search is hallucination, when the LLM generates plausible but incorrect facts. Even with RAG, models can misinterpret complex details. Strong systems usually stack multiple checks rather than betting on one verifier:

MethodWhat it checksRelative costMain weakness
Quote / span matchingDoes the cited span appear in the source?Very lowMisses paraphrases and logical contradictions
Natural Language Inference (NLI)Does the cited passage entail the claim?MediumCan struggle on ambiguous or very long passages
LLM-as-judge / fallback passShould unsupported claims be flagged or regenerated?HighRequires measured latency, cost, and calibration

NLI-based verification estimates whether a cited passage entails a claim rather than merely sharing words. TRUE[8]Reference 8TRUE: Re-evaluating Factual Consistency Evaluation.https://arxiv.org/abs/2204.04991 found that large-scale NLI methods and question-generation-plus-answering methods were strong automatic factual-consistency checks on its benchmarks, so NLI is a useful measured layer, not proof of correctness.

This function is deliberately only a lexical precheck. It can cheaply reject a missing phrase; it can't label entailment. Claims that pass it still need a calibrated verifier or a policy that keeps them provisional.

lexical-citation-precheck.py
1import json 2import re 3 4def lexical_precheck(claim: str, source_text: str) -> dict[str, str | bool]: 5 claim_terms = set(re.findall(r"[a-z0-9]+", claim.lower())) 6 source_terms = set(re.findall(r"[a-z0-9]+", source_text.lower())) 7 term_match = claim_terms.issubset(source_terms) 8 9 return { 10 "claim": claim, 11 "term_match": term_match, 12 "next_step": "entailment_check" if term_match else "block_claim", 13 } 14 15result = lexical_precheck( 16 claim="Parser-v2 migrations must start within 30 days", 17 source_text="Parser-v2 SDK migrations must start within 30 days.", 18) 19 20print(json.dumps(result, indent=2))
Output
1{ 2 "claim": "Parser-v2 migrations must start within 30 days", 3 "term_match": true, 4 "next_step": "entailment_check" 5}

Why can lexical matching not replace NLI or another calibrated verifier?

Answer

Lexical matching checks whether words appear in a source. Entailment evaluation estimates whether the passage supports the meaning of the claim, including paraphrases or contradictions.


Skills to defend in review

SkillWhat good looks like
Offline vs online splitYou separate ingestion, cleanup, chunking, and index refresh from the latency-sensitive query path.
Multi-stage query pathYou can explain planning, hybrid retrieval, reranking, evidence packing, synthesis, and verification as distinct stages.
Recall improvementsYou know when to use query rewriting, decomposition, routing, and HyDE-style hypothetical documents.
Citation verificationYou check claims against cited sources with span checks, NLI, and selective fallback regeneration.
Streaming UXYou design progressive rendering with SSE events for plan, sources, text, and verification state.
Latency budgetYou allocate TTFT across planning, retrieval, reranking, prefill, and generation instead of treating "LLM latency" as one number.
Serving leversYou know where PagedAttention and speculative decoding help, and why they don't replace retrieval discipline.
Evaluation loopYou combine RAGAS-style reference-free metrics, labeled retrieval metrics, citation metrics, and online feedback.

Production questions to answer

How do you handle queries that need real-time data? Real-time data needs its own freshness pipeline. Use a hot index for newly crawled content plus direct routes to live APIs or structured stores for exact numeric fields. When the planner marks a query as freshness-sensitive, search the hot tier first, apply recency-aware scoring, then merge with the warmer corpus so the LLM sees both new evidence and canonical background context.

How would you detect and handle hallucinated citations? Decompose the generated answer into atomic claims, attach each claim to its cited source, and verify entailment with NLI or a calibrated lighter check. If support is weak or contradictory, flag the statement, remove it, or regenerate with stricter grounding and a smaller evidence set.

What's your latency budget for each pipeline stage? For a consumer-facing experience, a reasonable example SLO is roughly 0.8-1.5 seconds to first token. Query planning often fits in 10-50ms, retrieval fan-out in 150-500ms, reranking in 50-150ms, evidence packing in 10-30ms, and synthesis in roughly 300-800ms to first token. Split synthesis into prefill and decode because prompt length drives prefill cost while answer length drives decode cost.

How do you evaluate answer quality at scale? Start with reference-free metrics such as faithfulness, answer relevance, and context relevance. Add labeled retrieval metrics such as Recall@k or nDCG on benchmark sets, plus citation precision and unsupported-claim rate. In production, track citation click-through, reformulation rate, dwell time, abandonment, and explicit user feedback.

Common design failures

  • Designing everything around a single LLM call instead of specialized routers, retrievers, rerankers, and verifiers.
  • Treating structured or freshness-critical queries like generic vector search.
  • Ignoring latency requirements for interactive search experiences.
  • Skipping rigorous citation verification and trusting prompt instructions alone.
  • Treating long context windows as a replacement for reranking and evidence packing.
  • Overlooking inference cost per query when scaling to millions of users.

Common mistakes when building generative search

SymptomCauseFix
TTFT is consistently >2 secondsRunning a frontier LLM on every query, even simple lookupsAdd a lightweight router that sends factual queries to a small model or cache
Answers cite the wrong sourceEvidence packing stuffs too many mediocre passages into the promptReduce top-K after reranking and place the strongest chunks at the start and end of context
Hallucinations persist despite RAGMissing claim-level verification; relying only on prompt instructionsStack span matching, NLI, and selective LLM-as-judge checks
Cost per query spikes unpredictablyNo freshness tiering; every query hits real-time web retrievalRoute only freshness-marked queries to the hot tier and cache stable answers
Retrieval misses exact API versions or build IDsUsing only dense semantic search without keyword fallbackHybrid sparse + dense retrieval with BM25 for exact matches

What running this costs

Building a search engine at scale requires careful cost management. Exact dollar figures drift with vendor pricing, model choice, and answer length, so reason from units of work rather than one fixed price table.

Cost routing ladder where requests escalate from cache to small model to mid-tier synthesis to frontier reasoning only when freshness, evidence burden, or ambiguity require it.
Promote queries only when requirements force it. Freshness, retrieval fan-out, prompt length, and verification policy change route cost.
Cost centerBillable unitWhat makes it expensive
Web retrievalAPI calls and fetched documentsMore sub-queries, more URLs fetched
RerankingQuery-document pairsLarger candidate set or heavier reranker
SynthesisPrompt + completion tokensLong context windows and long answers
VerificationClaims checked against passagesDense citation requirements, many claims

Before writing the formula, use scenario inputs rather than presenting provider pricing as fixed:

  • Web retrieval: ~$0.002
  • Reranking 200 query-document pairs: ~$0.001
  • 1,500 prompt tokens + 400 output tokens at $0.003 per 1K tokens: ~$0.006
  • Verifying 5 claims: ~$0.001

For these inputs, total cost is roughly $0.01 per query. At 1,000 QPS, that scenario approaches $600 per minute, which is why routing and caching need their own budgets.

query-cost-envelope.py
1retrieval_usd = 0.002 2rerank_usd = 0.001 3prompt_tokens = 1500 4output_tokens = 400 5token_usd_per_thousand = 0.003 6verification_usd = 0.001 7queries_per_second = 1000 8 9query_usd = ( 10 retrieval_usd 11 + rerank_usd 12 + (prompt_tokens + output_tokens) / 1000 * token_usd_per_thousand 13 + verification_usd 14) 15per_minute_usd = query_usd * queries_per_second * 60 16 17assert round(query_usd, 4) == 0.0097 18print("scenario_query_usd:", round(query_usd, 4)) 19print("scenario_per_minute_usd:", round(per_minute_usd, 2))
Output
1scenario_query_usd: 0.0097 2scenario_per_minute_usd: 582.0
cost/query≈  Csearch+Npairs⋅Crerank+(Tprompt+Toutput)⋅Ctoken+Nclaims⋅Cverify\begin{aligned} \text{cost/query} \approx\;& C_{\text{search}} + N_{\text{pairs}} \cdot C_{\text{rerank}} \\ &+ (T_{\text{prompt}} + T_{\text{output}}) \cdot C_{\text{token}} \\ &+ N_{\text{claims}} \cdot C_{\text{verify}} \end{aligned}cost/query≈​Csearch​+Npairs​⋅Crerank​+(Tprompt​+Toutput​)⋅Ctoken​+Nclaims​⋅Cverify​​

Which stage dominates variable cost depends on provider rates, fan-out, prompt length, and verification policy. Measure the breakdown, then route cacheable or structured queries away from unnecessary synthesis and trim top-K where retrieval evaluation allows it.

In the worked 1,000 QPS cost scenario, why does routing matter?

Answer

The scenario costs hundreds of dollars per minute. Cache hits, structured routes, smaller models, and evaluated top-K reduction can change that envelope before traffic reaches synthesis.

Serving-side latency levers

TTFT can be dominated by retrieval, prompt assembly, or prefill; long answers can shift pressure toward decode. Profile the critical path before choosing an optimization.

PagedAttention improves KV-cache memory management and can increase concurrency when KV allocation is the binding constraint.[9]Reference 9Efficient Memory Management for Large Language Model Serving with PagedAttention.https://arxiv.org/abs/2309.06180 If decode is the bottleneck, speculative decoding can reduce latency when a draft model is accepted often enough; benchmark it for the answer shapes and target model in this service.[10]Reference 10Fast Inference from Transformers via Speculative Decoding.https://arxiv.org/abs/2211.17192

These serving optimizations matter only after retrieval depth and prompt length are under control. If you keep sending 40 mediocre passages to the model, no amount of serving cleverness will rescue TTFT.

GPU capacity planning

Split concurrency by which latency you mean. Product SLOs here are often TTFT (about 0.8 to 1.5 seconds to first token), while end-to-end generation (full answer) and KV hold time are longer.

For a 1K QPS design point (the low end of the 1K to 10K QPS per-region range; size peak regions separately):

LTTFT≈1.0 sstreaming sessions at first token≈1000×1.0=1000\begin{aligned} L_{\text{TTFT}} &\approx 1.0\,\text{s} \\ \text{streaming sessions at first token} &\approx 1000 \times 1.0 = 1000 \end{aligned}LTTFT​streaming sessions at first token​≈1.0s≈1000×1.0=1000​ Le2e≈1.5 sfull in-flight answers and KV held≈1000×1.5=1500\begin{aligned} L_{\text{e2e}} &\approx 1.5\,\text{s} \\ \text{full in-flight answers and KV held} &\approx 1000 \times 1.5 = 1500 \end{aligned}Le2e​full in-flight answers and KV held​≈1.5s≈1000×1.5=1500​

Little's Law still holds (L=λWL = \lambda WL=λW); use W=LTTFTW = L_{\text{TTFT}}W=LTTFT​ for admission of streaming sessions and W=Le2eW = L_{\text{e2e}}W=Le2e​ for decode concurrency and KV residency. The 1500 figure is not first-token-only concurrency.

capacity-envelope.py
1from math import ceil 2 3queries_per_second = 1000 4average_e2e_latency_seconds = 1.5 5average_output_tokens = 120 6effective_decode_tokens_per_second_per_gpu = 6000 7 8in_flight_e2e = queries_per_second * average_e2e_latency_seconds 9decode_gpus = ceil( 10 queries_per_second 11 * average_output_tokens 12 / effective_decode_tokens_per_second_per_gpu 13) 14 15assert in_flight_e2e == 1500 16print("in_flight_e2e_requests:", int(in_flight_e2e)) 17print("decode_gpus_before_headroom:", decode_gpus)
Output
1in_flight_e2e_requests: 1500 2decode_gpus_before_headroom: 20

Decode throughput alone is a lower bound. Twenty GPUs at 6k tok/s can emit the tokens, but 1500 concurrent contexts may bind KV HBM first. Using the multi-tenant lesson's roughly 1.22 GiB per 4K-token request as a sketch: if average prompt plus generated context is about 2K tokens (roughly 0.61 GiB each) and packing leaves 60% of each 80 GiB GPU usable for KV, then 1500×0.61/0.6≈15251500 \times 0.61 / 0.6 \approx 15251500×0.61/0.6≈1525 GiB of raw HBM capacity, or about 20 80-GiB GPUs before weight residency and headroom. Formula sketch:

BKV=concurrent×average tokens×KV bytes/tokenNGPU, KV≈⌈BKVusable KV HBM/GPU⌉\begin{aligned} B_{\text{KV}} &= \text{concurrent} \times \text{average tokens} \\ &\quad \times \text{KV bytes/token} \\ N_{\text{GPU, KV}} &\approx \left\lceil \frac{B_{\text{KV}}}{\text{usable KV HBM/GPU}} \right\rceil \end{aligned}BKV​NGPU, KV​​=concurrent×average tokens×KV bytes/token≈⌈usable KV HBM/GPUBKV​​⌉​

PagedAttention improves packing; it doesn't remove the byte accounting.

The next step is to translate concurrency into throughput and memory requirements for your actual serving stack. Don't assume a universal "requests per GPU" number. Measure your own model, context length, batching strategy, and quantization settings. PagedAttention improves packing efficiency and reduces KV-cache fragmentation under concurrent load[9]Reference 9Efficient Memory Management for Large Language Model Serving with PagedAttention.https://arxiv.org/abs/2309.06180, but capacity still depends on empirical tokens-per-second benchmarks.

Rdecode=QPS×average output tokensNGPU, decode≈⌈Rdecodeeffective decode tok/s/GPU⌉\begin{aligned} R_{\text{decode}} &= \text{QPS} \times \text{average output tokens} \\ N_{\text{GPU, decode}} &\approx \left\lceil \frac{R_{\text{decode}}}{\text{effective decode tok/s/GPU}} \right\rceil \end{aligned}Rdecode​NGPU, decode​​=QPS×average output tokens≈⌈effective decode tok/s/GPURdecode​​⌉​

For long prompts, also budget prefill separately:

Prefill capacity≈QPS×avg prompt tokenseffective prefill tok/s/GPU\text{Prefill capacity} \approx \frac{\text{QPS} \times \text{avg prompt tokens}}{\text{effective prefill tok/s/GPU}}Prefill capacity≈effective prefill tok/s/GPUQPS×avg prompt tokens​

At the same 1K QPS design point, split pools explicitly (illustrative shares; measure yours):

PoolRoleSketch share of workSizing note
Planner / routerQuery plan, rewrite~all queries, CPU-msStateless CPU replicas
Sparse retrievalBM25 / lexical~all authorized queriesShard by corpus
Dense ANNVector search~all hybrid queriesIndex RAM + QPS
Rerank GPUCross-encoderTop-KKK pairs onlyBatch GPU workers
Synthesis GPUPrefill + decodeAnswer-generating routesDecode + KV lines above

Peak 10K QPS is an order of magnitude above this worked example: either shard regions, raise cache hit rates, or replicate every pool ~10× after you remeasure stage hit rates. Don't pretend the 1K QPS GPU count covers the top of the requirements range.

In practice, size the system against whichever pool saturates first: retriever GPUs, reranker GPUs, prefill workers, or decode workers. The answer model may be an expensive tier, but measured retrieval or reranking can still be the first bottleneck.

Why can reranking or retrieval be the bottleneck even when the LLM is expensive?

Answer

Large fan-out multiplies query-document pairs, web/API calls, and cleanup work before synthesis starts. If retrieval depth or reranker batches saturate first, faster decoding won't fix TTFT.


How to know it's working

RAGAS[11]Reference 11RAGAS: Automated Evaluation of Retrieval Augmented Generation.https://arxiv.org/abs/2309.15217 introduced reference-free evaluation around faithfulness, answer relevance, and context relevance. It's one offline layer when gold answers are scarce, not a replacement for labeled retrieval metrics, citation checks, or online product metrics: a response can be consistent with mediocre evidence and still fail the user.

LayerMetricWhat it catchesTypical requirement
Reference-free offlineFaithfulnessUnsupported claimsLLM/NLI judge over answer vs context
Reference-free offlineAnswer relevanceMissed user intentGenerated-question similarity or judge
Reference-free offlineContext relevanceNoisy, redundant retrieved contextJudge over question vs retrieved passages
Labeled offlineRecall@k / nDCG / MRRRetriever missed or buried the right sourceGold passages or reliable click labels
Citation qualityCitation precision / unsupported-claim rateWrong citations or uncited claimsClaim extraction + source verification
OnlineReformulation rate / citation CTR / abandonmentReal user dissatisfactionProduction logging and experiments

This example wires a scorecard together with simple token-overlap proxies so it stays runnable. Its citation-resolution metric only checks whether a visible citation ID maps to an available passage; it doesn't prove claim support. These proxies aren't RAGAS or calibrated faithfulness judges. Replace them in an evaluation service while keeping labeled retrieval metrics separate.

how-to-know-its-working.py
1import json 2import re 3from dataclasses import dataclass 4from collections.abc import Mapping 5from math import log2 6 7@dataclass(frozen=True) 8class Passage: 9 id: str 10 text: str 11 12def tokens(text: str) -> set[str]: 13 return set(text.lower().replace(",", "").replace(".", "").split()) 14 15def overlap(a: str, b: str) -> float: 16 left = tokens(a) 17 right = tokens(b) 18 return len(left & right) / max(1, len(left)) 19 20def citation_resolution_precision(answer: str, cited_passages: list[Passage]) -> float: 21 available_ids = {passage.id for passage in cited_passages} 22 cited_ids = re.findall(r"\[([^\[\]]+)\]", answer) 23 return sum(cited_id in available_ids for cited_id in cited_ids) / max(1, len(cited_ids)) 24 25def recall_at_k(retrieved_ids: list[str], gold_ids: set[str], k: int) -> float: 26 return len(set(retrieved_ids[:k]) & gold_ids) / max(1, len(gold_ids)) 27 28def ndcg_at_k(retrieved_ids: list[str], gold_ids: set[str], k: int) -> float: 29 gains = [1.0 if item in gold_ids else 0.0 for item in retrieved_ids[:k]] 30 dcg = sum(gain / log2(rank + 2) for rank, gain in enumerate(gains)) 31 ideal = sum(1.0 / log2(rank + 2) for rank in range(min(len(gold_ids), k))) 32 return dcg / ideal if ideal else 0.0 33 34def score_search_response( 35 query: str, 36 answer: str, 37 retrieved_passages: list[Passage], 38 cited_passages: list[Passage], 39 gold_passage_ids: set[str] | None = None, 40) -> Mapping[str, float]: 41 context = " ".join(passage.text for passage in retrieved_passages) 42 scorecard = { 43 "answer_context_token_overlap": overlap(answer, context), 44 "query_answer_token_overlap": overlap(query, answer), 45 "query_context_token_overlap": overlap(query, context), 46 "citation_resolution_precision": citation_resolution_precision(answer, cited_passages), 47 } 48 49 if gold_passage_ids is not None: 50 retrieved_ids = [p.id for p in retrieved_passages] 51 scorecard["recall_at_20"] = recall_at_k(retrieved_ids, gold_passage_ids, k=20) 52 scorecard["ndcg_at_10"] = ndcg_at_k(retrieved_ids, gold_passage_ids, k=10) 53 54 return {metric: round(value, 2) for metric, value in scorecard.items()} 55 56retrieved = [ 57 Passage("1", "Parser-v2 SDK migrations must start within 30 days."), 58 Passage("2", "Service-account OAuth rotation exceptions require security approval."), 59 Passage("3", "Legacy parser-v1 migrations follow a separate workflow."), 60] 61answer = "Parser-v2 SDK migrations must start within 30 days [1]." 62 63print(json.dumps( 64 score_search_response( 65 query="Which deprecation policy applies to parser-v2 SDK migration for service accounts?", 66 answer=answer, 67 retrieved_passages=retrieved, 68 cited_passages=retrieved[:1], 69 gold_passage_ids={"1", "2"}, 70 ), 71 indent=2, 72))
Output
1{ 2 "answer_context_token_overlap": 0.89, 3 "query_answer_token_overlap": 0.18, 4 "query_context_token_overlap": 0.18, 5 "citation_resolution_precision": 1.0, 6 "recall_at_20": 1.0, 7 "ndcg_at_10": 1.0 8}

Why keep reference-free RAGAS-style metrics separate from labeled retrieval metrics?

Answer

They answer different questions. Reference-free metrics judge answer/context consistency; labeled metrics reveal whether the retriever found and ranked known relevant passages.


Scaling without blowing the budget

As traffic grows, evaluate each latency or cost lever against quality and authorization tests:

StrategyImplementation DetailsPrimary Benefit
Semantic response cacheKey answers by authorization scope and evidence version; reuse only after similarity and freshness thresholds pass.Can avoid synthesis work for eligible repeat queries.
Model routingRoute exact live facts to structured backends and measured simple paths to smaller models.Can lower cost without weakening answer routes that require synthesis.
Speculative retrievalStart an allowed cheap retriever while rewrite or decomposition runs, then merge or cancel.Can shorten measured critical path when early retrieval is useful.
Asynchronous verificationMark streamed claims pending, or hold high-risk facts until checks return.Trades display latency against unsupported-claim exposure explicitly.
Geographic distributionPlace permitted retrieval and caches near users when data residency policy allows it.Can reduce network latency for eligible data paths.
Feedback integrationLog interactions with privacy controls and use labeled review before training rerankers.Supplies evidence for retriever or reranker changes.

Why is semantic response caching risky without freshness and permission checks?

Answer

Similar queries can have different tenant visibility, quota state, policy version, or time sensitivity. Cache reuse must satisfy similarity, freshness, and authorization thresholds.

Complete the lesson

Mastery Check

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

1.A developer asks, "Is deploy job build-1042 still running on the GPU runner, how much workspace quota remains today, and what policy applies to emergency overages?" Job state and quota change minute to minute, while overage policy must be cited. How should the query planner route it?

Correct answer: Call structured job-state and quota APIs first for live facts, then retrieve tenant-authorized overage-policy passages for cited synthesis and verification.

Exact live values such as job state and remaining quota should come from structured APIs or entity stores, not inferred from semantic text retrieval. Policy text can still be retrieved, reranked, cited, and verified so the generated answer explains the live result with grounded evidence.

2.A team wants user requests to crawl sources, clean documents, create chunks, attach access metadata, update indexes, retrieve evidence, and generate an answer in one synchronous path. Which split keeps the search path fast and permission-safe?

Correct answer: Put crawling, cleanup, chunking, ACL/source metadata, and index refresh in offline ingest; online should plan, retrieve only allowed evidence, rerank, pack, synthesize, and verify.

Ingest prepares and refreshes searchable evidence before users ask. The online path must stay fast and must not rely on the LLM or a final citation filter to enforce access control; it should retrieve only allowed evidence and then rerank, pack, synthesize, and verify it.

3.For a developer-doc query that includes exact API versions and policy titles but may use user paraphrases, why run BM25 and dense retrieval before reranking?

Correct answer: BM25 preserves exact lexical anchors, dense retrieval finds semantic matches, RRF avoids raw-score scale mismatch, and the reranker then chooses from both candidate types.

BM25 is strong for exact identifiers and titles, while dense retrieval improves recall for paraphrases and related terminology. Because sparse and dense scores live on different scales, the lesson recommends rank fusion such as RRF before a more expensive reranker refines the candidate set.

4.For tenant workspace-a, the ACL allows documents deprecation and rotation but not private-roadmap. Sparse ranks are [deprecation, private-roadmap, rotation]; dense ranks are [private-roadmap, rotation, deprecation]. If authorization filtering happens before RRF, which documents can appear in the fused evidence set?

Correct answer: deprecation and rotation only; private-roadmap is removed before its high rank can affect generation context.

Authorization is a trust boundary, not a display filter. Forbidden documents must be removed before rank fusion, reranking, packing, and generation, so a high-ranked private document cannot influence the answer or citations.

5.After authorization and reranking, eight strong passages still total 12,000 tokens, but the synthesis budget is 3,000. Version numbers, policy exceptions, and citation spans must survive. Which packing plan is safest?

Correct answer: Deduplicate and select first, protect exact evidence spans and source IDs, compress remaining text with a query-aware method, retain source offsets, and verify claims against original passages.

Compression is a lossy packing operator, not a substitute for authorization, reranking, or selection. Protecting exact spans reduces the risk of dropping identifiers, exceptions, negation, or citation anchors. Source-offset mappings let claim verification use the original evidence rather than trusting compressed wording.

6.A generated answer cites a passage and the cited words mostly overlap the claim, but the passage may not entail the claim's meaning. What verification policy should gate publication?

Correct answer: Extract atomic claims, bind each to cited spans, use NLI or another calibrated entailment check, and block, mark, or regenerate weakly supported claims.

Citation resolution and word overlap are weaker than entailment. A support gate should check whether each cited span supports the meaning of each atomic claim, then apply product policy to block, mark, or regenerate claims with weak or contradictory support.

7.A search endpoint uses SSE and wants sub-second perceived latency for low-risk policy text. Claim verification may finish after the first tokens. Which event policy matches the design?

Correct answer: Emit sources and support_state pending, stream text labeled provisional, then send supported or blocked status and audit transitions; hold high-risk facts until supported.

SSE solves one-way progressive delivery, not factuality. If text is streamed before verification, the UI must mark it pending or provisional and later update, block, or retract unsupported claims. For high-risk facts, the system can hold text until support is confirmed.

8.A TTFT budget has planner 40 ms, retrieval 260 ms, reranking 90 ms, evidence packing 20 ms, and LLM prefill to first token 430 ms. If speculative retrieval lets planning overlap the initial retrieval, what is the TTFT critical path?

Correct answer: 800 ms: max(40, 260) + 90 + 20 + 430.

When planning and initial retrieval overlap, the critical path takes the slower of those two stages, not their sum. The remaining dependent stages still run after evidence exists, so TTFT is max(40, 260) + 90 + 20 + 430 = 800 ms.

9.A semantic cache contains an answer for workspace-a built from evidence version 17. A similar query arrives for workspace-b after a policy update. What must be true before the cached answer can be reused?

Correct answer: The query is semantically similar, workspace-b is authorized for the same cited evidence, and the evidence version and freshness constraints still match.

Semantic similarity alone is unsafe. Cache reuse must respect tenant visibility, evidence version, and freshness because similar queries can have different permissions, quota state, or policy versions. Prompting cannot undo exposure to an unauthorized or stale cached answer.

10.An offline judge gives high faithfulness for an answer against retrieved passages, and every visible citation ID resolves to an available passage. In production, users often reformulate the query and abandon the result. What should the evaluation loop inspect next?

Correct answer: Inspect labeled retrieval and online behavior: Recall@k or nDCG for known relevant passages, citation precision, unsupported-claim rate, reformulation, citation CTR, and abandonment.

Reference-free faithfulness and citation ID resolution answer only narrow questions: whether the answer is consistent with retrieved context and whether citation IDs map to passages. Users can still abandon results when retrieval missed the right source, ranked it poorly, cited weak evidence, or failed intent. Labeled retrieval metrics and online behavior reveal those failures.

10 questions remaining.

Next Step
Continue to Vision-Language Models & CLIP

Search turned retrieved text evidence into cited answers. Now you'll make images searchable too: learn how CLIP aligns text and images, then see how modern vision-language models bring visual evidence into multimodal systems.

PreviousMulti-Tenant LLM Platform
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.

Lewis, P., et al. · 2020 · NeurIPS 2020

https://arxiv.org/abs/2005.11401

Precise Zero-Shot Dense Retrieval without Relevance Labels.

Gao, L., Ma, X., Lin, J., & Callan, J. · 2022 · arXiv preprint

https://arxiv.org/abs/2212.10496

Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods.

Cormack, G. V., Clarke, C. L. A., & Buettcher, S. · 2009 · SIGIR '09

https://dl.acm.org/doi/10.1145/1571941.1572114

ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT.

Khattab, O., & Zaharia, M. · 2020 · SIGIR 2020

https://arxiv.org/abs/2004.12832

Lost in the Middle: How Language Models Use Long Contexts

Liu, N.F., et al. · 2023 · TACL 2023

https://arxiv.org/abs/2307.03172

LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models.

Jiang, H., et al. · 2023 · EMNLP

https://arxiv.org/abs/2310.05736

Using server-sent events

MDN Web Docs · 2026

https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

TRUE: Re-evaluating Factual Consistency Evaluation.

Honovich, O., et al. · 2022 · NAACL 2022

https://arxiv.org/abs/2204.04991

Efficient Memory Management for Large Language Model Serving with PagedAttention.

Kwon, W., et al. · 2023 · SOSP 2023

https://arxiv.org/abs/2309.06180

Fast Inference from Transformers via Speculative Decoding.

Leviathan, Y., Kalman, M., & Matias, Y. · 2023 · ICML 2023

https://arxiv.org/abs/2211.17192

RAGAS: Automated Evaluation of Retrieval Augmented Generation.

Es, S., et al. · 2023 · arXiv preprint

https://arxiv.org/abs/2309.15217

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.