Módulo 1: RAG Pipeline Completo (Architecture Overview)
Componentes del RAG Pipeline
Descripción de la cápsula
RAG no es un monolito mágico. Es un pipeline con 4 componentes distintos que trabajan en secuencia: Indexing (preparar documentos), Retrieval (buscar información relevante), Generation (crear respuesta), y Evaluation (medir calidad). Cada componente tiene decisiones técnicas específicas y puntos de optimización.
Entender estos componentes es crítico porque las técnicas avanzadas que aprenderás en módulos 2-8 se aplican en componentes específicos: chunking optimiza Indexing, re-ranking mejora Retrieval, prompt engineering afecta Generation, y RAGAS mide Evaluation. Sin entender dónde va cada técnica, estarías aplicándolas al azar.
Esta cápsula te da el mapa detallado de cada componente: qué hace, qué decisiones tomas, dónde se aplican técnicas avanzadas, y cómo interactúan entre sí. Al final, podrás dibujar el pipeline completo y explicar el flujo de datos desde documento crudo hasta respuesta final.
🏗️ Arquitectura RAG Completa
Vista de alto nivel:
┌─────────────┐
│ INDEXING │ ← Preparación offline (una vez)
└──────┬──────┘
│
v
┌─────────────┐
│ RETRIEVAL │ ← Query time (cada request)
└──────┬──────┘
│
v
┌─────────────┐
│ GENERATION │ ← Query time (cada request)
└──────┬──────┘
│
v
┌─────────────┐
│ EVALUATION │ ← Continuo (medir y mejorar)
└─────────────┘
Flujo de datos:
- Indexing: Documento → Chunks → Embeddings → Vector DB
- Retrieval: User query → Query embedding → Similarity search → Top-K docs
- Generation: Top-K docs + Query → Context injection → LLM → Response
- Evaluation: Response → Metrics (faithfulness, relevancy) → Feedback loop
📥 Componente 1: Indexing
¿Qué hace?
Prepara documentos para búsqueda semántica: divide en chunks, crea embeddings, almacena en vector DB.
Subcomponentes de Indexing:
1.1 Chunking (División de documentos)
Decisión: ¿Cómo dividir documentos largos?
# Opción A: Fixed-size chunking (naive)
chunks = [document[i:i+500] for i in range(0, len(document), 500)]
# Opción B: Semantic chunking (preserva coherencia)
chunks = semantic_chunker.split(document) # Divide por temas
# Opción C: Recursive chunking (LangChain)
chunks = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
).split_text(document)
Trade-offs:
| Strategy | Pros | Cons | Cuándo usar |
|---|---|---|---|
| Fixed-size | Rápido, simple | Pierde contexto semántico | Prototipos, docs estructurados |
| Semantic | Preserva coherencia | Lento, chunks variables | Narrativo, artículos |
| Recursive | Balance, overlap | Complejidad media | Production default |
Técnicas avanzadas (Módulo 2):
- Semantic chunking basado en embeddings
- Recursive con separators custom
- Chunking por estructura (HTML, Markdown, JSON)
1.2 Embedding Model (Vectorización)
Decisión: ¿Qué modelo de embeddings usar?
# Opción A: OpenAI (managed, calidad alta)
from openai import OpenAI
client = OpenAI()
embedding = client.embeddings.create(
model="text-embedding-ada-002", # 1536 dimensiones
input="Tu texto aquí"
).data[0].embedding
# Opción B: Local (gratuito, privado)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2') # 384 dimensiones
embedding = model.encode("Tu texto aquí")
# Opción C: Cohere (multilingüe, calidad)
import cohere
co = cohere.Client("api_key")
embedding = co.embed(
texts=["Tu texto aquí"],
model="embed-multilingual-v3.0" # 1024 dimensiones
).embeddings[0]
Comparación de modelos:
| Modelo | Dimensiones | Costo | Calidad | Latency | Multilingüe |
|---|---|---|---|---|---|
| OpenAI ada-002 | 1536 | $0.0001/1K tokens | Alta | ~50ms | Sí |
| Cohere multilingual-v3 | 1024 | $0.0001/1K tokens | Muy alta | ~60ms | Excelente |
| SentenceTransformers local | 384-768 | Gratis | Media | ~20ms | Limitado |
Decisión típica:
- Prototipo: Local (gratis, rápido)
- Producción inglés: OpenAI (calidad/costo)
- Producción multilingüe: Cohere (mejor soporte)
1.3 Vector Storage (Base de datos)
Decisión: ¿Dónde almacenar embeddings?
# Opción A: ChromaDB (local, gratuito)
import chromadb
client = chromadb.Client()
collection = client.create_collection("docs")
collection.add(
documents=["Texto del chunk"],
embeddings=[[0.1, 0.2, ...]], # Vector 1536D
ids=["chunk_001"]
)
# Opción B: Pinecone (managed, production)
import pinecone
index = pinecone.Index("my-index")
index.upsert(
vectors=[
("chunk_001", [0.1, 0.2, ...], {"text": "Texto del chunk"})
]
)
# Opción C: Numpy (in-memory, simple)
import numpy as np
embeddings_matrix = np.array([
[0.1, 0.2, ...], # Chunk 1
[0.3, 0.4, ...], # Chunk 2
])
Comparación de Vector DBs:
| Vector DB | Setup | Costo | Performance | Escalabilidad | Cuándo usar |
|---|---|---|---|---|---|
| ChromaDB | Local, fácil | Gratis | 10K docs: rápido | 100K+ docs: lento | Dev, prototipos |
| Pinecone | Managed cloud | $70/mes+ | Siempre rápido | Millones de docs | Production |
| Numpy | In-memory | Gratis | <1K docs: ok | No escala | Testing |
Progresión típica:
- Desarrollo: ChromaDB local (módulos 1-6)
- Production: Pinecone managed (módulo 7)
Pipeline completo de Indexing:
# indexing_pipeline.py
from openai import OpenAI
import chromadb
def index_documents(documents: list[str]):
"""
Pipeline de indexing completo.
Input: Lista de documentos crudos
Output: Vector DB poblado con embeddings
"""
# 1. Chunking (naive fixed-size)
chunks = []
for doc in documents:
doc_chunks = [doc[i:i+500] for i in range(0, len(doc), 500)]
chunks.extend(doc_chunks)
print(f"✅ Creados {len(chunks)} chunks de {len(documents)} documentos")
# 2. Embeddings (OpenAI)
client = OpenAI()
embeddings = []
for chunk in chunks:
response = client.embeddings.create(
model="text-embedding-ada-002",
input=chunk
)
embeddings.append(response.data[0].embedding)
print(f"✅ Creados {len(embeddings)} embeddings (1536D cada uno)")
# 3. Storage (ChromaDB)
chroma_client = chromadb.Client()
collection = chroma_client.create_collection("my_docs")
collection.add(
documents=chunks,
embeddings=embeddings,
ids=[f"chunk_{i}" for i in range(len(chunks))]
)
print(f"✅ Almacenados en ChromaDB")
return collection
# Uso
documents = [
"FastAPI es un framework web moderno para Python...",
"LangChain es una librería para construir aplicaciones con LLMs...",
# ... más documentos
]
collection = index_documents(documents)
Output esperado:
✅ Creados 12 chunks de 3 documentos
✅ Creados 12 embeddings (1536D cada uno)
✅ Almacenados en ChromaDB
🔍 Componente 2: Retrieval
¿Qué hace?
Busca los chunks más relevantes para la query del usuario usando similarity search.
Subcomponentes de Retrieval:
2.1 Query Processing (Procesamiento de query)
Sin optimización (baseline):
# Query directa sin procesamiento
user_query = "¿Qué es FastAPI?"
# Crear embedding de la query
query_embedding = client.embeddings.create(
model="text-embedding-ada-002",
input=user_query
).data[0].embedding
Con optimización (Módulo 3):
# Query expansion (generar queries similares)
expanded_queries = [
"¿Qué es FastAPI?",
"Características de FastAPI",
"FastAPI framework explicación"
]
# Query rewriting (reformular para claridad)
rewritten_query = llm.invoke(
f"Reformula esta query para búsqueda: {user_query}"
)
Técnicas avanzadas (Módulo 3):
- Query expansion (aumentar recall)
- Query rewriting (claridad)
- Query decomposition (multi-hop reasoning)
- HyDE (Hypothetical Document Embeddings)
2.2 Similarity Search (Búsqueda vectorial)
Baseline: Cosine similarity
# ChromaDB similarity search
results = collection.query(
query_embeddings=[query_embedding],
n_results=5 # Top-5 documentos
)
# Resultado
{
'ids': [['chunk_3', 'chunk_7', 'chunk_1', 'chunk_9', 'chunk_5']],
'distances': [[0.15, 0.18, 0.21, 0.23, 0.25]], # Cosine distance
'documents': [['FastAPI es...', 'FastAPI tiene...', ...]]
}
Métricas de distancia:
| Métrica | Fórmula | Rango | Cuándo usar |
|---|---|---|---|
| Cosine | 1 - cos(θ) | [0, 2] | Default (normalizado) |
| Euclidean | ||a - b|| | [0, ∞] | Embeddings no normalizados |
| Dot product | a · b | [-∞, ∞] | Magnitud importa |
Decisión típica: Cosine similarity (default en 95% de casos)
2.3 Re-ranking (Mejora de precisión)
Sin re-ranking (baseline):
# Devolver top-5 directamente
top_k_docs = results['documents'][0][:5]
Con re-ranking (Módulo 4):
# Re-rankear con cross-encoder
from sentence_transformers import CrossEncoder
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# Re-rankear top-20 → top-5
scores = model.predict([
(user_query, doc) for doc in results['documents'][0][:20]
])
# Ordenar por score de cross-encoder
reranked_indices = np.argsort(scores)[::-1][:5]
top_k_docs = [results['documents'][0][i] for i in reranked_indices]
Mejora típica con re-ranking:
- Precision@5: 65% → 85% (+20%)
- Latency: +150-200ms
- Costo: +complejidad
Trade-off: Vale la pena en producción si precisión > latencia.
Pipeline completo de Retrieval:
# retrieval_pipeline.py
def retrieve_relevant_docs(
user_query: str,
collection,
top_k: int = 5
) -> list[str]:
"""
Pipeline de retrieval completo.
Input: Query del usuario
Output: Top-K documentos más relevantes
"""
# 1. Query embedding
client = OpenAI()
query_embedding = client.embeddings.create(
model="text-embedding-ada-002",
input=user_query
).data[0].embedding
print(f"✅ Query embedding creado (1536D)")
# 2. Similarity search
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
docs = results['documents'][0]
distances = results['distances'][0]
print(f"✅ Encontrados {len(docs)} documentos relevantes")
print(f" Distancias: {[f'{d:.3f}' for d in distances]}")
return docs
# Uso
docs = retrieve_relevant_docs(
user_query="¿Qué es FastAPI?",
collection=collection,
top_k=5
)
for i, doc in enumerate(docs, 1):
print(f"{i}. {doc[:100]}...")
Output esperado:
✅ Query embedding creado (1536D)
✅ Encontrados 5 documentos relevantes
Distancias: ['0.150', '0.180', '0.210', '0.230', '0.250']
1. FastAPI es un framework web moderno y rápido para Python...
2. FastAPI tiene validación automática de datos con Pydantic...
3. FastAPI genera documentación automática con Swagger UI...
4. FastAPI es async-first, soporta async/await nativo...
5. FastAPI es usado por Microsoft, Netflix, y Uber...
🤖 Componente 3: Generation
¿Qué hace?
Genera respuesta usando LLM con contexto de documentos recuperados.
Subcomponentes de Generation:
3.1 Context Injection (Inyectar contexto)
Pattern básico:
# Construir contexto desde documentos recuperados
context = "\n\n".join([
f"Documento {i+1}: {doc}"
for i, doc in enumerate(docs)
])
# Template de prompt
prompt_template = f"""
Usa el siguiente contexto para responder la pregunta.
Contexto:
{context}
Pregunta: {user_query}
Respuesta:
"""
Pattern avanzado (con metadata):
# Incluir metadata en contexto
context_with_metadata = "\n\n".join([
f"[Fuente: {doc['source']}, Fecha: {doc['date']}]\n{doc['text']}"
for doc in docs_with_metadata
])
3.2 LLM Prompting (Generación)
Baseline prompt:
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "Eres un asistente útil que responde preguntas basándote en el contexto proporcionado."},
{"role": "user", "content": prompt_template}
],
temperature=0.0 # Determinístico
)
answer = response.choices[0].message.content
Advanced prompt (con instrucciones):
system_prompt = """
Eres un asistente técnico experto.
Instrucciones:
1. Responde SOLO basándote en el contexto proporcionado
2. Si el contexto no contiene la información, di "No tengo suficiente información"
3. Cita los documentos que usaste (Documento 1, Documento 2, etc.)
4. Sé conciso pero completo
5. Si hay información contradictoria, menciona ambas versiones
"""
response = client.chat.completions.create(
model="gpt-4-turbo", # Mejor calidad
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt_template}
],
temperature=0.0
)
3.3 Response Formatting (Formato de respuesta)
Response structure:
# Estructura de respuesta completa
response_obj = {
"answer": answer,
"sources": [doc['id'] for doc in docs],
"confidence": calculate_confidence(answer, docs),
"model_used": "gpt-3.5-turbo",
"tokens_used": response.usage.total_tokens,
"latency_ms": latency
}
Pipeline completo de Generation:
# generation_pipeline.py
def generate_answer(
user_query: str,
retrieved_docs: list[str]
) -> dict:
"""
Pipeline de generation completo.
Input: Query + documentos recuperados
Output: Respuesta generada por LLM
"""
# 1. Context injection
context = "\n\n".join([
f"Documento {i+1}: {doc}"
for i, doc in enumerate(retrieved_docs)
])
prompt = f"""
Usa el siguiente contexto para responder la pregunta.
Contexto:
{context}
Pregunta: {user_query}
Respuesta:
"""
# 2. LLM generation
client = OpenAI()
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "Eres un asistente útil. Responde basándote solo en el contexto proporcionado."},
{"role": "user", "content": prompt}
],
temperature=0.0
)
answer = response.choices[0].message.content
tokens = response.usage.total_tokens
print(f"✅ Respuesta generada ({tokens} tokens)")
# 3. Response formatting
return {
"answer": answer,
"sources": [f"Doc {i+1}" for i in range(len(retrieved_docs))],
"model": "gpt-3.5-turbo",
"tokens": tokens
}
# Uso
result = generate_answer(
user_query="¿Qué es FastAPI?",
retrieved_docs=docs
)
print(f"Respuesta: {result['answer']}")
print(f"Fuentes: {result['sources']}")
print(f"Tokens: {result['tokens']}")
Output esperado:
✅ Respuesta generada (234 tokens)
Respuesta: FastAPI es un framework web moderno y rápido para Python, diseñado para construir APIs con alta performance. Sus características principales incluyen validación automática de datos con Pydantic, generación automática de documentación con Swagger UI, y soporte nativo para async/await. Es usado por empresas como Microsoft, Netflix y Uber en producción.
Fuentes: ['Doc 1', 'Doc 2', 'Doc 3']
Tokens: 234
📊 Componente 4: Evaluation
¿Qué hace?
Mide calidad del sistema RAG con métricas objetivas para identificar mejoras.
Métricas de Evaluation:
4.1 Retrieval Metrics (Calidad de búsqueda)
# Precision: ¿Cuántos de los recuperados son relevantes?
precision = relevant_retrieved / total_retrieved
# Recall: ¿Cuántos de los relevantes fueron recuperados?
recall = relevant_retrieved / total_relevant
# Ejemplo
# Total relevant docs en DB: 10
# Retrieved docs: 5
# Relevant en retrieved: 4
precision = 4 / 5 # 0.80 (80% de lo recuperado es relevante)
recall = 4 / 10 # 0.40 (solo encontramos 40% de los relevantes)
4.2 Generation Metrics (Calidad de respuesta)
Faithfulness (Groundedness):
# ¿La respuesta está basada en el contexto?
# Score: 0.0 (inventada) a 1.0 (completamente grounded)
# Ejemplo
context = "FastAPI es un framework web."
answer_grounded = "FastAPI es un framework web." # Faithfulness: 1.0
answer_hallucinated = "FastAPI fue creado en 2015." # Faithfulness: 0.0
Answer Relevancy:
# ¿La respuesta contesta la pregunta?
# Score: 0.0 (irrelevante) a 1.0 (perfectamente relevante)
# Ejemplo
question = "¿Qué es FastAPI?"
answer_relevant = "FastAPI es un framework web." # Relevancy: 1.0
answer_irrelevant = "Python es un lenguaje." # Relevancy: 0.3
Evaluation con RAGAS (Módulo 8):
# Evaluation automática con RAGAS
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy
# Dataset de evaluación
dataset = {
"question": ["¿Qué es FastAPI?"],
"answer": ["FastAPI es un framework web..."],
"contexts": [["FastAPI es un framework web moderno..."]],
"ground_truth": ["FastAPI es un framework web de Python"]
}
# Evaluar
results = evaluate(
dataset,
metrics=[faithfulness, answer_relevancy]
)
print(results)
# Output:
# {
# 'faithfulness': 0.95,
# 'answer_relevancy': 0.92
# }
Interpretación de scores:
| Score | Interpretación | Acción |
|---|---|---|
| 0.90+ | Excelente | Production-ready |
| 0.80-0.89 | Bueno | Optimizaciones menores |
| 0.70-0.79 | Aceptable | Revisar chunking/prompts |
| <0.70 | Necesita mejora | Rediseñar pipeline |
🔄 Feedback Loop (Mejora continua)
┌──────────────┐
│ Query │
└──────┬───────┘
│
v
┌──────────────┐
│ Retrieval │
└──────┬───────┘
│
v
┌──────────────┐
│ Generation │
└──────┬───────┘
│
v
┌──────────────┐
│ Evaluation │ ← Medir faithfulness, relevancy
└──────┬───────┘
│
│ (Si score <0.80)
v
┌──────────────┐
│ Mejoras │ ← Ajustar chunking, prompts, re-ranking
└──────┬───────┘
│
└───────> Loop back
🎯 Resumen
Conceptos clave:
- ✅ RAG tiene 4 componentes: Indexing, Retrieval, Generation, Evaluation
- ✅ Indexing: Chunking → Embeddings → Vector DB (offline, una vez)
- ✅ Retrieval: Query embedding → Similarity search → Top-K docs (query time)
- ✅ Generation: Context injection → LLM prompting → Response (query time)
- ✅ Evaluation: Retrieval metrics + Generation metrics → Feedback loop (continuo)
- ✅ Técnicas avanzadas se aplican en componentes específicos (chunking en Indexing, re-ranking en Retrieval)
- ✅ Pipeline completo: documento → respuesta en 4 pasos claros
Qué sigue:
Cápsula 03 te enseña decisiones de arquitectura: qué chunking strategy usar, qué embeddings elegir, qué vector DB seleccionar, y cuándo aplicar re-ranking. Decisiones técnicas basadas en trade-offs reales.
📚 Recursos Adicionales
- LangChain Components Docs - Documentación oficial de componentes
- ChromaDB Architecture - Arquitectura de vector DB
- OpenAI Embeddings Best Practices - Guía oficial
- RAGAS Evaluation Framework - Evaluation metrics para RAG
- Building Production RAG (Video) - Arquitectura en producción
- RAG Components Deep Dive - Artículo técnico de Pinecone
Creado: Febrero 6, 2026
Versión: 1.0