Este artículo explica cómo utilizar un LLM (modelo de lenguaje grande) para realizar la fragmentación de un documento según el concepto de “idea”.
Utilizo el modelo gpt-4o de OpenAI para este ejemplo, pero el mismo enfoque se puede aplicar con cualquier otro LLM, como los de Hugging Face, Mistral y otros.
Todos pueden acceder a esto. artículo gratis.
Consideraciones sobre la fragmentación de documentos
En psicología cognitiva, un fragmento representa una “unidad de información”.
Este concepto también se puede aplicar a la informática: al utilizar un LLM, podemos analizar un documento y producir un conjunto de fragmentos, generalmente de longitud variable, donde cada fragmento expresa una “idea” completa.
Esto significa que el sistema divide un documento en “fragmentos de texto” de modo que cada uno exprese un concepto unificado, sin mezclar diferentes ideas en el mismo fragmento.
El objetivo es crear una base de conocimientos compuesta por elementos independientes que puedan relacionarse entre sí sin superponer diferentes conceptos dentro del mismo fragmento.
Por supuesto, durante el análisis y la división, puede haber múltiples fragmentos que expresen la misma idea si esa idea se repite en diferentes secciones o se expresa de manera diferente dentro del mismo documento.
Empezando
El primer paso es identificar un documento que formará parte de nuestra base de conocimientos.
Suele ser un documento PDF o Word, que se lee página por página o por párrafos y se convierte en texto.
Para simplificar, supongamos que ya tenemos una lista de párrafos de texto como el siguiente, extraídos de La vuelta al mundo en ochenta días:
documents = [
"""On October 2, 1872, Phileas Fogg, an English gentleman, left London for an extraordinary journey.
He had wagered that he could circumnavigate the globe in just eighty days.
Fogg was a man of strict habits and a very methodical life; everything was planned down to the smallest detail, and nothing was left to chance.
He departed London on a train to Dover, then crossed the Channel by ship. His journey took him through many countries,
including France, India, Japan, and America. At each stop, he encountered various people and faced countless adventures, but his determination never wavered.""","""However, time was his enemy, and any delay risked losing the bet. With the help of his faithful servant Passepartout, Fogg had to face
unexpected obstacles and dangerous situations.""",
"""Yet, each time, his cunning and indomitable spirit guided him to victory, while the world watched in disbelief.""",
"""With one final effort, Fogg and Passepartout reached London just in time to prove that they had completed their journey in less than eighty days.
This extraordinary adventurer not only won the bet but also discovered that the true treasure was the friendship and experiences he had accumulated along the way."""
]
Supongamos también que estamos usando un LLM que acepta una cantidad limitada de tokens para entrada y salida, al que llamaremos nr_token_entrada y nr_token_salida.
Para este ejemplo, estableceremos nr_token_entrada = 300 y nr_token_salida = 250.
Esto significa que para una división exitosa, la cantidad de tokens tanto para la solicitud como para el documento a analizar debe ser inferior a 300, mientras que el resultado producido por el LLM no debe consumir más de 250 tokens.
Usando el tokenizador proporcionado por OpenAI vemos que nuestros documentos de base de conocimiento están compuestos por 254 tokens.
Por lo tanto, no es posible analizar todo el documento a la vez, ya que aunque la entrada se puede procesar en una sola llamada, no cabe en la salida.
Entonces, como paso preparatorio, debemos dividir el documento original en bloques de no más de 250 tokens.
Estos bloques luego se pasarán al LLM, que los dividirá en partes.
Para ser cautelosos, establezcamos el tamaño máximo de bloque en 200 tokens.
Generando bloques
El proceso de generación de bloques es el siguiente:
- Considere el primer párrafo de la base de conocimientos (KB), determine la cantidad de tokens que requiere y, si es menos de 200, se convierte en el primer elemento del bloque.
- Analice el tamaño del siguiente párrafo y, si el tamaño combinado con el bloque actual es inferior a 200 tokens, agréguelo al bloque y continúe con los párrafos restantes.
- Un bloque alcanza su tamaño máximo cuando intentar agregar otro párrafo hace que el tamaño del bloque supere el límite.
- Repita desde el paso uno hasta que se hayan procesado todos los párrafos.
El proceso de generación de bloques supone, por simplicidad, que cada párrafo es más pequeño que el tamaño máximo permitido (de lo contrario, el párrafo mismo debe dividirse en elementos más pequeños).
Para realizar esta tarea utilizamos el llm_chunkizer.split_document_into_blocks función de la biblioteca LLMChunkizerLib/chunkizer.py, que se puede encontrar en el siguiente repositorio: LLMChunkizer.
Visualmente, el resultado se parece a la Figura 1.
Al generar bloques, la única regla a seguir es no exceder el tamaño máximo permitido.
No se hacen análisis ni suposiciones sobre el significado del texto.
Generando fragmentos
El siguiente paso es dividir el bloque en partes, cada una de las cuales expresa la misma idea.
Para esta tarea utilizamos el llm_chunkizer.chunk_text_with_llm función de la LLMChunkizerLib/chunkizer.py biblioteca, que también se encuentra en el mismo repositorio.
El resultado se puede ver en la Figura 2.
Este proceso funciona de forma lineal, lo que permite al LLM decidir libremente cómo formar los fragmentos.
Manejo de la superposición entre dos bloques
Como se mencionó anteriormente, durante la división de bloques, solo se considera el límite de longitud, sin tener en cuenta si los párrafos adyacentes que expresan la misma idea se dividen en bloques diferentes.
Esto es evidente en la Figura 1, donde el concepto “bla bla bla” (que representa una idea unificada) se divide entre dos bloques adyacentes.
Como puede ver en la Figura 2, el fragmentador procesa solo un bloque a la vez, lo que significa que el LLM no puede correlacionar esta información con el siguiente bloque (ni siquiera sabe que existe un siguiente bloque) y, por lo tanto, lo coloca en el último trozo dividido.
Este problema ocurre con frecuencia durante la ingesta, particularmente cuando se importa un documento largo cuyo texto no puede caber en un solo mensaje de LLM.
Para abordarlo, llm_chunkizer.chunk_text_with_llm funciona como se muestra en la Figura 3:
- El último fragmento (o los últimos N fragmentos) producidos a partir del bloque anterior se elimina de la lista de fragmentos “válidos” y su contenido se agrega al siguiente bloque que se dividirá.
- El Nuevo bloque 2 se pasa nuevamente a la función de fragmentación.
Como se muestra en la Figura 3, el contenido del fragmento M se divide de manera más efectiva en dos fragmentos, manteniendo unido el concepto “bla bla bla”.
La idea detrás de esta solución es que los últimos N fragmentos del bloque anterior representen ideas independientes, no solo párrafos no relacionados.
Por lo tanto, agregarlos al nuevo bloque permite al LLM generar fragmentos similares y al mismo tiempo crear un nuevo fragmento que une párrafos que previamente se dividieron sin tener en cuenta su significado.
Resultado de la fragmentación
Al final, el sistema produce los siguientes 6 fragmentos:
0: On October 2, 1872, Phileas Fogg, an English gentleman, left London for an extraordinary journey. He had wagered that he could circumnavigate the globe in just eighty days. Fogg was a man of strict habits and a very methodical life; everything was planned down to the smallest detail, and nothing was left to chance.
1: He departed London on a train to Dover, then crossed the Channel by ship. His journey took him through many countries, including France, India, Japan, and America. At each stop, he encountered various people and faced countless adventures, but his determination never wavered.
2: However, time was his enemy, and any delay risked losing the bet. With the help of his faithful servant Passepartout, Fogg had to face unexpected obstacles and dangerous situations.
3: Yet, each time, his cunning and indomitable spirit guided him to victory, while the world watched in disbelief.
4: With one final effort, Fogg and Passepartout reached London just in time to prove that they had completed their journey in less than eighty days.
5: This extraordinary adventurer not only won the bet but also discovered that the true treasure was the friendship and experiences he had accumulated along the way.
Consideraciones sobre el tamaño del bloque
Veamos qué sucede cuando el documento original se divide en bloques más grandes con un tamaño máximo de 1000 tokens.
Con bloques de mayor tamaño, el sistema genera 4 fragmentos en lugar de 6.
Se esperaba este comportamiento porque el LLM pudo analizar una porción más grande de contenido a la vez y pudo usar más texto para representar un solo concepto.
Aquí están los fragmentos en este caso:
0: On October 2, 1872, Phileas Fogg, an English gentleman, left London for an extraordinary journey. He had wagered that he could circumnavigate the globe in just eighty days. Fogg was a man of strict habits and a very methodical life; everything was planned down to the smallest detail, and nothing was left to chance.
1: He departed London on a train to Dover, then crossed the Channel by ship. His journey took him through many countries, including France, India, Japan, and America. At each stop, he encountered various people and faced countless adventures, but his determination never wavered.
2: However, time was his enemy, and any delay risked losing the bet. With the help of his faithful servant Passepartout, Fogg had to face unexpected obstacles and dangerous situations. Yet, each time, his cunning and indomitable spirit guided him to victory, while the world watched in disbelief.
3: With one final effort, Fogg and Passepartout reached London just in time to prove that they had completed their journey in less than eighty days. This extraordinary adventurer not only won the bet but also discovered that the true treasure was the friendship and experiences he had accumulated along the way.
Conclusiones
Es importante intentar varias ejecuciones de fragmentación, variando el tamaño del bloque pasado al fragmentador cada vez.
Después de cada intento, se deben revisar los resultados para determinar qué enfoque se adapta mejor al resultado deseado.
Próximamente
En el próximo artículo, mostraré cómo utilizar un LLM para recuperar fragmentos: LLMRetriever .
Puedes encontrar todo el código y más ejemplos en mi repositorio. LLMChunkizer.