Inicio / Artículos / Por qué un mal chunking arruina la búsqueda vectorial, incluso con buenos embeddings

Por qué un mal chunking arruina la búsqueda vectorial, incluso con buenos embeddings

Un divisor fijo de 800 caracteres partió por la mitad una frase clave de un reglamento y estropeó la recuperación. Cómo ayudan el solapamiento y el chunking recursivo, y por qué importa cada etapa del pipeline.

809 palabras

Cuando la búsqueda vectorial devuelve tonterías, las primeras sospechosas son la base de datos y el modelo de embeddings. Casi siempre ambas son inocentes. En un pipeline de ingesta en TypeScript basado en el parseo de PDF, embeddings de Gemini y ChromaDB, la culpable resultó ser una sola línea de lógica de conteo de caracteres en el chunker.

Al principio todo parecía ir bien. El código compilaba y los vectores estaban bien guardados en el almacén. Luego, una consulta sobre las normas de matrícula de estudiantes, lanzada contra un PDF con el reglamento de una universidad, devolvió pura basura.

Qué hacía el primer chunker

El modelo mental inicial era de una simpleza vergonzosa:

  1. Extraer el texto del PDF.
  2. Cortarlo cada 800 caracteres.
  3. Generar el embedding de cada trozo.
  4. Meter los vectores en ChromaDB.

En lugar de retocar la configuración de la base de datos, lo útil fue imprimir los chunks en bruto y leerlos. Una frase clave sobre dar de baja a los estudiantes que no habían pagado sus tasas estaba cortada justo por la mitad de una palabra:

  • El chunk 0 terminaba con: "...the right to decline or d"
  • El chunk 1 empezaba con: "e-register students who have not completed fee payments."

"de-register" se convirtió en una "d" suelta en un chunk y en "e-register" en el siguiente. La palabra rota era fea, pero la pérdida de significado era peor. El chunk 0 ya no contenía la acción. El chunk 1 ya no contenía ni el sujeto ni la condición que explica por qué ocurre esa acción.

Así que la base vectorial no estaba pasando por alto el fragmento relevante. El significado se había destruido antes de que el modelo de embeddings llegara a verlo.

El solapamiento da una segunda oportunidad

La siguiente versión añadió un solapamiento de 100 caracteres, de modo que cada chunk volvía a recoger el final del anterior en vez de empezar como un bloque aislado de 800 caracteres.

Se suele creer que el solapamiento evita que una frase se parta. No es así. Un límite fijo puede seguir cayendo en un sitio terrible. Lo que hace de verdad el solapamiento es dar al texto cercano a un mal corte otra oportunidad de aparecer junto a su contexto.

Conviene verlo como un seguro y no como una prevención: el solapamiento no impide el corte, solo le da al significado una segunda ocasión para sobrevivir a él.

La recuperación mejoró de inmediato, pero el diseño seguía siendo erróneo en su base, porque razonaba en caracteres en bruto y no en lenguaje. Aún podía partir un token o un fragmento de código por la mitad y dejarte algo como function calculateT.

Eso obligaba a cambiar la pregunta que se hace el chunker:

  • Pregunta antigua: ¿dónde terminan los próximos 800 caracteres?
  • Pregunta nueva: ¿dónde está el límite semántico más natural para cortar este texto?

La respuesta fue el chunking recursivo. Primero se prueban los separadores estructurales más grandes, como secciones y párrafos, y solo se recurre a otros más pequeños, como líneas, frases y palabras, cuando un trozo sigue superando el límite de tamaño. Una regla que vale la pena colgar encima del código:

// The Golden Rule of Chunking:
// Preserve meaning at scale; cut aggressively only when forced to.

Cada etapa hereda la anterior

La división recursiva es solo un detalle de implementación. La lección más importante es cómo la representación se propaga en cascada por un sistema de IA:

Doc ➔ Extraction ➔ Chunking ➔ Embedding ➔ Storage ➔ Retrieval ➔ LLM ➔ Answer

Cada nodo posterior está limitado por lo que le entrega el nodo anterior. Si la ingesta destroza el contexto, ninguna ingeniería de prompts ni ningún ajuste del índice lo va a recuperar. Si entra basura, sale basura semántica.

Arreglar una cosa destapa dos más

En cuanto el chunker se portó bien, aparecieron justo detrás dos problemas nuevos.

  • Estructura: los párrafos son fáciles, pero ¿qué pasa con una tabla financiera que ocupa tres páginas o con un diseño a varias columnas? Si se divide de forma ingenua, los encabezados de las columnas quedan separados de los valores que tienen debajo y los datos dejan de significar nada.
  • El precio del solapamiento: el contexto no sale gratis. Más solapamiento significa más chunks, lo que implica más llamadas a la API de embeddings, más almacenamiento y una indexación más lenta. Con 10.000 vectores no lo notarás. Con 1.000.000 es un cuello de botella real, de ingeniería y de costes.

Cómo cambió el modelo mental

Antes, hacer chunking significaba partir el texto en trozos para que cupiera en el almacenamiento. Ahora significa decidir qué información tiene que permanecer unida para que los modelos posteriores puedan representarla y recuperarla correctamente.

Escribir el código fue lo fácil. Lo difícil fue encontrar la suposición oculta que lo rompía todo, y ese es el hábito que merece la pena conservar: constrúyelo, rómpelo, averigua por qué y sal con un modelo más preciso del sistema.