LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

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

Product

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

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 158 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/23
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsPrompt 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&BMixed Precision TrainingModel 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: Production Agent
🧠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 LabContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight InterpolationPrompt Optimization with DSPyRecursive Language Models (RLM)
🤖Advanced Agents & Retrieval0/14
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 & RecoveryMulti-Agent Orchestration
⚡Inference & Production Scale0/20
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 ManagementContext EngineeringMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning & Test-Time Compute
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
Back to Topics
LearnProduction ML SystemsRanking and Recommendation Systems
📊MediumEvaluation & Benchmarks

Ranking and Recommendation Systems

Rank documents for a developer using candidate retrieval, relevance metrics, and feedback-loop safeguards.

14 min read
Learning path
Step 45 of 158 in the full curriculum
Gradient Boosted Trees in ProductionForecasting and Anomaly Detection

An incident classifier chooses one action for one alert. A search ranker makes a different prediction: among thousands of documents, which few should appear first for a query or developer?

This is a ranking problem. Retrieval finds plausible candidates quickly; a ranking model orders them using query, document, freshness, and source-trust features. A recommendation feed uses the same shape without a typed query: developer and session context replace query features. In both cases, the displayed order becomes part of the future training data.

Ranking evidence dashboard showing eligibility, candidate recall, reranking, and click-position correction. Ranking evidence dashboard showing eligibility, candidate recall, reranking, and click-position correction.
Eligibility removes two blocked documents, a candidate budget of 5 restores all three relevant documents, reranking reaches NDCG@5 of 1.000, and propensity correction shows why raw click-through rate isn't a relevance label.

Separate eligibility, retrieval, and ranking

Suppose a developer searches for retry idempotency key. The corpus contains one million documents. A feature-heavy model shouldn't score every document on every request.

StageJobExample guard
eligibility filterremove forbidden or stale documentscurrent, permitted source
candidate generationrecover perhaps 200 plausible documentstext or embedding retrieval
rankingorder candidates preciselyrelevance, freshness, source trust
business policyenforce final constraintssponsored labels, diversity, safety

A document absent from the candidate set can't be rescued by a perfect ranker. A forbidden document shouldn't enter the candidate set at all. Candidate recall, policy filtering, and ranking quality are separate obligations.

Build a seven-document fixture. The relevance value is an offline human judgment for one frozen query. It belongs in evaluation data, not in online model features.

build-doc-search-fixture.py
1from hashlib import sha256 2from json import dumps 3from math import log2 4 5query = "retry idempotency key" 6corpus = [ 7 {"id": "api-guide", "title": "retry idempotency API guide", "current": True, "permitted": True, "retrieval": 0.96, "query_fit": 0.98, "reliability": 0.88, "relevance": 3}, 8 {"id": "blog-post", "title": "general release blog", "current": True, "permitted": True, "retrieval": 0.91, "query_fit": 0.35, "reliability": 0.97, "relevance": 0}, 9 {"id": "stale-changelog", "title": "legacy changelog", "current": True, "permitted": True, "retrieval": 0.90, "query_fit": 0.15, "reliability": 0.99, "relevance": 0}, 10 {"id": "retry-runbook", "title": "retry policy runbook", "current": True, "permitted": True, "retrieval": 0.84, "query_fit": 0.92, "reliability": 0.98, "relevance": 2}, 11 {"id": "sdk-example", "title": "idempotency SDK example", "current": True, "permitted": True, "retrieval": 0.80, "query_fit": 0.86, "reliability": 0.96, "relevance": 2}, 12 {"id": "private-draft", "title": "private retry incident draft", "current": True, "permitted": False, "retrieval": 0.99, "query_fit": 0.99, "reliability": 0.99, "relevance": 3}, 13 {"id": "stale-guide", "title": "stale retry migration guide", "current": False, "permitted": True, "retrieval": 0.95, "query_fit": 0.94, "reliability": 0.70, "relevance": 2}, 14] 15 16print("corpus rows:", len(corpus)) 17print("query:", query)
Output
1corpus rows: 7 2query: retry idempotency key

The corpus intentionally includes a private draft and a stale guide. Remove both before retrieval. A learned score can't override access policy.

filter-eligible-listings.py
1eligible = [ 2 item 3 for item in corpus 4 if item["current"] and item["permitted"] 5] 6blocked = sorted(item["id"] for item in corpus if item not in eligible) 7 8print("eligible:", [item["id"] for item in eligible]) 9print("blocked:", blocked)
Output
1eligible: ['api-guide', 'blog-post', 'stale-changelog', 'retry-runbook', 'sdk-example'] 2blocked: ['private-draft', 'stale-guide']

Measure candidate recall before ordering quality

Candidate generation runs a cheap retrieval stage. This lab uses a precomputed retrieval score so you can focus on the contract between stages. A real system might combine lexical retrieval, embeddings, popularity, and personalized retrieval.

The judged relevant eligible documents are api-guide, retry-runbook, and sdk-example. Measure how many survive candidate generation.

measure-candidate-recall.py
1relevant_ids = { 2 item["id"] 3 for item in eligible 4 if item["relevance"] > 0 5} 6 7def candidates_at(limit): 8 return sorted( 9 eligible, 10 key=lambda item: (-item["retrieval"], item["id"]), 11 )[:limit] 12 13def candidate_recall(items): 14 returned_ids = {item["id"] for item in items} 15 return len(returned_ids & relevant_ids) / len(relevant_ids) 16 17for limit in (3, 5): 18 items = candidates_at(limit) 19 print( 20 f"k={limit} ids={[item['id'] for item in items]} " 21 f"recall={candidate_recall(items):.3f}" 22 )
Output
1k=3 ids=['api-guide', 'blog-post', 'stale-changelog'] recall=0.333 2k=5 ids=['api-guide', 'blog-post', 'stale-changelog', 'retry-runbook', 'sdk-example'] recall=1.000

At k=3, retrieval keeps only one of three relevant eligible documents. No downstream ranker can recover the missing runbook or SDK example. Raising this tiny lab's candidate budget to 5 restores candidate recall before the expensive scorer runs.

Pause and predict: If the ranker becomes perfect while candidate budget stays at 3, can the runbook appear? No. It never reaches the ranker.

Rerank the surviving candidates

Keep five candidates. The first-stage retrieval order is fast but imprecise: blog-post and stale-changelog sit near the top on retrieval alone, yet their query_fit stays low for the idempotency intent.

The scorer below is hand-written so each feature remains visible. query_fit represents a richer query-document match feature. reliability represents source trust. Neither uses the offline relevance label.

score-and-rerank.py
1candidates = candidates_at(5) 2 3def rank_score(item): 4 return 2.0 * item["query_fit"] + 0.6 * item["reliability"] 5 6ranked = sorted( 7 candidates, 8 key=lambda item: (-rank_score(item), item["id"]), 9) 10 11print("retrieval order:", [item["id"] for item in candidates]) 12print("ranked order:", [item["id"] for item in ranked]) 13print("rank scores:", [(item["id"], round(rank_score(item), 3)) for item in ranked])
Output
1retrieval order: ['api-guide', 'blog-post', 'stale-changelog', 'retry-runbook', 'sdk-example'] 2ranked order: ['api-guide', 'retry-runbook', 'sdk-example', 'blog-post', 'stale-changelog'] 3rank scores: [('api-guide', 2.488), ('retry-runbook', 2.428), ('sdk-example', 2.296), ('blog-post', 1.282), ('stale-changelog', 0.894)]

A production scorer learns weights or nonlinear interactions from labeled examples. The request path remains the same: retrieve cheaply, score the bounded candidate set, then apply policy.

A metric that values the top slots

