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:

StrategyVelocidadCalidad RetrievalCostoTamaño ChunksCuándo usar
Fixed-sizeMuy rápidaMediaCeroFijo (500)Prototipo, docs estructurados
SemanticLentaAltaAltoVariable (200-800)Narrativo, calidad crítica
RecursiveRápidaMedia-AltaCeroControlado (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:

ModeloDimensionesCosto (1M tokens)CalidadMultilingüeLatencyCuándo usar
OpenAI ada-0021536$0.10⭐⭐⭐⭐⭐Bueno50msProduction inglés
Cohere multilingual1024$0.10⭐⭐⭐⭐⭐Excelente60msProduction multilingüe
Local MiniLM384Gratis⭐⭐⭐Limitado20msDev, 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 DBSetupCostoPerformanceMax DocsManagedCuándo usar
ChromaDBpip installGratis10K: rápido, 100K+: lento~100KDev, prototipos
PineconeAPI key$70+/mesSiempre <50msMillonesProduction
WeaviateDocker/CloudGratis (self) o $70+/mesBuenoMillones✅/❌Self-hosting, GraphQL
QdrantDockerGratis (self)Muy buenoMillonesSelf-hosting, Rust
MilvusDocker/CloudGratis (self)ExcelenteMillonesEnterprise, 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:

AspectoSin Re-rankingCon Re-rankingDelta
Latency200ms400ms+200ms
Precision@565%85%+20%
Costo computeBajoMedio+modelo adicional
ComplejidadSimpleMedia+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

  1. Vector Database Comparison - Comparación detallada de vector DBs
  2. Embedding Models Benchmark - MTEB leaderboard (benchmark oficial)
  3. ChromaDB vs Pinecone - Cuándo migrar a managed
  4. Chunking Strategies Overview - Análisis de chunk size
  5. OpenAI vs Cohere Embeddings - Comparación práctica
  6. RAG Architecture Patterns - Patterns de LangChain

Creado: Febrero 6, 2026
Versión: 1.0