Módulo 1: RAG Pipeline Completo (Architecture Overview)
Métricas de Éxito en RAG
Descripción de la cápsula
"¿Mi RAG es bueno?" No es una pregunta filosófica. Es cuantificable con métricas objetivas: latency (velocidad), accuracy (precisión), y cost (costo operativo). Esta cápsula te enseña a definir qué significa "bueno" según tu caso de uso y cómo medir tu sistema con números reales.
Sin métricas, estás optimizando a ciegas. Agregas re-ranking y "parece mejor", pero ¿cuánto mejor? ¿Vale la latencia adicional? ¿Justifica el costo? Con métricas puedes responder: "Re-ranking mejora precision de 65% a 85% (+20%), agrega 180ms de latency, y cuesta $0.002 extra por query. Para mi caso de uso (búsqueda de documentos legales), precision >80% es crítica, así que vale la pena."
Esta cápsula te da: (1) Métricas estándar de industria, (2) Targets típicos por tipo de aplicación, (3) Cómo medir cada métrica, (4) Cómo interpretar resultados y tomar decisiones basadas en datos.
⏱️ Métrica 1: Latency (Velocidad de Respuesta)
¿Qué es latency?
Tiempo desde que usuario hace query hasta que recibe respuesta completa.
Componentes de latency en RAG:
import time
start = time.time()
# 1. Query embedding (~50ms)
query_embedding = create_embedding(user_query)
t1 = time.time()
# 2. Vector search (~30ms para 10K docs)
docs = collection.query(query_embeddings=[query_embedding], n_results=5)
t2 = time.time()
# 3. LLM generation (~500-1500ms dependiendo de tokens)
answer = llm.invoke(context + query)
t3 = time.time()
# Latency total
total_latency = (t3 - start) * 1000 # En milisegundos
print(f"""
Latency breakdown:
- Query embedding: {(t1-start)*1000:.0f}ms
- Vector search: {(t2-t1)*1000:.0f}ms
- LLM generation: {(t3-t2)*1000:.0f}ms
- Total: {total_latency:.0f}ms
""")
Output típico (baseline RAG):
Latency breakdown:
- Query embedding: 52ms
- Vector search: 28ms (ChromaDB, 10K docs)
- LLM generation: 620ms (GPT-3.5-turbo, 150 tokens output)
- Total: 700ms
Latency Targets por Tipo de Aplicación:
| Aplicación | Target Latency | Justificación |
|---|---|---|
| Chatbot interactivo | <1,000ms (1 seg) | Usuario espera respuesta inmediata |
| Document search | <2,000ms (2 seg) | Búsqueda tradicional es ~1 seg, RAG puede ser 2x |
| Email assistant | <3,000ms (3 seg) | Async, no blocking UI |
| Batch processing | <10,000ms (10 seg) | Background jobs, latency no crítica |
Decisión típica para chatbot:
# Target: <1,000ms total
if total_latency > 1000:
# Optimizaciones:
# - Usar gpt-3.5-turbo (no gpt-4) → -300ms
# - Cache embeddings de queries comunes → -50ms
# - Reducir top-K de 10 a 5 → -10ms
# - Parallel retrieval si multi-query → -100ms
pass
Medir latency con percentiles:
import statistics
# Medir 100 queries
latencies = []
for query in test_queries:
start = time.time()
answer = rag_system.query(query)
latency = (time.time() - start) * 1000
latencies.append(latency)
# Calcular percentiles
p50 = statistics.median(latencies)
p95 = statistics.quantiles(latencies, n=20)[18] # Percentil 95
p99 = statistics.quantiles(latencies, n=100)[98] # Percentil 99
print(f"""
Latency analysis (n=100):
- P50 (median): {p50:.0f}ms
- P95: {p95:.0f}ms
- P99: {p99:.0f}ms
""")
Interpretación:
P50: 680ms → Experiencia típica del usuario
P95: 1,200ms → 95% de usuarios ven <1.2s
P99: 2,300ms → 1% de usuarios ve >2s (outliers)
Target típico: P95 <1,000ms (95% de usuarios ve respuesta en <1 seg)
🎯 Métrica 2: Accuracy (Precisión de Retrieval y Generation)
Accuracy tiene 2 partes:
- Retrieval accuracy: ¿Recuperamos documentos relevantes?
- Generation accuracy: ¿La respuesta es correcta y grounded?
2.1 Retrieval Accuracy:
Precision (Precisión):
# ¿Cuántos de los recuperados son relevantes?
precision = relevant_retrieved / total_retrieved
# Ejemplo
retrieved_docs = 5 # Top-5
relevant_in_retrieved = 4 # 4 son relevantes, 1 es basura
precision = 4 / 5 # 0.80 (80%)
Interpretación:
- Precision alta: Poco ruido, documentos son relevantes
- Precision baja: Mucho ruido, documentos irrelevantes confunden LLM
Target típico: Precision >0.70 (70%+ de top-K son relevantes)
Recall (Cobertura):
# ¿Cuántos de los relevantes fueron recuperados?
recall = relevant_retrieved / total_relevant_in_db
# Ejemplo
total_relevant_in_db = 10 # Hay 10 docs relevantes en total
relevant_retrieved = 4 # Recuperamos 4 de esos 10
recall = 4 / 10 # 0.40 (40%)
Interpretación:
- Recall alto: Encontramos la mayoría de documentos relevantes
- Recall bajo: Dejamos documentos relevantes sin recuperar
Target típico: Recall >0.50 (encontramos 50%+ de relevantes)
Trade-off Precision vs Recall:
# Aumentar K aumenta recall pero disminuye precision
top_k = 5: Precision 0.80, Recall 0.40
top_k = 10: Precision 0.70, Recall 0.70
top_k = 20: Precision 0.60, Recall 0.90
# Decisión: Balance según caso de uso
if precision_critical:
top_k = 5 # Menos ruido para LLM
elif recall_critical:
top_k = 20 # No dejar documentos relevantes fuera
else:
top_k = 10 # Balance
2.2 Generation Accuracy:
Faithfulness (Groundedness):
# ¿La respuesta está basada en el contexto?
# Score: 0.0 (inventada) a 1.0 (completamente grounded)
# Evaluar manualmente (baseline)
context = "FastAPI es un framework web."
answer = "FastAPI es un framework web de Python."
# ¿Está "de Python" en el contexto? No → Faithfulness <1.0
# ¿Es una inferencia razonable? Sí → Faithfulness ~0.90
Evaluar con RAGAS (Módulo 8):
from ragas.metrics import faithfulness
score = faithfulness.score(
question="¿Qué es FastAPI?",
answer="FastAPI es un framework web de Python.",
contexts=["FastAPI es un framework web."]
)
print(f"Faithfulness: {score:.2f}") # 0.90
Interpretación:
| Score | Interpretación | Acción |
|---|---|---|
| 0.95+ | Completamente grounded | ✅ Excelente |
| 0.85-0.94 | Mayormente grounded | ✅ Bueno |
| 0.70-0.84 | Algunas inferencias | ⚠️ Revisar prompts |
| <0.70 | Hallucinations | ❌ Rediseñar prompts |
Answer Relevancy:
# ¿La respuesta contesta la pregunta?
# Score: 0.0 (irrelevante) a 1.0 (perfecto)
# Ejemplo
question = "¿Qué es FastAPI?"
answer_relevant = "FastAPI es un framework web." # Relevancy: 1.0
answer_partial = "FastAPI es popular." # Relevancy: 0.60
answer_irrelevant = "Python es un lenguaje." # Relevancy: 0.10
Target típico: Relevancy >0.85 (respuesta contesta bien la pregunta)
💰 Métrica 3: Cost (Costo Operativo)
Componentes de costo en RAG:
# Costo por query
embedding_cost = 0.0001 # $0.0001 per 1K tokens (OpenAI)
llm_cost = 0.002 # $0.002 per 1K tokens (GPT-3.5 input + output)
vector_db_cost = 70 / queries_per_month # Pinecone $70/mes
reranking_cost = 0.001 # Cross-encoder compute
total_cost_per_query = embedding_cost + llm_cost + vector_db_cost + reranking_cost
# Para 10,000 queries/mes
monthly_cost = total_cost_per_query * 10_000
print(f"Costo por query: ${total_cost_per_query:.4f}")
print(f"Costo mensual: ${monthly_cost:.2f}")
Cálculo detallado:
# Ejemplo: RAG para documentation search
# 10K queries/día = 300K queries/mes
# Indexing (one-time)
indexing_cost = (100_000_docs * 500_tokens / 1000) * 0.0001 # $5.00
# Query embeddings
query_embedding_cost = (300_000 * 20_tokens / 1000) * 0.0001 # $0.60/mes
# LLM generation
llm_cost_per_query = (
(1000_tokens_input / 1000) * 0.0015 + # Input
(200_tokens_output / 1000) * 0.002 # Output
) = 0.0019
llm_cost_monthly = 0.0019 * 300_000 # $570/mes
# Vector DB
vector_db_cost = 70 # Pinecone serverless
# Total
total_monthly = 0.60 + 570 + 70 # $640.60/mes
cost_per_query = 640.60 / 300_000 # $0.0021 per query
Cost Optimization Strategies:
| Strategy | Saving | Trade-off | Módulo |
|---|---|---|---|
| Cache responses | 50-70% | Freshness | Módulo 7 |
| Use gpt-3.5 (no gpt-4) | 90% | Calidad -10% | Módulo 1 |
| Reduce top-K | 10-20% | Recall -15% | Módulo 2 |
| Local embeddings | 100% (embeddings) | Calidad -20% | Módulo 1 |
| Semantic cache | 60-80% | Complejidad | Módulo 7 |
Decisión típica:
# Para startup con budget limitado
if monthly_budget < 500:
# Optimizaciones agresivas:
use_gpt_3_5 = True # No GPT-4 ($570 → $57)
cache_enabled = True # -60% queries ($57 → $23)
top_k = 3 # No 5 ($23 → $20)
# Total: $90/mes (indexing + queries + vector DB)
📊 Benchmarking: Medir Tu Sistema
Setup de benchmark:
# benchmark_rag.py
import time
from dataclasses import dataclass
@dataclass
class BenchmarkResult:
latency_p50: float
latency_p95: float
precision: float
recall: float
faithfulness: float
cost_per_query: float
def benchmark_rag_system(
rag_system,
test_queries: list[str],
ground_truth: dict
) -> BenchmarkResult:
"""
Benchmark completo de sistema RAG.
Input:
- rag_system: Tu sistema RAG
- test_queries: Lista de queries de prueba (30-50)
- ground_truth: Dict con respuestas correctas y docs relevantes
Output: BenchmarkResult con métricas
"""
latencies = []
precisions = []
recalls = []
for query in test_queries:
# Medir latency
start = time.time()
result = rag_system.query(query)
latency = (time.time() - start) * 1000
latencies.append(latency)
# Medir precision/recall
retrieved = set(result['doc_ids'])
relevant = set(ground_truth[query]['relevant_docs'])
relevant_retrieved = retrieved & relevant
precision = len(relevant_retrieved) / len(retrieved) if retrieved else 0
recall = len(relevant_retrieved) / len(relevant) if relevant else 0
precisions.append(precision)
recalls.append(recall)
# Calcular agregados
return BenchmarkResult(
latency_p50=statistics.median(latencies),
latency_p95=statistics.quantiles(latencies, n=20)[18],
precision=statistics.mean(precisions),
recall=statistics.mean(recalls),
faithfulness=0.0, # Medir con RAGAS en Módulo 8
cost_per_query=0.0021 # Calculado según usage
)
# Uso
results = benchmark_rag_system(
rag_system=my_rag,
test_queries=test_queries,
ground_truth=ground_truth_dict
)
print(f"""
Benchmark Results:
- Latency P50: {results.latency_p50:.0f}ms
- Latency P95: {results.latency_p95:.0f}ms
- Precision: {results.precision:.2%}
- Recall: {results.recall:.2%}
- Cost per query: ${results.cost_per_query:.4f}
""")
Output esperado (baseline):
Benchmark Results:
- Latency P50: 680ms
- Latency P95: 1,120ms
- Precision: 68%
- Recall: 52%
- Cost per query: $0.0021
Interpretar resultados:
Latency:
- ✅ P50 <1,000ms: Experiencia de usuario buena
- ⚠️ P95 >2,000ms: 5% de usuarios ve respuesta lenta
- ❌ P99 >5,000ms: Outliers problemáticos
Precision:
- ✅ >70%: La mayoría de docs recuperados son relevantes
- ⚠️ 60-70%: Hay ruido, pero LLM puede manejarlo
- ❌ <60%: Demasiado ruido, respuestas inconsistentes
Recall:
- ✅ >60%: Encontramos mayoría de docs relevantes
- ⚠️ 40-60%: Dejamos docs relevantes fuera
- ❌ <40%: Retrieval muy pobre
🎯 Targets por Tipo de RAG
RAG Type 1: Chatbot General
Caso de uso: Asistente conversacional (FAQs, soporte)
targets = {
"latency_p95": 1_500, # <1.5s
"precision": 0.65, # 65%+ (LLM tolera ruido)
"recall": 0.50, # 50%+ (encontrar suficiente info)
"faithfulness": 0.80, # 80%+ (grounded en docs)
"cost_per_query": 0.003 # <$0.003 (budget-friendly)
}
RAG Type 2: Technical Documentation Search
Caso de uso: Buscar en docs técnicas (código, APIs)
targets = {
"latency_p95": 2_000, # <2s (no interactivo)
"precision": 0.85, # 85%+ (precision crítica)
"recall": 0.70, # 70%+ (encontrar todas las refs)
"faithfulness": 0.95, # 95%+ (no inventar APIs)
"cost_per_query": 0.005 # <$0.005 (calidad > costo)
}
RAG Type 3: Legal/Medical Search
Caso de uso: Búsqueda en documentos críticos
targets = {
"latency_p95": 5_000, # <5s (precisión > velocidad)
"precision": 0.95, # 95%+ (cero falsos positivos)
"recall": 0.85, # 85%+ (no dejar info relevante)
"faithfulness": 0.98, # 98%+ (cero hallucinations)
"cost_per_query": 0.010 # <$0.01 (calidad máxima)
}
Decision Matrix: ¿Tu RAG cumple targets?
def evaluate_against_targets(results: BenchmarkResult, targets: dict) -> dict:
"""Compara resultados vs targets"""
evaluation = {}
# Latency
if results.latency_p95 < targets['latency_p95']:
evaluation['latency'] = "✅ PASS"
else:
evaluation['latency'] = f"❌ FAIL ({results.latency_p95:.0f}ms > {targets['latency_p95']}ms)"
# Precision
if results.precision >= targets['precision']:
evaluation['precision'] = "✅ PASS"
else:
evaluation['precision'] = f"❌ FAIL ({results.precision:.2%} < {targets['precision']:.2%})"
# Recall
if results.recall >= targets['recall']:
evaluation['recall'] = "✅ PASS"
else:
evaluation['recall'] = f"❌ FAIL ({results.recall:.2%} < {targets['recall']:.2%})"
return evaluation
# Uso
evaluation = evaluate_against_targets(results, targets)
for metric, status in evaluation.items():
print(f"{metric}: {status}")
Output:
latency: ✅ PASS
precision: ❌ FAIL (68% < 85%)
recall: ❌ FAIL (52% < 70%)
Acción: Necesitas mejorar precision y recall → Módulos 2-6 enseñan técnicas para esto.
🔄 Mejoras Basadas en Métricas
Si latency es problema:
# Optimizaciones de latency
optimizations = {
"Use gpt-3.5 instead of gpt-4": -300, # ms
"Cache query embeddings": -50,
"Reduce top-K from 10 to 5": -20,
"Use smaller embedding model": -30,
"Parallel retrieval": -100
}
# Aplicar y re-medir
Si precision es problema:
# Mejoras de precision
improvements = {
"Add re-ranking (Módulo 4)": +20, # % improvement
"Better chunking (Módulo 2)": +15,
"Query optimization (Módulo 3)": +10,
"Metadata filtering (Módulo 6)": +12
}
# Aplicar y re-medir
Si recall es problema:
# Mejoras de recall
improvements = {
"Query expansion (Módulo 3)": +25, # % improvement
"Increase top-K from 5 to 10": +20,
"Hybrid search (Módulo 5)": +18,
"Better embeddings": +10
}
# Aplicar y re-medir
🎯 Resumen
Conceptos clave:
- ✅ Latency: Velocidad de respuesta (P50, P95, P99) - Target típico: P95 <1,000ms
- ✅ Retrieval accuracy: Precision (relevancia) y Recall (cobertura) - Target: >70% y >50%
- ✅ Generation accuracy: Faithfulness (grounded) y Relevancy (contesta pregunta) - Target: >85% y >85%
- ✅ Cost: Costo por query (embeddings + LLM + vector DB) - Target: <$0.005 típicamente
- ✅ Targets varían por caso de uso: Chatbot vs Technical Search vs Legal tienen targets diferentes
- ✅ Benchmarking: Medir con test queries + ground truth → Identificar gaps → Aplicar mejoras
- ✅ Mejoras basadas en métricas: Latency problema → optimizar componentes; Precision problema → re-ranking
Qué sigue:
Cápsula 05 te muestra casos de uso reales: cómo Perplexity, Notion AI, y ChatGPT implementan RAG en producción, qué técnicas usan, y qué puedes aprender de sus arquitecturas.
📚 Recursos Adicionales
- RAGAS Metrics - Documentación oficial de métricas
- Retrieval Metrics Explained - Precision, Recall, MRR, NDCG
- Benchmarking RAG Systems - Metodología de evaluation
- Cost Optimization for LLMs - OpenAI best practices
- Latency Optimization - LangChain performance guide
- RAG Evaluation Framework - HuggingFace blog post
Creado: Febrero 6, 2026
Versión: 1.0