In a ranked list, placing the best result first matters more than moving it from position 40 to position 39. Discounted Cumulative Gain (DCG) gives graded relevance near the top more weight:

DCG@k=∑i=1k2reli−1log⁡2(i+1)\text{DCG@k} = \sum_{i=1}^{k} \frac{2^{rel_i} - 1}{\log_2(i+1)}DCG@k=i=1∑k​log2​(i+1)2reli​−1​

Here relirel_ireli​ is the judged relevance at position iii. Position 1 has denominator log⁡2(2)=1\log_2(2)=1log2​(2)=1, so it keeps full weight. Lower positions receive progressively smaller weight. Normalized DCG (NDCG) divides by the score of the ideal ordering, producing a value between zero and one for a query.[1]Reference 1Introduction to Information Retrieval.https://nlp.stanford.edu/IR-book/

Calculate NDCG@5 for both orders. Build the ideal top five by sorting all candidate relevances before truncating; otherwise a relevant candidate below the evaluated slate could disappear from the denominator. The guard for best == 0 handles a query with no judged relevant documents instead of dividing by zero.

This lab computes reranker NDCG over documents that survived retrieval. That boundary is deliberate: candidate recall measures misses before ranking, while reranker NDCG measures ordering quality inside the bounded candidate set. A whole-system evaluation can also compute NDCG against the eligible judged corpus so retrieval misses reduce the final score.

compare-ndcg.py
1def dcg(relevances): 2 return sum( 3 (2**relevance - 1) / log2(rank + 2) 4 for rank, relevance in enumerate(relevances) 5 ) 6 7def reranker_ndcg(items, limit): 8 relevances = [item["relevance"] for item in items[:limit]] 9 ideal = sorted( 10 (item["relevance"] for item in items), 11 reverse=True, 12 )[:limit] 13 best = dcg(ideal) 14 return dcg(relevances) / best if best else 0.0 15 16print("retrieval-order reranker ndcg@5:", round(reranker_ndcg(candidates, 5), 3)) 17print("ranked-order reranker ndcg@5:", round(reranker_ndcg(ranked, 5), 3))
Output
1retrieval-order reranker ndcg@5: 0.91 2ranked-order reranker ndcg@5: 1.0

The scorer lifts reranker NDCG@5 from 0.910 to 1.000. That doesn't prove the model will improve a live docs portal. It only proves this frozen judged query improved after reranking.

One query isn't an evaluation set. Average reranker NDCG over a frozen query set, report slices such as new documents and languages, and inspect failed queries. Keep candidate recall separate so a strong reranker score can't hide retrieval loss.

Turn judgments into pair preferences

Pairwise learning-to-rank methods such as RankNet train from preferences so a relevant item scores above a less relevant one.[2]Reference 2Learning to Rank using Gradient Descent.https://www.microsoft.com/en-us/research/publication/learning-to-rank-using-gradient-descent/ For this one query, create the preference pairs from offline judgments.

build-pair-preferences.py
1pairs = [ 2 (preferred["id"], other["id"]) 3 for preferred in ranked 4 for other in ranked 5 if preferred["relevance"] > other["relevance"] 6] 7 8print("pair count:", len(pairs)) 9print("first pairs:", pairs[:5])
Output
1pair count: 8 2first pairs: [('api-guide', 'retry-runbook'), ('api-guide', 'sdk-example'), ('api-guide', 'blog-post'), ('api-guide', 'stale-changelog'), ('retry-runbook', 'blog-post')]

The pair ("api-guide", "blog-post") says the API guide should score higher for this query. A learned pairwise model uses features to reduce preference mistakes across many queries. The hand-written scorer stays useful as a transparent baseline.

Diagram showing Eligible corpus freshness + policy, Candidates fast retrieval, Ranker query + document features, and Top results logged exposure. Diagram showing Eligible corpus freshness + policy, Candidates fast retrieval, Ranker query + document features, and Top results logged exposure.
Eligible corpus freshness + policy, Candidates fast retrieval, Ranker query + document features, and Top results logged exposure.

Log exposure before reading clicks

The ranked slate is now ready to display. Persist one immutable impression row per exposed document before reading clicks. Later click and resolved events join back through request and document IDs. Without the slate, position, eligibility-policy version, candidate-set version, and ranker version, you can't reproduce what the developer saw.

write-impression-log.py
1slate = ranked[:3] 2impressions = [ 3 { 4 "request_id": "req-1042", 5 "query": query, 6 "eligibility_policy": "docs-access-policy-v4", 7 "candidate_set": "retrieval-v3", 8 "ranker": "docs-ranker-v7", 9 "document_id": item["id"], 10 "position": position, 11 } 12 for position, item in enumerate(slate, start=1) 13] 14outcomes = [ 15 { 16 "event_id": "evt-click-1042", 17 "occurred_at": "2026-05-01T12:00:03Z", 18 "request_id": "req-1042", 19 "document_id": "api-guide", 20 "event": "click", 21 }, 22 { 23 "event_id": "evt-resolved-1042", 24 "occurred_at": "2026-05-01T12:04:18Z", 25 "request_id": "req-1042", 26 "document_id": "api-guide", 27 "event": "resolved", 28 }, 29] 30 31print("slate:", [item["id"] for item in slate]) 32print("logged positions:", [ 33 (row["document_id"], row["position"]) 34 for row in impressions 35]) 36print("later outcomes:", [ 37 (row["document_id"], row["event"]) 38 for row in outcomes 39])
Output
1slate: ['api-guide', 'retry-runbook', 'sdk-example'] 2logged positions: [('api-guide', 1), ('retry-runbook', 2), ('sdk-example', 3)] 3later outcomes: [('api-guide', 'click'), ('api-guide', 'resolved')]

The first row doesn't mean api-guide is universally best. It means this request exposed it at position 1, and one developer clicked it.

Log enough context to interpret later outcomes:

Logged fieldReason
request and query IDgroup displayed slate and join later outcomes
eligibility-policy versionknow which documents were allowed
candidate set versionknow what could have ranked
final positionsmeasure exposure
ranker versionattribute outcomes
timestamped click, save, resolved, bad-result eventsdistinguish curiosity from value
freshness and permission snapshotreproduce context

Clicks are biased labels

When a model places a document first, it receives more attention and therefore more clicks. Training directly on clicks rewards past ranking position as if it were document relevance. This is a feedback loop.

Take two documents with equal underlying appeal. Historical ranker always places same-quality-a first and same-quality-b second. Slot one is examined more often.

reproduce-position-bias.py
1historical = [ 2 {"document": "same-quality-a", "position": 1, "impressions": 100, "clicks": 40}, 3 {"document": "same-quality-b", "position": 2, "impressions": 100, "clicks": 20}, 4] 5 6for row in historical: 7 raw_ctr = row["clicks"] / row["impressions"] 8 print( 9 f"{row['document']}: position={row['position']} " 10 f"raw_ctr={raw_ctr:.2f}" 11 )
Output
1same-quality-a: position=1 raw_ctr=0.40 2same-quality-b: position=2 raw_ctr=0.20

Naive training labels say document a is twice as attractive. The log doesn't justify that conclusion because document and position never vary independently.

A simple counterfactual correction weights each click by the inverse of the probability that its position was examined. The propensities below are deliberately fixed for the miniature example: slot one is examined with probability 1.0, slot two with probability 0.5.

correct-toy-position-bias.py
1examination_propensity = {1: 1.0, 2: 0.5} 2 3for row in historical: 4 corrected_signal = ( 5 row["clicks"] 6 / examination_propensity[row["position"]] 7 / row["impressions"] 8 ) 9 print(f"{row['document']}: corrected_signal={corrected_signal:.2f}")
Output
1same-quality-a: corrected_signal=0.40 2same-quality-b: corrected_signal=0.40

