Módulo 2: Chunking Strategies

Cápsula 07: Decision framework — qué chunking strategy elegir

Descripción de la cápsula

Cubrimos cuatro estrategias de chunking en este módulo: fixed-size, recursive, semantic, structural. Cada una con sus trade-offs, sus casos de uso, sus modos de falla. La pregunta operativa que cierra el módulo: ¿cuál eliges para un proyecto nuevo?

Esta cápsula consolida lo aprendido en un decision framework reproducible. Vas a aprender a tomar la decisión en menos de 10 minutos basándote en cinco preguntas, y a justificarla con datos cuantitativos cuando un Tech Lead te pregunte. Es la cápsula que vas a consultar cada vez que empiezas un nuevo proyecto RAG.

Al finalizar esta cápsula serás capaz de:

  • ✅ Comparar las cuatro estrategias sobre seis dimensiones: precision, recall, latencia, costo, coherencia, complejidad
  • ✅ Aplicar un flowchart de 5 preguntas para elegir strategy en proyectos nuevos
  • ✅ Diferenciar cuándo conviene una strategy híbrida (mezclar dos para distintas partes del corpus)
  • ✅ Calcular el TCO (total cost of ownership) de cada strategy para un volumen dado
  • ✅ Anticipar cuándo migrar de una strategy a otra mientras el producto evoluciona
  • ✅ Identificar el error costoso: elegir semantic chunking "porque es lo más nuevo" sin justificarlo

Tiempo estimado: 25-30 minutos


Benchmark consolidado de las cuatro estrategias

Calidad y performance

StrategyPrecision@5 (típico)Recall@50Coherence ScoreLatency de indexing
Fixed-size68-72%50-55%0.62 (baja)Instantáneo
Recursive78-82%57-62%0.89 (alta)+20% sobre fixed
Semantic83-87%62-68%0.96 (muy alta)+600% (cuesta dinero)
Structural85-90% (en code/HTML)67-72%0.94 (alta)+12% sobre fixed

Lectura clave:

  • Fixed-size es el baseline. Casi siempre subóptimo, solo justificable para prototipos.
  • Recursive es el sweet spot calidad/costo para texto narrativo. Default razonable.
  • Semantic gana en coherencia pero cuesta dinero (LLM o embeddings extra) y tiempo.
  • Structural gana cuando el documento tiene estructura clara (código, HTML, markdown).

Costo y operación

StrategyCosto de indexing por 100K docsMantenimientoVendor lock-in
Fixed-size$00Ninguno
Recursive$00Ninguno (LangChain o equivalente)
Semantic$1-3 (embeddings extra para boundary detection)BajoBajo
Structural$0Medio (mantener parsers por tipo de doc)Ninguno

Decision framework: las 5 preguntas

                    ┌──────────────────────────────────┐
                    │ 1. ¿Es prototipo rápido o MVP    │
                    │    donde "funciona" alcanza?      │
                    └──────────────┬───────────────────┘
                                   │
                  ┌────────────────┴────────────────┐
                  │ SÍ                              │ NO
                  ▼                                 ▼
         ┌──────────────────────┐         ┌──────────────────────────┐
         │ Fixed-size.          │         │ 2. ¿Tu corpus tiene      │
         │ Es subóptimo pero    │         │    estructura formal     │
         │ vas a iterar ya.     │         │    (código, HTML, MD)?   │
         └──────────────────────┘         └──────────┬───────────────┘
                                                     │
                                    ┌────────────────┴────────────┐
                                    │ SÍ                          │ NO
                                    ▼                             ▼
                        ┌──────────────────────┐      ┌────────────────────────┐
                        │ Structural chunking. │      │ 3. ¿Es texto narrativo │
                        │ Respeta funciones,   │      │    largo donde la      │
                        │ headers, secciones.  │      │    coherencia importa? │
                        └──────────────────────┘      │    (legal, médico,     │
                                                      │     papers académicos) │
                                                      └──────────┬─────────────┘
                                                                 │
                                            ┌────────────────────┴───────────┐
                                            │ SÍ                             │ NO
                                            ▼                                ▼
                                 ┌──────────────────────┐         ┌──────────────────────────┐
                                 │ 4. ¿Presupuesto      │         │ Recursive chunking.      │
                                 │    para indexing y   │         │ Default para 80% de      │
                                 │    tolera latencia   │         │ casos. Empieza acá.       │
                                 │    de re-index?      │         └──────────────────────────┘
                                 └──────────┬───────────┘
                                            │
                              ┌─────────────┴────────────┐
                              │ SÍ                       │ NO
                              ▼                          ▼
                   ┌──────────────────────┐    ┌──────────────────────┐
                   │ Semantic chunking.   │    │ Recursive chunking.  │
                   │ Mejor coherencia,    │    │ Buena coherencia sin │
                   │ vale el costo.       │    │ costo extra.         │
                   └──────────────────────┘    └──────────────────────┘

