RAG - Retrieval Augmented Generation
Generación Aumentada por Recuperación
Ejemplo práctico RAG – Parte 2: generar una respuesta con OpenAI usando los documentos recuperados
En la Parte 1 conseguimos indexar documentos en una base vectorial (Chroma) y recuperar
los fragmentos relevantes mediante búsqueda semántica.
En esta Parte 2 añadimos la “G” de RAG (Generation):
usaremos un modelo de lenguaje para redactar una respuesta, pero basándose únicamente
en los textos recuperados.
1. Recuperación de contexto (lo que ya funciona)
Partimos de una pregunta:
¿Es obligatorio el NIF para poder facturar?
El sistema devuelve los documentos más relevantes:
- clientes.txt: “El NIF es obligatorio para poder facturar.”
- facturacion.txt: “Si el cliente no tiene NIF, la factura no se puede emitir.”
Esto demuestra que el sistema no busca palabras exactas, sino significado.
2. ¿Qué aporta el modelo de lenguaje aquí?
Hasta ahora, el sistema solo “recupera” texto.
Un modelo de lenguaje aporta:
- Redacción clara y natural
- Respuesta resumida a partir de varias fuentes
- Capacidad de estructurar la información
Pero hay una regla muy importante:
El modelo debe responder solo con el contexto proporcionado.
Si la información no está en los documentos, debe decirlo.
3. Resultado esperado
La respuesta generada debería ser algo similar a:
Sí, el NIF es obligatorio para poder facturar. Según la documentación, si el cliente no tiene NIF, no se puede emitir la factura.
Si has llegado hasta aquí…
Acabas de montar un RAG real de extremo a extremo:
📄 Documentación propia (tus
.txt)🧮 Embeddings + base vectorial (Chroma)
🔍 Búsqueda semántica (no por palabras)
✍️ Generación controlada (OpenAI)
🔒 Sin inventarse nada (temperature 0 + prompt restrictivo)
👉 Esto no es un chatbot
👉 Esto no es “entrena a la IA”
👉 Esto es arquitectura RAG correcta
Muchísima gente no llega hasta aquí o lo hace sin entender nada. Tú sí lo has entendido.
En la siguiente parte se podrá mejorar añadiendo:
- Citas del documento usado
- Metadatos (módulo, rol, etc.)
- Filtros y control de permisos
Código completo del ejemplo RAG y configuración de OpenAI
Una vez comprobado que el sistema RAG funciona correctamente, conviene dejar documentado el código completo utilizado y el paso necesario para configurar el acceso al modelo de lenguaje.
Esto permite:
- Reproducir el ejemplo desde cero
- Entender claramente el flujo completo
- Modificarlo o ampliarlo en el futuro
1. Configurar la API Key de OpenAI
Para que el modelo de lenguaje pueda generar respuestas, es necesario disponer de una API Key de OpenAI.
La forma más sencilla (y segura) para este ejemplo es usar una variable de entorno.
Desde la terminal de Visual Studio Code (PowerShell), con el entorno virtual activo:
$env:OPENAI_API_KEY="TU_API_KEY_AQUI"
Este comando:
- Define la clave solo para esa sesión de terminal
- Evita escribir la clave directamente en el código
- Reduce el riesgo de subirla a un repositorio por error
Nunca se debe guardar la API Key directamente en el código ni subirla a un repositorio público.
2. Código completo del archivo main.py
A continuación se muestra el contenido completo del archivo main.py utilizado en el ejemplo hello-world-rag.
import os
import chromadb
from chromadb.utils import embedding_functions
from openai import OpenAI
def load_documents(docs_path: str):
documents = []
metadatas = []
ids = []
for i, filename in enumerate(os.listdir(docs_path)):
if not filename.lower().endswith(".txt"):
continue
with open(os.path.join(docs_path, filename), "r", encoding="utf-8") as f:
documents.append(f.read())
metadatas.append({"archivo": filename})
ids.append(f"doc_{i}")
return documents, metadatas, ids
def build_context(results: dict) -> str:
retrieved_docs = results["documents"][0]
retrieved_metas = results["metadatas"][0]
parts = []
for doc, meta in zip(retrieved_docs, retrieved_metas):
parts.append(f"[Fuente: {meta['archivo']}]\n{doc}")
return "\n\n".join(parts)
def main():
client = chromadb.Client()
embedding_function = embedding_functions.DefaultEmbeddingFunction()
collection = client.get_or_create_collection(
name="documentacion",
embedding_function=embedding_function
)
documents, metadatas, ids = load_documents("docs")
collection.add(documents=documents, metadatas=metadatas, ids=ids)
print("Documentos indexados correctamente")
query = "¿Es obligatorio el NIF para poder facturar?"
results = collection.query(
query_texts=[query],
n_results=2
)
print("\nPregunta:", query)
print("Resultados encontrados:\n")
for doc, meta in zip(results["documents"][0], results["metadatas"][0]):
print(f"- Archivo: {meta['archivo']}")
print(doc)
print("------")
context = build_context(results)
client_ai = OpenAI()
system_prompt = (
"Eres un asistente técnico. Responde SOLO usando el contexto proporcionado. "
"Si el contexto no contiene la respuesta, di claramente que no hay información suficiente."
)
user_prompt = f"""
Pregunta del usuario:
{query}
Contexto (documentación recuperada):
{context}
"""
response = client_ai.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0
)
print("\nRespuesta generada por IA:\n")
print(response.choices[0].message.content)
if __name__ == "__main__":
main()
3. Flujo completo del sistema RAG
Con este archivo, el flujo completo es el siguiente:
- Se cargan los documentos desde la carpeta
docs - Se convierten en embeddings y se almacenan en Chroma
- Se realiza una búsqueda semántica con la pregunta
- Se construye un contexto a partir de los documentos recuperados
- El modelo de lenguaje genera una respuesta usando solo ese contexto
Este ejemplo demuestra un RAG completo y funcional, donde el modelo no está entrenado con los datos, sino que los consulta dinámicamente en cada pregunta.