Both corrected signals become 0.40. The arithmetic exposes the idea, not a production estimator. Real propensities need careful estimation, often through randomized interventions or a validated click model. Very small propensities also create large, noisy weights, so a production estimator needs variance controls and evaluation. Joachims, Swaminathan, and Schnabel describe propensity-weighted learning-to-rank as a counterfactual correction for biased implicit feedback.

Protect users and the dataset

Ranking can narrow what developers see. Add constraints for stale documents, private drafts, source policy, and overconcentration. Monitor slices such as new documents, small doc sets, languages, and stale categories rather than accepting one portal-wide NDCG.

Policy must stay outside the learned score. Reproduce a bad integration that ranks the raw corpus without applying eligibility first.

reproduce-policy-bypass.py
1unsafe_order = sorted( 2 corpus, 3 key=lambda item: (-rank_score(item), item["id"]), 4) 5 6print("unsafe first result:", unsafe_order[0]["id"]) 7print("permitted:", unsafe_order[0]["permitted"])
Output
1unsafe first result: private-draft 2permitted: False

The private draft ranks first because its query match is strong. Better model training won't fix this bug. Eligibility filtering must run before retrieval and remain covered by release checks.

Gate an offline candidate

A useful release packet includes offline query judgments, candidate recall, reranker NDCG, p95 scoring latency, diversity checks, impression and outcome schemas, and an A/B stopping rule. A list that looks good offline isn't allowed to silently write its own future training labels.

Use concrete gates for this candidate:

  • candidate recall at 5 must be 1.0
  • reranker NDCG@5 must be at least 0.95
  • p95 scorer latency must be at most 20 milliseconds
  • blocked documents must stay absent
  • impression rows must include fields needed for replay
  • outcome events must join a logged impression
check-offline-ranking-gates.py
1latencies_ms = [11, 13, 12, 15, 14, 16, 13, 12, 17, 18] 2 3def percentile_nearest_rank(values, percentile): 4 rank = max(1, int(len(values) * percentile + 0.999999)) 5 return sorted(values)[rank - 1] 6 7required_impression_fields = { 8 "request_id", 9 "query", 10 "eligibility_policy", 11 "candidate_set", 12 "ranker", 13 "document_id", 14 "position", 15} 16required_outcome_fields = { 17 "event_id", 18 "occurred_at", 19 "request_id", 20 "document_id", 21 "event", 22} 23exposed_keys = { 24 (row["request_id"], row["document_id"]) 25 for row in impressions 26} 27release_checks = { 28 "candidate_recall_at_5": candidate_recall(candidates) >= 1.0, 29 "reranker_ndcg_at_5": reranker_ndcg(ranked, 5) >= 0.95, 30 "p95_latency_ms": percentile_nearest_rank(latencies_ms, 0.95) <= 20, 31 "blocked_items_absent": not ( 32 {"private-draft", "stale-guide"} 33 & {item["id"] for item in ranked} 34 ), 35 "impression_schema": all( 36 required_impression_fields <= row.keys() 37 for row in impressions 38 ), 39 "outcome_schema": all( 40 required_outcome_fields <= row.keys() 41 for row in outcomes 42 ), 43 "outcomes_join_impressions": all( 44 (row["request_id"], row["document_id"]) in exposed_keys 45 for row in outcomes 46 ), 47} 48 49for name, passed in release_checks.items(): 50 print(f"{name}: {passed}") 51print("release gate:", all(release_checks.values()))
Output
1candidate_recall_at_5: True 2reranker_ndcg_at_5: True 3p95_latency_ms: True 4blocked_items_absent: True 5impression_schema: True 6outcome_schema: True 7outcomes_join_impressions: True 8release gate: True