Aplicado a casos reales

ProyectoStrategy elegidaPor qué
MVP de chatbot de soporte (1 semana de dev)Fixed-sizeIterar rápido; cambiar después
Buscador interno de Stack OverflowStructural (por bloques de código) + Recursive (texto)Híbrida — código y texto necesitan tratamientos distintos
Sistema RAG sobre documentación FastAPIRecursive con chunk_size=500, overlap=50Mezcla narrative + code blocks; recursive maneja los dos OK
RAG sobre jurisprudencia legal en españolSemanticArgumentos largos, coherencia crítica, presupuesto disponible
Asistente de papers académicosSemanticHipótesis y conclusiones referencian premisas previas; coherencia paga
RAG sobre código fuente PythonStructural (por funciones/clases)El código tiene unidades naturales
RAG genérico empresa SaaS, presupuesto bajoRecursiveDefault, gratis, suficiente

Strategies híbridas: cuando un solo chunking no alcanza

A veces tu corpus tiene tipos de documentos muy distintos. En ese caso, una sola strategy no es óptima — usa chunking diferenciado por tipo de documento.

# hybrid_chunking.py
from langchain_text_splitters import RecursiveCharacterTextSplitter, MarkdownHeaderTextSplitter
import re


def chunk_by_doc_type(document: str, doc_type: str) -> list[str]:
    """
    Aplica strategy distinta según el tipo de documento.
    """
    if doc_type == "code":
        return chunk_code_structurally(document)

    elif doc_type == "markdown":
        # Respeta headers de markdown
        splitter = MarkdownHeaderTextSplitter(
            headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")]
        )
        return [section.page_content for section in splitter.split_text(document)]

    elif doc_type == "html":
        return chunk_html_structurally(document)

    elif doc_type == "legal":
        # Texto narrativo largo con coherencia crítica → semantic
        return semantic_chunk(document, threshold=0.7)

    else:  # narrative general
        # Recursive es el default razonable
        splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,
            chunk_overlap=50,
            separators=["\n\n", "\n", ". ", " ", ""]
        )
        return splitter.split_text(document)


def chunk_code_structurally(code: str) -> list[str]:
    """Divide código por funciones y clases (parser-based)."""
    # Implementación simplificada usando AST
    import ast
    chunks = []
    try:
        tree = ast.parse(code)
        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
                chunk = ast.get_source_segment(code, node)
                if chunk:
                    chunks.append(chunk)
    except SyntaxError:
        # Fallback a recursive si no parsea
        return RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50).split_text(code)
    return chunks


def detect_doc_type(content: str, filename: str = "") -> str:
    """Heurística simple para detectar tipo de documento."""
    if filename.endswith(('.py', '.js', '.ts', '.java', '.go')):
        return "code"
    if filename.endswith(('.md', '.markdown')):
        return "markdown"
    if filename.endswith(('.html', '.htm')):
        return "html"
    if re.search(r'^(SECTION|ARTICLE|WHEREAS)', content, re.MULTILINE):
        return "legal"
    return "narrative"

