Curso sobre RAG: 9 – Ejemplo práctico RAG – Parte 2: generación

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:

Regla RAG:
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:

  1. 📄 Documentación propia (tus .txt)

  2. 🧮 Embeddings + base vectorial (Chroma)

  3. 🔍 Búsqueda semántica (no por palabras)

  4. ✍️ Generación controlada (OpenAI)

  5. 🔒 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
Nota de seguridad:
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:

  1. Se cargan los documentos desde la carpeta docs
  2. Se convierten en embeddings y se almacenan en Chroma
  3. Se realiza una búsqueda semántica con la pregunta
  4. Se construye un contexto a partir de los documentos recuperados
  5. El modelo de lenguaje genera una respuesta usando solo ese contexto
Conclusión:
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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio