Lab Guide — Advanced RAG
Three exercises upgrading your RAG pipeline with hybrid search, reranking, and evaluation.
Prerequisites: This lab builds on the Lesson 08 pipeline. Make sure you have
sentence-transformers, chromadb, and rank_bm25 installed. The notebook reuses the 10 sample documents from Lesson 08.
Exercise 1 — Hybrid Search with BM25
EX 1
Add BM25 and implement reciprocal rank fusion
Extend your Lesson 08 pipeline with a BM25 retriever and merge results using RRF.
- Create a BM25 index from the same 10 documents using
rank_bm25.BM25Okapi - Implement
bm25_search(query, k=5)that returns document indices sorted by BM25 score - Implement
dense_search(query, collection, k=5)that returns document indices from ChromaDB - Implement
reciprocal_rank_fusion(ranked_lists, k=60)that merges multiple ranked lists - Implement
hybrid_search(query, k=5)that combines BM25 and dense via RRF
Test your implementation
# This query should benefit from BM25 (exact keyword match)
results = hybrid_search("lru_cache decorator", k=3)
print(results) # functools doc should rank high
# This query should benefit from dense search (semantic match)
results = hybrid_search("how to measure code performance", k=3)
print(results)
Checkpoint: Hybrid search returns relevant results for both keyword-heavy and semantic queries.
Exercise 2 — Cross-Encoder Reranking
EX 2
Add cross-encoder reranking to the hybrid pipeline
- Load a cross-encoder model:
CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2") - Implement
rerank(query, doc_indices, top_k=3)that scores each (query, doc) pair and returns the top_k by score - Build
advanced_search(query, retrieve_k=10, final_k=3)that does hybrid retrieval then reranking
Test your implementation
# Compare naive vs hybrid vs hybrid+rerank
query = "How do I create a custom dictionary with default values?"
print("Naive:", naive_search(query, k=3))
print("Hybrid:", hybrid_search(query, k=3))
print("Advanced:", advanced_search(query, retrieve_k=10, final_k=3))
Checkpoint: Reranking consistently promotes the most relevant document to position 1.
Exercise 3 — LLM-as-Judge Evaluation
EX 3
Evaluate naive vs advanced on 5 questions using faithfulness scoring
- Define 5 test questions with expected ground truth answers
- For each question, generate answers using both the naive pipeline (Lesson 08) and the advanced pipeline
- Implement
judge_faithfulness(question, context, answer)that asks Claude to rate faithfulness 1–5 - Build a comparison table: Question | Naive Score | Advanced Score
Judge prompt template
judge_prompt = f"""Rate the faithfulness of this answer on a scale of 1-5.
1 = completely unfaithful (hallucinated or contradicts context)
5 = completely faithful (every claim is supported by context)
Context: {context}
Question: {question}
Answer: {answer}
Respond with only a single integer (1-5) and a one-sentence justification."""
Checkpoint: You have a comparison table showing faithfulness scores for both pipelines across 5 questions.
Tip: If the cross-encoder is slow, batch your predictions. Also consider using
claude-haiku-4-5 for the judge calls to reduce cost.