Inicio / Inteligencia Artificial / AI Engineering Pro / Fundamentos de RAG

Fundamentos de RAG

Arquitectura RAG, chunking, embeddings, vector storage y query pipeline.

Avanzado
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Fundamentos de RAG: Arquitectura y Componentes

¿Qué es RAG?

RAG (Retrieval-Augmented Generation) es el patrón más importante para aplicaciones de IA generativa en producción. Combina la capacidad de generación de un LLM con búsqueda en bases de conocimiento propias.

¿Por Qué RAG?

Los LLMs tienen limitaciones críticas que RAG resuelve:

Problema del LLM Solución RAG
Conocimiento desactualizado Recupera datos actuales
Alucinaciones Contexto verificable con fuentes
Sin datos privados Indexa documentos internos
Contexto limitado Recupera solo lo relevante
Costo de fine-tuning No requiere re-entrenamiento

Arquitectura RAG Completa

┌─────────────────────────────────────────────────────────────┐
│                    PIPELINE DE INDEXACIÓN                     │
│                                                               │
│  Documentos ──► Loader ──► Splitter ──► Embedder ──► VectorDB │
│  (PDF, MD,      (parse)   (chunk)      (vectorize)  (store)   │
│   HTML, DB)                                                    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    PIPELINE DE CONSULTA                       │
│                                                               │
│  Query ──► Embedder ──► VectorDB ──► Reranker ──► Prompt ──► LLM │
│  (user)   (vectorize)  (search)    (refine)    (augment)  (gen) │
│                                                               │
│  ◄────────────────── Respuesta con fuentes ──────────────────► │
└─────────────────────────────────────────────────────────────┘

Pipeline de Indexación

1. Document Loading

from langchain_community.document_loaders import (
    PyPDFLoader,
    UnstructuredMarkdownLoader,
    WebBaseLoader,
    CSVLoader,
)

# PDF
pdf_docs = PyPDFLoader("manual_tecnico.pdf").load()

# Markdown
md_docs = UnstructuredMarkdownLoader("docs/api.md").load()

# Web
web_docs = WebBaseLoader("https://docs.example.com/api").load()

# Cada documento tiene: page_content + metadata
print(pdf_docs[0].metadata)
# {'source': 'manual_tecnico.pdf', 'page': 0}

2. Text Splitting (Chunking)

El chunking es crítico para la calidad de RAG. Chunks muy grandes diluyen información, muy pequeños pierden contexto.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,        # ~1000 caracteres por chunk (ajustar según modelo de embedding)
    chunk_overlap=200,      # 200 chars de overlap: evita perder ideas que quedan en el borde
                            # entre dos chunks — asegura que aparezcan en ambos
    separators=[            # Prioridad de corte (intenta el primero, si no cabe, el siguiente):
        "\n\n",             # 1. Párrafo doble (mejor: preserva secciones completas)
        "\n",               # 2. Salto de línea
        ". ",               # 3. Fin de oración
        " ",                # 4. Espacio (entre palabras)
        ""                  # 5. Carácter a carácter (último recurso)
    ],
    length_function=len,    # Cuenta caracteres; alternativa: tiktoken para contar tokens
)

chunks = splitter.split_documents(documents)
print(f"Documentos: {len(documents)} → Chunks: {len(chunks)}")

Estrategias de Chunking:

Estrategia Cuándo Usar Pros Contras
Fixed-size Texto general Simple, predecible Corta en medio de ideas
Recursive Texto estructurado Respeta estructura Tamaños variables
Semantic Alta calidad Chunks coherentes Más lento, más costoso
Document-based Docs cortos Preserva contexto completo Chunks grandes

3. Embedding

from langchain_openai import OpenAIEmbeddings

# Los embeddings convierten texto a vectores numéricos donde textos semánticamente
# similares quedan cerca en el espacio vectorial — esto permite búsqueda semántica
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-large",
    dimensions=1536,   # Matryoshka embedding: el modelo soporta hasta 3072 dims
                       # Reducir dimensiones ahorra ~50% almacenamiento/costos,
                       # con pérdida mínima de calidad (~1-2% en benchmarks)
)

# embed_query: para la pregunta del usuario (puede usar prefijo interno optimizado)
vector = embeddings.embed_query("¿Cómo configuro autenticación?")
print(f"Dimensiones: {len(vector)}")  # 1536