Passing offline checks earns a controlled online experiment, not an immediate global rollout. Engagement changes can reflect latency, layout, freshness, source trust, and ranking behavior. Randomized A/B assignment isolates the candidate change more reliably than comparing two time windows.[3]Reference 3Trustworthy Online Controlled Experiments: A Practical Guide to A/B Testing.https://experimentguide.com/ Predeclare minimum sample size, minimum duration, and safety stops before exposure starts so the team doesn't stop when a noisy result happens to look favorable. The miniature receipt below uses illustrative commitments; real values come from traffic, power analysis, and risk policy.

Publish a receipt before exposure begins.

publish-ranking-candidate-receipt.py
1receipt = { 2 "artifact": "docs-ranker-v7", 3 "eligibility_policy": "docs-access-policy-v4", 4 "candidate_set": "retrieval-v3", 5 "offline": { 6 "candidate_recall_at_5": round(candidate_recall(candidates), 3), 7 "reranker_ndcg_at_5": round(reranker_ndcg(ranked, 5), 3), 8 "p95_latency_ms": percentile_nearest_rank(latencies_ms, 0.95), 9 }, 10 "release_checks": release_checks, 11 "required_checks_pass": all(release_checks.values()), 12 "experiment": { 13 "assignment_unit": "developer_id", 14 "control": "docs-ranker-v6", 15 "treatment": "docs-ranker-v7", 16 "primary_metric": "successful_resolutions_per_session", 17 "minimum_duration_days": 14, 18 "minimum_requests_per_arm": 5000, 19 "guardrails": [ 20 "bad_result_reports_per_session", 21 "p95_latency_ms", 22 "blocked_document_impressions", 23 ], 24 "safety_stops": { 25 "blocked_document_impressions": 0, 26 "p95_latency_ms": 20, 27 }, 28 }, 29 "status": "candidate_for_ab_test" if all(release_checks.values()) else "blocked", 30} 31payload = dumps(receipt, sort_keys=True) 32print(dumps(receipt, indent=2, sort_keys=True)) 33print("receipt sha256:", sha256(payload.encode()).hexdigest()[:12])
Output
1{ 2 "artifact": "docs-ranker-v7", 3 "candidate_set": "retrieval-v3", 4 "eligibility_policy": "docs-access-policy-v4", 5 "experiment": { 6 "assignment_unit": "developer_id", 7 "control": "docs-ranker-v6", 8 "guardrails": [ 9 "bad_result_reports_per_session", 10 "p95_latency_ms", 11 "blocked_document_impressions" 12 ], 13 "minimum_duration_days": 14, 14 "minimum_requests_per_arm": 5000, 15 "primary_metric": "successful_resolutions_per_session", 16 "safety_stops": { 17 "blocked_document_impressions": 0, 18 "p95_latency_ms": 20 19 }, 20 "treatment": "docs-ranker-v7" 21 }, 22 "offline": { 23 "candidate_recall_at_5": 1.0, 24 "p95_latency_ms": 18, 25 "reranker_ndcg_at_5": 1.0 26 }, 27 "release_checks": { 28 "blocked_items_absent": true, 29 "candidate_recall_at_5": true, 30 "impression_schema": true, 31 "outcome_schema": true, 32 "outcomes_join_impressions": true, 33 "p95_latency_ms": true, 34 "reranker_ndcg_at_5": true 35 }, 36 "required_checks_pass": true, 37 "status": "candidate_for_ab_test" 38} 39receipt sha256: d0fe7347e924

The receipt binds eligibility policy, candidate generator, ranker, offline evidence, release checks, assignment unit, primary metric, minimum exposure, and guardrails. Your later analysis must keep these versions pinned.

Explain the slate without looking back

Practice

  1. Reduce candidate budget from 5 to 4. Predict which relevant document disappears, then rerun candidate recall and reranker NDCG. Explain why both metrics matter.
  2. Change blog-post relevance from 0 to 1. Recompute reranker NDCG and explain how graded judgments change evaluation.
  3. Add a timestamped save outcome event. Decide whether saves should replace resolved sessions as the primary online metric or remain a diagnostic signal.
  4. Change slot-two propensity from 0.5 to 0.25. Recompute corrected signal and explain why an incorrect propensity model creates a different bias.
  5. Add a diversity gate that allows at most two documents from one source in top three. Extend fixture with source_id, then reproduce one failure.

