Inicio / Artículos

RAG desde cero: fragmentar, incrustar, almacenar, recuperar, responder con contexto

Un recorrido paso a paso por un pipeline RAG con LangChain y FAISS: fragmentación, embeddings, recuperación de información, anclaje de prompts, errores comunes y próximas mejoras.

Editor: REACTAPP.TOP8 min de lectura1915 palabras

Fuentes: RAG, Explained the Way I Wish Someone Had Explained It to Me

Si le preguntas a un modelo de chat cuál es la política de reembolsos de una empresa en particular, rara vez admitirá no saberlo. Con mayor frecuencia, genera un párrafo fluido y creíble que no tiene nada que ver con la política real. La causa no es un defecto: un modelo de lenguaje se entrena una sola vez y luego se congela. Nunca ha leído un PDF privado, una wiki interna ni un espacio de trabajo de Notion, y no sabe nada de lo que se escribió después de su fecha de corte de entrenamiento.

La generación mejorada por recuperación de información, abreviada habitualmente como RAG, es la solución estándar, y la idea es sorprendentemente sencilla. En lugar de esperar que el modelo recuerde una respuesta, el sistema encuentra el texto relevante y lo incluye directamente en la solicitud, para luego pedirle al modelo que lo lea y responda. Todo lo demás consiste en ingeniería para garantizar que esa búsqueda sea fiable. El ejemplo que se muestra a continuación es un bot de soporte que responde a preguntas extraídas de un PDF con la política de la empresa.

Dos estudiantes, un examen

Imagínese a dos personas realizando la misma prueba. La primera memorizó un libro de texto hace meses y escribe de memoria. Si el libro estaba desactualizado o algún capítulo estaba parcialmente olvidado, sus respuestas salen seguras pero erróneas. La segunda persona puede usar un libro abierto. Antes de escribir, busca la página correspondiente, la lee y combina esa información con su razonamiento general.

Por lo general, el segundo estudiante gana, y no gracias a una inteligencia superior. Buscar información antes de responder es simplemente una forma mejor de dar una respuesta. En un sistema RAG, el modelo de lenguaje actúa como el segundo estudiante, los datos de la empresa son el libro abierto, y la base de datos vectorial es el índice que indica qué página consultar.

Dos pipelines, no uno

Cualquier sistema RAG, por complejo que sea, consta de dos pipelines separados conectados en su parte central.

  • El indexado se ejecuta sin conexión, una vez al principio y nuevamente cada vez que cambian los documentos. Los archivos en bruto se convierten en un índice buscable.
  • La recuperación y la generación se realizan con cada pregunta: se busca en el índice, se seleccionan las mejores partes y se las proporciona al modelo.

Los principiantes suelen intentar imaginar un único proceso largo, lo que genera confusión. Dividirlo en dos partes ayuda, ya que cada mitad se ejecuta en momentos diferentes y resuelve problemas distintos.

Creación del índice

Cargar los documentos

Comience por convertir el contenido en bruto a texto plano. Los PDF, los archivos de Word, las páginas web y las notas exportadas necesitan un cargador que devuelva texto.

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader("company_policy.pdf")
documents = loader.load()

print(f"Loaded {len(documents)} pages")

Dividir el texto en fragmentos

La división del texto en fragmentos merece más atención de la que normalmente recibe. Un error muy común es introducir todo un documento en el almacén vectorial como una única unidad enorme. Un modelo de embedding comprime un pasaje en un vector de tamaño fijo. Si se le proporcionan veinte páginas, el resultado es un promedio difuso de todo, lo cual puede indicar que el archivo trata en general sobre la política de la empresa, pero no puede reflejar un párrafo específico sobre el plazo de reembolso de treinta días.

La solución consiste en dividir los documentos en fragmentos pequeños que se superpongan. La superposición es importante porque una oración cortada exactamente en un límite pierde su contexto en ambos lados, y un margen de aproximadamente el diez al veinte por ciento del tamaño del fragmento funciona como una red de seguridad.

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,      # characters per chunk
    chunk_overlap=80,    # overlap between consecutive chunks
    separators=["\n\n", "\n", ". ", " "]
)

chunks = splitter.split_documents(documents)
print(f"Split into {len(chunks)} chunks")

Como guía, los fragmentos demasiado grandes hacen que la recuperación sea vaga, mientras que los demasiado pequeños eliminan el contexto. Un rango inicial de unos 300 a 800 caracteres es razonable; luego se puede ajustar después de examinar las respuestas reales.

Incrustar cada fragmento

Un modelo de incrustación convierte el texto en una lista de números que reflejan su significado y no solo su redacción. Dos oraciones que dicen lo mismo terminan como vectores situados cerca uno del otro, incluso cuando no comparten ninguna palabra en absoluto.

from langchain_openai import OpenAIEmbeddings

embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")

# under the hood, this is what happens per chunk:
vector = embedding_model.embed_query("30-day refund window for annual plans")
print(len(vector))   # e.g. 1536 numbers representing this sentence's meaning

Una forma de imaginarlo es como un mapa plano donde las ideas relacionadas se agrupan en clústeres.

        "refund policy" •
                          \
                           • "money-back guarantee"
   "vacation days" •
                     \
                      • "paid time off"

Una frase como “política de reembolso” aparece junto a “garantía de devolución de dinero”, aunque las palabras sean diferentes; ese es todo el truco detrás de la búsqueda semántica. Esto explica por qué RAG supera al antiguo método de coincidencia de palabras clave basado en Ctrl+F.

Almacenar los vectores

Cada vector se guarda en una base de datos de vectores junto con el texto original. Ese almacenamiento constituye el índice que el estudiante que consulta como si fuera un libro abierto examina página por página.

from langchain_community.vectorstores import FAISS

vectorstore = FAISS.from_documents(chunks, embedding_model)
vectorstore.save_local("faiss_index")

FAISS es un punto de partida adecuado ya que es gratuito y funciona en una máquina local, y Chroma es igual de sencillo. Los servicios gestionados como Pinecone o Weaviate resultan útiles cuando se alcanza una escala de producción. El concepto no cambia de uno a otro, solo difieren el almacenamiento y la infraestructura.

El indexado ya está completo. El resultado es una memoria buscable construida a partir de los documentos, y solo es necesario reconstruirla cuando esos documentos cambian, no por cada consulta del usuario.

Responder a una pregunta

Esta parte se ejecuta cada vez que alguien escribe una consulta.

Incrustar la pregunta con el mismo modelo

query = "Can I get a refund on my annual subscription?"

La pregunta debe ser incrustada con exactamente el mismo modelo que se utilizó durante el indexado. La gente se equivoca constantemente en esto. Si el índice se creó con un modelo de incrustación y se consulta con otro, los vectores se encuentran en espacios semánticos diferentes, y la búsqueda de similitud deja de funcionar silenciosamente. No aparece ningún error, solo resultados deficientes; por lo tanto, la regla es utilizar siempre el mismo modelo.

Obtener los fragmentos más cercanos

retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
relevant_chunks = retriever.invoke(query)

for chunk in relevant_chunks:
    print(chunk.page_content[:100], "...")

Internamente, FAISS realiza una búsqueda de similitud. Mide la distancia, generalmente similitud coseno, entre el vector de la pregunta y el vector de cada fragmento almacenado, y luego devuelve los más cercanos. Es nuevamente la imagen de los clústeres en un mapa, aplicada al revés para encontrar el clúster al que pertenece la pregunta.

Pegue los fragmentos en un prompt

Este es el paso que hace que RAG funcione: el texto recuperado se inserta literalmente en el prompt.

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template("""
Answer the question using ONLY the context below.
If the answer isn't in the context, say "I don't have that information."

Context:
{context}

Question: {question}
""")

La instrucción de responder utilizando únicamente el contexto proporcionado es lo que mantiene al modelo centrado y evita que invente cosas. Esto diferencia un examen de libro abierto de uno de libro cerrado, donde se permite improvisar.

Conectarlo en una cadena

Ahora las piezas se ensamblan en la cadena RAG, que es a lo que se refieren las personas cuando hablan de crear una. En LangChain normalmente se escribe con el operador de tubería, el cual pasa la salida de la izquierda al paso de la derecha.

from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-4o-mini")

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

answer = rag_chain.invoke("Can I get a refund on my annual subscription?")
print(answer)

Al leer de izquierda a derecha, la cadena narra su propio proceso. La pregunta se ingresa, se recuperan fragmentos que se formatean como contexto mientras la pregunta permanece sin cambios, se completa el modelo de prompt, se llama al modelo y se analiza el texto plano de su respuesta.

Al ejecutarlo, en lugar de una suposición errónea, se obtiene algo como: sí, los planes anuales califican para un reembolso completo dentro de los treinta días posteriores a la compra, tal como establece la política de reembolsos. La respuesta es concreta y específica, y se puede rastrear hasta un párrafo real del PDF.

¿Por qué no pegar todo?

Una objeción válida es que las ventanas de contexto son ahora muy grandes. ¿Por qué no incluir todo el documento en cada prompt y omitir la búsqueda vectorial?

  • Costo y latencia. Cada token enviado cuesta dinero y lleva tiempo. Con una base de conocimientos de 500 páginas, enviar todo en cada pregunta es un derroche cuando la respuesta está en un solo párrafo.
  • Rendimiento reducido en la recuperación de información. Incluso con ventanas grandes, los modelos utilizan de manera significativa peor la información incluida en medio del prompt que la información que se les proporciona directamente y de forma breve.
  • Escala. Pegar todo falla por completo una vez que la base de conocimientos supera el tamaño de cualquier ventana, como ocurre con miles de PDFs, una wiki completa de la empresa o años de tickets de soporte.

RAG permite que un sistema pase de un documento a toda una base de conocimientos manteniendo el mismo tamaño del prompt. Solo el paso de recuperación accede al conjunto de datos completo, y el prompt recibe solo unos pocos fragmentos relevantes.

Errores que aparecen temprano

  • Un muestreo deficiente arruina la recuperación de información, independientemente de la calidad del modelo. Si una tabla se divide por la mitad o una oración se interrumpe a mitad de frase, nada posterior puede repararlo. Verifique la calidad de la recuperación antes de culpar al modelo.
  • Modelos de embedding incompatibles hacen que las puntuaciones de similitud carezcan de sentido. Un índice creado con text-embedding-3-small no puede consultarse con otro modelo.
  • Recuperar demasiados pocos o demasiados fragmentos perjudica el resultado. Con k=1 es posible pasar por alto la respuesta real, mientras que con k=20 el modelo se ve abrumado por ruido y costos elevados. Comience con tres a cinco y ajuste según los resultados obtenidos.
  • Sin una solución alternativa para cuando no se conoce la respuesta, el modelo seguirá inventando una respuesta incluso cuando cuenta con el contexto relevante. Especifique claramente qué hacer cuando el contexto no contiene la respuesta.

Siguientes pasos

Cuando el ciclo básico funcione, tres mejoras representan los siguientes pasos lógicos.

  • La búsqueda híbrida combina la similitud vectorial con la coincidencia de palabras clave, ya que algunas consultas, como códigos o nombres de productos exactos, se manejan mejor con palabras clave.
  • El reclasificado primero obtiene un conjunto más amplio de candidatos y luego permite que un modelo segundo y más preciso los reordene antes de que el modelo de lenguaje los vea.
  • El filtrado de metadatos adjunta detalles como fecha, fuente o departamento a cada fragmento, de modo que se puede restringir la búsqueda antes incluso de que se ejecute la búsqueda por similitud.

Ninguno de estos elementos es importante hasta que el ciclo básico de fragmentar, incrustar, almacenar, recuperar y generar funcione correctamente. La forma más rápida de comprenderlo realmente es cargar un PDF relevante, romper algo intencionadamente, como modelos de incrustación incompatibles o fragmentos sin solapamiento, y observar cómo empeoran las respuestas. Con RAG, los modos de fallo enseñan más que el proceso sin problemas.

Leer más

  • Notas prácticas: Los tres niveles de Google Cloud RAG: Búsqueda vectorial, motor RAG y recuperación por agente — Guía paso a paso de las Notas prácticas: Los tres niveles de Google Cloud RAG: Búsqueda vectorial, motor RAG y recuperación por agente: contratos, verificaciones y espacios para código listo para usar.
  • RAG con estado en Databricks: Memoria multi-turno con Lakebase y LangGraph — Otorgar a un agente RAG de Databricks una memoria real de conversación: analizar PDFs, crear búsquedas vectoriales, guardar puntos de control de las conversaciones en Lakebase Postgres y, luego, desplegarlo mediante MLflow y Model Serving.
  • Convertirse en ingeniero de IA en 2026 sin título en ciencias de la computación — Un plan paso a paso para convertirse en ingeniero de IA en 2026 sin tener título en esta área: las siete áreas de habilidad, proyectos pequeños, experiencia en el mundo real y networking inteligente.