Module 06: Vector Search & Retrieval Architecture Lesson 18 / 41
25–30 min read Level: Advanced
Part 2 — Knowledge Retrieval & Production RAG

Lesson 16.18: Reranking, Query Transformation & Context Assembly

Precision Candidate Reranking, Query Transformation & Production Context Assembly: How to transform raw retrieval hits through cross-encoder attention, deduplication, and strict token-budget packing into clean, faithful context for generative models.

What You Will Learn

  • 1. Understand why Reranking is critical and how Two-Stage Retrieval operates.
  • 2. Analyze architectural differences between Bi-Encoders and Cross-Encoders.
  • 3. Re-order candidate chunks using the Cohere Rerank API and local Cross-Encoders.
  • 4. Implement Query Rewriting, Multi-Query Expansion, and Sub-Query Decomposition.
  • 5. Deploy Hypothetical Document Embeddings (HyDE) while recognizing hallucination risks.
  • 6. Maintain strict multi-tenant authorization before transmitting candidates to 3rd-party APIs.
  • 7. Deduplicate identical and redundant chunks via identity hashing and content fingerprints.
  • 8. Build greedy context packers constrained by strict Tiktoken token budgets.
  • 9. Preserve exact source citations, page numbers, and document metadata provenance.
  • 10. Benchmark retrieval pipelines with Recall, MRR, Context Precision, and Latency metrics.

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:

"How is annual leave calculated for new employees joining after July 1st?"

Your hybrid vector engine returns five candidate chunks:

Chunk A (General Policy): "Employees receive 20 days of annual leave per calendar year."
Chunk B (Direct Answer): "Employees who join after July 1 may receive a prorated annual leave entitlement based on months served."
Chunk C (Workflow): "Annual Leave requests must be submitted 14 days in advance and approved by the department manager."
Chunk D (Insurance Policy): "The company provides comprehensive medical and dental insurance to all confirmed employees."
Chunk E (Financial Audit): "Annual financial reporting and audit reconciliations are completed at the end of each fiscal year."

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.

1. Query Transformation

Restructuring messy, ambiguous user prompts into clean, specialized search queries before hitting the index.

2. Reranking (Cross-Encoder)

Re-evaluating retrieved candidates through deep cross-attention transformer models to promote true answer chunks.

3. Context Assembly

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 ]
        
Core Engineering Constraint: If Stage 1 fails to include the target chunk in the candidate pool, Stage 2 can never resurrect it (Garbage In, Garbage Out). Stage 1 must maximize Recall@K so Stage 2 can optimize Precision@K.

03 Bi-Encoder vs. Cross-Encoder Architectures

Understanding the neural architecture separating Bi-Encoders from Cross-Encoders explains why rerankers deliver superior relevance:

Bi-Encoder (Vector Embeddings)
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.

Cross-Encoder (Full Cross-Attention)
[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:

cohere_rerank_service.py
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
Security Imperative: Never transmit unauthorized chunks to an external reranking provider! Always execute server-side tenant isolation and RBAC checks during Stage 1 candidate retrieval, so only pre-authorized candidates reach external APIs.

05 Local Open-Source Cross-Encoders

When regulatory compliance or zero-egress policies prohibit 3rd-party APIs, execute local cross-encoders via sentence-transformers:

local_cross_encoder.py
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.

HyDE Risk Warning: The hypothetical document is purely a search vector coordinate. It must never be shown to users or cited as factual evidence.

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:

12,000 Token Context Window Budget Blueprint
1. System Prompt & Security Delimiters 1,000 Tokens
2. Conversation History (Multi-turn Memory) 2,000 Tokens
3. Retrieved Knowledge Context (Dynamic Budget) 6,000 Tokens
4. User Prompt & Formatting Directives 1,000 Tokens
5. Reserved for Generation Output 2,000 Tokens
context_assembler.py (Deduplication + Token Packing)
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

User Inquiry:

"How is annual leave calculated for new employees joining after July 1st?"

Stage 1: Vector Search (Bi-Encoder) Fast Recall
Rank #1Cosine: 0.88

Chunk A: Employees receive 20 days of annual leave per calendar year.

Rank #2Cosine: 0.84

Chunk C: Annual leave requests must be approved by the department manager.

Rank #3 (Buried!)Cosine: 0.79

Chunk B: Employees joining after July 1 receive prorated leave.

Stage 2: Cross-Encoder Reranked Deep Attention
Rank #1 (Promoted!)Score: 0.96

Chunk B: Employees joining after July 1 receive prorated leave.

Rank #2Score: 0.82

Chunk A: Employees receive 20 days of annual leave per calendar year.

Rank #3Score: 0.54

Chunk C: Annual leave requests must be approved by department manager.

Observation: Bi-Encoder favored Chunk A due to generic token overlap ("annual leave"). However, full cross-attention evaluated the conjunction of "new employee" and "after July 1", promoting Chunk B to the top position.

09 End-to-End Production Retrieval Service

We now unify all stages into an enterprise-ready retrieve_production_context() service:

enterprise_retrieval_service.py
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:

test_rerank_and_context.py (Pytest Suite)
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:

Question 1

What is the primary architectural purpose of Reranking in RAG?

A. Backing up relational database partitions.
B. Re-ordering candidate chunks using deeper cross-attention models to maximize top-tier relevance. ✓ Correct
C. Re-training embedding neural weights.
D. Deleting browser session cookies.

Explanation: Rerankers serve as the second stage in two-stage retrieval to elevate the most relevant candidate.

Question 2

What is the primary difference between Bi-Encoders and Cross-Encoders?

A. Bi-Encoders encode query and document in isolation; Cross-Encoders concatenate both into full cross-attention. ✓ Correct
B. Both architectures execute identical algorithms.
C. Cross-Encoders pre-compute vectors for the entire database.
D. Bi-Encoders require GPUs while Cross-Encoders do not.

Explanation: Cross-encoders enable all-to-all token attention, delivering superior semantic precision.

Question 3

Why is Query Transformation deployed before vector search?

A. To drop database schemas automatically.
B. To convert ambiguous, conversational, or compound questions into clear, search-friendly queries. ✓ Correct
C. To re-render UI button colors.
D. To reboot web servers.

Explanation: Query transformation improves candidate recall by resolving conversational ambiguity and sub-questions.

Question 4

What risk is introduced by deploying Multi-Query Retrieval indiscriminately?

A. Increased query fan-out latency, search API costs, and candidate noise. ✓ Correct
B. Immediate vector database corruption.
C. Permanent user account lockout.
D. Context token size drops to zero.

Explanation: Uncontrolled query expansion multiplies downstream requests and increases candidate noise.

Question 5

Which statement is true regarding HyDE (Hypothetical Document Embeddings)?

A. The generated hypothetical text is guaranteed factual corporate truth.
B. It is strictly a retrieval search tool; final generation must rely exclusively on retrieved source chunks. ✓ Correct
C. It must be presented directly to end-users as real citations.
D. It purges all original files from the database.

Explanation: HyDE generates a hallucinated search vector coordinate, not factual evidence.

Question 6

Why is Token Budgeting essential during Context Assembly?

A. To prevent context overflow, minimize input latency/costs, and protect LLM attention from distraction. ✓ Correct
B. Because Python scripts refuse to run without token caps.
C. To enlarge database storage footprint.
D. To accelerate browser CSS parsing.

Explanation: Disciplined budgeting prevents lost-in-the-middle degradation and truncation failures.

Question 7

Does a high Reranker score (e.g., 0.96) verify that a document's claims are factually true?

A. Yes, high scores prove the facts are 100% accurate.
B. No, it measures relative semantic relevance to the query; factuality and authorization require independent validation. ✓ Correct
C. It certifies the user possesses administrator rights.
D. It proves the document was authored today.

Explanation: Relevance scoring measures semantic fit, not corporate policy validity or user clearance.

Question 8

Why do production systems feed only Top-20 candidates to Cross-Encoders instead of the entire 1M-document database?

A. Cross-encoders require real-time all-to-all attention; running them across 1M chunks would take minutes per query. ✓ Correct
B. Cross-encoders cannot process more than 20 strings.
C. Python limits loop iterations to 20 items.
D. It damages embedding weights.

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.
Next Lesson

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.

Proceed to Next Lesson