# embed_documents: para los chunks a indexar (prefijo diferente para búsqueda asimétrica)
vectors = embeddings.embed_documents([chunk.page_content for chunk in chunks])

Modelos de Embedding Populares:

Modelo Dimensiones Contexto Nota
text-embedding-3-large 3072 (ajustable) 8K Mejor calidad OpenAI
text-embedding-3-small 1536 8K Económico
Cohere embed-v4 1024 512 Multilingüe
voyage-3-large 1024 32K Código y texto
BGE-M3 1024 8K Open-source, multilingüe

4. Vector Storage

from langchain_community.vectorstores import Chroma

# Crear e indexar vectores en Chroma (base de datos vectorial ligera)
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",    # Persiste a disco; sin esto, es solo in-memory
    collection_metadata={
        "hnsw:space": "cosine"          # HNSW: algoritmo de indexación para búsqueda rápida
                                        # "cosine": mide similitud por ángulo entre vectores
                                        # Alternativas: "l2" (euclidiana), "ip" (producto interno)
    },
)

# similarity_search: retorna los k documentos más similares semánticamente
results = vectorstore.similarity_search(
    "autenticación JWT", k=5
)

# Con scores: score más bajo = más similar (distancia coseno)
# k=5: balance entre contexto suficiente y no agregar ruido/tokens innecesarios
results_with_scores = vectorstore.similarity_search_with_score(
    "autenticación JWT", k=5
)
for doc, score in results_with_scores:
    print(f"Score: {score:.4f} | {doc.page_content[:80]}...")

Pipeline de Consulta

5. Retrieval

retriever = vectorstore.as_retriever(
    search_type="similarity",     # o "mmr" para diversidad
    search_kwargs={
        "k": 5,                   # Número de resultados
        "score_threshold": 0.7,   # Mínimo de relevancia (0-1); documentos debajo se descartan
                                  # Puede retornar menos de k si no hay docs suficientemente similares
    },
)

# MMR (Maximum Marginal Relevance) — diversifica resultados
# ¿Por qué? Si los top-5 dicen lo mismo, desperdicias contexto del LLM
# MMR selecciona docs que son relevantes Y diferentes entre sí
retriever_mmr = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 5,
        "fetch_k": 20,            # Candidatos iniciales (más = mejor diversidad, más lento)
        "lambda_mult": 0.7,       # 1.0 = solo relevancia, 0.0 = solo diversidad
                                  # 0.7: prioriza relevancia con algo de diversidad
    },
)

6. Prompt Augmentation

from langchain_core.prompts import ChatPromptTemplate

rag_prompt = ChatPromptTemplate.from_template("""
Eres un asistente técnico. Responde SOLO con información del contexto proporcionado.
Si no encuentras la respuesta en el contexto, di "No tengo información suficiente".

Contexto:
{context}

Pregunta: {question}

Instrucciones:
- Responde de forma precisa y concisa
- Cita las fuentes cuando sea posible
- Si la información está incompleta, indícalo
""")

7. Chain Completo

from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# temperature=0: respuestas deterministas y fieles al contexto (no creativas)
llm = ChatOpenAI(model="gpt-4o", temperature=0)

# Convierte la lista de Document objects del retriever en texto formateado con fuentes
def format_docs(docs):
    return "\n\n---\n\n".join(
        f"[Fuente: {d.metadata.get('source', 'N/A')}]\n{d.page_content}"
        for d in docs
    )

# LCEL (LangChain Expression Language) — el | encadena pasos secuencialmente
# Flujo: pregunta → [retriever + passthrough] → prompt → LLM → parser
rag_chain = (
    {
        # retriever busca docs relevantes → format_docs los convierte a texto
        "context": retriever | format_docs,
        # RunnablePassthrough pasa la pregunta del usuario sin modificar
        "question": RunnablePassthrough()
    }
    | rag_prompt       # Inserta context y question en el template
    | llm              # Envía al LLM, recibe AIMessage
    | StrOutputParser() # Extrae el string de texto del AIMessage
)

# Uso
response = rag_chain.invoke("¿Cómo configuro autenticación JWT?")
print(response)

Métricas de Evaluación para RAG

