Módulo 1: RAG Pipeline Completo (Architecture Overview)
Decisiones de Arquitectura RAG
Descripción de la cápsula
Construir RAG avanzado no es solo conectar componentes. Es tomar decisiones técnicas en cada capa: qué chunking strategy usar, qué embeddings elegir, qué vector DB seleccionar, cuándo aplicar re-ranking. Cada decisión tiene trade-offs reales: performance vs calidad, costo vs precisión, simplicidad vs features.
Esta cápsula te da un decision framework completo: tablas comparativas, criterios de selección, casos de uso típicos, y ejemplos de decisiones reales. No hay respuestas únicas ("siempre usa X"), sino contexto para decidir inteligentemente según tu caso de uso específico: dataset size, latency requirements, budget constraints, quality targets.
Al final de esta cápsula, podrás justificar tus decisiones de arquitectura con datos y trade-offs claros. No dirás "uso Pinecone porque es popular", sino "uso Pinecone porque mi dataset tiene 5M documentos, necesito <100ms latency, y tengo budget de $200/mes para managed service".
🧩 Decisión 1: Chunking Strategy
Problema:
Documentos largos (5,000+ tokens) no caben en vector DB o LLM context window. Necesitas dividir en chunks.
Opciones disponibles:
Opción A: Fixed-Size Chunking
Cómo funciona:
def fixed_size_chunking(document: str, chunk_size: int = 500) -> list[str]:
"""Divide documento en chunks de tamaño fijo"""
return [document[i:i+chunk_size] for i in range(0, len(document), chunk_size)]
# Ejemplo
doc = "Python es un lenguaje. " * 100 # 2400 caracteres
chunks = fixed_size_chunking(doc, chunk_size=500)
print(f"Chunks: {len(chunks)}") # 5 chunks
print(f"Primer chunk: {chunks[0]}")
Pros:
- ✅ Simple de implementar
- ✅ Rápido
- ✅ Chunks de tamaño predecible
Cons:
- ❌ Puede cortar en medio de oración
- ❌ Pierde contexto semántico
- ❌ No respeta estructura del documento
Cuándo usar:
- Documentos estructurados (tablas, listas)
- Prototipos rápidos
- Dataset pequeño (<1,000 docs)
Opción B: Semantic Chunking
Cómo funciona:
from langchain.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
def semantic_chunking(document: str) -> list[str]:
"""Divide documento preservando coherencia semántica"""
embeddings = OpenAIEmbeddings()
splitter = SemanticChunker(embeddings)
chunks = splitter.split_text(document)
return chunks
# Ejemplo
doc = """
Python es un lenguaje de programación interpretado.
Fue creado por Guido van Rossum en 1991.
FastAPI es un framework web para Python.
Fue creado por Sebastián Ramírez en 2018.
"""
chunks = semantic_chunking(doc)
# Output: 2 chunks (Python vs FastAPI)
# Chunk 1: "Python es... 1991."
# Chunk 2: "FastAPI es... 2018."
Pros:
- ✅ Preserva coherencia semántica
- ✅ No corta en lugares arbitrarios
- ✅ Mejor retrieval quality
Cons:
- ❌ Más lento (genera embeddings)
- ❌ Chunks de tamaño variable
- ❌ Requiere API calls (costo)
Cuándo usar:
- Documentos narrativos (artículos, libros)
- Calidad > velocidad
- Budget permite embeddings extras
Opción C: Recursive Chunking
Cómo funciona:
from langchain.text_splitter import RecursiveCharacterTextSplitter
def recursive_chunking(
document: str,
chunk_size: int = 500,
chunk_overlap: int = 50
) -> list[str]:
"""
Divide documento recursivamente con separadores.
Intenta dividir por:
1. Párrafos (\n\n)
2. Oraciones (. )
3. Palabras ( )
4. Caracteres (si todo falla)
"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(document)
return chunks
# Ejemplo
chunks = recursive_chunking(doc, chunk_size=200, chunk_overlap=30)
# Output: Chunks respetan párrafos/oraciones
# Overlap de 30 chars preserva contexto entre chunks
Pros:
- ✅ Balance: respeta estructura pero tamaño controlado
- ✅ Overlap preserva contexto entre chunks
- ✅ No requiere embeddings adicionales
Cons:
- ❌ Más complejo que fixed-size
- ❌ Separators custom requieren tuning
- ❌ Overlap aumenta storage (duplicación)
Cuándo usar:
- Default para producción (mejor balance)
- Documentos mixtos (narrativo + código + listas)
- Necesitas preservar contexto sin costo de semantic
Comparación de Chunking Strategies:
| Strategy | Velocidad | Calidad Retrieval | Costo | Tamaño Chunks | Cuándo usar |
|---|---|---|---|---|---|
| Fixed-size | Muy rápida | Media | Cero | Fijo (500) | Prototipo, docs estructurados |
| Semantic | Lenta | Alta | Alto | Variable (200-800) | Narrativo, calidad crítica |
| Recursive | Rápida | Media-Alta | Cero | Controlado (500±50) | Production default |
Decisión recomendada para baseline (Módulo 1): Fixed-size (simple)
Decisión recomendada para production (Módulo 7): Recursive (balance)
Profundización: Módulo 2 cubre chunking strategies en detalle
🧠 Decisión 2: Embedding Model
Problema:
Necesitas convertir texto a vectores. ¿Qué modelo usar?
Opciones disponibles:
Opción A: OpenAI text-embedding-ada-002
from openai import OpenAI
client = OpenAI()
embedding = client.embeddings.create(
model="text-embedding-ada-002",
input="Tu texto aquí"
).data[0].embedding
# Dimensiones: 1536
# Costo: $0.0001 per 1K tokens
Pros:
- ✅ Alta calidad (state-of-the-art)
- ✅ Managed (no deployment)
- ✅ 1536 dimensiones (buena resolución)
Cons:
- ❌ Costo por call
- ❌ Requiere API key
- ❌ Latency de red (~50ms)
Cuándo usar:
- Production con budget
- Calidad crítica
- Inglés o multilingüe
Opción B: Local (Sentence-Transformers)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2') # 22MB model
embedding = model.encode("Tu texto aquí")
# Dimensiones: 384
# Costo: Cero (local)
# Latency: ~20ms (local)
Pros:
- ✅ Gratuito (cero costo)
- ✅ Privacidad total (local)
- ✅ Rápido (~20ms sin red)
Cons:
- ❌ Calidad menor que OpenAI
- ❌ 384 dimensiones (menos resolución)
- ❌ Requiere deployment del modelo
Cuándo usar:
- Budget cero
- Privacidad crítica
- Prototipo sin dependencias externas
Opción C: Cohere embed-multilingual-v3
import cohere
co = cohere.Client("api_key")
embedding = co.embed(
texts=["Tu texto aquí"],
model="embed-multilingual-v3.0",
input_type="search_document" # O "search_query"
).embeddings[0]
# Dimensiones: 1024
# Costo: $0.0001 per 1K tokens
Pros:
- ✅ Excelente multilingüe (100+ idiomas)
- ✅ Input types diferentes (document vs query)
- ✅ Alta calidad
Cons:
- ❌ Costo similar a OpenAI
- ❌ Menos adoption que OpenAI
- ❌ Requiere API key diferente
Cuándo usar:
- Multilingüe crítico (español, francés, etc.)
- Necesitas separar document vs query embeddings
Comparación de Embedding Models:
| Modelo | Dimensiones | Costo (1M tokens) | Calidad | Multilingüe | Latency | Cuándo usar |
|---|---|---|---|---|---|---|
| OpenAI ada-002 | 1536 | $0.10 | ⭐⭐⭐⭐⭐ | Bueno | 50ms | Production inglés |
| Cohere multilingual | 1024 | $0.10 | ⭐⭐⭐⭐⭐ | Excelente | 60ms | Production multilingüe |
| Local MiniLM | 384 | Gratis | ⭐⭐⭐ | Limitado | 20ms | Dev, prototipo, privacidad |
Decisión recomendada:
- Baseline (Módulo 1): OpenAI ada-002 (simple, calidad)
- Production: OpenAI si inglés, Cohere si multilingüe
- Dev local: Sentence-Transformers (cero costo)
🗄️ Decisión 3: Vector Database
Problema:
Necesitas almacenar millones de vectores y buscar rápidamente (<100ms).
Opciones disponibles:
Opción A: ChromaDB (Local)
import chromadb
# Client local (en memoria)
client = chromadb.Client()
# Client persistente (en disco)
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.create_collection("docs")
# Agregar documentos
collection.add(
documents=["texto1", "texto2"],
embeddings=[[0.1, ...], [0.2, ...]],
metadatas=[{"source": "doc1"}, {"source": "doc2"}],
ids=["1", "2"]
)
# Query
results = collection.query(
query_embeddings=[[0.15, ...]],
n_results=5
)
Pros:
- ✅ Cero setup (pip install)
- ✅ Gratuito (local)
- ✅ Persistencia en disco
- ✅ Perfecto para desarrollo
Cons:
- ❌ No escala >100K docs (lento)
- ❌ Single-machine (no distributed)
- ❌ No managed (tú administras)
Performance:
- 10K docs: <50ms query ✅
- 100K docs: ~200-500ms ⚠️
- 1M+ docs: No recomendado ❌
Cuándo usar:
- Development y prototipos (módulos 1-6)
- Dataset <50K documentos
- No necesitas managed service
Opción B: Pinecone (Managed Cloud)
from pinecone import Pinecone
# Inicializar
pc = Pinecone(api_key="tu-api-key")
# Crear index
index = pc.create_index(
name="my-index",
dimension=1536,
metric="cosine"
)
# Agregar documentos
index.upsert(
vectors=[
("id1", [0.1, ...], {"text": "texto1"}),
("id2", [0.2, ...], {"text": "texto2"})
]
)
# Query
results = index.query(
vector=[0.15, ...],
top_k=5,
include_metadata=True
)
Pros:
- ✅ Escalabilidad infinita (millones de docs)
- ✅ Performance consistente (<50ms siempre)
- ✅ Managed (no administras tú)
- ✅ Features avanzados (namespaces, filtering)
Cons:
- ❌ Costo ($70/mes minimum para serverless)
- ❌ Requiere API key y cuenta
- ❌ Vendor lock-in
Performance:
- 10K docs: <50ms ✅
- 1M docs: <50ms ✅
- 100M docs: <50ms ✅ (con pods correctos)
Cuándo usar:
- Production (módulo 7)
- Dataset >100K documentos
- Necesitas <100ms latency garantizado
- Budget para managed service
Opción C: Weaviate (Self-Hosted o Cloud)
import weaviate
# Client cloud
client = weaviate.Client(
url="https://tu-cluster.weaviate.network",
auth_client_secret=weaviate.AuthApiKey("api_key")
)
# Crear schema
client.schema.create_class({
"class": "Document",
"vectorizer": "text2vec-openai"
})
# Agregar documentos
client.data_object.create(
data_object={"text": "texto1"},
class_name="Document"
)
# Query
results = client.query.get(
"Document", ["text"]
).with_near_text({"concepts": ["query"]}).with_limit(5).do()
Pros:
- ✅ Self-hosted posible (control total)
- ✅ GraphQL queries (flexible)
- ✅ Vectorizers integrados
Cons:
- ❌ Más complejo que ChromaDB
- ❌ Self-hosted requiere DevOps
- ❌ Cloud pricing similar a Pinecone
Cuándo usar:
- Necesitas self-hosting (privacidad/compliance)
- GraphQL queries son ventaja
- Ya usas Weaviate en empresa
Comparación de Vector Databases:
| Vector DB | Setup | Costo | Performance | Max Docs | Managed | Cuándo usar |
|---|---|---|---|---|---|---|
| ChromaDB | pip install | Gratis | 10K: rápido, 100K+: lento | ~100K | ❌ | Dev, prototipos |
| Pinecone | API key | $70+/mes | Siempre <50ms | Millones | ✅ | Production |
| Weaviate | Docker/Cloud | Gratis (self) o $70+/mes | Bueno | Millones | ✅/❌ | Self-hosting, GraphQL |
| Qdrant | Docker | Gratis (self) | Muy bueno | Millones | ❌ | Self-hosting, Rust |
| Milvus | Docker/Cloud | Gratis (self) | Excelente | Millones | ❌ | Enterprise, scale |
Decision Matrix:
Dataset <10K docs + Dev → ChromaDB ✅
Dataset 10K-100K + Dev → ChromaDB ⚠️
Dataset >100K + Production → Pinecone ✅
Privacidad/Compliance → Weaviate/Qdrant (self-hosted) ✅
Budget cero + <100K docs → ChromaDB ✅
Budget disponible + >100K docs → Pinecone ✅
🎯 Decisión 4: Re-ranking
Problema:
Top-K documentos de similarity search incluyen irrelevantes (false positives).
¿Cuándo aplicar re-ranking?
Sin re-ranking (baseline):
# Similarity search devuelve top-5
results = collection.query(query_embeddings=[...], n_results=5)
top_k_docs = results['documents'][0]
# Problema: Algunos docs son irrelevantes
# Doc 1: Relevante (cosine: 0.85)
# Doc 2: Irrelevante (cosine: 0.83) ← Falso positivo
# Doc 3: Relevante (cosine: 0.81)
Con re-ranking (Módulo 4):
# 1. Retrieval amplio (top-20)
results = collection.query(query_embeddings=[...], n_results=20)
# 2. Re-ranking con cross-encoder
from sentence_transformers import CrossEncoder
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
scores = model.predict([
(user_query, doc) for doc in results['documents'][0]
])
# 3. Seleccionar top-5 re-rankeados
top_5_indices = np.argsort(scores)[::-1][:5]
top_k_docs = [results['documents'][0][i] for i in top_5_indices]
# Resultado: Solo documentos realmente relevantes
Trade-offs de Re-ranking:
| Aspecto | Sin Re-ranking | Con Re-ranking | Delta |
|---|---|---|---|
| Latency | 200ms | 400ms | +200ms |
| Precision@5 | 65% | 85% | +20% |
| Costo compute | Bajo | Medio | +modelo adicional |
| Complejidad | Simple | Media | +componente |
Decisión:
# Decision tree para re-ranking
if precision_requirements > 0.80:
if latency_budget > 500ms:
use_reranking = True # ✅ Vale la pena
else:
use_reranking = False # ❌ Latency crítico
else:
use_reranking = False # ❌ Precision 65% suficiente
Casos de uso típicos:
✅ Usar re-ranking cuando:
- Precision crítica (medical, legal, financial)
- Latency budget >500ms
- False positives costosos
❌ NO usar re-ranking cuando:
- Latency <300ms crítico
- Precision 60-70% suficiente
- Prototipos simples
🔗 Decisión 5: Componentes Opcionales
Hybrid Search (Módulo 5):
¿Cuándo agregar?
- ✅ Queries con keywords exactos (nombres, códigos, IDs)
- ✅ Semantic search falla en casos específicos
- ❌ Solo narrativo (semantic suficiente)
Trade-off: +complejidad, +mejor cobertura
Metadata Filtering (Módulo 6):
¿Cuándo agregar?
- ✅ Necesitas filtrar por contexto (fecha, autor, categoría)
- ✅ Corpus grande con subsets lógicos
- ❌ Corpus pequeño homogéneo
Trade-off: +metadata storage, +relevancia contextual
Query Optimization (Módulo 3):
¿Cuándo agregar?
- ✅ Queries del usuario son ambiguas
- ✅ Necesitas aumentar recall (encontrar más relevantes)
- ❌ Queries ya son claras y específicas
Trade-off: +LLM calls, +mejor retrieval
📋 Decision Framework Completo
Paso 1: Definir requisitos
Mi caso de uso:
- Dataset size: [X documentos]
- Query volume: [Y queries/día]
- Latency target: [Z ms]
- Precision target: [W%]
- Budget: [$X/mes]
- Deployment: [Cloud/On-prem]
Paso 2: Seleccionar componentes
# Chunking
if document_type == "narrative":
chunking = "semantic" # Módulo 2
elif document_type == "technical":
chunking = "recursive" # Default
else:
chunking = "fixed" # Simple
# Embeddings
if budget == "zero":
embeddings = "sentence-transformers" # Local
elif multilingual:
embeddings = "cohere" # Mejor multilingüe
else:
embeddings = "openai" # Default
# Vector DB
if dataset_size > 100_000:
vector_db = "pinecone" # Production
else:
vector_db = "chromadb" # Dev
# Re-ranking
if precision_target > 0.80 and latency_budget > 500:
use_reranking = True # Módulo 4
else:
use_reranking = False
# Hybrid search
if queries_contain_keywords:
use_hybrid = True # Módulo 5
else:
use_hybrid = False
# Metadata filtering
if need_contextual_filtering:
use_metadata = True # Módulo 6
else:
use_metadata = False
Paso 3: Estimar costo y performance
# Estimación de costo mensual
embeddings_cost = (docs * avg_tokens_per_doc / 1000) * 0.0001
queries_cost = (queries_per_month * avg_tokens_per_query / 1000) * 0.0001
vector_db_cost = 70 if use_pinecone else 0
reranking_cost = queries_per_month * 0.001 if use_reranking else 0
total_monthly_cost = embeddings_cost + queries_cost + vector_db_cost + reranking_cost
# Estimación de performance
baseline_latency = 200 # Retrieval + generation
reranking_latency = 200 if use_reranking else 0
total_latency = baseline_latency + reranking_latency
print(f"Costo mensual estimado: ${total_monthly_cost:.2f}")
print(f"Latency esperada: {total_latency}ms")
🎯 Resumen
Conceptos clave:
- ✅ Chunking: Fixed (simple) vs Semantic (calidad) vs Recursive (balance) - Módulo 2 profundiza
- ✅ Embeddings: OpenAI (calidad) vs Local (gratis) vs Cohere (multilingüe) - Módulo 1 usa OpenAI
- ✅ Vector DB: ChromaDB (dev) vs Pinecone (production) - Módulos 1-6 usan Chroma, Módulo 7 migra a Pinecone
- ✅ Re-ranking: Agregar si precision >80% crítica - Módulo 4 profundiza
- ✅ Decision framework: Requisitos → Componentes → Costo/Performance → Decisión
- ✅ Trade-offs: Todo tiene trade-off (costo vs calidad, latency vs precision, simplicidad vs features)
Qué sigue:
Cápsula 04 te enseña métricas de éxito: cómo definir qué es "bueno" en RAG (latency targets, accuracy targets, cost budgets), y cómo medir tu sistema con métricas objetivas.
📚 Recursos Adicionales
- Vector Database Comparison - Comparación detallada de vector DBs
- Embedding Models Benchmark - MTEB leaderboard (benchmark oficial)
- ChromaDB vs Pinecone - Cuándo migrar a managed
- Chunking Strategies Overview - Análisis de chunk size
- OpenAI vs Cohere Embeddings - Comparación práctica
- RAG Architecture Patterns - Patterns de LangChain
Creado: Febrero 6, 2026
Versión: 1.0