Cómo construir un sistema RAG en 10 líneas de Python sin marcos

Desbloquea el poder de RAG (Retrieval Augmented Generation) con este tutorial de código Python de 10 líneas. Sumerge en la recuperación de información, construye tu propio sistema de documentos y aprovecha los LLM para respuestas sólidas, todo sin depender de marcos complejos. Optimiza tu contenido para SEO y compromiso.

24 de febrero de 2025

party-gif

Descubre cómo construir un poderoso sistema de generación aumentada por recuperación (RAG) desde cero en solo 10 líneas de código de Python, sin depender de ningún marco externo. Este enfoque conciso pero integral te brinda una comprensión profunda de los componentes centrales, lo que te permite crear soluciones robustas y personalizables impulsadas por IA para tus aplicaciones basadas en documentos.

Comprender el concepto de Retrieval Augmented Generation (RAG)

Retrieval Augmented Generation (RAG) es una técnica utilizada para la recuperación de información de sus propios documentos. Implica un proceso de dos pasos:

  1. Creación de la base de conocimiento: Los documentos se dividen en subdocumentos más pequeños y se calculan los incrustaciones para cada fragmento. Estos incrustaciones y los fragmentos originales se almacenan, generalmente en un almacén de vectores.

  2. Recuperación y generación: Cuando llega una nueva consulta del usuario, se calcula una incrustación para la consulta. Se recuperan los fragmentos más relevantes comparando la incrustación de la consulta con las incrustaciones de los fragmentos almacenados. Los fragmentos recuperados se agregan entonces a la consulta original y se envían a un modelo de lenguaje (LLM) para generar la respuesta final.

Esta configuración se denomina "pipeline de generación con recuperación aumentada", ya que la generación del LLM se aumenta con la información relevante recuperada de la base de conocimiento.

Si bien existen marcos como Langchain y LlamaIndex que simplifican la implementación de los pipelines de RAG, los componentes principales se pueden construir usando solo Python, un modelo de incrustación y un LLM. Este enfoque proporciona una mejor comprensión de los conceptos subyacentes y permite una mayor flexibilidad para personalizar el pipeline.

Aprender a dividir documentos en párrafos

Al construir un sistema de generación con recuperación aumentada (RAG), uno de los pasos clave es dividir los documentos de entrada en piezas más pequeñas y manejables. En este ejemplo, estamos dividiendo el documento de entrada (un artículo de Wikipedia) en párrafos.

La justificación de este enfoque es que los párrafos a menudo representan unidades lógicas de información que se pueden recuperar y utilizar eficazmente para aumentar la consulta del usuario. Al descomponer el documento de esta manera, podemos identificar mejor las secciones más relevantes para incluir en la respuesta final.

El código para este paso es el siguiente:

# Dividir el documento en párrafos
chunks = [paragraph.strip() for paragraph in text.split('\n') if paragraph.strip()]

Aquí, utilizamos un enfoque simple de dividir el texto de entrada en caracteres de nueva línea para extraer los párrafos individuales. Luego, eliminamos los espacios en blanco iniciales y finales de cada párrafo para asegurar una representación limpia.

La lista de chunks resultante contiene los párrafos individuales, que luego se pueden incrustar y almacenar en el almacén de vectores. Esto nos permite recuperar de manera eficiente los párrafos más relevantes en función de la consulta del usuario e incluirlos en la respuesta final generada por el modelo de lenguaje.

Incrustar documentos y consultas de usuario utilizando Sentence Transformers

Para incrustar los fragmentos del documento y la consulta del usuario, utilizaremos la biblioteca Sentence Transformers. Esta biblioteca proporciona modelos preentrenados que pueden generar incrustaciones de alta calidad para el texto.

Primero, cargamos el modelo de incrustación:

from sentence_transformers import SentenceTransformer
embedding_model = SentenceTransformer('all-mpnet-base-v2')

A continuación, calculamos las incrustaciones de los fragmentos del documento:

chunk_embeddings = embedding_model.encode(chunks, normalize_embeddings=True)

Aquí, chunks es una lista de los fragmentos de texto del documento. La opción normalize_embeddings=True asegura que las incrustaciones se normalicen, lo cual es importante para el cálculo de similitud posterior.

Para obtener la incrustación de la consulta del usuario, simplemente ejecutamos:

query_embedding = embedding_model.encode([user_query], normalize_embeddings=True)[0]

Ahora tenemos las incrustaciones de los fragmentos del documento y la consulta del usuario, que podemos usar para el paso de recuperación.

Recuperar fragmentos relevantes en función de la similitud del coseno

Para recuperar los fragmentos más relevantes en función de la consulta del usuario, primero debemos calcular la similitud del coseno entre la incrustación de la consulta y las incrustaciones de los fragmentos del documento. Así es como podemos hacerlo:

  1. Calcular la incrustación de la consulta del usuario utilizando el mismo modelo de incrustación que los fragmentos del documento.
  2. Calcular el producto punto entre la incrustación de la consulta y cada una de las incrustaciones de los fragmentos del documento. Esto nos da las puntuaciones de similitud del coseno.
  3. Ordenar los fragmentos del documento en función de las puntuaciones de similitud del coseno y seleccionar los K fragmentos más relevantes.
  4. Recuperar el contenido de texto de los fragmentos seleccionados para usarlo como contexto para el modelo de lenguaje.

Los pasos clave son:

  1. Calcular la incrustación de la consulta
  2. Calcular las puntuaciones de similitud del coseno
  3. Ordenar y seleccionar los K fragmentos superiores
  4. Recuperar el contenido de texto de los fragmentos

Este enfoque simple nos permite recuperar de manera eficiente la información más relevante de la colección de documentos para aumentar la consulta del usuario antes de pasarla al modelo de lenguaje para la generación.

Aumentar la consulta del usuario con los fragmentos recuperados y generar una respuesta utilizando OpenAI GPT-4

Para generar una respuesta utilizando los fragmentos recuperados y la consulta del usuario, primero creamos un mensaje que incluye los fragmentos relevantes y la consulta del usuario. Luego, utilizamos el modelo OpenAI GPT-4 para generar una respuesta en función de este mensaje.

Aquí está el código:

# Obtener la consulta del usuario
user_query = "¿Cuál es la capital de Francia?"

# Recuperar los fragmentos más relevantes en función de la consulta del usuario
top_chunk_ids = [6, 8, 5]
top_chunks = [chunks[i] for i in top_chunk_ids]

# Crear el mensaje
prompt = "Usa el siguiente contexto para responder a la pregunta al final. Si no sabes la respuesta, di que no lo sabes y no intentes inventar una respuesta.\n\nContexto:\n"
for chunk in top_chunks:
    prompt += chunk + "\n\n"
prompt += f"\nPregunta: {user_query}"

# Generar la respuesta utilizando OpenAI GPT-4
response = openai_client.chat.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": prompt}
    ],
    max_tokens=1024,
    n=1,
    stop=None,
    temperature=0.7,
).choices[0].message.content

print(response)

En este código, primero recuperamos los fragmentos más relevantes en función de la consulta del usuario al encontrar los 3 fragmentos superiores con las puntuaciones de similitud más altas. Luego, creamos un mensaje que incluye estos fragmentos y la consulta del usuario. Finalmente, utilizamos el modelo OpenAI GPT-4 para generar una respuesta en función de este mensaje.

La respuesta generada será una respuesta concisa y relevante a la consulta del usuario, aprovechando la información de los fragmentos recuperados.

Conclusión

En este tutorial, hemos aprendido a construir un sistema de chat completo con un sistema de recuperación de documentos utilizando solo 10 líneas de código de Python. Cubrimos los componentes clave de un pipeline de Generación Aumentada por Recuperación (RAG), incluyendo la creación de la base de conocimiento, la división de documentos, la incrustación, la recuperación y la generación utilizando un modelo de lenguaje grande.

Los puntos clave son:

  1. Puedes implementar un pipeline básico de RAG sin la necesidad de marcos complejos como Langchain o LlamaIndex. Python puro y algunas bibliotecas son suficientes.
  2. Dividir tus documentos en función de la estructura (por ejemplo, párrafos) es una estrategia simple pero efectiva para la mayoría de los casos de uso.
  3. Incrustar los fragmentos del documento y la consulta del usuario, y luego calcular las puntuaciones de similitud, te permite recuperar la información más relevante para aumentar la consulta del usuario.
  4. Integrar los fragmentos recuperados con la consulta del usuario y enviarlos a un modelo de lenguaje grande permite generar una respuesta relevante e informativa.

Si bien este ejemplo proporciona una base sólida, hay muchas oportunidades para construir sistemas de RAG más robustos y avanzados. Marcos como Langchain y LlamaIndex pueden ser útiles al integrarse con varios almacenes de vectores y modelos de lenguaje. Sin embargo, comenzar con una implementación de Python puro puede ayudarte a comprender mejor los conceptos y componentes centrales de un pipeline de RAG.

Si estás interesado en explorar técnicas de RAG más avanzadas, te recomiendo que revises mi curso "RAG Beyond Basics", que profundiza en la construcción de sistemas de RAG complejos y listos para producción.

Preguntas más frecuentes