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 en la creación de un sistema RAG funcional de línea de comandos con Python, ChromaDB y los modelos de embedding 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. También permite a las empresas usar bases de conocimiento internas que contienen información confidencial o propietaria.

Implementar la RAG puede ser muy complejo. Sin embargo, crear un sistema RAG básico es sorprendentemente sencillo. Para los principiantes, construir una arquitectura simple puede aclarar cómo funciona la RAG en el mundo real y servir de trampolín para seguir experimentando.

En este artículo, trazaremos una implementación básica de RAG, repasaremos las prácticas recomendadas y, después, crearemos un sistema funcional con Python, ChromaDB y dos modelos de Perplexity para la generación de embeddings y de respuestas.

Proceso de implementación básica de RAG: una visión 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 introduce un prompt y el orquestador lo modifica para buscar en las fuentes de información conectadas. A continuación, la información recuperada se añade al contexto de entrada original y se envía al LLM para que genere una respuesta.

Deberás tener en cuenta estos pasos al crear tu implementación de RAG:

  1. Indexación y embedding: En esta fase, tus documentos se convierten en una fuente de información en la que el orquestador puede buscar. Una de las formas más habituales de hacerlo es crear un índice vectorial con capacidad de búsqueda. Usaremos un modelo específico 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 sitúa entre la fuente de información externa (en nuestro caso, una base de datos vectorial) y el LLM. Crearemos un sencillo script de Python que hará de orquestador.
  3. Recuperación y generación: Cuando un usuario haga una pregunta a través de la interfaz de chat, el orquestador la recogerá, ejecutará una búsqueda vectorial y enviará la entrada ampliada al LLM. En lugar de crear un frontend específico, 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: Los datos de origen deben estar lo más limpios posible antes de indexarlos. También conviene usar un modelo de embedding adecuado para tu contenido.
  • Usa la 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 costes.
  • Evalúa tu sistema: Tras la implementación, debes evaluarlo según tres criterios fundamentales: precisión, relevancia y latencia. La precisión se refiere a la exactitud de los datos; la relevancia, a la adecuación de la información recuperada a las consultas de los usuarios; y la latencia, a la velocidad con la que se recupera la información.
  • Trata la información de forma segura: Si tu sistema RAG utiliza información interna privada, asegúrate de que se almacena de forma segura y de que el acceso a la interfaz del sistema está controlado. 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 a preguntas sobre un único documento. Usaremos Python, ChromaDB y la API de Perplexity.

El objetivo no es crear un sistema RAG listo para producción, sino ofrecer un tutorial para principiantes que ilustre 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 ordenador y de disponer de 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 embedding, ChromaDB y Perplexity.
  • Visual Studio: Usaremos Visual Studio como editor de código. Una vez instalado, abre el panel Extensions y descarga la extensión Python.
  • Cuenta de la API de Perplexity: Perplexity proporcionará tanto el modelo de embedding (que convierte tus datos en embeddings vectoriales) como el LLM que procesará el prompt ampliado. No necesitas una cuenta de pago para ello, aunque supondrá un pequeño coste en créditos.

Paso 2: Crea una carpeta de proyecto

Empieza creando una carpeta llamada rag-project y ábrela en VS Code seleccionando File > Open Folder. A continuación, abre el terminal integrado mediante Terminal > New Terminal.

Introduce lo siguiente para crear un entorno virtual de Python:

bash
py -3.12 -m venv .venv

Ahora tienes que 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á en local. Python-dotenv carga tus claves de API de forma segura desde un archivo local, y NumPy es necesario 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

Añade los siguientes archivos a rag-project (la raíz del proyecto) seleccionando New File… en la barra lateral Explorer:

  • index.py: Es la aplicación que indexará tu documento. Puedes volver a ejecutarla para añadir más texto.
  • rag.py: Es la aplicación de Python que hará de 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, introduce lo siguiente:

plaintext
PERPLEXITY_API_KEY=your_api_key_here

Sustituye your_api_key_here por tu clave real. Para ello, tendrás que ir al panel de inicio de la consola de la API de Perplexity y seleccionar Create an API key. Mantén esta clave de API en privado.

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 a través de la API de Perplexity), que procesará la información recuperada.

En document.txt, añade el texto que recuperará tu pipeline de RAG. Si no tienes ningún documento a mano, usa Songs of Innocence and of Experience, de William Blake, disponible en Project Gutenberg, para este tutorial. 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 fragmento de código que lo sigue. Este es el programa que convertirá tu material de origen en embeddings vectoriales y los almacenará en una base de datos de ChromaDB mediante el modelo de embedding 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 embedding pplx-embed-v1-0.6b de Perplexity.
  4. Decodifica y normaliza los vectores devueltos 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 el documento cada vez que lo ejecutes. Si añades 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 utiliza el SDK de Perplexity y la funcionalidad de base de datos vectorial local de ChromaDB.

Esto es lo que ocurrirá cuando ejecutes el programa:

  1. Se conecta a Perplexity y a tu nueva base de datos de ChromaDB.
  2. Comprueba que la base de datos contiene documentos indexados.
  3. Acepta una pregunta del usuario.
  4. Envía la pregunta al modelo de embedding 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 a través de 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 el terminal de PowerShell de VS Code, introduce lo siguiente para generar los embeddings del contenido de document.txt:

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

Si tu documento se ha indexado 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 el momento de ejecutar la aplicación principal del orquestador. Introduce 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):

Ya puedes hacer preguntas que se basarán en la base de datos vectorial que has creado, como estas:

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: Comprende lo que has creado

El sistema que has creado contiene los dos pipelines fundamentales presentes en toda arquitectura RAG: un pipeline de indexación, o «ingesta», y un pipeline de recuperación y generación.

Tu implementación básica de RAG ya hace todo lo siguiente:

  • Almacenamiento vectorial: Los embeddings, basados en tu archivo de conocimiento, se guardan en tu base de datos de ChromaDB en tu entorno local.
  • Indexación incremental: Los fragmentos de documento que no han cambiado no se vuelven a procesar para generar embeddings, y puedes actualizar el documento siempre que quieras.
  • Recuperación semántica: ChromaDB recupera fragmentos en función de la similitud vectorial (a petición del orquestador).
  • Generación de respuestas fundamentadas: El LLM recibe los pasajes recuperados como contexto y los incorpora a una respuesta.
  • Indexación y consulta 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 ordenador.

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. Añadir la búsqueda web mediante la Search API de Perplexity también te permitiría fundamentar las respuestas en datos actuales.
  • Añade un modelo de reordenación: Integra un modelo de reordenación (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 proporciona al LLM.
  • Crea un sistema RAG adaptativo: La RAG adaptativa utiliza un LLM o un clasificador (un componente del orquestador) para determinar qué método de recuperación usar en un sistema RAG modular. Crear uno es una buena forma de explorar implementaciones de RAG más sofisticadas.

Aunque todo esto pueda parecer complejo, ten en cuenta que las principales plataformas de IA, como Perplexity, pueden ofrecer ayuda avanzada con la programación. Son excelentes para proyectos personales y para experimentar, y los costes pueden mantenerse relativamente bajos.

Nota: Este tutorial utiliza 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.