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ónTarget LatencyJustificació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:

  1. Retrieval accuracy: ¿Recuperamos documentos relevantes?
  2. 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:

ScoreInterpretaciónAcción
0.95+Completamente grounded✅ Excelente
0.85-0.94Mayormente grounded✅ Bueno
0.70-0.84Algunas inferencias⚠️ Revisar prompts
<0.70Hallucinations❌ 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:

StrategySavingTrade-offMódulo
Cache responses50-70%FreshnessMódulo 7
Use gpt-3.5 (no gpt-4)90%Calidad -10%Módulo 1
Reduce top-K10-20%Recall -15%Módulo 2
Local embeddings100% (embeddings)Calidad -20%Módulo 1
Semantic cache60-80%ComplejidadMó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

  1. RAGAS Metrics - Documentación oficial de métricas
  2. Retrieval Metrics Explained - Precision, Recall, MRR, NDCG
  3. Benchmarking RAG Systems - Metodología de evaluation
  4. Cost Optimization for LLMs - OpenAI best practices
  5. Latency Optimization - LangChain performance guide
  6. RAG Evaluation Framework - HuggingFace blog post

Creado: Febrero 6, 2026
Versión: 1.0