RAG


Primero, el nombre. RAG son las siglas de retrieval-augmented generation (generación aumentada por recuperación): tú mismo buscas el texto relevante y se lo entregas al modelo en el momento de la pregunta, de modo que responda a partir de datos que le proporcionaste en lugar de su memoria congelada. Este capítulo trata sobre cómo funciona ese ciclo de buscar y luego responder, y dónde puede fallar.
El patrón de recuperar y luego generar
Un modelo no conoce los documentos de ayuda de tu empresa, los detalles de tu producto, ni nada que haya ocurrido después de su entrenamiento. Si le preguntas sobre eso, admitirá que no puede ayudar o, peor aún, se inventará algo que suena convincente. RAG resuelve esto entregándole al modelo el texto relevante en el momento de la pregunta.
La idea toma forma una vez que entiendes los embeddings, el truco que convierte el texto en vectores que puedes comparar por cercanía (la similitud coseno mide cuán cerca apuntan dos vectores). La generación aumentada por recuperación son dos pasos añadidos a una llamada normal al modelo:
- Recuperar. Toma la pregunta del usuario, busca en tus documentos las partes más relevantes y reúne las mejores coincidencias.
- Generar. Coloca esas partes en el prompt como contexto y luego pídele al modelo que responda la pregunta usando solo ese contexto.
La capacidad general del modelo para redactar hace la escritura; el texto que recuperaste aporta los hechos. Así obtienes respuestas fluidas que están fundamentadas en tu contenido y que pueden mantenerse actualizadas, porque tú controlas lo que entra.
Ayuda entender por qué esto funciona tan bien, en términos del ciclo de predicción. Sin RAG, le estás pidiendo al modelo que recuerde un dato a partir de los patrones congelados en sus parámetros, dato que puede no tener, lo que lleva a una suposición hecha con confianza. Con RAG, el dato está ahí mismo, en el contexto, así que la tarea del modelo cambia de "recuerda esto" a "lee lo que tienes enfrente y responde a partir de eso". La recuperación traslada el trabajo de la memoria a la lectura. La imagen clara es la de un examen: un examen a libro cerrado invita a adivinar, mientras que RAG lo convierte en un examen a libro abierto donde la página relevante ya está abierta sobre el escritorio.
Fragmentación (chunking)
No conviertes en embeddings documentos completos. Primero los divides en fragmentos (chunks) más pequeños, un párrafo o unas pocas oraciones cada uno, y conviertes eso en embeddings. Por dos razones. Primero, la recuperación se vuelve más precisa: extraes el único párrafo relevante en lugar de un manual completo de 40 páginas. Segundo, los fragmentos caben en la ventana de contexto, mientras que un documento gigante podría no caber.
# un fragmentador simple: divide por párrafos
def chunk(text):
return [c.strip() for c in text.split("\n\n") if c.strip()]El tamaño del fragmento es un equilibrio, y la razón se remonta a los embeddings. Cada fragmento se convierte en un único vector que resume todo su significado. Si haces un fragmento demasiado grande, cubrirá varios temas, así que su único vector será un promedio confuso de todos ellos que no coincidirá bien con ninguna pregunta específica. Si lo haces demasiado pequeño, pierde el significado que lo rodeaba y que necesitaba para tener sentido, así que el vector apunta a un fragmento inconexo. Los fragmentos del tamaño de un párrafo contienen cada uno una idea coherente. A partir de ahí ajustas según el rendimiento de tu recuperación.
Dónde viven los vectores
Para un puñado de fragmentos, puedes mantener los vectores en memoria y compararlos con la función de similitud coseno del capítulo de embeddings, que es justo lo que hace el ejemplo más adelante. Para miles o millones de fragmentos, esto se vuelve lento, y ahí recurres a un almacén de vectores (vector store): una base de datos construida para encontrar los vectores más cercanos rápidamente a gran escala.
No necesitas uno para aprender RAG, ni necesitas elegir uno ahora mismo. Existen varios como servicios alojados y como bibliotecas; lo que importa es el concepto: un lugar donde guardar embeddings y buscarlos rápido. Empieza en memoria y muévete a un almacén cuando la colección lo supere.
Fundamentar y citar
El paso de generación es un prompt, y la forma en que lo escribes decide si RAG realmente reduce las respuestas inventadas. Dos instrucciones hacen el trabajo pesado: decirle al modelo que responda solo a partir del contexto proporcionado, y decirle que lo indique cuando el contexto no contenga la respuesta.
context_text = "\n\n".join(retrieved_chunks)
system_prompt = f'''Responde la pregunta usando SOLO el contexto de abajo.
Si el contexto no contiene la respuesta, di "No tengo esa información."
Cita la parte relevante del contexto en tu respuesta.
Contexto:
"""
{context_text}
"""'''Esto reúne dos lecciones anteriores. Los delimitadores y el "responde solo a partir de esto" explícito vienen de los prompts. El permiso para decir "no tengo esa información" es la defensa contra las alucinaciones: un modelo fundamentado con una vía de escape inventa muchísimo menos que uno al que se le deja adivinar. Fundamentar más una vía de escape es lo que reduce las respuestas inventadas. Pedirle que cite la fuente también le da a los usuarios algo que verificar.
Más allá del vecino más cercano: búsqueda híbrida y reclasificación
La búsqueda vectorial pura coincide por significado, que es lo que quieres la mayor parte del tiempo. Pero tiene un punto ciego: puede pasar por alto una palabra exacta que importa. Busca un código de error como E_4021 o el nombre específico de un producto, y una búsqueda basada en significado puede devolverte fragmentos que tratan del mismo tema mientras se salta el que contiene la cadena exacta.
La solución tiene nombre, búsqueda híbrida: combinar la búsqueda basada en significado con la búsqueda tradicional por palabras clave, de modo que los términos exactos y el significado general tengan ambos voz y voto. No necesitas construir esto para empezar con RAG. Recurre a ella cuando los términos exactos importan y los vectores puros sigan fallando en encontrarlos.
En la práctica
Una respuesta mínima con RAG, reutilizando los ayudantes search y embed del capítulo de embeddings:
def answer_from_docs(question, index):
# 1. recuperar: los fragmentos que más coinciden con la pregunta
query_vector = embed(question)
scored = [{"text": item["text"], "score": cosine_similarity(query_vector, item["vector"])} for item in index]
top = sorted(scored, key=lambda x: x["score"], reverse=True)[:3]
# 2. generar: responder usando solo esos fragmentos
context = "\n\n".join(t["text"] for t in top)
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": f'Responde usando SOLO este contexto. Si no está aquí, di que no lo sabes.\n\n"""{context}"""'},
{"role": "user", "content": question},
],
)
return response.choices[0].message.contentRecupera los tres fragmentos más cercanos, colócalos en el prompt y deja que el modelo responda a partir de ellos. Eso es todo el pipeline de RAG: la búsqueda de embeddings alimentando una llamada al modelo, sostenida por un prompt fundamentado. Casi toda la calidad vive en recuperar los fragmentos correctos.
Cuándo no necesitas RAG
RAG no es gratis, y no siempre es la respuesta correcta. Sáltatelo cuando:
- Los datos son pequeños y caben en el prompt. Si toda tu base de conocimiento es de unos pocos párrafos, pégala directamente. RAG es para cuando hay demasiado como para enviarlo todo de una vez.
- Un modelo de contexto largo puede contenerlo todo. Algunos modelos aceptan entradas muy grandes, suficientes para tomar un documento entero de una sola vez, lo cual puede ser menos trabajo que construir una recuperación.
Y recuerda el límite real: la calidad de RAG es principalmente la calidad de la recuperación. Si el paso de búsqueda devuelve los fragmentos equivocados, ni el prompt más perfecto puede salvar la respuesta. La mayor parte del esfuerzo en los sistemas RAG reales va en recuperar el texto correcto, no en la llamada final al modelo.
Los embeddings y RAG le dan conocimiento al modelo en el momento de la pregunta, sin cambiar nunca el modelo en sí. El siguiente capítulo, Fine-tuning, cubre la otra opción: cambiar el modelo con tus propios ejemplos, y cuándo eso vale la pena frente a usar prompts o RAG.