What strong answers show

EvidenceWhat a strong answer shows
candidate contractseparates eligibility and retrieval from learned reranking
offline evaluationmeasures candidate recall, graded reranker NDCG, slices, and scorer latency
feedback safetypersists immutable impressions before joining later outcome events
debiasing judgmenttreats toy propensity weighting as a concept demonstration, not an automatic production fix
experiment planpins versions, assignment unit, primary metric, minimum exposure, guardrails, and safety stops

When ranking breaks

SymptomCauseFix
NDCG looks strong but desired document never appearscandidates lost recallmeasure retrieval recall separately
Prohibited document appears firstpolicy filter ran after scoring or not at allfilter eligibility before retrieval and gate blocked impressions
Popular documents dominate foreverclick-position looplog exposure and use controlled evaluation
Corrected click score still looks suspiciouspropensity estimate is wrongvalidate click model or collect randomized intervention data
Corrected click score becomes unstabletiny propensities create large inverse weightsadd variance controls and evaluate estimator behavior
Relevance improves while bad-result reports risewrong online objectivepair engagement with success and bad-result guardrails
Complete the lesson

Mastery Check

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

1.A candidate generator for the query returns, in order, ['api-guide', 'blog-post', 'stale-changelog']. The judged relevant eligible documents are {'api-guide', 'retry-runbook', 'sdk-example'}. What does this imply before any reranker runs?
2.An engineer proposes using each document's offline human relevance grade as an online ranker feature. What is the correct design?
3.For a fixed candidate set, the retrieval order has relevance grades [3, 0, 0, 2, 2], while the reranked order has [3, 2, 2, 0, 0]. Why is the reranked slate's NDCG@5 equal to 1.0?
4.A developer accidentally sorts the raw corpus with rank_score before eligibility filtering. The top result is 'private-draft' with permitted=False because it has strong query_fit and reliability. What change fixes the required policy invariant?
5.A ranked top-three slate contains three permitted, current documents from one source. A hard rule allows at most two documents from one source in the top three. What should the system do?
6.Which logging design preserves the evidence needed to replay a slate, join outcomes, and analyze position bias?
7.For one query, api-guide has relevance 3, retry-runbook and sdk-example have relevance 2, and blog-post has relevance 0. Which pairwise labels follow from strict higher-relevance preferences?
8.Document A at slot 1 has 40 clicks from 100 impressions with propensity 1.0. Document B at slot 2 has 20 clicks from 100 impressions with assumed propensity 0.25. Using clicks / propensity / impressions, what follows?
9.A reranker achieves NDCG@5 of 1.0 on one frozen query. What evidence is still needed before treating its offline ranking quality as established?
10.A candidate passes these offline gates: candidate_recall_at_5=1.0, reranker_ndcg_at_5=1.0, nearest-rank p95 latency=18 ms with a 20 ms limit, blocked documents absent, valid impression and outcome schemas, and outcomes join impressions. What status is justified?

10 questions remaining.

Next Step
Continue to Forecasting and Anomaly Detection

Ranking orders choices at one moment. Next you'll predict values over future time periods and distinguish recurring demand patterns from operational surprises.

PreviousGradient Boosted Trees in Production
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Introduction to Information Retrieval.

Manning, C. D., Raghavan, P., Schutze, H. · 2008 · Cambridge University Press

Learning to Rank using Gradient Descent.

Burges, C. J. C., et al. · 2005 · ICML 2005

Trustworthy Online Controlled Experiments: A Practical Guide to A/B Testing.

Kohavi, R., Tang, D., Xu, Y. · 2020

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.