Optimización de Embeddings en RAG
Segmentación Semántica Avanzada
Más allá del tamaño fijo: La segmentación semántica
Cuando construyes un sistema RAG, la forma en que divides tus documentos es una de las decisiones más críticas. El enfoque más simple es la segmentación por tamaño fijo, que corta el texto en fragmentos de, por ejemplo, 500 caracteres, sin importar el contenido.
El problema es que las ideas no respetan los límites de caracteres. Este método a menudo corta oraciones a la mitad o separa una pregunta de su respuesta, creando fragmentos (chunks) que carecen de contexto. Cuando estos fragmentos incompletos se convierten en embeddings, sus representaciones vectoriales son débiles y confusas. El resultado es un sistema de recuperación que devuelve información irrelevante porque no puede captar el significado completo de ninguna sección.
La calidad de la segmentación determina el rendimiento del sistema RAG. Fragmentos incoherentes conducen a respuestas imprecisas.
La solución es la segmentación semántica, una técnica que divide el texto basándose en el significado. En lugar de contar caracteres, utilizamos modelos de embeddings para analizar las relaciones semánticas entre oraciones. La meta es identificar los puntos donde un tema termina y otro comienza, asegurando que cada fragmento contenga una unidad de pensamiento coherente y completa.
Detectando puntos de ruptura semánticos
Para encontrar los puntos de ruptura naturales en un texto, medimos qué tan similares son las oraciones adyacentes. El proceso funciona así:
- Divide el texto en oraciones individuales.
- Genera un embedding para cada oración. Cada vector captura el significado de esa oración.
- Calcula la similitud del coseno entre oraciones consecutivas. Una alta similitud (cercana a 1) significa que las oraciones tratan sobre lo mismo. Una baja similitud (cercana a 0) indica un cambio de tema.
Estos valles de baja similitud son nuestros puntos de ruptura semánticos. Son las fronteras naturales entre diferentes ideas dentro del documento.
Una vez que tenemos las puntuaciones de similitud para todo el documento, necesitamos un método para decidir qué valles son lo suficientemente profundos como para justificar una división. Aquí es donde entra en juego la división basada en percentiles. En lugar de usar un umbral fijo (por ejemplo, dividir si la similitud es menor a 0.3), identificamos un percentil de las puntuaciones de similitud más bajas. Por ejemplo, podríamos decidir que cualquier puntuación por debajo del percentil 5 es un punto de ruptura. Este enfoque es adaptable, ya que se ajusta a la naturaleza del documento específico; un texto denso con transiciones sutiles tendrá umbrales diferentes a un artículo con secciones claramente definidas.
Implementación con LangChain
Afortunadamente, no tenemos que construir todo esto desde cero. ofrece herramientas que simplifican enormemente la segmentación semántica. Usando SemanticChunker junto con un modelo de embedding de la biblioteca como all-MiniLM-L6-v2, podemos implementar esta lógica con solo unas pocas líneas de código.
# Primero, asegúrate de tener las bibliotecas necesarias instaladas
# pip install langchain-experimental langchain-openai
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
# Texto de ejemplo con un claro cambio de tema
texto_ejemplo = (
"El sistema solar tiene ocho planetas. Mercurio, Venus, Tierra, Marte, Júpiter, Saturno, Urano y Neptuno. " \
"Júpiter es el más grande, con una masa dos veces y media la de todos los otros planetas juntos. " \
"Las manzanas son una fruta popular, rica en fibra y vitamina C. Existen más de 7,500 variedades de manzanas en todo el mundo. " \
"La variedad Granny Smith es conocida por su sabor ácido y su piel verde brillante."
)
# Inicializamos el segmentador semántico con un modelo de embedding
# Aquí usamos OpenAI, pero también podrías usar un modelo local de Sentence-Transformers
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile" # Usamos el método de percentil
)
# Creamos los fragmentos
docs = text_splitter.create_documents([texto_ejemplo])
# Imprimimos el resultado
for i, doc in enumerate(docs):
print(f"--- Fragmento {i+1} ---")
print(doc.page_content)
Resultado esperado del código:
--- Fragmento 1 --- El sistema solar tiene ocho planetas. Mercurio, Venus, Tierra, Marte, Júpiter, Saturno, Urano y Neptuno. Júpiter es el más grande, con una masa dos veces y media la de todos los otros planetas juntos.
--- Fragmento 2 --- Las manzanas son una fruta popular, rica en fibra y vitamina C. Existen más de 7,500 variedades de manzanas en todo el mundo. La variedad Granny Smith es conocida por su sabor ácido y su piel verde brillante.
Como puedes ver, SemanticChunker identificó correctamente la transición del tema de la astronomía a la fruticultura y dividió el texto en dos fragmentos coherentes. Cada uno de estos fragmentos ahora se puede convertir en un embedding mucho más preciso y útil para nuestro sistema de recuperación.
Al adoptar la segmentación semántica, mejoras drásticamente la capacidad de tu sistema RAG para comprender y recuperar la información correcta. Esto se traduce directamente en respuestas más precisas y relevantes para el usuario final.