Cuándo vale la pena chunking híbrido

  • Corpus mixto significativo: si >20% de tus docs son de un tipo distinto al dominante, vale el esfuerzo.
  • Calidad crítica por tipo: si los docs de un tipo (ej: código) son los más consultados, optimizarlos paga.
  • Equipo con bandwidth: mantener múltiples chunkers tiene costo de ingeniería.

Cuándo NO vale

  • Corpus 90%+ del mismo tipo (un solo chunker basta).
  • MVP donde "funciona" alcanza.
  • Equipo pequeño sin bandwidth para mantener parsers múltiples.

Cálculo de TCO (total cost of ownership)

Antes de elegir, haz el cálculo concreto. Asume 100K documentos a indexar, dataset que crece 10K docs/mes:

# tco_calculator.py
DOCS_INITIAL = 100_000
DOCS_PER_MONTH_NEW = 10_000
AVG_TOKENS_PER_DOC = 800

# Costos por strategy
def calculate_tco(strategy: str, months: int = 12) -> dict:
    """Calcula TCO para una strategy específica a 12 meses."""

    # Costos de indexing inicial
    if strategy == "fixed":
        initial_cost = 0  # gratis
        recurring_monthly = 0
    elif strategy == "recursive":
        initial_cost = 0
        recurring_monthly = 0
    elif strategy == "semantic":
        # Semantic requiere embebir cada oración para detectar boundaries
        # ~3x los tokens del documento
        sentences_factor = 3
        initial_cost = (DOCS_INITIAL * AVG_TOKENS_PER_DOC * sentences_factor / 1_000_000) * 0.02
        recurring_monthly = (DOCS_PER_MONTH_NEW * AVG_TOKENS_PER_DOC * sentences_factor / 1_000_000) * 0.02
    elif strategy == "structural":
        initial_cost = 0
        recurring_monthly = 0
    elif strategy == "hybrid_recursive_structural":
        initial_cost = 0
        recurring_monthly = 0  # solo costo es de ingeniería al setup

    # Costo de ingeniería al setup (estimado)
    if strategy == "fixed":
        eng_setup_hours = 1
    elif strategy == "recursive":
        eng_setup_hours = 4
    elif strategy == "semantic":
        eng_setup_hours = 16
    elif strategy == "structural":
        eng_setup_hours = 24  # parsers por tipo de doc
    elif strategy == "hybrid_recursive_structural":
        eng_setup_hours = 32

    eng_cost = eng_setup_hours * 80  # $80/hr

    total_recurring = recurring_monthly * months
    total_cost = initial_cost + total_recurring + eng_cost

    return {
        "strategy": strategy,
        "initial_cost_usd": initial_cost,
        "monthly_recurring_usd": recurring_monthly,
        "engineering_setup_usd": eng_cost,
        "total_12_months_usd": total_cost,
    }


# Comparar
strategies = ["fixed", "recursive", "semantic", "structural", "hybrid_recursive_structural"]
for s in strategies:
    tco = calculate_tco(s, months=12)
    print(f"\n{s.upper()}:")
    for k, v in tco.items():
        if isinstance(v, (int, float)):
            print(f"  {k}: ${v:.2f}")
        else:
            print(f"  {k}: {v}")

Output típico:

FIXED:
  initial_cost_usd: $0.00
  monthly_recurring_usd: $0.00
  engineering_setup_usd: $80.00
  total_12_months_usd: $80.00

RECURSIVE:
  initial_cost_usd: $0.00
  monthly_recurring_usd: $0.00
  engineering_setup_usd: $320.00
  total_12_months_usd: $320.00

SEMANTIC:
  initial_cost_usd: $4.80
  monthly_recurring_usd: $0.48
  engineering_setup_usd: $1280.00
  total_12_months_usd: $1290.56

STRUCTURAL:
  initial_cost_usd: $0.00
  monthly_recurring_usd: $0.00
  engineering_setup_usd: $1920.00
  total_12_months_usd: $1920.00

HYBRID_RECURSIVE_STRUCTURAL:
  initial_cost_usd: $0.00
  monthly_recurring_usd: $0.00
  engineering_setup_usd: $2560.00
  total_12_months_usd: $2560.00

Lecturas:

  • El costo dominante NO es el costo de embeddings sino el de ingeniería al setup.
  • Semantic, structural e híbridas son notablemente más caras de implementar.
  • Si la mejora de calidad sobre recursive es <5%, posiblemente no justifica la inversión.
  • Para que semantic chunking valga $1290 sobre $320 de recursive, la mejora tiene que ser sustancial.

Cuándo migrar entre strategies

Tu primer chunking no es necesariamente el final. Señales para migrar:

De fixed-size a recursive:

  • Empezaste con fixed para MVP. El sistema funciona pero tickets de soporte mencionan "respuestas cortadas a la mitad".
  • Inversión: ~4 horas de ingeniería + re-indexar todo el corpus.
  • Mejora típica esperada: +10 puntos de precision.

De recursive a structural:

  • Tu corpus tiene 30%+ de código o HTML estructurado.
  • Recursive parte funciones a la mitad o ignora headers de markdown.
  • Inversión: ~16-24 horas + parsers por tipo + re-indexar.
  • Mejora típica: +5-10 puntos en queries que tocan los tipos estructurados.

De recursive a semantic:

  • Tu corpus es texto narrativo largo con argumentos extensos (legal, médico, papers).
  • Recursive parte argumentos justo en transiciones críticas.
  • Inversión: ~16 horas + costo recurrente de embeddings extra.
  • Mejora típica: +5-8 puntos pero solo si tu dominio realmente necesita coherencia narrativa.

De cualquiera a híbrida:

  • Identificaste que tipos específicos de docs subperforman vs el resto.
  • Considera optimizar solo esos tipos en lugar de migrar todo.

El error costoso: elegir lo más nuevo en vez de lo apropiado

El antipatrón: ves un blog post sobre "semantic chunking is the new state-of-the-art", lo eliges sin medir contra recursive.

Por qué es trampa:

  1. Costo de implementación: semantic toma 4x más esfuerzo que recursive.
  2. Costo recurrente: cada re-index cuesta dinero (embeddings extra).
  3. Mejora marginal en muchos casos: si tu corpus no tiene argumentos largos, semantic mejora ~2-3% sobre recursive — no justifica el costo.

Cómo evitarlo:

  • Empieza con recursive. Funciona en 80% de casos.
  • Construye eval set robusto.
  • Si el sistema no llega a tu target de calidad, diagnostica la causa antes de cambiar chunking.
  • A veces el problema no es chunking sino otra cosa (embeddings, rerank, queries).
# Diagnóstico: ¿es realmente chunking el problema?
def diagnose_chunking_impact(eval_set, current_strategy="recursive"):
    """
    Mide si el problema de calidad viene del chunking o de otro componente.
    """
    issues = []

    for item in eval_set:
        # Recuperar con strategy actual
        chunks_retrieved = retrieve(item["query"], strategy=current_strategy)

        # ¿La info correcta existe en algún chunk?
        relevant_in_top_k = any(item["expected_text"] in c for c in chunks_retrieved)

        # ¿La info correcta existe en algún chunk del corpus, en cualquier ranking?
        relevant_in_corpus = check_full_corpus(item["expected_text"])

        if not relevant_in_corpus:
            issues.append(("missing_data", item["query"]))  # no es chunking
        elif not relevant_in_top_k:
            # La info está pero no se rankea — chunking puede ser, pero también retrieval
            issues.append(("retrieval_or_chunking", item["query"]))
        # Si está en top-K, no es problema de chunking

    return issues

Trampas y errores comunes

Trampa 1: cambiar strategy sin re-indexar

El error: modificas el código del chunker, deployas. Los chunks viejos siguen igual.

Síntoma: queries sobre docs viejos siguen fallando como antes; queries sobre docs nuevos mejoran. Resultados inconsistentes.

Cómo prevenir: cuando cambias chunking, re-procesar todo el corpus. No es opcional.

Trampa 2: migrar a semantic sin medir el ROI

El error: "el blog dice que semantic chunking mejora 15%". Migras. Tu mejora real es 3% pero gastaste $1500 en ingeniería.

Cómo prevenir: medir mejora esperada sobre tu eval set antes de invertir.

Trampa 3: structural sin fallback

El error: structural chunker falla cuando el documento tiene formato roto. No hay fallback. Esos docs no se chunkean — quedan fuera del corpus indexado.

Síntoma: algunos docs nunca aparecen en queries.

Cómo prevenir: structural con fallback a recursive cuando el parser falla.

Trampa 4: chunk_size copiado sin entender

El error: copias chunk_size=500 de un tutorial. Tu corpus es código (donde 500 chars rara vez son una función completa).

Síntoma: funciones partidas a la mitad. Chunks que terminan en medio de un loop.

Cómo prevenir: ajustar chunk_size al tipo de contenido. Para código, chunk_size=800-1500. Para conversaciones, chunk_size=200-400.

Trampa 5: ignorar overlap al elegir strategy

El error: eliges structural chunking. Los chunks son funciones (cerradas). Asumes que no necesitas overlap.

Síntoma: referencias entre funciones (clase con métodos que dependen entre sí) se pierden.

Cómo prevenir: incluso en structural, agregar 1-2 oraciones de overlap entre chunks consecutivos si hay referencias cruzadas.

Trampa 6: no monitorear quality de chunking en producción

El error: eliges strategy en setup. Nunca vuelves a verificar.

Síntoma: después de 6 meses, el corpus cambió (nuevo tipo de doc agregado), pero el chunker sigue como antes. Calidad degrada gradualmente.

Cómo prevenir: dashboard de quality del chunking — promedio de chars por chunk, distribución de tamaños, % de chunks que terminan en mitad de oración. Alertar si métricas se desvían.


Ejercicio aplicado

Escenario: eres AI Engineer en una startup de educación online. Tu corpus son materiales de cursos:

  • 40% texto narrativo (lecciones explicativas)
  • 30% código fuente Python con explicaciones inline
  • 20% transcripciones de video lecturas
  • 10% diagramas (texto OCR de imágenes)

Volumen: 50K documentos. Crece 5K/mes. Equipo: 2 ingenieros, sin MLE dedicado.

Tu pipeline actual usa fixed-size con chunk_size=500. Métricas:

  • Precision@5: 65%
  • Recall@5: 58%
  • Quejas frecuentes: "el bot corta el código al medio", "las explicaciones están descontextualizadas"

Tu trabajo:

  1. Aplica el framework de 5 preguntas. ¿Qué strategy(s) eliges?
  2. Justifica considerando el corpus mixto.
  3. Estima el costo de migración y el impacto esperado.
Solución

1. Aplicación del framework:

  • Pregunta 1: ¿prototipo? No, está en producción con quejas reales.
  • Pregunta 2: ¿estructura formal? 30% del corpus es código — sí parcialmente.
  • Pregunta 3: ¿narrativo largo crítico? 40% es narrativo educativo, importante pero no "crítico" como legal.
  • Pregunta 4: ¿presupuesto + tolera latencia de re-index? Equipo pequeño, asumo presupuesto modesto.

Decisión: chunking híbrido (recursive + structural).

  • Texto narrativo + transcripciones + OCR (70%): recursive con chunk_size=500, overlap=50.
  • Código (30%): structural (por funciones/clases) con fallback a recursive si el parser falla.

2. Justificación detallada

El corpus es mixto pero los problemas principales son:

  • "El bot corta el código al medio" → fixed-size parte funciones. Structural lo resuelve.
  • "Las explicaciones están descontextualizadas" → falta de overlap. Recursive con overlap lo resuelve.

Semantic chunking sería overkill: el corpus educativo no tiene argumentos legales/médicos largos donde la coherencia narrativa sea crítica. El costo de ingeniería + costo recurrente de embeddings no se justifica.

3. Costo estimado y plan

