La asimetría útil: los dos fallos se ven diferente. Con chunks demasiado pequeños, las respuestas salen incompletas o mezclan trozos que no encajan. Con chunks demasiado grandes, salen genéricas —el modelo tenía la respuesta delante, enterrada entre material irrelevante—. Antes de mover el número, mira cuál de los dos síntomas tienes.
Trocear un texto por longitud corta donde toca la cuenta, no donde termina la idea. El
chunk_overlap repite el final de cada chunk al principio del siguiente, para que una
frase partida por la mitad siga apareciendo completa en al menos uno de los dos.
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # se mide en CARACTERES, no en tokens
chunk_overlap=100, # el 20% del chunk se repite
separators=["\n\n", "\n", ". ", " ", ""]
)
Esa lista de separators es la parte que casi nadie mira y la que hace el trabajo:
se intenta cortar por párrafo; si el trozo sigue siendo largo, por línea; luego por frase; luego
por espacio; y en último extremo, a mitad de palabra. Es decir, el corte por longitud es el
último recurso, no el primero. De ahí el nombre «recursivo».
El overlap no es gratis: si repites el 20 % del texto, indexas un 20 % más de material, pagas un 20 % más de embeddings y aumentas la probabilidad de recuperar dos chunks que dicen casi lo mismo. Es un seguro contra los cortes desafortunados, y como todo seguro, tiene prima.
El troceo por longitud es el que se usa cuando el documento no tiene estructura aprovechable. Cuando la tiene, hay cortes mucho mejores, y son gratis:
| Tipo de documento | Dónde cortar | Por qué ahí |
|---|---|---|
| Documentación técnica | Por encabezados, respetando la jerarquía | Una sección ya es una unidad de sentido |
| FAQ | Un chunk por pregunta-respuesta | El documento ya viene troceado por su autor |
| Contratos y textos legales | Por párrafo o cláusula | La cláusula es la unidad que se cita |
| Conversaciones y emails | Un chunk por mensaje | El turno es la unidad natural |
| Código fuente | Por función o clase | Media función no responde a nada |
| Texto corrido sin estructura | Recursivo por longitud | No hay nada mejor a mano |
Fíjate en el patrón: en cinco de los seis casos, el corte bueno ya está escrito en el documento. El troceo por longitud es lo que se hace cuando se ha renunciado a leer la estructura, y por eso es el que más overlap necesita.
500/100 en el
código de la primera unidad, 800/200 unas páginas después como remedio a
«respuestas genéricas», y 1000/200 en la unidad siguiente sin comentario. Todos
caen dentro de su propio rango, así que no hay contradicción — pero tampoco hay criterio.
Y hay una unidad oculta: su tabla de estrategias pone «256-512 tokens» en una fila y
«500-1000 chars» en otra como si fueran la misma magnitud, cuando
RecursiveCharacterTextSplitter cuenta caracteres. ¿Cuántos tokens son de
verdad tus 500 caracteres en tu idioma y tu tokenizador? Es una llamada al tokenizador y
cambia la cuenta de coste por consulta.
Antes de crear ningún embedding, imprime tres cosas de la lista de chunks: cuántos hay, la
distribución de sus longitudes ([len(c.page_content) for c in chunks]) y el
contenido entero de tres chunks al azar. No un preview de 100 caracteres: el chunk entero.
Lo que buscas es si cada uno se sostiene solo. Si al leer un chunk suelto no sabes de qué habla —empieza a media frase, o es todo cabecera de página—, ningún ajuste de retrieval posterior lo va a salvar. Y esa lectura de tres minutos te dice antes que ninguna métrica si tu estrategia de corte es la correcta para ese documento.