Phase 05 - Lesson 23
Estrategias de Chunking para RAG
La configuracion del chunking influye en la calidad de la recuperacion tanto como la eleccion del modelo de embedding (Vectara NAACL 2025). Si te equivocas en el chunking, ninguna cantidad de reranking te salva.
Tipo: Build Lenguajes: Python Requisitos previos: Fase 5 · 14 (Recuperacion de Informacion), Fase 5 · 22 (Modelos de Embedding) Tiempo: ~60 minutos
El Problema
Pones un contrato de 50 paginas en un sistema RAG. El usuario pregunta: "Cual es la clausula de rescision?" El recuperador devuelve la portada. Por que? Porque el modelo fue entrenado con chunks de 512 tokens y la clausula de rescision esta 20 paginas adentro, dividida por un salto de pagina, sin palabras clave locales que la vinculen a la consulta.
La solucion no es "comprar un mejor modelo de embedding". La solucion es el chunking. De que tamano? Con solapamiento? Donde dividir? Con contexto alrededor?
Los benchmarks de febrero de 2026 muestran resultados sorprendentes:
- Estudio de Vectara de 2026: el chunking recursivo de 512 tokens supero al chunking semantico, 69% → 54% de exactitud.
- SPLADE + Mistral-8B en Natural Questions: el solapamiento no aporto ningun beneficio medible.
- Acantilado de contexto: la calidad de la respuesta cae bruscamente alrededor de los 2.500 tokens de contexto.
La respuesta "obvia" (chunking semantico, 20% de solapamiento, 1000 tokens) suele estar equivocada. Esta leccion construye intuicion para seis estrategias y te dice cuando recurrir a cada una.
El Concepto
Chunking fijo. Divide cada N caracteres o tokens. La linea base mas simple. Corta a mitad de oracion. Buena compresion, mala coherencia.
Recursivo. El RecursiveCharacterTextSplitter de LangChain. Intenta dividir primero en \n\n, luego \n, luego ., luego espacio. Retrocede de forma limpia. El estandar de 2026.
Semantico. Genera el embedding de cada oracion. Calcula la similitud del coseno entre oraciones adyacentes. Divide donde la similitud cae por debajo de un umbral. Preserva la coherencia tematica. Mas lento; a veces produce fragmentos diminutos de 40 tokens que perjudican la recuperacion.
Por oracion. Divide en los limites de oracion. Una oracion por chunk o una ventana de N oraciones. Iguala al chunking semantico hasta ~5k tokens a una fraccion del costo.
Parent-document (documento-padre). Almacena pequenos chunks-hijo para la recuperacion y el chunk-padre mas grande para el contexto. Recupera por el hijo; devuelve el padre. Se degrada con elegancia: los chunks-hijo malos aun devuelven padres razonables.
Late chunking (2024). Genera primero el embedding de todo el documento a nivel de token, luego agrupa (pooling) los embeddings de token en embeddings de chunk. Preserva el contexto entre chunks. Funciona con embedders de contexto largo (BGE-M3, Jina v3). Mayor costo computacional.
Recuperacion contextual (Anthropic, 2024). Antepone a cada chunk un resumen generado por LLM de su posicion en el documento ("Este chunk es la seccion 3.2 de las clausulas de rescision..."). Mejora del 35-50% en la recuperacion en el propio benchmark de Anthropic. Costoso de indexar.
La regla que supera a todos los valores por defecto
Ajusta el tamano del chunk al tipo de consulta:
| Tipo de consulta | Tamano del chunk |
|---|---|
| Factual ("cual es el nombre del CEO?") | 256-512 tokens |
| Analitica / multi-hop | 512-1024 tokens |
| Comprension de seccion completa | 1024-2048 tokens |
Benchmark de NVIDIA de 2026. El chunk debe ser lo bastante grande como para contener la respuesta mas el contexto local, y lo bastante pequeno como para que los top-K que devuelve el recuperador se enfoquen en la respuesta en lugar del ruido de contexto.
Construyelo
Paso 1: chunking fijo y recursivo
def chunk_fixed(text, size=512, overlap=0):
step = size - overlap
return [text[i:i + size] for i in range(0, len(text), step)]
def chunk_recursive(text, size=512, seps=("\n\n", "\n", ". ", " ")):
if len(text) <= size:
return [text]
for sep in seps:
if sep not in text:
continue
parts = text.split(sep)
chunks = []
buf = ""
for p in parts:
if len(p) > size:
if buf:
chunks.append(buf)
buf = ""
chunks.extend(chunk_recursive(p, size=size, seps=seps[1:] or (" ",)))
continue
candidate = buf + sep + p if buf else p
if len(candidate) <= size:
buf = candidate
else:
if buf:
chunks.append(buf)
buf = p
if buf:
chunks.append(buf)
return [c for c in chunks if c.strip()]
return chunk_fixed(text, size)
Paso 2: chunking semantico
def chunk_semantic(text, encoder, threshold=0.6, min_chars=200, max_chars=2048):
sentences = split_sentences(text)
if not sentences:
return []
embs = encoder.encode(sentences, normalize_embeddings=True)
chunks = [[sentences[0]]]
for i in range(1, len(sentences)):
sim = float(embs[i] @ embs[i - 1])
current_len = sum(len(s) for s in chunks[-1])
if sim < threshold and current_len >= min_chars:
chunks.append([sentences[i]])
else:
chunks[-1].append(sentences[i])
result = []
for group in chunks:
text_group = " ".join(group)
if len(text_group) > max_chars:
result.extend(chunk_recursive(text_group, size=max_chars))
else:
result.append(text_group)
return result
Ajusta el threshold a tu dominio. Demasiado alto → fragmentos. Demasiado bajo → un unico chunk gigante.
Paso 3: parent-document
def chunk_parent_child(text, parent_size=2048, child_size=256):
parents = chunk_recursive(text, size=parent_size)
mapping = []
for p_idx, parent in enumerate(parents):
children = chunk_recursive(parent, size=child_size)
for child in children:
mapping.append({"child": child, "parent_idx": p_idx, "parent": parent})
return mapping
def retrieve_parent(child_query, mapping, encoder, top_k=3):
child_embs = encoder.encode([m["child"] for m in mapping], normalize_embeddings=True)
q_emb = encoder.encode([child_query], normalize_embeddings=True)[0]
scores = child_embs @ q_emb
top = np.argsort(-scores)[:top_k]
seen, parents = set(), []
for i in top:
if mapping[i]["parent_idx"] not in seen:
parents.append(mapping[i]["parent"])
seen.add(mapping[i]["parent_idx"])
return parents
Idea clave: deduplica los padres. Multiples hijos pueden mapear al mismo padre; devolverlos todos desperdiciaria contexto.
Paso 4: recuperacion contextual (patron Anthropic)
def contextualize_chunks(document, chunks, llm):
context_prompts = [
f"""<document>{document}</document>
Here is the chunk to situate: <chunk>{c}</chunk>
Write 50-100 words placing this chunk in the document's context."""
for c in chunks
]
contexts = llm.batch(context_prompts)
return [f"{ctx}\n\n{c}" for ctx, c in zip(contexts, chunks)]
Indexa los chunks contextualizados. En el momento de la consulta, la recuperacion se beneficia de la senal extra del entorno.
Paso 5: evalua
def recall_at_k(queries, corpus_chunks, encoder, k=5):
chunk_embs = encoder.encode(corpus_chunks, normalize_embeddings=True)
hits = 0
for q_text, gold_idxs in queries:
q_emb = encoder.encode([q_text], normalize_embeddings=True)[0]
top = np.argsort(-(chunk_embs @ q_emb))[:k]
if any(i in gold_idxs for i in top):
hits += 1
return hits / len(queries)
Haz benchmark siempre. La "mejor" estrategia para tu corpus puede no coincidir con ninguna publicacion de blog.
Trampas
- Chunking evaluado solo en consultas factuales. Las consultas multi-hop revelan ganadores muy diferentes. Usa un conjunto de evaluacion estratificado por tipo de consulta.
- Chunking semantico sin tamano minimo. Produce fragmentos de 40 tokens que perjudican la recuperacion. Impon siempre
min_tokens. - Solapamiento como culto al cargamento. Los estudios de 2026 encuentran que el solapamiento a menudo no aporta beneficio y duplica el costo del indice. Mide, no supongas.
- Sin imposicion de min/max. Chunks de 5 tokens o de 5000 tokens, ambos rompen la recuperacion. Acota (clamp).
- Chunking entre documentos. Nunca dejes que un chunk abarque dos documentos. Haz siempre el chunk por documento y luego fusiona.
Usalo
El stack de 2026:
| Situacion | Estrategia |
|---|---|
| Primera construccion, corpus desconocido | Recursivo, 512 tokens, sin solapamiento |
| QA factual | Recursivo, 256-512 tokens |
| Analitica / multi-hop | Recursivo, 512-1024 tokens + parent-document |
| Muchas referencias cruzadas (contratos, articulos) | Late chunking o recuperacion contextual |
| Corpus conversacional / de dialogo | Chunks por turno + metadatos del hablante |
| Enunciados cortos (tweets, resenas) | Un documento = un chunk |
Empieza con recursivo de 512. Mide recall@5 en un conjunto de evaluacion de 50 consultas. Ajusta a partir de ahi.
Despliegalo
Guarda como outputs/skill-chunker.md:
---
name: chunker
description: Pick a chunking strategy, size, and overlap for a given corpus and query distribution.
version: 1.0.0
phase: 5
lesson: 23
tags: [nlp, rag, chunking]
---
Given a corpus (document types, avg length, domain) and query distribution (factoid / analytical / multi-hop), output:
1. Strategy. Recursive / sentence / semantic / parent-document / late / contextual. Reason.
2. Chunk size. Token count. Reason tied to query type.
3. Overlap. Default 0; justify if >0.
4. Min/max enforcement. `min_tokens`, `max_tokens` guards.
5. Evaluation plan. Recall@5 on 50-query stratified eval set (factoid, analytical, multi-hop).
Refuse any chunking strategy without min/max chunk size enforcement. Refuse overlap above 20% without an ablation showing it helps. Flag semantic chunking recommendations without a min-token floor.
Ejercicios
- Facil. Haz el chunk de un documento de 20 paginas con fixed(512, 0), recursive(512, 0) y recursive(512, 100). Compara el conteo de chunks y la calidad de los limites.
- Medio. Construye un conjunto de evaluacion de 30 consultas sobre 5 documentos. Mide recall@5 para recursivo, semantico y parent-document. Cual gana? Coincide con las publicaciones de blog?
- Dificil. Implementa la recuperacion contextual. Mide la mejora de MRR sobre el recursivo de linea base. Reporta el costo de indexacion (llamadas a LLM) frente a la ganancia de exactitud.
Terminos Clave
| Termino | Lo que la gente dice | Lo que realmente significa |
|---|---|---|
| Chunk | Un trozo de un documento | Unidad de subdocumento que recibe embedding, se indexa y se recupera. |
| Solapamiento (overlap) | Margen de seguridad | N tokens compartidos entre chunks adyacentes; a menudo inutil en los benchmarks de 2026. |
| Chunking semantico | Chunking inteligente | Divide donde cae la similitud de embedding entre oraciones adyacentes. |
| Parent-document | Recuperacion en dos niveles | Recupera hijos pequenos, devuelve padres mas grandes. |
| Late chunking | Chunk despues del embedding | Genera el embedding de todo el documento a nivel de token y lo agrupa en vectores de chunk. |
| Recuperacion contextual | El truco de Anthropic | Resumen generado por LLM antepuesto a cada chunk antes de la indexacion. |
| Acantilado de contexto | El muro de los 2500 tokens | Caida de calidad observada alrededor de los 2,5k tokens de contexto en RAG (ene 2026). |
Lecturas Adicionales
- Yepes et al. / LangChain — Recursive Character Splitting docs — el estandar en produccion.
- Vectara (2024, NAACL 2025). Chunking configurations analysis — el chunking importa tanto como la eleccion del embedding.
- Jina AI — Late Chunking in Long-Context Embedding Models (2024) — el articulo sobre late chunking.
- Anthropic — Contextual Retrieval — mejora del 35-50% en la recuperacion con prefijos de contexto generados por LLM.
- NVIDIA 2026 chunk-size benchmark — Premai summary — tamano del chunk por tipo de consulta.