Módulo 2: Chunking Strategies
Introducción a Chunking Strategies
Descripción de la cápsula
Chunking es la primera decisión técnica que tomas en RAG: ¿cómo dividir documentos largos en chunks recuperables? Esta decisión tiene impacto directo en calidad: chunks mal formados (cortados mid-sentence, sin contexto semántico) generan retrieval pobre, confunden al LLM, y bajan precision 15-20%.
En Módulo 1 usaste fixed-size chunking (naive: cortar cada 500 caracteres). Funciona para baseline, pero es subóptimo. Este módulo te enseña 4 estrategias avanzadas que respetan estructura del documento, preservan contexto semántico, y mejoran precision +10-20% con zero cambios en otros componentes.
Esta cápsula te da el mapa completo del módulo: qué aprenderás, por qué chunking importa, roadmap de las 8 cápsulas, setup técnico, y conexión con el proyecto evolutivo donde integrarás chunking optimizado.
🎯 Objetivos de Aprendizaje
Al finalizar este módulo, podrás:
- ✅ Explicar por qué fixed-size chunking es subóptimo (pierde contexto, corta arbitrariamente)
- ✅ Implementar recursive chunking con LangChain (respeta párrafos/oraciones)
- ✅ Implementar semantic chunking basado en embeddings (agrupa por tema)
- ✅ Implementar structural chunking para código/HTML/Markdown
- ✅ Usar chunk overlap para preservar contexto entre chunks
- ✅ Comparar 4 strategies con benchmarks cuantitativos
- ✅ Seleccionar strategy óptima según document type
- ✅ Mejorar baseline RAG +10-15% precision con chunking optimizado
📐 Por Qué Chunking Importa
Problema: Documentos largos no caben en embeddings ni LLM context
# Documento largo
document = """
FastAPI es un framework web moderno y rápido (alto rendimiento) para construir APIs con Python 3.8+ basado en estándares de tipos de Python.
Características principales:
- Rápido: Muy alto rendimiento, a la par con NodeJS y Go (gracias a Starlette y Pydantic).
- Rápido de codificar: Aumenta la velocidad de desarrollo en aproximadamente 200% a 300%.
- Menos errores: Reduce los errores humanos en aproximadamente un 40%.
- Intuitivo: Gran soporte en editores con autocompletado en todas partes.
- Fácil: Diseñado para ser fácil de usar y aprender. Menos tiempo leyendo documentación.
- Corto: Minimiza la duplicación de código. Múltiples características de cada declaración de parámetro.
- Robusto: Obtén código listo para producción con documentación automática e interactiva.
- Basado en estándares: Basado en (y totalmente compatible con) los estándares abiertos para APIs: OpenAPI y JSON Schema.
FastAPI fue creado por Sebastián Ramírez y lanzado en 2018. Desde entonces, ha sido adoptado por empresas como Microsoft, Netflix, y Uber para construir APIs de producción.
Historia:
FastAPI nació de la necesidad de construir APIs rápidas con validación automática de datos...
"""
# Problema:
len(document) # 1,247 caracteres
# - Embedding models: max 8,191 tokens (OpenAI ada-002)
# - LLM context: max 4,096-128K tokens (GPT-3.5 a GPT-4)
# - Pero documentos reales tienen 10K-100K+ caracteres
Solución: Dividir en chunks más pequeños.
Impacto de Chunking en Pipeline RAG:
┌─────────────────────────────────────────┐
│ INDEXING (Offline) │
├─────────────────────────────────────────┤
│ 1. Chunking: [Fixed | Recursive | │ ← ESTE MÓDULO
│ Semantic | Structural] │
│ 2. Embeddings: 1 embedding per chunk │ ← Afectado por calidad de chunks
│ 3. Storage: Store chunks + embeddings │ ← Afectado por número/tamaño de chunks
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ RETRIEVAL (Query time) │
├─────────────────────────────────────────┤
│ 1. Search: Find top-K chunks │ ← Chunks mal formados = poor retrieval
│ 2. Chunks recuperados → LLM context │ ← Chunks sin contexto = poor generation
└─────────────────────────────────────────┘
Chunking malo:
- Chunks cortados mid-sentence → Incoherentes → Embeddings pobres → Poor retrieval
- Chunks sin contexto → LLM no puede generar respuesta correcta
Chunking bueno:
- Chunks coherentes → Embeddings de calidad → Good retrieval
- Chunks con contexto → LLM genera respuestas grounded
🗺️ Roadmap del Módulo
Cápsula 01 (Esta): Introducción
- Por qué chunking importa
- Overview de 4 strategies
- Setup técnico
Cápsula 02: Problemas de Fixed-Size Chunking
- Problema 1: Corte arbitrario
- Problema 2: Pérdida de contexto
- Impacto en métricas (-15-20% precision)
Cápsula 03: Recursive Chunking
- Cómo funciona (separators)
- Implementación con LangChain
- Trade-offs y tuning
Cápsula 04: Semantic Chunking
- Cómo funciona (embeddings + clustering)
- Implementación con SemanticChunker
- Trade-offs (costo vs calidad)
Cápsula 05: Structural Chunking
- Código: chunking por funciones (ast)
- HTML: chunking por secciones (BeautifulSoup)
- Markdown: chunking por headers
Cápsula 06: Chunk Overlap
- Por qué overlap preserva contexto
- Fixed vs semantic overlap
- Trade-offs (storage vs recall)
Cápsula 07: Comparación de Strategies
- Benchmark de 4 strategies
- Decision matrix
- Casos híbridos
Cápsula 08: Proyecto - Chunking Optimizer
- Implementar 4 strategies
- Comparar con benchmarks
- Integrar en baseline RAG
📊 Overview de Chunking Strategies
Strategy 1: Fixed-Size (Baseline Módulo 1)
# Baseline naive
chunks = [document[i:i+500] for i in range(0, len(document), 500)]
# Problema: Corta en lugares arbitrarios
# Chunk 1: "FastAPI es un framework web modern..." ← Cortado mid-word
# Chunk 2: "...o y rápido (alto rendimiento) par..." ← Sin contexto
Pros: ✅ Simple, rápido
Cons: ❌ Pierde contexto, corta arbitrariamente
Precision: Baseline (68% en Módulo 1)
Strategy 2: Recursive (LangChain)
from langchain.text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""] # Intenta dividir por párrafos primero
)
chunks = splitter.split_text(document)
# Output: Chunks respetan párrafos y oraciones
# Chunk 1: "FastAPI es un framework web moderno y rápido..." ← Oración completa
# Chunk 2: "Características principales:\n- Rápido: ..." ← Sección completa
Pros: ✅ Respeta estructura, overlap preserva contexto
Cons: ⚠️ Ligeramente más complejo que fixed-size
Precision: +10-15% vs baseline
Cápsula: 03
Strategy 3: Semantic (Embeddings-Based)
from langchain.text_splitters import SemanticChunker
from langchain_openai import OpenAIEmbeddings
splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile" # Divide cuando similarity baja
)
chunks = splitter.split_text(document)
# Output: Chunks agrupados por tema semántico
# Chunk 1: Todo sobre características de FastAPI (tema: features)
# Chunk 2: Todo sobre historia de FastAPI (tema: background)
Pros: ✅ Máxima coherencia semántica
Cons: ❌ Lento (genera embeddings), costo API
Precision: +15-20% vs baseline
Cápsula: 04
Strategy 4: Structural (Code/HTML/Markdown)
import ast
def chunk_by_functions(python_code: str) -> list[str]:
"""Divide código por funciones/clases"""
tree = ast.parse(python_code)
chunks = []
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
chunk = ast.get_source_segment(python_code, node)
chunks.append(chunk)
return chunks
# Output: Cada chunk es una función completa
# Chunk 1: def create_user(...): ...
# Chunk 2: def update_user(...): ...
Pros: ✅ Respeta estructura del documento (código, HTML, Markdown)
Cons: ⚠️ Requiere parser específico por formato
Precision: +20-25% vs baseline (para código/HTML)
Cápsula: 05
🛠️ Setup Técnico
Paso 1: Instalar dependencias nuevas
# Activar virtual environment
source venv/bin/activate # Windows: venv\Scripts\activate
# Instalar librerías nuevas
pip install langchain-text-splitters==0.0.1
pip install sentence-transformers==2.3.1
pip install beautifulsoup4==4.12.3
pip install markdown==3.5.2
# Actualizar requirements.txt
pip freeze > requirements.txt
Paso 2: Verificar instalación
# test_module2_setup.py
from langchain.text_splitters import RecursiveCharacterTextSplitter, SemanticChunker
from langchain_openai import OpenAIEmbeddings
from sentence_transformers import SentenceTransformer
from bs4 import BeautifulSoup
import markdown
import ast
print("✅ LangChain text splitters")
print("✅ Sentence-Transformers")
print("✅ BeautifulSoup4")
print("✅ Markdown")
print("✅ ast (built-in)")
# Test RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=20)
test_text = "Hola mundo. " * 20
chunks = splitter.split_text(test_text)
print(f"\n✅ RecursiveCharacterTextSplitter funciona ({len(chunks)} chunks)")
print("\n🎉 Setup de Módulo 2 completo!")
Ejecutar:
python test_module2_setup.py
Output esperado:
✅ LangChain text splitters
✅ Sentence-Transformers
✅ BeautifulSoup4
✅ Markdown
✅ ast (built-in)
✅ RecursiveCharacterTextSplitter funciona (4 chunks)
🎉 Setup de Módulo 2 completo!
📋 Estructura del Proyecto (Módulo 2)
rag_baseline_project/ (del Módulo 1)
├── src/
│ ├── indexing.py # Baseline (Módulo 1)
│ ├── retrieval.py # Sin cambios
│ ├── generation.py # Sin cambios
│ ├── evaluation.py # Sin cambios
│ └── chunking/ # NUEVO (Módulo 2)
│ ├── __init__.py
│ ├── fixed_size.py # Baseline
│ ├── recursive.py # Strategy 2
│ ├── semantic.py # Strategy 3
│ ├── structural.py # Strategy 4
│ └── comparator.py # Benchmark de strategies
├── notebooks/
│ └── chunking_comparison.ipynb # Demo interactivo
└── README.md # Actualizar con decisiones de chunking
🎯 Objetivos del Proyecto (Cápsula 08)
Al finalizar el módulo, implementarás:
# chunking_optimizer.py (Preview)
class ChunkingOptimizer:
"""Comparador de chunking strategies"""
def __init__(self):
self.strategies = {
"fixed": FixedSizeChunker(chunk_size=500),
"recursive": RecursiveChunker(chunk_size=500, overlap=50),
"semantic": SemanticChunker(embeddings=OpenAIEmbeddings()),
"structural": StructuralChunker() # Para código
}
def compare_strategies(self, document: str) -> dict:
"""Comparar 4 strategies con benchmarks"""
results = {}
for name, chunker in self.strategies.items():
chunks = chunker.chunk(document)
# Medir métricas
results[name] = {
"num_chunks": len(chunks),
"avg_chunk_size": statistics.mean([len(c) for c in chunks]),
"precision": self.measure_precision(chunks), # Con golden dataset
"recall": self.measure_recall(chunks),
"latency": self.measure_latency(chunks)
}
return results
# Uso
optimizer = ChunkingOptimizer()
results = optimizer.compare_strategies(document)
# Output:
# {
# "fixed": {"precision": 0.68, "recall": 0.52, ...},
# "recursive": {"precision": 0.78, "recall": 0.57, ...}, ← +10% precision
# "semantic": {"precision": 0.83, "recall": 0.62, ...}, ← +15% precision
# "structural": {"precision": 0.75, "recall": 0.55, ...}
# }
📊 Mejora Esperada por Strategy
Baseline (Fixed-Size):
# Módulo 1 baseline
chunks = [document[i:i+500] for i in range(0, len(document), 500)]
# Métricas baseline:
# - Precision@5: 68%
# - Recall@50: 52%
# - Latency: 0ms (instantáneo)
Recursive (+10-15%):
# Recursive con overlap
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(document)
# Mejora esperada:
# - Precision@5: 78% (+10% vs baseline)
# - Recall@50: 57% (+5% vs baseline)
# - Latency: 0ms (instantáneo)
# - Trade-off: +10-15% storage (overlap)
Semantic (+15-20%):
# Semantic con embeddings
splitter = SemanticChunker(OpenAIEmbeddings())
chunks = splitter.split_text(document)
# Mejora esperada:
# - Precision@5: 83% (+15% vs baseline)
# - Recall@50: 62% (+10% vs baseline)
# - Latency: +slow indexing (embeddings)
# - Trade-off: +costo API embeddings
Structural (+20-25% para código):
# Structural para código
chunks = chunk_by_functions(python_code)
# Mejora esperada (código):
# - Precision@5: 88% (+20% vs baseline)
# - Recall@50: 67% (+15% vs baseline)
# - Latency: 0ms (instantáneo)
# - Trade-off: Requiere parser específico
🔗 Conexión con Proyecto Evolutivo
Módulo 1 (Baseline):
# indexing.py (Módulo 1)
def chunk_document(document: str) -> list[str]:
"""Fixed-size chunking (naive)"""
return [document[i:i+500] for i in range(0, len(document), 500)]
Métricas baseline: Precision 68%, Recall 52%
Módulo 2 (Optimizado):
# indexing.py (Módulo 2 - actualizado)
from langchain.text_splitters import RecursiveCharacterTextSplitter
def chunk_document(document: str) -> list[str]:
"""Recursive chunking (optimized)"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
return splitter.split_text(document)
Métricas optimizadas: Precision 78% (+10%), Recall 57% (+5%)
Módulo 8 (Final):
# advanced_rag_system.py (Módulo 8 - final)
class AdvancedRAGSystem:
def __init__(self, document_type: str):
# Seleccionar chunking strategy según document type
if document_type == "code":
self.chunker = StructuralChunker() # +20-25% precision
elif document_type == "narrative":
self.chunker = SemanticChunker() # +15-20% precision
else:
self.chunker = RecursiveChunker() # +10-15% precision (default)
Métricas finales: Precision 93% (acumulado de módulos 1-8)
🎯 Resumen
Conceptos clave:
- ✅ Chunking es crítico: Primera decisión técnica en RAG, impacto directo en precision
- ✅ Fixed-size es subóptimo: Corta arbitrariamente, pierde contexto (-15-20% precision)
- ✅ 4 strategies avanzadas: Recursive (balance), Semantic (calidad), Structural (específico), Overlap (contexto)
- ✅ Mejoras esperadas: +10-20% precision según strategy
- ✅ Trade-offs: Velocidad vs calidad, costo vs precision, storage vs recall
- ✅ Decision depends on doc type: Código → Structural, Narrativo → Semantic, General → Recursive
Qué sigue:
Cápsula 02 desglosa los problemas específicos de fixed-size chunking con ejemplos concretos y métricas de impacto.
📚 Recursos Adicionales
- LangChain Text Splitters Guide - Documentación oficial
- Chunking Strategies for RAG - Pinecone guide completo
- Optimal Chunk Size Research - Paper académico
- LlamaIndex Node Parsers - Alternative implementations
- Semantic Chunking Deep Dive - LlamaIndex blog
- RAG Chunking Best Practices - Community discussion
Creado: Febrero 6, 2026
Versión: 1.0