Capítulo 2: Estrategias de División de Texto
Introducción
La división de texto (chunking) es crucial para el rendimiento de RAG. El tamaño y la estrategia adecuados de los fragmentos impactan significativamente en la calidad de la recuperación y la precisión de la generación.
Por qué es importante la división en fragmentos
Problemas sin división en fragmentos:
- Los documentos completos son demasiado grandes
- La recuperación devuelve contenido irrelevante
- Se desperdicia la ventana de contexto del LLM
Parámetros clave
Tamaño del fragmento
El número de caracteres/tokens por fragmento:
| Tamaño del fragmento | Ventajas | Desventajas |
|---|---|---|
| Pequeño (256) | Recuperación precisa | Puede perder contexto |
| Mediano (512-1024) | Equilibrado | Bueno para la mayoría de los casos |
| Grande (2000+) | Más contexto | Recuperación menos precisa |
Superposición de fragmentos
Texto compartido entre fragmentos consecutivos:
Chunk 1: [_______________]
Chunk 2: [_______________]
^overlap^
¿Por qué superponer?
- Evita cortar oraciones a la mitad
- Preserva el contexto entre los límites de los fragmentos
- Típico: 10-20% del tamaño del fragmento
Estrategias de división
1. División recursiva por caracteres
El enfoque más común: divide por jerarquía de separadores:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_documents(documents)
Jerarquía de separadores:
- Doble salto de línea (párrafos)
- Salto de línea simple
- Puntos (oraciones)
- Espacios (palabras)
- Caracteres (último recurso)
2. División basada en tokens
Utiliza el conteo real de tokens (importante para los límites de los LLM):
from langchain.text_splitter import TokenTextSplitter
splitter = TokenTextSplitter(
chunk_size=500, # tokens, no caracteres
chunk_overlap=50,
encoding_name="cl100k_base" # Tokenizador de GPT-4
)
3. División por oraciones
Preserva los límites de las oraciones:
from langchain.text_splitter import SentenceTransformersTokenTextSplitter
splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=50,
tokens_per_chunk=256
)
4. División semántica
Divide según la similitud semántica:
from langchain_experimental.text_splitter import SemanticChunker
from langchain.embeddings import OpenAIEmbeddings
splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile"
)
# Fragmentos basados en similitud semántica
semantic_chunks = splitter.split_documents(documents)
5. División por encabezados Markdown
Respeta la estructura del documento:
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on
)
Cómo elegir el tamaño del fragmento
def optimal_chunk_size(embedding_model, llm_context_window, top_k):
"""
Calculate optimal chunk size
"""
# Leave room for prompt template and response
available_tokens = llm_context_window * 0.6
# Divide by number of chunks to retrieve
chunk_tokens = available_tokens / top_k
# Convert to characters (roughly 4 chars per token)
return int(chunk_tokens * 4)
# Example: GPT-4 with 8K context, retrieving 5 chunks
optimal = optimal_chunk_size(
embedding_model="text-embedding-3-small",
llm_context_window=8192,
top_k=5
)
# Result: ~3900 characters per chunk
Tabla comparativa
| Estrategia | Mejor para | Calidad del fragmento |
|---|---|---|
| Recursiva por caracteres | Propósito general | Buena |
| Basada en tokens | Gestión de contexto LLM | Excelente |
| Por oraciones | Sistemas de preguntas y respuestas | Buena |
| Semántica | Contenido variado | Excelente |
| Encabezados Markdown | Documentos estructurados | Excelente |
Mejores prácticas
- Comience con los valores predeterminados - 1000 caracteres, 200 de superposición
- Pruebe diferentes tamaños - Evalúe la calidad de la recuperación
- Adapte al contenido - Documentación técnica puede requerir fragmentos más grandes
- Considere los embeddings - Algunos modelos tienen límites máximos de tokens
- Preserve los metadatos - Registre el origen y la posición del fragmento
# Pipeline de fragmentación para producción
def chunk_documents(documents, chunk_size=1000, overlap=200):
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=overlap,
length_function=len,
add_start_index=True # Registrar la posición
)
chunks = splitter.split_documents(documents)
# Agregar índice de fragmento a los metadatos
for i, chunk in enumerate(chunks):
chunk.metadata["chunk_index"] = i
chunk.metadata["total_chunks"] = len(chunks)
return chunks
Resumen
- El tamaño del fragmento afecta la precisión de la recuperación frente al contexto
- La superposición previene la pérdida de información en los límites
- La estrategia debe coincidir con el tipo de documento
- Pruebe e itere para encontrar la configuración óptima
Próximos pasos
Continúe con Capítulo 3: Tipos de Archivos para aprender a manejar documentos complejos.