Qué resuelve RAG y qué no
RAG (Retrieval-Augmented Generation, generación aumentada por recuperación) conecta un modelo de lenguaje a un cuerpo de conocimiento externo — tu documentación, tus políticas, tu histórico. En vez de reentrenar el modelo con tus datos, recuperas los fragmentos relevantes para cada pregunta y los inyectas en el prompt como contexto. El modelo responde fundamentándose en esos fragmentos en lugar de en su memoria interna.
Esto resuelve tres problemas de golpe: reduce las alucinaciones (el modelo cita de tus fuentes, no inventa), mantiene la información actualizada (cambias un documento y la respuesta cambia, sin reentrenar nada) y da trazabilidad (puedes mostrar de qué fragmento salió cada afirmación). Lo que RAG no resuelve: si tus datos son un caos, RAG amplifica el caos. La calidad de la recuperación es el techo de la calidad de las respuestas.
La arquitectura de un pipeline RAG
Un sistema RAG tiene dos fases: una de indexación (offline, cuando ingieres documentos) y una de consulta (online, cuando el usuario pregunta). Los componentes:
- Ingesta y chunking: los documentos se dividen en fragmentos (chunks) de tamaño manejable. Es la decisión que más impacta la calidad final.
- Embeddings: cada chunk se convierte en un vector numérico que captura su significado semántico mediante un modelo de embeddings.
- Base de datos vectorial: los vectores se almacenan en un índice que permite buscar por similitud semántica de forma eficiente.
- Recuperación (retrieval): en cada consulta se busca la pregunta contra el índice y se traen los chunks más relevantes; idealmente combinando búsqueda semántica y por palabra clave.
- Reranking: un segundo modelo reordena los candidatos recuperados por relevancia real a la pregunta, subiendo la precisión.
- Generación con grounding: los chunks recuperados se ensamblan en el prompt y el modelo de lenguaje redacta la respuesta fundamentada en ellos, con citas.
Ingesta y chunking: la decisión que más impacta
El chunking parece trivial y es donde más sistemas se rompen. Si los fragmentos son demasiado grandes, diluyen la señal y desperdician contexto; si son demasiado pequeños, pierden el sentido y fragmentan ideas a la mitad. El objetivo es que cada chunk sea una unidad de significado autocontenida.
Chunking con solapamiento y respeto de estructura
Partir por un número fijo de caracteres corta frases por la mitad. La estrategia que funciona: respetar los límites naturales del documento (párrafos, secciones, encabezados) y añadir un solapamiento entre chunks para no perder el contexto en los bordes.
# Chunking que respeta estructura, con solapamiento
def chunk_document(text, target_size=800, overlap=150):
# Partir primero por límites semánticos (párrafos), no por caracteres
paragraphs = split_on_headings_and_paragraphs(text)
chunks, current = [], ""
for para in paragraphs:
if len(current) + len(para) <= target_size:
current += "\n\n" + para
else:
chunks.append(current.strip())
# Arrastrar el final del chunk anterior como solapamiento
current = current[-overlap:] + "\n\n" + para
if current.strip():
chunks.append(current.strip())
return chunks
# Adjunta metadatos a cada chunk: de qué documento y sección viene.
# Son la base de las citas y del filtrado por permisos.
def to_records(chunks, doc_id, source, section):
return [
{"text": c, "doc_id": doc_id, "source": source, "section": section}
for c in chunks
]Embeddings y la base de datos vectorial
Un modelo de embeddings convierte cada chunk en un vector de cientos o miles de dimensiones que ubica el texto en un espacio semántico: dos textos con significado parecido quedan cerca. Para almacenarlos y buscarlos no necesitas una base de datos exótica — PostgreSQL con la extensión pgvector es suficiente para la enorme mayoría de los casos, y evita sumar otra pieza de infraestructura.
-- PostgreSQL + pgvector: almacenamiento y búsqueda por similitud
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
doc_id text NOT NULL,
source text NOT NULL,
section text,
content text NOT NULL,
embedding vector(1536) -- dimensión del modelo de embeddings
);
-- Índice ANN para búsqueda aproximada rápida a escala
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);
-- Recuperar los 8 chunks más cercanos a la pregunta (ya embebida)
SELECT id, content, source, section
FROM chunks
ORDER BY embedding <=> $1 -- distancia coseno contra el vector de la query
LIMIT 8;El operador de distancia (aquí coseno) mide qué tan cerca está cada chunk de la pregunta en el espacio semántico. El índice HNSW convierte esa búsqueda en algo que escala a millones de vectores con latencia de milisegundos. Para volúmenes muy grandes o requisitos específicos existen bases dedicadas (Qdrant, Weaviate, Milvos), pero empezar por pgvector es la decisión pragmática.
Recuperación: búsqueda híbrida y reranking
La búsqueda semántica sola tiene un punto ciego: falla con términos exactos que no tienen sinónimos — códigos de producto, nombres propios, siglas internas. La búsqueda por palabra clave (BM25, full-text) es lo contrario: excelente con términos exactos, ciega al significado. La combinación de ambas — búsqueda híbrida — recupera mejor que cualquiera por separado.
# Búsqueda híbrida: combinar recuperación semántica y por palabra clave
def hybrid_retrieve(query, k=20):
q_vector = embed(query)
semantic = vector_search(q_vector, limit=k) # pgvector, distancia coseno
keyword = fulltext_search(query, limit=k) # BM25 / tsvector de Postgres
# Fusión por rango recíproco (RRF): un chunk que aparece alto en
# ambas listas sube; no depende de escalas de score incompatibles.
scores = {}
for rank, item in enumerate(semantic):
scores[item.id] = scores.get(item.id, 0) + 1 / (60 + rank)
for rank, item in enumerate(keyword):
scores[item.id] = scores.get(item.id, 0) + 1 / (60 + rank)
ranked_ids = sorted(scores, key=scores.get, reverse=True)
return [load_chunk(cid) for cid in ranked_ids[:k]]Reranking: el paso que más sube la precisión
La recuperación inicial prioriza recall: trae muchos candidatos (20-30) para no dejar fuera lo relevante. Pero meter 30 chunks en el prompt es caro y diluye la señal. Un modelo de reranking (cross-encoder) toma la pregunta y cada candidato juntos y produce un score de relevancia mucho más preciso que la distancia vectorial. Reordenas y te quedas solo con los mejores 4-6 para el prompt. Este paso, barato de añadir, es de los que más mejora la calidad percibida del sistema.
Ensamblar el prompt y generar con grounding
Con los chunks finales seleccionados, ensamblas el prompt: instrucciones claras, el contexto recuperado y la pregunta. La instrucción clave es exigir que el modelo responda solo con base en el contexto y que cite sus fuentes — así conviertes al modelo en un lector de tus documentos, no en un oráculo que improvisa.
SISTEMA:
Eres un asistente que responde únicamente con base en el CONTEXTO provisto.
Reglas:
- Si la respuesta no está en el contexto, di exactamente: "No tengo esa
información en la documentación disponible." No inventes.
- Cita la fuente de cada afirmación con [source: <nombre>].
- Sé conciso y directo.
CONTEXTO:
{chunks_recuperados_con_su_source}
PREGUNTA:
{pregunta_del_usuario}Para la generación conviene usar un modelo de lenguaje capaz y actual — por ejemplo, los modelos Claude de Anthropic — a través de su API, sin reentrenarlo: todo el conocimiento específico entra por el contexto en cada consulta. El modelo aporta la comprensión del lenguaje y la redacción; tus datos aportan la verdad.
Evaluación: cómo saber si tu RAG funciona
Un RAG sin evaluación es una demo con suerte. Como el sistema tiene dos etapas, se mide en dos niveles: qué tan bien recupera y qué tan bien responde. Sin separarlas, no sabes si un error viene de traer el contexto equivocado o de redactar mal sobre el contexto correcto.
- Métricas de recuperación: con un conjunto de preguntas y sus chunks correctos conocidos, mides context recall (¿trajo el chunk que contenía la respuesta?) y context precision (¿qué proporción de lo recuperado era relevante?). Es lo primero que debes medir — si la recuperación falla, nada aguas abajo se salva.
- Fidelidad (faithfulness): ¿la respuesta se sostiene solo con el contexto recuperado, o el modelo agregó cosas de su cuenta? Es la métrica directa contra la alucinación.
- Relevancia de la respuesta: ¿responde realmente lo que se preguntó? Una respuesta fiel pero que no aborda la pregunta también es un fallo.
- Evaluación con un modelo como juez: para escalar, un LLM evalúa cada respuesta contra el contexto y la pregunta según una rúbrica. Se calibra contra un set anotado a mano y automatiza la regresión en cada cambio del pipeline.
Errores comunes al construir RAG
- Saltarse el chunking y partir por caracteres fijos: corta ideas a la mitad y es la causa número uno de recuperaciones pobres. Respeta la estructura del documento.
- Solo búsqueda semántica: falla con siglas, códigos y nombres exactos. La búsqueda híbrida con reranking es el estándar que deberías tener por defecto.
- No medir la recuperación por separado: sin métricas de retrieval, optimizas a ciegas y culpas al modelo de errores que son de recuperación.
- Ignorar la gobernanza de datos: sin filtrar por permisos en la recuperación, tu RAG puede filtrar a un usuario información que no debería ver. Los metadatos de acceso no son opcionales.
- Confiar solo en la instrucción de no alucinar: combínala con un umbral de relevancia que corte la generación cuando no hay contexto suficiente.
Preguntas frecuentes
¿RAG o afinar (fine-tuning) un modelo con mis datos?
¿Qué base de datos vectorial debo usar?
¿Cómo evito que el sistema alucine?
¿Cuánto contexto (cuántos chunks) debo pasarle al modelo?
¿Cuánto tarda construir un RAG en producción?
¿Quieres poner la IA a trabajar sobre el conocimiento de tu empresa, con respuestas fundamentadas y trazables? Diseñamos e implementamos sistemas RAG en producción — desde la ingesta hasta la evaluación.
Habla con el equipo