RAG
Rerank / rewrite
Add a reranker or query-rewrite step
- RAG building blocks (browse)
- Embeddings and similarity (browse)
- Vector databases — what, why, and how (browse)
- Chunking and metadata (browse)
- Hybrid search and rerankers (browse)
- Production RAG: chunking, hybrid search, rerank, and eval gates (example)
- Marketplace ranking meets LLMs: Uber/Airbnb-style re-rank patterns (example)
Learning objectives
- Add a reranker or query-rewrite step
- Measure lift on a fixed eval set
- Watch latency/cost impact
Second-stage ranking buys quality cheaply
First-stage retrieval (vector + BM25 hybrid) optimizes for recall under latency budgets — return 20–50 candidates fast. Many are loosely related. A reranker cross-encoder scores each (query, chunk) pair jointly and reorders the shortlist. Often this lifts answer quality more than swapping to a bigger embedding model, because cross-encoders see full interaction between query and passage.
Alternative lever: query rewriting — an LLM expands acronyms, disambiguates intent, or generates hypothetical answer text (HyDE) before retrieval. Rewrite and rerank can stack; measure each separately.
Callout — profile the P95: Reranking 50 pairs adds 100–400 ms depending on model. Ship only if offline eval gain justifies online latency.
Reranker architecture
query → first-stage top-20 chunks
↓
cross-encoder scores each pair
↓
top-5 → LLM contextModels:
- API: Cohere Rerank, Voyage rerank, Jina rerank endpoints.
- Local:
cross-encoder/ms-marco-MiniLM-L-6-v2via sentence-transformers — fine for course projects.
from sentence_transformers import CrossEncoder
model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
pairs = [(query, c.text) for c in candidates]
scores = model.predict(pairs)
ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])Rerank after hybrid fusion so you pay cross-encoder cost on a bounded set.
Query rewriting patterns
| Pattern | When to use | Risk |
|---|---|---|
| HyDE (hypothetical doc) | Vague conceptual questions | Hallucinated doc pulls wrong chunks |
| Multi-query | Recall gaps on paraphrase | 3× embed cost |
| Acronym expansion | Internal jargon corpora | Wrong expansion without glossary |
| Step-back | Complex multi-hop | Extra LLM call latency |
Example rewrite prompt:
Given a user question, produce two search queries that would retrieve
relevant documentation. Output JSON: {"queries": ["...", "..."]}Retrieve for each query; union + dedupe candidates before rerank.
Always log original and rewritten queries for debugging.
Measuring lift
On frozen eval set from prior lessons, report:
- Recall@k after first stage
- Recall@k after rerank (gold chunk in top-5 post-rerank)
- MRR (mean reciprocal rank) if single gold chunk
- p50/p95 latency end-to-end
- Cost per query (embed + rerank API + optional rewrite LLM)
Table template:
| Pipeline | Recall@5 | p95 ms | $/1k queries |
|---|---|---|---|
| Hybrid only | 0.72 | 45 | 0.02 |
| + Rerank | 0.81 | 180 | 0.05 |
| + Rewrite + Rerank | 0.84 | 320 | 0.08 |
If +Rewrite barely moves recall, drop it for production simplicity.
Callout — same eval, same chunks: Reranker experiments must use identical first-stage candidate pools logged to disk — otherwise you confound retrieval randomness.
Failure modes
- Overfitting reranker to dev set — keep held-out queries.
- Near-duplicate chunks — reranker tops five copies of same paragraph; dedupe by
source_idfirst. - Language mismatch — English reranker on multilingual corpus without language filter.
Cost-aware pipeline design
Stack components multiply cost:
rewrite (LLM) → embed × N → hybrid retrieve → rerank 50 pairs → generate (LLM)Profile each stage on 100 queries. Often rerank on 20 candidates captures 90% of lift vs reranking 100 — diminishing returns matter at scale.
Cache embeddings of frequent queries (head terms in support portals). Cache rerank results keyed by (query_hash, candidate_ids_hash) with short TTL when corpus is mostly static.
For rewrite, use a small model or distilled prompt — HyDE with frontier model on every query rarely pays on identifier-heavy workloads.
Debugging rerank regressions
When rerank hurts recall:
- Candidates never contained gold chunk — rerank cannot help; fix first stage.
- Gold chunk ranked low by cross-encoder — training domain mismatch; try multilingual reranker or fine-tune reranker on domain pairs (advanced).
- Duplicate chunks crowd out diversity — dedupe before rerank.
Log pre- and post-rerank rankings side by side in eval output for manual inspection on miss cases.
Query rewrite eval isolation
When testing HyDE or multi-query rewrite, ablate:
- Baseline hybrid only
- Hybrid + rewrite (no rerank)
- Hybrid + rerank (no rewrite)
- Full stack
Otherwise you cannot attribute lift. Rewrite hurts identifier queries if hypothetical doc drifts off-domain — disable rewrite when query matches identifier regex.
Latency budget worksheet
| Stage | p95 ms | Cumulative |
|---|---|---|
| Rewrite | 180 | 180 |
| Embed query | 40 | 220 |
| Hybrid retrieve | 35 | 255 |
| Rerank 20 | 120 | 375 |
| Generate | 800 | 1175 |
If product SLA is 800 ms to first token, full stack above fails — drop rewrite or rerank fewer candidates.
When to skip rerank
- Tiny corpus (<200 chunks) where top-5 is exhaustive scan anyway.
- Strict sub-100ms SLA with acceptable recall already.
- Edge deployment without GPU and tight CPU budget.
Document skip decision with numbers, not preference.
Engineering problem (staff framing)
First-stage recall + second-stage precision. Query rewrite expands recall carefully.
Diagram — Rewrite → retrieve → rerank
flowchart LR
Q --> RW[Rewrite/expand] --> Ret[Retrieve] --> RR[Cross-encoder rerank] --> Ctx
Precise definitions & mental model
Cross-encoders, HyDE/multi-query, risk of drift in rewrite.
Tradeoffs — when to use what
Latency/cost of rerank vs quality; rewrite helps or hallucinates queries.
Failure modes (interview + on-call)
Rewrite changes intent; rerank model domain mismatch.
Production & OSS practices
Cap candidates; timeout budgets; eval rewrite separately.
Micro-project: Measure lift
In m5/rerank/:
- Log first-stage candidates for eval queries to
candidates/. - Add reranker step; optionally one rewrite variant.
- Produce
lift_report.jsonwith recall and latency vs baseline. - README recommendation: ship rerank yes/no with evidence.
Checklist
- Baseline and rerank pipelines on identical candidates
- Latency measured (not estimated)
- Clear ship/no-ship recommendation
- Rewrite logged if used
ShipAI delivery model is: