Módulo 2: Chunking Strategies

Proyecto: Chunking Strategy Optimizer

Descripción del proyecto

Este proyecto integra todo el módulo: implementar las 4 chunking strategies, comparar con benchmarks cuantitativos, seleccionar strategy óptima para tu caso de uso, e integrar en baseline RAG del Módulo 1.

Objetivo: Mejorar precision del baseline +10-15% con chunking optimizado, documentar decisión con datos, y tener código reutilizable para production.


🎯 Objetivos del Proyecto

  1. ✅ Implementar 4 chunking strategies (fixed, recursive, semantic, structural)
  2. ✅ Comparar con benchmarks (precision, recall, latency, coherence)
  3. ✅ Seleccionar strategy óptima según document type
  4. ✅ Integrar en baseline RAG (reemplazar fixed-size)
  5. ✅ Documentar mejora (+10-15% precision esperado)

📁 Estructura del Proyecto

rag_baseline_project/  (del Módulo 1)
├── src/
│   ├── chunking/                    # NUEVO
│   │   ├── __init__.py
│   │   ├── base_chunker.py          # Interface base
│   │   ├── fixed_size_chunker.py    # Strategy 1
│   │   ├── recursive_chunker.py     # Strategy 2
│   │   ├── semantic_chunker.py      # Strategy 3
│   │   ├── structural_chunker.py    # Strategy 4
│   │   └── chunking_comparator.py   # Benchmark tool
│   └── indexing.py                  # Actualizar con chunking optimizado
└── notebooks/
    └── chunking_comparison.ipynb    # Demo interactivo

💻 Implementación

Paso 1: Base Chunker Interface

# src/chunking/base_chunker.py
from abc import ABC, abstractmethod
from typing import List

class BaseChunker(ABC):
    """Interface base para chunking strategies"""
    
    @abstractmethod
    def chunk(self, text: str) -> List[str]:
        """Dividir texto en chunks"""
        pass
    
    @abstractmethod
    def name(self) -> str:
        """Nombre de la strategy"""
        pass

Paso 2: Implementar Strategies

# src/chunking/recursive_chunker.py
from langchain.text_splitters import RecursiveCharacterTextSplitter
from .base_chunker import BaseChunker

class RecursiveChunker(BaseChunker):
    def __init__(self, chunk_size=500, chunk_overlap=50):
        self.splitter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,
            chunk_overlap=chunk_overlap,
            separators=["\n\n", "\n", ". ", " ", ""]
        )
    
    def chunk(self, text: str) -> list[str]:
        return self.splitter.split_text(text)
    
    def name(self) -> str:
        return "recursive"

(Implementar similar para fixed_size, semantic, structural)


Paso 3: Comparator

# src/chunking/chunking_comparator.py
import time
import statistics
from typing import Dict

class ChunkingComparator:
    """Comparar chunking strategies con benchmarks"""
    
    def __init__(self, strategies: list):
        self.strategies = strategies
    
    def compare(self, document: str) -> Dict:
        """Comparar todas las strategies"""
        results = {}
        
        for strategy in self.strategies:
            # Medir latency
            start = time.time()
            chunks = strategy.chunk(document)
            latency = (time.time() - start) * 1000  # ms
            
            # Calcular métricas
            results[strategy.name()] = {
                "num_chunks": len(chunks),
                "avg_chunk_size": statistics.mean([len(c) for c in chunks]),
                "latency_ms": latency,
                "coherence": self._measure_coherence(chunks)
            }
        
        return results
    
    def _measure_coherence(self, chunks: list[str]) -> float:
        """Proxy: % de chunks que terminan con puntuación"""
        ends_with_punct = sum(1 for c in chunks if c.rstrip()[-1] in '.!?')
        return ends_with_punct / len(chunks) if chunks else 0

Paso 4: Ejecutar Benchmark

# benchmark_chunking.py
from src.chunking.fixed_size_chunker import FixedSizeChunker
from src.chunking.recursive_chunker import RecursiveChunker
from src.chunking.semantic_chunker import SemanticChunker
from src.chunking.chunking_comparator import ChunkingComparator

# Cargar documento
with open("data/fastapi_docs.txt", "r") as f:
    document = f.read()

# Configurar strategies
strategies = [
    FixedSizeChunker(chunk_size=500),
    RecursiveChunker(chunk_size=500, chunk_overlap=50),
    SemanticChunker(),  # Requiere OpenAI API key
]

# Comparar
comparator = ChunkingComparator(strategies)
results = comparator.compare(document)

# Mostrar resultados
for strategy_name, metrics in results.items():
    print(f"\n{strategy_name.upper()}:")
    print(f"  Chunks: {metrics['num_chunks']}")
    print(f"  Avg size: {metrics['avg_chunk_size']:.0f} chars")
    print(f"  Latency: {metrics['latency_ms']:.2f}ms")
    print(f"  Coherence: {metrics['coherence']:.2%}")

Output esperado:

FIXED_SIZE:
  Chunks: 250
  Avg size: 500 chars
  Latency: 0.08ms
  Coherence: 45%

RECURSIVE:
  Chunks: 268
  Avg size: 467 chars
  Latency: 1.25ms
  Coherence: 89%

SEMANTIC:
  Chunks: 185
  Avg size: 676 chars
  Latency: 18500ms
  Coherence: 96%

Paso 5: Integrar en Baseline RAG

# src/indexing.py (actualizado)
from src.chunking.recursive_chunker import RecursiveChunker  # NUEVO

class OptimizedIndexingPipeline:
    def __init__(self, chunking_strategy="recursive"):
        # Seleccionar chunking strategy
        if chunking_strategy == "recursive":
            self.chunker = RecursiveChunker(chunk_size=500, chunk_overlap=50)
        elif chunking_strategy == "semantic":
            self.chunker = SemanticChunker()
        # ... otros
        
        self.openai_client = OpenAI()
        self.chroma_client = chromadb.PersistentClient(path="./chroma_db")
    
    def chunk_document(self, document: str) -> list[str]:
        """Usar chunking strategy seleccionada (no fixed-size)"""
        return self.chunker.chunk(document)
    
    # ... resto del código igual

📊 Medir Mejora

Antes (Módulo 1 baseline):

# Fixed-size chunking
baseline_results = {
    "precision@5": 0.68,
    "recall@50": 0.52,
    "coherence": 0.62
}

Después (Módulo 2 optimizado):

# Recursive chunking
optimized_results = {
    "precision@5": 0.78,  # +10%
    "recall@50": 0.57,    # +5%
    "coherence": 0.89     # +27%
}

Mejora conseguida: ✅ +10% precision, +5% recall (target alcanzado)


📝 Documentar Decisión

README.md actualizado:

# RAG System - Módulo 2: Chunking Optimizado

## Chunking Strategy Seleccionada: Recursive

### Justificación:
- **Mejora:** +10% precision, +5% recall vs baseline fixed-size
- **Costo:** Cero (no API calls adicionales)
- **Latency:** +1.2ms (negligible)
- **Coherence:** 89% vs 45% baseline (+44%)

### Alternativas Consideradas:
- **Semantic:** +15% precision pero $12/10K docs y +16s latency → Descartada por budget
- **Structural:** Solo aplicable para código → No aplica para dataset narrativo
- **Fixed-size:** Baseline simple pero -10% precision → Rechazada

### Configuración:
```python
RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,  # 10% overlap
    separators=["\n\n", "\n", ". ", " ", ""]
)

Performance Comparison (Módulo 1 vs Módulo 2)

MétricaMódulo 1 (Fixed)Módulo 2 (Recursive)Delta
Precision@568%78%+10%
Recall@5052%57%+5%
Coherence62%89%+27%

---

## 🎯 Criterios de Éxito

✅ **Proyecto completo si:**

1. 4 strategies implementadas con código funcional
2. Benchmark comparativo ejecutado con resultados documentados
3. Strategy seleccionada con justificación basada en datos
4. Integración en baseline RAG completa
5. Mejora +10-15% precision vs Módulo 1 alcanzada

---

## 🚀 Bonus (Opcional)

- Notebook interactivo con visualizaciones de comparación
- Tests unitarios para cada chunking strategy
- CLI para probar diferentes strategies interactivamente
- Hybrid chunking (structural para código, recursive para texto)

---

## 📚 Entregables

1. **Código:** src/chunking/ folder completo con 4 strategies
2. **Benchmark results:** Tabla comparativa documentada
3. **Decision doc:** Justificación de strategy seleccionada
4. **Integration:** indexing.py actualizado con chunking optimizado
5. **Performance report:** Mejora vs baseline cuantificada

---

## 🎯 Resumen

**Proyecto Chunking Optimizer:**

- ✅ Implementar 4 strategies (fixed, recursive, semantic, structural)
- ✅ Comparar con benchmarks cuantitativos
- ✅ Seleccionar strategy óptima (típicamente: recursive)
- ✅ Mejorar baseline +10-15% precision
- ✅ Documentar decisión con datos

**Mejora esperada:** Precision 68% → 78% (+10%)

**Próximo módulo:** Módulo 3 optimiza query processing (+15-25% recall con query expansion, rewriting, HyDE).

---

## 📚 Recursos Adicionales

1. **[LangChain Text Splitters Cookbook](https://python.langchain.com/docs/modules/data_connection/document_transformers/)** - Ejemplos de código
2. **[Chunking Strategies Evaluation](https://arxiv.org/abs/2307.03172)** - Research paper
3. **[RAG Chunking Best Practices](https://www.pinecone.io/learn/chunking-strategies/)** - Pinecone guide
4. **[LlamaIndex Chunking](https://docs.llamaindex.ai/en/stable/module_guides/loading/node_parsers/)** - Alternative implementations

---

**Creado:** Febrero 6, 2026  
**Versión:** 1.0