# Plan de migración
phases = {
    "phase_1_recursive_for_text": {
        "effort": "8 horas (1 día)",
        "covers": "70% del corpus (narrativo + transcripciones + OCR)",
        "expected_improvement": "+10-12 puntos precision en queries narrativas",
        "eng_cost": "$640",
    },
    "phase_2_structural_for_code": {
        "effort": "24 horas (3 días — parser Python + tests)",
        "covers": "30% del corpus (código)",
        "expected_improvement": "+15-20 puntos precision en queries de código",
        "eng_cost": "$1920",
    },
    "phase_3_validation": {
        "effort": "8 horas",
        "covers": "construir eval set de 100 queries (mezcla código/narrativo) + medición A/B",
        "eng_cost": "$640",
    },
}

total_cost = sum(p["eng_cost"].lstrip("$").replace("$", "") for p in phases.values())
print(f"Total ingeniería: $3,200")
print(f"Costos recurrentes: $0 (no hay LLM extra)")
print(f"Tiempo total: 5 días de trabajo distribuidos")

Impacto esperado:

  • Precision@5: 65% → 80-83% (+15-18 puntos, ponderado por % del corpus)
  • Recall@5: 58% → 72-75% (+14-17 puntos)
  • Quejas de "código cortado" deberían desaparecer.
  • Quejas de "explicaciones descontextualizadas" deberían reducir 70-80%.

Plan de validación:

  1. Día 1-2: Implementar phase 1 (recursive para texto). Tests unitarios.
  2. Día 3-5: Implementar phase 2 (structural para código). Parser robusto con fallback.
  3. Día 6: Construir eval set + medir antes/después.
  4. Si mejora significativa, deployar con feature flag.

Riesgo principal: detectar correctamente qué documento es código vs texto. Solución: heurística simple (extension del archivo, si existe; si no, detectar palabras clave de Python como def, class, import).

Plan B si structural no llega: considerar semantic chunking solo para código (vs structural). Más caro pero más robusto cuando el código tiene formato no-estándar.


Resumen y siguiente paso

Lo que aprendiste:

  • Fixed-size es baseline solo para prototipos. Recursive es el default para 80% de casos.
  • Structural es óptima cuando el corpus tiene estructura formal (código, HTML, markdown).
  • Semantic gana en coherencia pero cuesta dinero y tiempo. Solo justificable en dominios narrativos críticos.
  • Strategies híbridas son válidas para corpus mixtos significativos. Costo de ingeniería extra, pero mejor calidad por tipo de documento.
  • TCO de chunking incluye más que costo de embeddings — el costo dominante suele ser ingeniería al setup.
  • Migrar entre strategies requiere re-indexar todo el corpus. No es solo cambio de código.
  • Antipatrón clave: elegir semantic "porque es lo más nuevo" sin medir ROI sobre tu eval set.

Checkpoint: antes de avanzar, deberías poder:

  • Aplicar el framework de 5 preguntas a un proyecto nuevo y elegir strategy en <10 min.
  • Calcular TCO aproximado para distintas strategies en tu volumen.
  • Diseñar un chunking híbrido cuando el corpus es mixto.

Siguiente cápsula: 08 — Proyecto Chunking Optimizer.

Cierre del módulo: vas a construir un sistema que toma un corpus, prueba múltiples strategies sobre el mismo eval set, y produce un reporte comparativo que permite tomar la decisión basada en datos. Es la herramienta que usarías el día 1 de cualquier proyecto RAG nuevo.


Recursos

  1. LangChain — Text Splitters Comparison — Comparación de splitters
  2. LlamaIndex — Node Parsers — Implementaciones alternativas
  3. Greg Kamradt — 5 Levels of Chunking — Tutorial visual de strategies
  4. Pinecone — Chunking Strategies — Comparación con benchmarks
  5. Anthropic — Contextual Retrieval — Técnica complementaria al chunking
  6. LangChain — Markdown Header Splitter — Para chunking estructural en MD

Tiempo estimado: 25-30 minutos Siguiente: 08-project-chunking-optimizer.md