Implementación básica de RAG: tutorial paso a paso

Este tutorial para principiantes explica cómo funciona una implementación básica de RAG, repasa las prácticas recomendadas y te guía paso a paso para crear un sistema RAG funcional de línea de comandos con Python, ChromaDB y los modelos de embeddings y de lenguaje de Perplexity.

Basic RAG implementation process diagram: the interface sends input to the orchestrator, which retrieves data from a vector database, builds the input context for the LLM, and returns the output to the interface.

La RAG es un componente esencial de la infraestructura moderna basada en LLM. Al permitir que los modelos de IA accedan a datos externos, ayuda a resolver problemas como las alucinaciones y los datos de entrenamiento incompletos. Además, permite que las empresas usen bases de conocimiento internas con información confidencial o propietaria.

Implementar RAG puede ser muy complejo. Sin embargo, crear un sistema RAG básico es sorprendentemente fácil. Para los principiantes, construir una arquitectura sencilla puede aclarar cómo funciona la RAG en el mundo real y servir como punto de partida para seguir experimentando.

En este artículo, describiremos una implementación básica de RAG, repasaremos las prácticas recomendadas y luego crearemos un sistema funcional con Python, ChromaDB y dos modelos de Perplexity: uno para generar embeddings y otro para generar respuestas.

Proceso básico de implementación de RAG: panorama general

Un sistema RAG básico tiene tres componentes principales: un orquestador, una fuente de información y un LLM. En un flujo de trabajo típico, el usuario escribe un prompt y el orquestador lo modifica para buscar en las fuentes de información conectadas. Luego, la información recuperada se agrega al contexto de entrada original y se envía al LLM para que genere una respuesta.

Tendrás que contemplar estos pasos al crear tu implementación de RAG:

  1. Indexación y embeddings: En esta etapa, tus documentos se convierten en una fuente de información en la que el orquestador puede buscar. Una de las formas más comunes de hacerlo es crear un índice vectorial que admita búsquedas. Usaremos un modelo dedicado de Perplexity para convertir los fragmentos de tus documentos en embeddings. Después, almacenaremos los embeddings con ChromaDB, una popular base de datos vectorial de código abierto.
  2. Orquestación: Es la aplicación que se ubica entre la fuente de información externa (en nuestro caso, una base de datos vectorial) y el LLM. Crearemos un script sencillo de Python que funcione como orquestador.
  3. Recuperación y generación: Cuando un usuario hace una pregunta a través de la interfaz de chat, el orquestador la toma, ejecuta una búsqueda vectorial y envía la entrada ampliada al LLM. En lugar de crear un frontend dedicado, usaremos la línea de comandos para comunicarnos con nuestro orquestador de Python.

Prácticas recomendadas: ¿qué hace que una implementación de RAG sea eficaz?

Existen varios tipos de arquitectura RAG, y cada caso de uso exige un enfoque distinto. Dicho esto, hay una serie de prácticas recomendadas que se aplican en todos los casos y garantizan sistemas precisos, rentables, escalables y fáciles de mantener.

Las siguientes prácticas recomendadas se aplican a todos los tipos de implementación de RAG:

  • Asegúrate de tener un pipeline de ingesta limpio: Tus datos de origen deben estar lo más limpios posible antes de la indexación. También debes usar un modelo de embeddings adecuado para tu contenido.
  • Usa búsqueda híbrida: Los mejores métodos de recuperación, ya sea la búsqueda en bases de datos vectoriales, la búsqueda web mediante API, el recorrido de grafos de conocimiento, la búsqueda por palabras clave u otros, varían según el caso de uso. Elegir la combinación adecuada es importante para minimizar los costos.
  • Evalúa tu sistema: Después de la implementación, debes evaluarlo con base en tres medidas fundamentales: precisión, relevancia y latencia. La precisión se refiere a la exactitud de los hechos; la relevancia, a qué tan aplicable es la información recuperada a las consultas de los usuarios, y la latencia, a la velocidad con la que se recupera la información.
  • Maneja la información de forma segura: Si tu sistema RAG utiliza información interna privada, asegúrate de que se almacene de forma segura y de que se administre el acceso a la interfaz del sistema. Nunca se deben compartir las claves de API.

Implementación básica de RAG: tutorial en 7 pasos

En este tutorial crearemos una aplicación de línea de comandos que responde preguntas sobre un solo documento. Usaremos Python, ChromaDB y la API de Perplexity.

El objetivo no es crear un sistema RAG listo para producción. Más bien, se trata de un tutorial para principiantes diseñado para ilustrar cómo funciona en la práctica una arquitectura RAG rudimentaria.

Paso 1: Instala el software

Asegúrate de tener instalado lo siguiente en tu computadora y de contar con una cuenta de la API de Perplexity:

  • Python: Es el lenguaje de programación que usaremos para crear el orquestador que coordinará el flujo de datos entre el modelo de embeddings, ChromaDB y Perplexity.
  • Visual Studio: Usaremos Visual Studio como editor de código. Una vez instalado, abre el panel Extensiones y descarga la extensión Python.
  • Cuenta de la API de Perplexity: Perplexity proporcionará tanto el modelo de embeddings (que convierte tus datos en embeddings vectoriales) como el LLM que procesará el prompt ampliado. No necesitas una cuenta de pago para hacerlo, aunque tendrá un pequeño costo en créditos.

Paso 2: Crea una carpeta de proyecto

Primero, crea una carpeta llamada rag-project y ábrela en VS Code seleccionando Archivo > Abrir carpeta. Luego, abre la terminal integrada desde Terminal > Nueva terminal.

Escribe lo siguiente para crear un entorno virtual de Python:

bash
py -3.12 -m venv .venv

Ahora necesitas instalar cuatro bibliotecas de Python: perplexityai, chromadb, python-dotenv y numpy. El SDK de Perplexity te permitirá conectarte a los modelos de Perplexity mediante la API. ChromaDB crea la base de datos vectorial y se ejecutará de forma local. Python-dotenv carga tus claves de API de forma segura desde un archivo local, y NumPy se necesita durante el proceso de ingesta.

Instálalas con el siguiente comando:

bash
.\.venv\Scripts\python.exe -m pip install perplexityai chromadb python-dotenv numpy

Paso 3: Configura tus archivos

Agrega los siguientes archivos a rag-project (la raíz del proyecto) seleccionando Nuevo archivo… en la barra lateral del Explorador:

  • index.py: Es la aplicación que indexará tu documento. Puedes volver a ejecutarla para agregar más texto.
  • rag.py: Es la aplicación de Python que funcionará como orquestador.
  • document.txt: Contendrá la documentación que convertirás en una base de datos vectorial.
  • .env: Aquí guardarás tu clave de API de Perplexity.

En .env, escribe lo siguiente:

plaintext
PERPLEXITY_API_KEY=your_api_key_here

Reemplaza your_api_key_here por tu clave real. Para obtenerla, ve al panel principal de la consola de la API de Perplexity y selecciona Create an API key. Mantén esta clave de API en secreto.

Usaremos dos modelos: pplx-embed-v1-0.6b de Perplexity, para generar los embeddings de nuestra documentación antes de subirla a ChromaDB, y GPT-5.6-Luna (al que se accede mediante la API de Perplexity), que procesará la información recuperada.

En document.txt, agrega el texto que recuperará tu pipeline de RAG. Si no tienes un documento a la mano, para este tutorial puedes usar Songs of Innocence and of Experience, de William Blake, disponible en Project Gutenberg. Es un texto breve, fácil de entender y de dominio público.

Paso 4: Escribe el programa de indexación

Copia y pega el siguiente código en index.py. Cada subtítulo explica brevemente lo que hace el segmento de código que le sigue. Este es el programa que convertirá tu material de origen en embeddings vectoriales y los almacenará en una base de datos de ChromaDB usando el modelo de embeddings de Perplexity, pplx-embed-v1-0.6b, y la biblioteca NumPy.

Esto es lo que hace el programa:

  1. Abre document.txt.
  2. Divide el texto en fragmentos superpuestos.
  3. Envía esos fragmentos al modelo de embeddings pplx-embed-v1-0.6b de Perplexity.
  4. Decodifica y normaliza los vectores obtenidos con NumPy, una biblioteca de Python con funciones de cálculo.
  5. Almacena los vectores y los fragmentos originales en una base de datos de ChromaDB (verás que aparece en el directorio de tu proyecto).

Está configurado para no volver a indexar tu documento cada vez que lo ejecutes. Si agregas información a document.txt, solo generará embeddings del texto nuevo.

python
from pathlib import Path from collections import defaultdict, deque from hashlib import sha256 import base64 import numpy as np import chromadb from dotenv import load_dotenv from perplexity import Perplexity # 1. Connect to Perplexity and ChromaDB load_dotenv() ai = Perplexity() db = chromadb.PersistentClient(path="./rag_database_perplexity") collection = db.get_or_create_collection( name="documents", metadata={"hnsw:space": "cosine"} ) # Convert Perplexity's encoded embeddings into numerical vectors def decode_embedding(encoded): vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32) norm = np.linalg.norm(vector) if norm == 0: raise ValueError("Received a zero-length embedding vector.") return (vector / norm).tolist() # 2. Read the document and split it into chunks text = Path("document.txt").read_text(encoding="utf-8") words = text.split() chunk_size = 180 overlap = 30 step = chunk_size - overlap chunks = [" ".join(words[i:i + chunk_size]) for i in range(0, len(words), step)] if not chunks: raise ValueError("document.txt is empty.") # 3. Retrieve the chunks already stored in ChromaDB existing = collection.get(include=["documents"]) stored_chunks = defaultdict(deque) existing_ids = set(existing["ids"]) for chunk_id, document in zip(existing["ids"], existing["documents"]): stored_chunks[document].append(chunk_id) # 4. Identify unchanged and new chunks new_chunks = [] new_ids = [] kept_ids = set() unchanged = 0 for chunk in chunks: if stored_chunks[chunk]: chunk_id = stored_chunks[chunk].popleft() kept_ids.add(chunk_id) unchanged += 1 else: chunk_hash = sha256(chunk.encode("utf-8")).hexdigest() chunk_id = f"chunk-{chunk_hash}" suffix = 1 while chunk_id in existing_ids or chunk_id in kept_ids or chunk_id in new_ids: chunk_id = f"chunk-{chunk_hash}-{suffix}" suffix += 1 new_chunks.append(chunk) new_ids.append(chunk_id) kept_ids.add(chunk_id) # 5. Generate embeddings in batches of up to 100 chunks batch_size = 100 for i in range(0, len(new_chunks), batch_size): batch_chunks = new_chunks[i:i + batch_size] batch_ids = new_ids[i:i + batch_size] embeddings = ai.embeddings.create( model="pplx-embed-v1-0.6b", input=batch_chunks, encoding_format="base64_int8" ) vectors = [decode_embedding(item.embedding) for item in embeddings.data] # 6. Store each batch in ChromaDB collection.upsert( ids=batch_ids, documents=batch_chunks, embeddings=vectors ) print(f"Indexed batch {(i // batch_size) + 1}") # 7. Remove outdated chunks old_ids = [chunk_id for chunk_id in existing["ids"] if chunk_id not in kept_ids] if old_ids: collection.delete(ids=old_ids) # 8. Display indexing results print("Indexing complete!") print("Unchanged chunks:", unchanged) print("New or modified chunks:", len(new_chunks)) print("Removed chunks:", len(old_ids)) print("Total stored chunks:", collection.count())

Paso 5. Escribe el orquestador del sistema RAG

Copia y pega el siguiente código en rag.py. Es una implementación de RAG completa pero mínima que usa el SDK de Perplexity y la funcionalidad de base de datos vectorial local de ChromaDB.

Esto es lo que sucederá cuando ejecutes el programa:

  1. Se conecta a Perplexity y a tu nueva base de datos de ChromaDB.
  2. Verifica que la base de datos contenga documentos indexados.
  3. Recibe una pregunta del usuario.
  4. Envía la pregunta al modelo de embeddings de Perplexity.
  5. Decodifica y normaliza el embedding resultante con NumPy.
  6. Busca en ChromaDB los tres fragmentos de documento más similares.
  7. Construye un prompt con los fragmentos recuperados y la pregunta del usuario.
  8. Envía el prompt a GPT-5.6 Luna mediante la Agent API de Perplexity.
  9. Muestra la respuesta y espera otra pregunta.
python
import base64 import numpy as np import chromadb from dotenv import load_dotenv from perplexity import Perplexity # 1. Connect to Perplexity and the existing ChromaDB database load_dotenv() ai = Perplexity() db = chromadb.PersistentClient(path="./rag_database_perplexity") collection = db.get_collection("documents") # Convert Perplexity's encoded embeddings into numerical vectors def decode_embedding(encoded): vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32) norm = np.linalg.norm(vector) if norm == 0: raise ValueError("Received a zero-length embedding vector.") return (vector / norm).tolist() # 2. Check that the database contains indexed documents count = collection.count() if count == 0: raise ValueError("The database is empty. Run index.py first.") print("Connected to existing ChromaDB database.") print("Stored chunks:", count) # 3. Accept questions from the user while True: question = input("\nAsk a question (or type exit): ") if question.lower().strip() == "exit": break if not question.strip(): continue # 4. Convert the question into a Perplexity embedding question_response = ai.embeddings.create( model="pplx-embed-v1-0.6b", input=[question], encoding_format="base64_int8" ) question_vector = decode_embedding(question_response.data[0].embedding) # 5. Retrieve the most similar document chunks from ChromaDB results = collection.query( query_embeddings=[question_vector], n_results=min(3, count) ) context = "\n\n".join(results["documents"][0]) # 6. Construct a prompt and send it to the fixed LLM response = ai.responses.create( model="openai/gpt-5.6-luna", instructions=( "Answer using only the supplied document excerpts. " "Do not use external information. " "If the answer is not present, say you don't know." ), input=f"Document excerpts:\n{context}\n\nQuestion: {question}" ) # 7. Display the answer print("\nAnswer:\n", response.output_text)

