Module 4: Evaluation and Chunking Strategies
Introduction to Module 4: Evaluation and Chunking Strategies
Module welcome
You already know what embeddings are (M1), how they work internally (M2), and how to choose the right model (M3). Now comes the critical step for implementing RAG: chunking (splitting long documents into fragments) and evaluation (measuring whether your retrieval system works well).
In this module you'll learn chunking strategies (fixed-size, semantic, recursive), how to evaluate retrieval quality (metrics like nDCG, MRR, Recall@K), how to optimize chunk size and overlap, and how to build a RAG system with intelligent chunking.
By the end, you'll be able to implement production-ready RAG with optimal chunking and robust evaluation metrics.
Module objectives
By completing this module, you'll be able to:
- ✅ Implement chunking: Fixed-size, semantic, recursive
- ✅ Optimize chunks: Optimal size, overlap, boundaries
- ✅ Evaluate retrieval: nDCG, MRR, Recall@K, Precision@K
- ✅ Build datasets: Evaluation sets for RAG
- ✅ A/B testing: Compare chunking strategies
- ✅ Context window: Handle token limits (8K)
- ✅ Metadata enrichment: Add context to chunks
- ✅ Project: A RAG system with intelligent chunking and evaluation
Module roadmap
Phase 1: Chunking fundamentals (Capsules 01-03)
Capsule 01: Introduction (this capsule)
- Objectives, roadmap, context
Capsule 02: Fixed-Size Chunking
- Character-based, token-based
- Overlap strategies
- Implementation code
Capsule 03: Semantic Chunking
- Sentence boundaries
- Paragraph-based
- Topic-based segmentation
- Trade-offs
Phase 2: Retrieval evaluation (Capsules 04-06)
Capsule 04: Retrieval Metrics
- nDCG (Normalized Discounted Cumulative Gain)
- MRR (Mean Reciprocal Rank)
- Recall@K, Precision@K, F1@K
Capsule 05: Create Evaluation Datasets
- Formats (queries + relevant docs)
- Synthetic data generation
- Human annotation
- Quality assurance
Capsule 06: A/B Testing Chunking Strategies
- Compare fixed vs semantic
- Statistical significance
- Optimization loops
Phase 3: Production patterns (Capsules 07-08)
Capsule 07: Advanced Chunking Patterns
- Recursive chunking (LangChain-style)
- Metadata enrichment
- Hierarchical chunks
- Context preservation
Capsule 08: Mini-Project - RAG System with Intelligent Chunking
- Automatic chunking
- Embedding + vector storage (in-memory)
- Retrieval with evaluation
- End-to-end metrics
Connection with the AI Engineering Path
Prerequisites (completed):
✅ Module 1: What are embeddings? ✅ Module 2: Embedding architecture ✅ Module 3: Model comparison
This module in the path:
Embeddings Deep Dive - M3 (Model comparison)
↓
Embeddings Deep Dive - M4 (Chunking + Evaluation) ← YOU ARE HERE
↓
Embeddings Deep Dive - M5 (Distance Metrics)
↓
Vector Databases Guide
↓
RAG Production Patterns
Why chunking is critical for RAG
The problem:
# Long document (10,000 tokens):
document = """
[10,000 words of technical documentation]
"""
# Problem 1: Token limit
# OpenAI embeddings: Max 8,191 tokens
# This document does NOT fit in 1 embedding
# Problem 2: Granularity
# An embedding of the whole document = too general
# The user asks about a specific feature
# The embedding doesn't capture enough detail
# Problem 3: Retrieval precision
# If you retrieve the whole document, the LLM receives 10K irrelevant tokens
# The context window is wasted
Solution: Chunking (splitting into fragments).
Chunking example:
# Original (10,000 tokens):
document = """
Chapter 1: Introduction to Python
Python is a high-level programming language...
[8,000 more words]
Chapter 2: Variables and Data Types
Variables store data values...
[2,000 more words]
"""
# After chunking (500-token chunks):
chunks = [
"Chapter 1: Introduction to Python. Python is a high-level...",
"...programming language created by Guido van Rossum...",
"Chapter 2: Variables and Data Types. Variables store...",
"...data values. Python has several data types..."
]
# Now:
# - Each chunk < 8,191 tokens ✅
# - Specific granularity ✅
# - Precise retrieval (only relevant chunks) ✅
Chunking strategies (overview)
1. Fixed-Size Chunking:
# Split every N characters/tokens
chunk_size = 500 # tokens
overlap = 50 # overlap tokens
# Pros:
# - Simple, deterministic
# - Fast
# Cons:
# - Can cut in the middle of a sentence/paragraph
# - Doesn't respect semantics
2. Semantic Chunking:
# Split at natural boundaries (sentences, paragraphs, sections)
# Pros:
# - Respects the document structure
# - More coherent chunks
# Cons:
# - Variable-size chunks (some very large/small)
# - More complex
3. Recursive Chunking:
# Split hierarchically:
# 1. Try to split by paragraphs
# 2. If a paragraph is too large → split by sentences
# 3. If a sentence is too large → split by characters
# Pros:
# - Balance between fixed and semantic
# - Guarantees a maximum size
# Cons:
# - More complex to implement
Chunking trade-offs
Chunk size:
# Small chunks (200 tokens):
✅ Very precise retrieval (only relevant info)
✅ Less noise for the LLM
❌ May lose context (fragments too small)
❌ More chunks = more embeddings = more storage
# Large chunks (1000 tokens):
✅ More context preserved
✅ Fewer chunks = less storage
❌ Less precise retrieval (more noise)
❌ Wastes the LLM's context window
Optimal range: 400-800 tokens (depends on the case).
Overlap:
# Without overlap:
Chunk 1: [tokens 0-500]
Chunk 2: [tokens 500-1000]
# Problem: If important info is at the boundary (token 499-501),
# it's split between 2 chunks → loses coherence
# With overlap (50 tokens):
Chunk 1: [tokens 0-500]
Chunk 2: [tokens 450-950] ← 50-token overlap
# Advantage: Info at the boundary appears complete in both chunks
# Optimal overlap: 10-20% of the chunk size
Retrieval evaluation
Why evaluate:
# Without evaluation:
# "The system works... I think?"
# You don't know if chunking is optimal, if the model is right, etc.
# With evaluation:
# "500-token chunking has Recall@5 = 0.85"
# "Changing to 800 tokens improves it to 0.90"
# → Data-driven decisions
Main metrics:
1. Recall@K:
# Of the relevant docs, how many are in the top-K?
# Example:
# - Query: "How to install Python?"
# - Relevant docs: [doc_3, doc_7]
# - Top-5 retrieved: [doc_1, doc_3, doc_5, doc_7, doc_9]
# Recall@5 = 2/2 = 1.0 (both relevant docs in the top-5)
2. Precision@K:
# Of the top-K retrieved, how many are relevant?
# Precision@5 = 2/5 = 0.4 (2 relevant out of 5 retrieved)
3. nDCG@K:
# Like Recall but it considers ranking (a better score for docs higher up)
# nDCG@5 = 0.85 (doc_3 in position 2, doc_7 in position 4)
4. MRR (Mean Reciprocal Rank):
# Position of the FIRST relevant doc
# doc_3 is in position 2 → RR = 1/2 = 0.5
What you'll learn (in detail)
1. Chunking strategies:
# Fixed-size (implementation):
def fixed_size_chunking(text, chunk_size=500, overlap=50):
"""Split text into fixed-size chunks"""
# You'll implement this function with tiktoken
pass
# Semantic (implementation):
def semantic_chunking(text, model="sentence-transformers"):
"""Split by semantic boundaries"""
# You'll implement it with spaCy or LangChain
pass
# Recursive (implementation):
def recursive_chunking(text, max_chunk_size=800):
"""Split hierarchically"""
# You'll implement a simplified version of LangChain
pass
2. Systematic evaluation:
# Create an evaluation dataset:
eval_dataset = [
{
"query": "How to install Python?",
"relevant_doc_ids": [3, 7, 12],
"corpus": [...1000 documents...]
},
# ... more queries
]
# Evaluate retrieval:
metrics = evaluate_retrieval(
queries=eval_dataset,
retrieval_function=my_rag_system,
k=5
)
print(f"Recall@5: {metrics['recall@5']:.2f}")
print(f"nDCG@5: {metrics['ndcg@5']:.2f}")
3. Optimization:
# A/B test different chunk sizes:
chunk_sizes = [200, 400, 600, 800, 1000]
for size in chunk_sizes:
chunks = fixed_size_chunking(docs, chunk_size=size)
metrics = evaluate_retrieval(chunks)
print(f"Size {size}: Recall@5 = {metrics['recall@5']:.2f}")
# Output:
# Size 200: Recall@5 = 0.75
# Size 400: Recall@5 = 0.85
# Size 600: Recall@5 = 0.90 ← Optimal
# Size 800: Recall@5 = 0.88
# Size 1000: Recall@5 = 0.82
Theory/practice balance (40/60)
Theory (40%):
- Chunking strategies (concepts)
- Evaluation metrics (formulas)
- Trade-offs (size, overlap)
Practice (60%):
- Implement chunking (code)
- Calculate metrics (code)
- A/B testing (code)
- Complete RAG project
What you will NOT learn (out of scope)
❌ Vector databases (Pinecone, Weaviate):
Reason: A dedicated module comes next (Vector Databases Guide).
Coverage: In-memory storage (numpy) is enough to learn.
❌ LLM generation (GPT-4 responses):
Reason: Focus on retrieval (the R of RAG), not generation (the G).
Coverage: Only up to retrieving relevant chunks.
❌ Advanced NLP (NER, POS tagging):
Reason: Not critical for basic chunking.
Coverage: Only sentence/paragraph boundaries.
Module tools
Python libraries:
# Chunking
import tiktoken # Token counting (OpenAI)
from langchain.text_splitter import RecursiveCharacterTextSplitter
# NLP (sentence boundaries)
import spacy # Optional for semantic chunking
# Evaluation
import numpy as np # Custom metrics
# Embeddings (already seen)
from openai import OpenAI
from sentence_transformers import SentenceTransformer
Required setup:
# Install LangChain (chunking utilities)
pip install langchain
# Install spaCy (optional - sentence boundaries)
pip install spacy
python -m spacy download en_core_web_sm
Module structure
module-04-chunking-evaluation/
└── en/
├── 01-module-introduction-4.md ← You are here
├── 02-fixed-size-chunking.md
├── 03-semantic-chunking.md
├── 04-retrieval-metrics.md
├── 05-evaluation-datasets.md
├── 06-ab-testing-chunking.md
├── 07-advanced-chunking.md
└── 08-project-smart-rag-chunking.md
Professional skills
By completing this module, you'll demonstrate:
1. RAG Implementation:
- Implement production-ready chunking
- Optimize chunk size (data-driven)
- Preserve context
2. Evaluation mindset:
- Create evaluation datasets
- Calculate metrics (nDCG, MRR)
- Systematic A/B testing
3. Trade-off analysis:
- Balance size vs context
- Optimal overlap
- Fixed vs semantic chunking
Real use cases
1. Documentation search (1000 technical docs):
Requirements:
- Chunks must respect sections (semantic)
- Recall@5 > 0.85
- Chunk size: 400-600 tokens
Decision:
Strategy: Semantic chunking (by section)
Average size: 500 tokens
Overlap: 50 tokens
Result: Recall@5 = 0.88 ✅
2. Legal document Q&A (contracts):
Requirements:
- Chunks must preserve complete clauses
- Critical context (don't cut mid-clause)
- Recall@10 > 0.90
Decision:
Strategy: Semantic chunking (by clause)
Variable size: 300-1200 tokens
Overlap: 0 (clauses don't overlap)
Result: Recall@10 = 0.92 ✅
3. Customer support chatbot (FAQs):
Requirements:
- Small chunks (concise answers)
- Latency <100ms
- Precision@3 > 0.80
Decision:
Strategy: Fixed-size chunking (simple, fast)
Size: 200 tokens
Overlap: 20 tokens
Result: Precision@3 = 0.83 ✅
Module methodology
Iterative learning:
Step 1: Implement basic chunking
↓
Step 2: Evaluate with metrics
↓
Step 3: Optimize (size, overlap, strategy)
↓
Step 4: Re-evaluate (loop)
↓
Step 5: Complete RAG project
Initial exercise (reflection)
Scenario:
You have 500 technical documents (Python tutorials), each averaging 2,000 tokens. A user asks: "How to use list comprehensions?"
Questions:
- Which chunking strategy would you use? Why?
- What chunk size would be optimal?
- Do you need overlap? How much?
- How would you evaluate whether your chunking works well?
There's no single answer (it depends on trade-offs).
By the end of the module, you'll be able to answer with an implementation and data.
Module resources
Papers and references:
- LangChain Text Splitters - Chunking strategies
- Retrieval Metrics - nDCG, MRR, etc.
- RAG Best Practices - Pinecone guide
Tools:
In the next capsule
Capsule 02: Fixed-Size Chunking
You'll learn:
- Character-based vs token-based chunking
- Implement chunking with tiktoken
- Overlap strategies (sliding window)
- Fixed-size trade-offs
- Production-ready code
From introduction to practical implementation.
Module 4 - Embeddings Deep Dive Guide Chunking and evaluation: from long documents to production-ready RAG