Métrica Qué Mide Rango
Context Precision ¿Los docs recuperados son relevantes? 0-1
Context Recall ¿Se recuperaron todos los docs necesarios? 0-1
Faithfulness ¿La respuesta es fiel al contexto? 0-1
Answer Relevancy ¿La respuesta es relevante a la pregunta? 0-1
# RAGAS: framework de evaluación automática para pipelines RAG
# NOTA: usa un LLM internamente para evaluar (genera costo de API)
from ragas import evaluate
from ragas.metrics import (
    faithfulness,         # ¿Cada afirmación de la respuesta está respaldada por el contexto?
    answer_relevancy,     # ¿La respuesta realmente responde la pregunta?
    context_precision,    # ¿Los chunks relevantes están en las primeras posiciones?
    context_recall,       # ¿Se recuperaron TODOS los chunks necesarios?
)

# eval_dataset debe tener columnas: question, answer, contexts, ground_truth
result = evaluate(
    dataset=eval_dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)
print(result)
# {'faithfulness': 0.89, 'answer_relevancy': 0.92, ...}

Errores Comunes en RAG

Error Síntoma Solución
Chunks muy grandes Respuestas vagas Reducir chunk_size
Chunks muy pequeños Falta contexto Aumentar overlap
Embedding incorrecto Baja relevancia Probar otro modelo
Sin metadata filtering Ruido en resultados Añadir filtros
Prompt débil Alucinaciones Instrucciones más estrictas
Sin reranking Docs irrelevantes en top-k Añadir reranker

Resumen

RAG es el patrón fundamental para aplicaciones de IA generativa con datos propios. Los componentes clave son:

  1. Chunking inteligente que preserve semántica
  2. Embeddings de calidad adecuados al dominio
  3. Vector database eficiente y escalable
  4. Retrieval con reranking para máxima relevancia
  5. Evaluación continua con métricas específicas de RAG

🧠 Preguntas de Repaso

1. En RAG, ¿cuál es la función del parámetro lambda_mult en MMR (Maximum Marginal Relevance)?

  • A) Controla el número máximo de documentos retornados
  • B) Controla el balance entre relevancia y diversidad: 1.0 = solo relevancia, 0.0 = solo diversidad
  • C) Define el tamaño mínimo de los chunks
  • D) Establece el umbral de confianza para filtrar resultados

Respuesta: B)lambda_mult en MMR controla el balance: con 1.0 se prioriza solo relevancia, con 0.0 solo diversidad. Un valor de 0.7 prioriza relevancia pero con suficiente diversidad para evitar redundancia.

2. ¿Por qué el modelo text-embedding-3-large soporta "Matryoshka embeddings"?

  • A) Porque puede procesar múltiples idiomas simultáneamente
  • B) Porque sus dimensiones son reducibles (ej: de 3072 a 1536) ahorrando ~50% de almacenamiento con solo 1-2% de pérdida de calidad
  • C) Porque puede generar embeddings para imágenes y texto
  • D) Porque permite entrenar sub-modelos dentro del modelo principal

Respuesta: B) — Matryoshka embeddings permiten reducir las dimensiones del vector (de 3072 a 1536 o menos) sin necesidad de re-entrenamiento, ahorrando almacenamiento con pérdida mínima de calidad.

3. ¿Cuáles son las 4 métricas principales de RAGAS para evaluar un sistema RAG?

  • A) Accuracy, Precision, Recall, F1
  • B) Context Precision, Context Recall, Faithfulness, Answer Relevancy
  • C) BLEU, ROUGE, METEOR, BERTScore
  • D) Latency, Throughput, Error Rate, Cost

Respuesta: B) — RAGAS evalúa con: Context Precision (relevancia del contexto recuperado), Context Recall (cobertura), Faithfulness (fidelidad al contexto) y Answer Relevancy (relevancia de la respuesta). Todas en rango 0-1.

4. ¿Cuál es un error común al configurar el chunking en RAG y su consecuencia?

  • A) Chunks muy grandes producen respuestas más precisas
  • B) Chunks muy pequeños pierden contexto necesario y chunks muy grandes producen respuestas vagas
  • C) El overlap entre chunks no tiene ningún efecto en la calidad
  • D) Usar separadores jerárquicos reduce la calidad de búsqueda

Respuesta: B) — Un error frecuente es no calibrar el tamaño: chunks muy grandes producen respuestas vagas porque incluyen información irrelevante, mientras que chunks muy pequeños pierden el contexto necesario para respuestas coherentes.

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes