Chunking en IA: cómo dividir datos de forma inteligente
En el procesamiento de lenguaje natural, los sistemas de recuperación de información y las aplicaciones que trabajan con grandes volúmenes de texto, hay una técnica que marca la diferencia entre un sistema limitado y uno realmente útil: el chunking.El chunking consiste en dividir un texto o un conjunto de datos en fragmentos más pequeños y manejables llamados chunks. Aunque suena simple, la forma en que se realiza esta división tiene un impacto directo en la calidad de los resultados de un modelo de lenguaje, en la precisión de un sistema RAG y en el rendimiento general de cualquier aplicación que procese documentos largos.
¿Por qué es tan importante el chunking?
Los modelos de lenguaje tienen un límite de contexto. Si se intenta enviar un documento de 50 páginas de una sola vez, el modelo simplemente no puede procesarlo completo o pierde información relevante. Además, cuando se trabaja con bases de datos vectoriales, los embeddings se generan a nivel de chunk. Un mal chunking genera embeddings poco representativos y, por tanto, recuperaciones irrelevantes.Un buen chunking busca el equilibrio entre:
- Mantener suficiente contexto semántico.
- No superar los límites de tokens del modelo.
- Permitir una recuperación precisa y eficiente.
Principales estrategias de chunking
No existe una estrategia universal. La mejor elección depende del tipo de documento y del objetivo. Estas son las más utilizadas:Chunking por tamaño fijo
Se divide el texto cada X caracteres o tokens, normalmente con un pequeño solapamiento, o overlap, para no cortar ideas a la mitad. Es simple y rápido, pero puede romper oraciones o párrafos en puntos poco naturales.Chunking por oraciones o párrafos
Se respetan los límites naturales del lenguaje. Funciona bien con textos bien estructurados, pero puede generar chunks de tamaños muy desiguales.Chunking recursivo
Es una de las estrategias más usadas actualmente. Intenta dividir primero por separadores de mayor prioridad, como doble salto de línea, salto de línea, punto, espacio…, y solo si el fragmento sigue siendo demasiado grande, baja al siguiente separador. Ofrece un buen equilibrio entre coherencia semántica y control de tamaño.Chunking semántico
Utiliza embeddings o modelos de lenguaje para agrupar frases que tratan del mismo tema. Es más costoso computacionalmente, pero produce chunks mucho más coherentes.Chunking basado en estructura del documento
Especialmente útil en PDFs, Markdown o HTML: se aprovechan títulos, secciones, tablas o listas para crear límites naturales.En la práctica, la combinación más efectiva suele ser el chunking recursivo con solapamiento, normalmente entre el 10 % y el 20 % del tamaño del chunk.
Ejemplo práctico en PHP para PDFs
La mayoría de los documentos reales llegan en formato PDF. Por eso es importante extraer el texto de forma limpia antes de aplicar el chunking.Para este ejemplo usaremos la librería "smalot/pdfparser", una de las más utilizadas y estables en PHP.composer require smalot/pdfparser- Extrae el texto de un PDF
- Limpia el contenido (saltos de línea excesivos, caracteres extraños, etc.
- Aplica chunking recursivo con solapamiento
require 'vendor/autoload.php';
use Smalot\PdfParser\Parser;
class PdfTextChunker
{
private array $separators;
private int $chunkSize;
private int $chunkOverlap;
public function __construct(
int $chunkSize = 1000,
int $chunkOverlap = 200,
array $separators = ["\n\n", "\n", ". ", " ", ""]
) {
$this->chunkSize = $chunkSize;
$this->chunkOverlap = $chunkOverlap;
$this->separators = $separators;
}
/**
* Extrae el texto de un PDF y lo divide en chunks
*/
public function splitFromPdf(string $pdfPath): array
{
$parser = new Parser();
$pdf = $parser->parseFile($pdfPath);
$text = $pdf->getText();
// Limpieza básica del texto extraído
$text = $this->cleanText($text);
return $this->split($text);
}
/**
* Limpia el texto extraído del PDF
*/
private function cleanText(string $text): string
{
// Normalizar saltos de línea
$text = str_replace(["\r\n", "\r"], "\n", $text);
// Eliminar múltiples saltos de línea consecutivos
$text = preg_replace("/\n{3,}/", "\n\n", $text);
// Eliminar espacios múltiples
$text = preg_replace("/[ \t]+/", " ", $text);
// Eliminar caracteres de control (excepto saltos de línea y tabs)
$text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $text);
return trim($text);
}
public function split(string $text): array
{
return $this->recursiveSplit($text, $this->separators);
}
private function recursiveSplit(string $text, array $separators): array
{
$finalChunks = [];
$separator = $separators[0] ?? "";
$remainingSeparators = array_slice($separators, 1);
if (mb_strlen($text) <= $this->chunkSize) {
return $text === "" ? [] : [$text];
}
$splits = $separator === ""
? mb_str_split($text, $this->chunkSize)
: explode($separator, $text);
$currentChunk = "";
foreach ($splits as $split) {
$candidate = $currentChunk === ""
? $split
: $currentChunk . $separator . $split;
if (mb_strlen($candidate) <= $this->chunkSize) {
$currentChunk = $candidate;
} else {
if ($currentChunk !== "") {
$finalChunks[] = $currentChunk;
}
if (mb_strlen($split) > $this->chunkSize && !empty($remainingSeparators)) {
$subChunks = $this->recursiveSplit($split, $remainingSeparators);
$finalChunks = array_merge($finalChunks, $subChunks);
$currentChunk = "";
} else {
$currentChunk = $split;
}
}
}
if ($currentChunk !== "") {
$finalChunks[] = $currentChunk;
}
return $this->applyOverlap($finalChunks);
}
private function applyOverlap(array $chunks): array
{
if ($this->chunkOverlap <= 0 || count($chunks) <= 1) {
return $chunks;
}
$result = [];
$previous = "";
foreach ($chunks as $chunk) {
if ($previous !== "") {
$overlapText = mb_substr($previous, -$this->chunkOverlap);
$chunk = $overlapText . $chunk;
}
$result[] = $chunk;
$previous = $chunk;
}
return $result;
}
}
// ==================== Ejemplo de uso ====================
$chunker = new PdfTextChunker(
chunkSize: 900, // Ajusta según el modelo (aprox. 200-300 tokens)
chunkOverlap: 150
);
$pdfPath = 'documento.pdf'; // Cambia por la ruta de tu archivo
try {
$chunks = $chunker->splitFromPdf($pdfPath);
echo "Total de chunks generados: " . count($chunks) . PHP_EOL . PHP_EOL;
foreach ($chunks as $i => $chunk) {
echo "=== Chunk " . ($i + 1) . " (" . mb_strlen($chunk) . " caracteres) ===" . PHP_EOL;
echo mb_substr($chunk, 0, 180) . "..." . PHP_EOL . PHP_EOL;
}
} catch (Exception $e) {
echo "Error al procesar el PDF: " . $e->getMessage() . PHP_EOL;
}
Notas importantes sobre PDFs
- Calidad de extracción: Algunos PDFs, especialmente los escaneados o con diseños complejos, extraen el texto de forma desordenada. En esos casos conviene usar OCR, por ejemplo con "thiagoalessio/tesseract_ocr" antes del chunking.
- Páginas: Si se necesita mantener la información de página, se puede modificar el método para recorrer "$pdf->getPages()" y añadir metadatos a cada chunk.
- Rendimiento: Para PDFs muy grandes, cientos de páginasm es recomendable procesar página por página en lugar de cargar todo el texto de golpe.