Algoritmos de Chunking para Embeddings: Guia Completa para RAG
El chunking es el factor mas importante para el rendimiento de RAG. La diferencia entre la mejor y peor estrategia puede significar hasta un 9% de gap en recall. Si tu sistema RAG no encuentra la informacion correcta, probablemente el problema no es el retriever, son los chunks.
En esta guia cubrimos desde lo basico hasta tecnicas avanzadas como Late Chunking y Contextual Retrieval, con ejemplos de codigo en TypeScript listos para usar.
Por que Importa el Chunking
Los modelos de embeddings tienen dos limitaciones fundamentales:
Contexto muy grande:
- El vector pierde especificidad
- Mezcla multiples temas, resultando en un embedding "difuso"
- Puede exceder la ventana de contexto del modelo (truncamiento)
Contexto muy pequeno:
- Pierde informacion contextual critica
- "La ciudad tiene 3.85 millones de habitantes" sin saber que habla de Paris
- Referencias anaforicas rotas
El chunking es el arte de encontrar el balance correcto.
Ventanas de Contexto de Modelos de Embedding
| Modelo | Max Tokens |
|---|---|
| text-embedding-3-small | 8,191 |
| text-embedding-3-large | 8,191 |
| jina-embeddings-v3 | 8,192 |
| Cohere embed-v3 | 512 |
Estrategias de Chunking
1. Fixed-Size Chunking (Tamano Fijo)
El mas usado: ~80% de aplicaciones RAG.
La estrategia mas simple: dividir el texto en fragmentos de tamano predeterminado con overlap.
typescript// Concepto basico const chunks = []; for (let i = 0; i < text.length; i += chunkSize - overlap) { chunks.push(text.slice(i, i + chunkSize)); }
Parametros optimos segun research:
- Chunk size: 400-500 tokens (88-89% recall con 400 tokens)
- Overlap: 10-20% (50-100 tokens)
Ventajas: Simple, rapido, predecible, sin dependencias externas.
Desventajas: Corta en lugares arbitrarios, puede romper oraciones/parrafos, no entiende estructura del documento.
2. Recursive Character Splitting
El estandar de la industria. Intenta dividir por separadores jerarquicos.
typescript// Orden de separadores (del mas al menos preferido) const separators = ["\n\n", "\n", " ", ""]; // Algoritmo: // 1. Intenta dividir por "\n\n" (parrafos) // 2. Si chunks > chunkSize, divide por "\n" (lineas) // 3. Si aun > chunkSize, divide por " " (palabras) // 4. Ultimo recurso: caracteres individuales
Implementacion con LangChain:
typescriptimport { RecursiveCharacterTextSplitter } from "@langchain/textsplitters"; const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000, chunkOverlap: 200, separators: ["\n\n", "\n", ".", "!", "?", " ", ""] }); const chunks = await splitter.splitText(document);
Ventajas: Respeta estructura del texto, configurable con separadores custom, ampliamente probado.
Desventajas: No entiende semantica, dependencia pesada (21MB).
3. Sentence-Based Chunking
Divide por oraciones completas y las agrupa hasta alcanzar el tamano deseado.
typescript// Pseudocodigo const sentences = text.split(/[.!?]+/); let currentChunk = ""; for (const sentence of sentences) { if ((currentChunk + sentence).length < maxSize) { currentChunk += sentence; } else { chunks.push(currentChunk); currentChunk = sentence; } }
Ventajas: Nunca rompe oraciones, unidades semanticas completas.
Desventajas: Tamano de chunks variable, no detecta cambios de tema.
4. Semantic Chunking
El mas sofisticado. Usa embeddings para detectar cambios de tema.
El corazon del chunking semantico es su algoritmo de deteccion de breakpoints:
- Dividir texto en oraciones
- Generar embedding para cada oracion
- Calcular similitud coseno entre oraciones adyacentes
- Detectar "breakpoints" donde similitud cae bajo umbral
- Agrupar oraciones entre breakpoints
Metodos de umbral (threshold):
| Metodo | Descripcion | Uso |
|---|---|---|
| Percentile | Breakpoint si distancia > percentil 95 | Mas comun |
| Standard Deviation | Breakpoint si > X desviaciones estandar | Tecnico |
| Interquartile | Usa cuartiles estadisticos | Papers academicos |
Implementacion conceptual:
typescriptasync function semanticChunk(text: string, embedder: Embedder) { const sentences = splitIntoSentences(text); const embeddings = await Promise.all( sentences.map(s => embedder.embed(s)) ); const distances: number[] = []; for (let i = 0; i < embeddings.length - 1; i++) { distances.push(1 - cosineSimilarity(embeddings[i], embeddings[i + 1])); } // Percentile 95 como umbral const threshold = percentile(distances, 95); const chunks: string[] = []; let currentChunk = sentences[0]; for (let i = 0; i < distances.length; i++) { if (distances[i] > threshold) { chunks.push(currentChunk); currentChunk = sentences[i + 1]; } else { currentChunk += " " + sentences[i + 1]; } } chunks.push(currentChunk); return chunks; }
Ventajas: Chunks semanticamente coherentes, respeta cambios de tema naturales, ideal para documentos densos.
Desventajas: Computacionalmente costoso (N embeddings), tamanos de chunk variables. Research reciente sugiere que no siempre justifica el costo.
Algoritmos Avanzados
Late Chunking (Jina AI)
Innovacion 2024. Invierte el orden: primero embeddings, luego chunking.
El problema que resuelve:
El chunking tradicional pierde contexto. Si tienes:
"Paris es la capital de Francia. La ciudad tiene mas de 3.85 millones de habitantes..."
Un chunk tradicional con "La ciudad tiene mas de 3.85 millones..." no sabe que "la ciudad" es Paris. El embedding pierde esa conexion.
Como funciona Late Chunking:
- Pasar documento completo al modelo de embeddings (long-context)
- Obtener token-level embeddings (cada token tiene contexto global)
- Despues dividir en chunks
- Mean pooling de los token embeddings para cada chunk
El embedding de "La ciudad" ya incluye que se refiere a Paris porque se proceso con contexto global.
Requiere: Modelos long-context como jina-embeddings-v3 (8K tokens).
Paper: Late Chunking: Contextual Chunk Embeddings
Contextual Retrieval (Anthropic)
Innovacion 2024. Usa un LLM para anadir contexto a cada chunk.
El flujo es:
- Chunking normal del documento
- Para cada chunk, llamar al LLM con el documento completo + el chunk
- El LLM genera 50-100 tokens de contexto explicativo
- Prepend del contexto al chunk antes de crear el embedding
Prompt usado por Anthropic:
<document>
{{WHOLE_DOCUMENT}}
</document>
Here is the chunk we want to situate:
<chunk>
{{CHUNK_CONTENT}}
</chunk>
Give a short, succinct context to situate this chunk within
the overall document for improving search retrieval.
Resultados:
- -49% errores de retrieval (solo contextual embeddings)
- -67% errores (contextual + reranking)
Costo con prompt caching: ~$1.02 por millon de tokens de documento.
Fuente: Anthropic Blog
Hierarchical/Parent-Child Chunking
Arquitectura de dos niveles para balancear precision y contexto.
La idea:
- Parent chunks (500-2000 tokens): Secciones completas, preservan contexto amplio
- Child chunks (100-500 tokens): Fragmentos precisos, mejor match con queries especificos
Flujo de retrieval:
- Indexar solo los children (embeddings mas precisos)
- Buscar en children (mejor match con queries especificos)
- Retornar el parent del child encontrado (contexto completo para el LLM)
Con LlamaIndex:
typescriptimport { HierarchicalNodeParser, AutoMergingRetriever } from "llamaindex"; const nodeParser = new HierarchicalNodeParser.fromDefaults({ chunkSizes: [2048, 512, 128] // 3 niveles }); // AutoMerging: si se recuperan >50% de children de un parent, // automaticamente retorna el parent completo
Librerias TypeScript
Comparativa de Peso
| Libreria | Tamano (node_modules) | Enfoque |
|---|---|---|
| chonkie | ~2MB | Lightweight, multiples chunkers |
| llm-splitter | ~1MB | Greedy single-pass |
| llm-chunk | ~1KB | Ultra minimal |
| @langchain/textsplitters | 21MB | Full-featured |
| llamaindex | 36MB | Framework completo |
Chonkie (Recomendada para nuevos proyectos)
bashnpm install chonkie # o minimal: npm install chonkie --omit=optional
Chunkers disponibles:
TokenChunker- Por tokensSentenceChunker- Por oracionesRecursiveChunker- Estilo LangChainCodeChunker- Optimizado para codigo
Proximamente: SemanticChunker, LateChunker
typescriptimport { TokenChunker, RecursiveChunker } from 'chonkie'; // Token-based const tokenChunker = await TokenChunker.create({ chunkSize: 512, chunkOverlap: 50 }); // Recursive (como LangChain) const recursiveChunker = await RecursiveChunker.create({ chunkSize: 1000, chunkOverlap: 200, separators: ["\n\n", "\n", ".", " "] }); const chunks = await tokenChunker.chunk(text); chunks.forEach(c => console.log(c.text, c.tokenCount));
Docs: docs.chonkie.ai
llm-splitter (Nearform)
bashnpm install llm-splitter
Enfoque greedy single-pass - sacrifica optimalidad por velocidad.
typescriptimport { split, getChunk } from 'llm-splitter'; const chunks = split(text, { chunkSize: 512, chunkOverlap: 50, chunkStrategy: 'paragraph', // o 'character' }); // Cada chunk tiene posiciones para reconstruir chunks.forEach(chunk => { console.log(chunk.text); console.log(`Posicion: ${chunk.start}-${chunk.end}`); });
Feature util: Almacenar solo {start, end, embedding} sin duplicar texto.
@langchain/textsplitters
bashnpm install @langchain/textsplitters
El mas completo, pero pesado.
typescriptimport { RecursiveCharacterTextSplitter, TokenTextSplitter, MarkdownTextSplitter } from "@langchain/textsplitters"; // Recursive (mas usado) const splitter = new RecursiveCharacterTextSplitter({ chunkSize: 1000, chunkOverlap: 200 }); // Por tokens (con tiktoken) const tokenSplitter = new TokenTextSplitter({ chunkSize: 500, chunkOverlap: 50, encodingName: "cl100k_base" // GPT-4 encoding }); // Para Markdown const mdSplitter = new MarkdownTextSplitter({ chunkSize: 1000, chunkOverlap: 0 }); const docs = await splitter.createDocuments([text]);
Splitters especializados: MarkdownTextSplitter, LatexTextSplitter, HTMLSectionSplitter, PythonCodeTextSplitter.
Recomendaciones Practicas
Matriz de Decision
| Escenario | Estrategia | Libreria |
|---|---|---|
| MVP / Prototipo | Fixed-size 400 tokens, 10% overlap | llm-chunk |
| Produccion general | Recursive con separadores | chonkie |
| Documentos tecnicos | Semantic chunking | llamaindex |
| Codigo fuente | Code-aware splitter | chonkie CodeChunker |
| Maxima precision | Late Chunking + Reranking | Jina API |
| Budget ilimitado | Contextual Retrieval | Anthropic API |
| Documentos largos | Hierarchical + AutoMerging | llamaindex |
Configuracion Inicial Recomendada
Para el 80% de casos de uso:
typescriptconst config = { chunkSize: 500, // tokens chunkOverlap: 50, // 10% strategy: "recursive", separators: [ "\n\n", // Parrafos "\n", // Lineas ". ", // Oraciones "! ", "? ", "; ", ", ", " ", // Palabras "" // Caracteres (fallback) ] };
Pipeline Completo para 2025
- Pre-procesamiento: Limpieza, normalizacion, extraccion de metadata
- Chunking Primario: Recursive splitter (500 tokens, 10% overlap)
- [Opcional] Contextualizacion: Anthropic Contextual Retrieval o Late Chunking con Jina v3
- Embedding: text-embedding-3-small (costo/beneficio) o text-embedding-3-large (maxima calidad)
- [Opcional] Reranking: Cohere rerank-v3 (Top 20 → Top 5)
- Storage: Vector DB (Pinecone, MongoDB Atlas, Weaviate)
Fuentes
Articulos tecnicos:
- Weaviate: Chunking Strategies for RAG
- Pinecone: Chunking Strategies
- Stack Overflow: Breaking up is hard to do
- Firecrawl: Best Chunking Strategies 2025
Papers:
Tecnicas avanzadas:
Implementa RAG con Formmy
En Formmy, usamos chunking semantico optimizado para nuestro sistema RAG. Sube tus documentos y deja que nuestros agentes encuentren la informacion que necesitas.
Crea tu chatbot con conocimiento contextual en minutos:
Prueba Formmy Gratis - Manejamos la complejidad del RAG por ti.
¿Tienes preguntas sobre como implementar chunking en tu proyecto? Nuestro equipo esta listo para ayudarte.
