01 The Core Dilemma: Is Vector Retrieval Alone Enough?
In previous lessons, we mastered document ingestion, chunking, vector embeddings, BM25, and hybrid search. But once your vector engine returns the Top 5 or 10 chunks, can you safely dump them verbatim into the LLM's prompt window?
Consider a staff member querying your Byteion AI Knowledge Assistant:
Your hybrid vector engine returns five candidate chunks:
In real-world vector searches, high lexical frequency of "annual leave" often pushes Chunk A (standard quota) to rank #1, while the exact answer in Chunk B is buried at rank #3 or #4. Chunks D and E clutter the context window as distracting noise.
Restructuring messy, ambiguous user prompts into clean, specialized search queries before hitting the index.
Re-evaluating retrieved candidates through deep cross-attention transformer models to promote true answer chunks.
Deduplicating chunks, enforcing strict token budget ceilings, and compiling clean XML citation blocks for generation.
02 What is Reranking and Why is It Necessary?
Reranking is the second phase of a Two-Stage Retrieval architecture. Stage 1 executes fast candidate discovery across millions of vectors; Stage 2 applies deep attention scoring across a small candidate subset:
[ Total Knowledge Base: 1,000,000 Chunks ]
│
▼ Stage 1: Fast Candidate Retrieval (Bi-Encoder / BM25)
[ Top-20 Candidate Pool ] ──> Fast, low latency (< 20ms)
│
▼ Stage 2: Deep Cross-Attention Reranking
[ Top-5 High-Precision Chunks ] ──> Deep transformer relevance scoring
│
▼
[ LLM Context Window ]
03 Bi-Encoder vs. Cross-Encoder Architectures
Understanding the neural architecture separating Bi-Encoders from Cross-Encoders explains why rerankers deliver superior relevance:
Query ───> [ Encoder ] ───> Vec(Q) ┐
├──> Dot Product / Cosine
Doc ───> [ Encoder ] ───> Vec(D) ┘
Processes query and document independently. Query words cannot directly interact with document tokens during encoding. While fast and pre-computable, fine-grained cross-token nuances are lost.
[CLS] Query [SEP] Document [SEP]
│
[ Transformer Layers ]
(Full Cross-Attention All-to-All)
│
▼
Relevance Score: 0.94
Concatenates query and document into a single sequence. Every query token attends directly to every document token across all transformer layers. Yields exceptional accuracy but requires on-the-fly pair computation.
| Dimension | Bi-Encoder (Vector Search) | Cross-Encoder (Reranker) |
|---|---|---|
| Processing | Query and Document encoded in isolation | Jointly processed with full cross-attention |
| Pre-computable | Yes; embeddings stored in vector DB ahead of time | No; must compute query-document pairs in real time |
| Search Scope | Millions of vectors in milliseconds | Small candidate sets (10–100 chunks) |
| Ranking Quality | Moderate (generic coordinate proximity) | Exceptional (nuanced semantic alignment) |
| Latency Overhead | < 10–20 milliseconds | 50–250 milliseconds depending on candidate count |
04 Production Reranking with the Cohere Rerank API
In cloud environments, Cohere Rerank API (such as rerank-v4.0-pro or rerank-v4.0-fast) provides a managed, state-of-the-art multilingual reranker:
import os
import cohere
from dotenv import load_dotenv
load_dotenv()
co = cohere.ClientV2(api_key=os.environ["COHERE_API_KEY"])
def rerank_candidates(
query: str,
candidates: list[dict],
top_n: int = 5
) -> list[dict]:
if not candidates:
return []
# Structure text representation with metadata headers
documents = [
f"Source: {c.get('source', '')}\nSection: {c.get('section', '')}\nContent: {c['text']}"
for c in candidates
]
response = co.rerank(
model="rerank-v4.0-pro",
query=query,
documents=documents,
top_n=min(top_n, len(candidates)),
)
reranked = []
for hit in response.results:
original_chunk = candidates[hit.index]
reranked.append({
**original_chunk,
"rerank_score": hit.relevance_score, # Model relevance confidence
})
return reranked
05 Local Open-Source Cross-Encoders
When regulatory compliance or zero-egress policies prohibit 3rd-party APIs, execute local cross-encoders via sentence-transformers:
from sentence_transformers import CrossEncoder
# Load model (e.g., BAAI/bge-reranker-large or ms-marco)
local_reranker = CrossEncoder("BAAI/bge-reranker-large", max_length=512)
def local_rerank(query: str, candidates: list[dict], top_n: int = 5) -> list[dict]:
if not candidates:
return []
# Construct (Query, Document) pairs
pairs = [[query, c["text"]] for c in candidates]
# Predict relevance scores
scores = local_reranker.predict(pairs)
for i, c in enumerate(candidates):
c["rerank_score"] = float(scores[i])
# Sort descending by relevance score
ranked = sorted(candidates, key=lambda x: x["rerank_score"], reverse=True)
return ranked[:top_n]
06 Query Transformation: Rewriting, Multi-Query, Decomposition & HyDE
Natural language prompts are frequently ambiguous ("What was that price again?") or compound. Query Transformation re-engineers raw questions into high-fidelity search inputs:
1. Query Rewriting (Context-Aware Disambiguation)
Injects antecedent conversation state into ambiguous follow-up questions.
• Raw Prompt: "What is the IP of the server we bought last week?"
• Rewritten Query: "Production Database Server Frankfurt IP address configuration 2026"
2. Multi-Query Expansion (Synonymous Fan-out)
Generates 3 to 4 distinct paraphrases to broaden candidate capture, reducing reliance on a single specific keyword.
3. Query Decomposition (Sub-question Splitting)
Splits composite inquiries into discrete atomic questions (e.g., standard leave policy + mid-year joiner exceptions) and gathers isolated evidence for each part.
4. HyDE (Hypothetical Document Embeddings)
Prompts an LLM to hallucinate a hypothetical answer paragraph, and uses its embedding vector to navigate toward real corpus documents.
07 Context Assembly, Deduplication & Token Budgeting
Passing all reranked chunks directly into the prompt exhausts token limits and triggers "lost-in-the-middle" attention degradation. A disciplined Context Assembly Pipeline is essential:
import tiktoken
tokenizer = tiktoken.get_encoding("cl100k_base")
def deduplicate_chunks(candidates: list[dict]) -> list[dict]:
"""Filters duplicate candidate chunks based on stable chunk identifiers."""
seen = set()
unique = []
for c in candidates:
cid = c.get("chunk_id")
if cid and cid not in seen:
seen.add(cid)
unique.append(c)
return unique
def assemble_context(
reranked_chunks: list[dict],
token_budget: int = 6000
) -> tuple[str, list[dict]]:
"""Packs reranked chunks within the token budget and formats citation blocks."""
context_blocks = []
selected_chunks = []
accumulated_tokens = 0
for idx, chunk in enumerate(reranked_chunks, 1):
text = chunk.get("text", "").strip()
source = chunk.get("source", "Unknown Document")
page = chunk.get("page_number", "N/A")
# Structured citation block
block = (
f"[Source {idx}]\n"
f"Document: {source} (Page {page})\n"
f"Content: {text}"
)
block_tokens = len(tokenizer.encode(block)) + 4 # Separator overhead
if accumulated_tokens + block_tokens > token_budget:
# Token budget exceeded; stop packing
break
context_blocks.append(block)
selected_chunks.append(chunk)
accumulated_tokens += block_tokens
formatted_context = "\n\n---\n\n".join(context_blocks)
return formatted_context, selected_chunks
08 Interactive Lab: Precision Reranking & Context Studio
Experiment interactively with Cross-Encoder reranking, query transformation routing, and dynamic token packing:
Reranking & Context Assembly Studio
"How is annual leave calculated for new employees joining after July 1st?"
Chunk A: Employees receive 20 days of annual leave per calendar year.
Chunk C: Annual leave requests must be approved by the department manager.
Chunk B: Employees joining after July 1 receive prorated leave.
Chunk B: Employees joining after July 1 receive prorated leave.
Chunk A: Employees receive 20 days of annual leave per calendar year.
Chunk C: Annual leave requests must be approved by department manager.
09 End-to-End Production Retrieval Service
We now unify all stages into an enterprise-ready retrieve_production_context() service:
from typing import Dict, Any, List
def retrieve_production_context(
*,
query: str,
tenant_id: str,
user_id: str,
roles: set[str],
candidate_limit: int = 25,
final_limit: int = 5,
token_budget: int = 6000
) -> Dict[str, Any]:
# Step 1: Permission-aware hybrid candidate recall
raw_candidates = hybrid_search(
query=query,
tenant_id=tenant_id,
limit=candidate_limit
)
# Step 2: Deduplicate candidate chunks
unique_candidates = deduplicate_chunks(raw_candidates)
# Step 3: Deep Cross-Encoder reranking
reranked = rerank_candidates(
query=query,
candidates=unique_candidates,
top_n=min(len(unique_candidates), candidate_limit)
)
# Step 4: Authoritative server-side access & version validation
authorized_chunks = [
c for c in reranked
if verify_document_access(c["document_id"], user_id, roles)
][:final_limit]
# Step 5: Pack context within strict token budget
context_text, selected = assemble_context(
reranked_chunks=authorized_chunks,
token_budget=token_budget
)
return {
"context": context_text,
"citations": [
{
"chunk_id": c["chunk_id"],
"source": c["source"],
"page": c["page_number"]
}
for c in selected
]
}
10 Practical Project: Reranked RAG Pipeline & Pytest Suite
Automated integration tests validating reranking accuracy, deduplication, and token budget safety:
import pytest
def test_deduplication_removes_identical_chunks():
"""Verify duplicate chunks are purged correctly."""
raw = [
{"chunk_id": "c1", "text": "leave policy"},
{"chunk_id": "c1", "text": "leave policy"},
{"chunk_id": "c2", "text": "medical insurance"},
]
unique = deduplicate_chunks(raw)
assert len(unique) == 2
assert [x["chunk_id"] for x in unique] == ["c1", "c2"]
def test_token_budget_never_exceeded():
"""Context assembler must never exceed the allocated token ceiling."""
chunks = [
{"chunk_id": f"c_{i}", "text": "Word " * 200, "source": "doc.pdf", "page_number": 1}
for i in range(10)
]
context, selected = assemble_context(chunks, token_budget=500)
tokens_used = len(tokenizer.encode(context))
assert tokens_used <= 500
def test_reranker_prioritizes_specific_answer():
"""Cross-encoder promotes exact answering chunks over generic policy text."""
query = "How is leave calculated for employees joining after July?"
candidates = [
{"chunk_id": "c_gen", "text": "Employees receive 20 days annual leave.", "source": "p.pdf"},
{"chunk_id": "c_spec", "text": "Employees joining after July receive prorated leave.", "source": "p.pdf"},
]
reranked = rerank_candidates(query, candidates, top_n=2)
assert reranked[0]["chunk_id"] == "c_spec"
11 Knowledge Check: Test Your Understanding
Verify your mastery of reranking, query transformation, and context assembly:
What is the primary architectural purpose of Reranking in RAG?
Explanation: Rerankers serve as the second stage in two-stage retrieval to elevate the most relevant candidate.
What is the primary difference between Bi-Encoders and Cross-Encoders?
Explanation: Cross-encoders enable all-to-all token attention, delivering superior semantic precision.
Why is Query Transformation deployed before vector search?
Explanation: Query transformation improves candidate recall by resolving conversational ambiguity and sub-questions.
What risk is introduced by deploying Multi-Query Retrieval indiscriminately?
Explanation: Uncontrolled query expansion multiplies downstream requests and increases candidate noise.
Which statement is true regarding HyDE (Hypothetical Document Embeddings)?
Explanation: HyDE generates a hallucinated search vector coordinate, not factual evidence.
Why is Token Budgeting essential during Context Assembly?
Explanation: Disciplined budgeting prevents lost-in-the-middle degradation and truncation failures.
Does a high Reranker score (e.g., 0.96) verify that a document's claims are factually true?
Explanation: Relevance scoring measures semantic fit, not corporate policy validity or user clearance.
Why do production systems feed only Top-20 candidates to Cross-Encoders instead of the entire 1M-document database?
Explanation: Two-stage retrieval pairs the logarithmic search speed of bi-encoders with the deep precision of cross-encoders.
12 Summary & What's Next
Reranking and Context Assembly elevate vector search from a noisy similarity lookup into an enterprise-grade knowledge delivery pipeline:
- Two-Stage Precision: Stage 1 optimizes recall via Bi-Encoder and BM25; Stage 2 optimizes precision via Cross-Encoder cross-attention.
- Query Adaptation: Rewriting, expansion, and decomposition ensure complex customer questions match relevant corpus representations.
- Context Discipline: Deduplication and strict token budgeting pack maximum verified evidence into the LLM window without overflow.
Lesson 16.19: Corrective RAG & Retrieval Fallback Strategies
Evaluating retrieval quality, automated query retry loops, alternative fallback search strategies, and human-in-the-loop escalation.