Paso 6: Ejecuta las aplicaciones

En la terminal de PowerShell de VS Code, escribe lo siguiente para generar los embeddings del contenido de document.txt:

bash
.\.venv\Scripts\python.exe index.py

Si tu documento se indexó correctamente, deberías ver un resultado similar a este:

plaintext
Indexing complete! Unchanged chunks: 0 New or modified chunks: 58 Removed chunks: 0 Total stored chunks: 58

Ahora es momento de ejecutar la aplicación principal del orquestador. Escribe este comando:

bash
.\.venv\Scripts\python.exe rag.py

Si se ejecuta correctamente, verás lo siguiente:

plaintext
Connected to existing ChromaDB database. Stored chunks: 58 Ask a question (or type exit):

Ahora puedes hacer preguntas que se responderán a partir de la base de datos vectorial que creaste, por ejemplo:

plaintext
Ask a question (or type exit): What animals does William Blake talk about? Answer: The excerpts mention sheep, lambs, birds, beasts, wolves, tigers, and a lion.

Paso 7: Entiende lo que creaste

El sistema que creaste incluye los dos pipelines fundamentales de toda arquitectura RAG: un pipeline de indexación, o de “ingesta”, y un pipeline de recuperación y generación.

Ahora tu implementación básica de RAG hace todo lo siguiente:

  • Almacenamiento vectorial: Los embeddings generados a partir de tu archivo de conocimiento se guardan en tu base de datos de ChromaDB, en tu entorno local.
  • Indexación incremental: No se vuelven a generar embeddings de los fragmentos de documento que no cambiaron, y puedes actualizar el documento cuando quieras.
  • Recuperación semántica: ChromaDB recupera fragmentos según su similitud vectorial (a solicitud del orquestador).
  • Generación de respuestas fundamentadas: El LLM recibe los pasajes recuperados como contexto y los incorpora en una respuesta.
  • Indexación y consultas independientes: Puedes hacer preguntas sin volver a procesar todo el texto de tu archivo de conocimiento.
  • Conexión con el modelo mediante API: Todas las respuestas se generan con GPT-5.6 Luna a través de Perplexity, en lugar de con un modelo que se ejecuta en tu computadora.

Próximos pasos: cómo llevar esta implementación más allá

El objetivo de este tutorial es crear una implementación básica y sencilla. Abarca algunos de los componentes fundamentales de la arquitectura RAG presentes en los sistemas modernos. Si quieres ir más allá, hay varias formas de ampliarla.

Estas son algunas ideas para ampliar el alcance de tu implementación:

  • Experimenta con LangChain: LangChain es uno de los frameworks de orquestación de código abierto más populares. Incluye componentes prediseñados para crear aplicaciones capaces de ejecutar pipelines de RAG complejos.
  • Amplía la funcionalidad de búsqueda: Combina la búsqueda vectorial densa con la búsqueda léxica (por palabras clave), como BM25. Agregar búsqueda web mediante la Search API de Perplexity también te permitiría fundamentar las respuestas en datos actuales.
  • Agrega un modelo de reordenamiento: Integra un modelo de reordenamiento (reranking) que clasifique los pasajes recuperados en función del prompt inicial y seleccione los más relevantes para incluirlos en el contexto de entrada que se le proporciona al LLM.
  • Crea un sistema RAG adaptativo: La RAG adaptativa usa un LLM o un clasificador (un componente dentro del orquestador) para determinar qué método de recuperación usar en un sistema RAG modular. Crear uno es una buena manera de explorar implementaciones de RAG más sofisticadas.

Aunque todo esto pueda sonar complejo, ten en cuenta que las principales plataformas de IA, como Perplexity, pueden brindarte asistencia avanzada de programación. Son excelentes para proyectos personales y para experimentar, y los costos pueden mantenerse relativamente bajos.

Nota: Este tutorial usa GPT-5.6 Luna. La disponibilidad de los modelos puede cambiar, por lo que es posible que tengas que actualizar la llamada a la API.