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:

  1. ✅ Explicar por qué fixed-size chunking es subóptimo (pierde contexto, corta arbitrariamente)
  2. ✅ Implementar recursive chunking con LangChain (respeta párrafos/oraciones)
  3. ✅ Implementar semantic chunking basado en embeddings (agrupa por tema)
  4. ✅ Implementar structural chunking para código/HTML/Markdown
  5. ✅ Usar chunk overlap para preservar contexto entre chunks
  6. ✅ Comparar 4 strategies con benchmarks cuantitativos
  7. ✅ Seleccionar strategy óptima según document type
  8. ✅ 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

  1. LangChain Text Splitters Guide - Documentación oficial
  2. Chunking Strategies for RAG - Pinecone guide completo
  3. Optimal Chunk Size Research - Paper académico
  4. LlamaIndex Node Parsers - Alternative implementations
  5. Semantic Chunking Deep Dive - LlamaIndex blog
  6. RAG Chunking Best Practices - Community discussion

Creado: Febrero 6, 2026
Versión: 1.0