Módulo 3: Query Optimization
Cápsula 07: Decision framework — qué técnica de query optimization elegir
Descripción de la cápsula
Cubrimos las cuatro técnicas principales de query optimization en este módulo: expansion (cápsula 03), rewriting (04), decomposition (05), HyDE (06). Cada una con sus trade-offs, casos de uso, modos de falla. Esta cápsula es la consolidación operativa: decision framework para elegir la técnica correcta dado un escenario, benchmarks lado a lado, y patrones de combinación cuando una sola técnica no alcanza.
Es la cápsula que vas a consultar cada vez que diagnostiques que tu sistema RAG necesita query optimization. Te ahorra releer las 4 cápsulas previas para decidir.
Al finalizar esta cápsula serás capaz de:
- ✅ Comparar las cuatro técnicas sobre seis dimensiones (recall, precision, latencia, costo, complejidad, dominio)
- ✅ Aplicar un flowchart de decisión para elegir técnica en menos de 10 minutos
- ✅ Diseñar pipelines híbridos que combinan dos o más técnicas en cascada
- ✅ Calcular el TCO mensual de cada opción para un volumen dado
- ✅ Identificar las tres anti-patrones más comunes al combinar técnicas
- ✅ Diferenciar cuándo el problema requiere query optimization vs cuándo es problema de retrieval o re-ranking
Tiempo estimado: 25-30 minutos
Benchmark consolidado de las 4 técnicas
Calidad
| Técnica | Recall@50 (típico) | Precision@5 (típico) | Best for |
|---|---|---|---|
| Sin optimization (baseline) | 57-65% | 75-82% | MVP donde todo funciona OK |
| Query Expansion | 72-80% (+15-20pts) | 78-82% (+1-2pts) | Queries ambiguas, recall crítico |
| Query Rewriting | 60-65% (+3-5pts) | 86-90% (+8-12pts) | Queries incompletas o keyword-style |
| Query Decomposition | 65-72% (+8-15pts) | 83-87% (+5-10pts) | Queries complejas (comparativas, multi-paso) |
| HyDE | 75-82% (+18-25pts) | 80-84% (+3-5pts) | Queries abiertas, dominios técnicos |
Costo y latencia
| Técnica | Latencia extra | Costo por query (gpt-4o-mini) | LLM calls | Retrieval calls |
|---|---|---|---|---|
| Sin optimization | 0 ms | $0 | 0 | 1 |
| Query Expansion | +650-850 ms | $0.0010-0.0012 | 1 | 5 (paralelo) |
| Query Rewriting | +400-700 ms | $0.0006-0.0008 | 1 | 1 |
| Query Decomposition | +900-1200 ms | $0.0018-0.0022 | 1 (decision) + 1 (synthesis) | 3-5 (paralelo) |
| HyDE | +700-900 ms | $0.0012-0.0017 | 1 (generation) | 1 |
Complejidad operativa
| Técnica | Setup time | Mantenimiento | Riesgos |
|---|---|---|---|
| Expansion | 4-6 hrs | Bajo (prompt estable) | Expansiones que cambian intención |
| Rewriting | 4-8 hrs | Bajo | Rewrites incorrectos en queries factuales |
| Decomposition | 8-12 hrs | Medio (más complejo) | Sub-queries no independientes |
| HyDE | 6-8 hrs | Medio | Docs hipotéticos que alucinan |
Decision framework
¿Cuál es el problema dominante?
│
┌────────────────────────────┼────────────────────────────┐
│ │ │
▼ ▼ ▼
RECALL bajo PRECISION baja QUERIES COMPLEJAS
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────────┐
│ ¿Queries cortas │ │ ¿Queries │ │ ¿Multi-aspecto, │
│ y ambiguas? │ │ keyword-style │ │ comparativas o │
│ │ │ o incompletas? │ │ multi-paso? │
└───────┬────────┘ └───────┬────────┘ └─────────┬──────────┘
│ │ │
┌────┴────┐ ┌──┴──┐ ┌──────┴──────┐
│ SÍ │ NO │ SÍ │ NO │ SÍ │ NO
▼ ▼ ▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ │ ┌──────────────┐ │
│Expan-│ │HyDE │ │Rewri-│ │ │Decomposition │ │
│sion │ │ │ │ting │ │ │ │ │
└──────┘ └──────┘ └──────┘ │ └──────────────┘ │
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────┐
│ ¿Latencia <500ms? │ │ Considerar: │
│ └─ Sí: skip optim. │ │ - Re-ranking │
│ └─ No: HyDE │ │ - Hybrid search │
└──────────────────────┘ │ - Metadata filter │
└──────────────────┘
Aplicado a casos reales
| Caso | Técnica recomendada | Razón |
|---|---|---|
| Chatbot SaaS con queries cortas ("auth issue", "deploy fail") | Expansion | Queries ambiguas, recall es problema |
| Soporte técnico con chat history disponible | Rewriting con contexto | Queries incompletas que el chat completa |
| Plataforma de papers académicos | HyDE + rerank | Queries abiertas, dominio con doc style predecible |
| Asistente de DevOps ("deploy fastapi+postgres+aws") | Decomposition | Queries multi-aspecto explícitas |
| Buscador de productos e-commerce ("nike air max barato") | Rewriting estructural | Keyword-style, transformar a natural language |
| Sistema legal con queries específicas | Sin optimization | Las queries de abogados ya son específicas |
Patrones híbridos
A veces una técnica sola no resuelve el problema. Tres patrones probados:
Patrón 1: Rewriting → Expansion (cascada)
Para queries que están mal formuladas Y son ambiguas (ej: keyword-style + corta).
def rewrite_then_expand(query: str) -> list[str]:
"""Rewrite primero, después expandir el rewrite."""
# Paso 1: convertir a natural language
rewritten = rewrite_structural(query).rewritten
# Paso 2: si sigue siendo ambigua, expandir
if len(rewritten.split()) <= 6:
expansion = expand_query(rewritten, num_expansions=4)
return [rewritten] + expansion.queries
return [rewritten]
Caso de uso: "k8s pod fail" (keyword + ambigua):
- Rewriting → "Why is my Kubernetes pod failing?"
- Expansion → 4 queries cubriendo distintas razones de fallo
Patrón 2: HyDE + Decomposition
Para queries abiertas Y multi-aspecto (ej: research questions complejas).
def hyde_with_decomposition(query: str, top_k: int = 8):
"""Si la query es decomponible, descompone primero, después HyDE para cada sub."""
decomp = decompose_query(query)
if not decomp.is_decomposable:
# Query simple → HyDE directo
return hyde_search(query, top_k)
# Query compleja → HyDE para cada sub-query
all_results = []
for sub_q in decomp.sub_queries:
hyde_doc = generate_hypothetical_document(sub_q)
results = collection.query(query_texts=[hyde_doc], n_results=10)
all_results.append(results['ids'][0])
# Fusionar con RRF
fused = reciprocal_rank_fusion(all_results)
top_ids = [doc_id for doc_id, _ in fused[:top_k]]
return collection.get(ids=top_ids)
Caso de uso: "Compare transformer architectures and recommend the best for time-series forecasting":
- Decomposition → 3 sub-queries
- HyDE para cada sub-query → encontrar papers específicos
- Fusion → top-K final
Patrón 3: Adaptive (LLM clasifica y elige)
El LLM decide qué técnica aplicar por query:
def adaptive_query_optimization(query: str, chat_history: list = None):
"""LLM clasifica la query y aplica la técnica óptima."""
# Clasificación con LLM
classification = classify_query_type(query, chat_history)
if classification == "well_formed":
return query # skip optimization
elif classification == "ambiguous":
expansion = expand_query(query, num_expansions=4)
return [query] + expansion.queries
elif classification == "incomplete":
if chat_history:
return [rewrite_with_context(query, chat_history).rewritten]
else:
return [rewrite_structural(query).rewritten]
elif classification == "complex":
decomp = decompose_query(query)
if decomp.is_decomposable:
return decomp.sub_queries
return [query]
elif classification == "open_research":
# HyDE encaja bien para research questions
return ["use_hyde"] # signal para usar HyDE en lugar de query directa
else:
return [query]
Trade-off: más costo (LLM call extra para clasificación) pero mejor selección. Útil cuando tu tráfico tiene mezcla amplia de tipos de query.
Calculando TCO mensual
Para decidir entre opciones, calcula el costo mensual concreto:
# tco_query_optimization.py
QUERIES_PER_DAY = 10_000
DAYS_PER_MONTH = 30
# Costos por query (gpt-4o-mini, mayo 2026)
COST_BY_TECHNIQUE = {
"no_optimization": 0,
"rewriting": 0.0007,
"expansion": 0.0011,
"decomposition": 0.0020,
"hyde": 0.0015,
"adaptive": 0.0010, # average dependiendo de mix
}
# Latencia extra
LATENCY_BY_TECHNIQUE = {
"no_optimization": 0,
"rewriting": 500,
"expansion": 750,
"decomposition": 1100,
"hyde": 800,
"adaptive": 700,
}
# Mejora típica de recall
RECALL_IMPROVEMENT_BY_TECHNIQUE = {
"no_optimization": 0,
"rewriting": 4,
"expansion": 17,
"decomposition": 12,
"hyde": 22,
"adaptive": 18,
}
def calculate_tco_and_impact(technique: str, baseline_recall: float = 0.65):
monthly_queries = QUERIES_PER_DAY * DAYS_PER_MONTH
cost_monthly = monthly_queries * COST_BY_TECHNIQUE[technique]
new_recall = baseline_recall + RECALL_IMPROVEMENT_BY_TECHNIQUE[technique] / 100
latency = LATENCY_BY_TECHNIQUE[technique]
return {
"technique": technique,
"monthly_cost_usd": round(cost_monthly, 2),
"expected_recall": f"{new_recall:.0%}",
"extra_latency_ms": latency,
}
for tech in COST_BY_TECHNIQUE.keys():
result = calculate_tco_and_impact(tech)
print(f"\n{tech.upper()}:")
for k, v in result.items():
print(f" {k}: {v}")
Output (10K queries/día):
NO_OPTIMIZATION:
monthly_cost_usd: 0.00
expected_recall: 65%
extra_latency_ms: 0
REWRITING:
monthly_cost_usd: 210.00
expected_recall: 69%
extra_latency_ms: 500
EXPANSION:
monthly_cost_usd: 330.00
expected_recall: 82%
extra_latency_ms: 750
DECOMPOSITION:
monthly_cost_usd: 600.00
expected_recall: 77%
extra_latency_ms: 1100
HYDE:
monthly_cost_usd: 450.00
expected_recall: 87%
extra_latency_ms: 800
ADAPTIVE:
monthly_cost_usd: 300.00
expected_recall: 83%
extra_latency_ms: 700
Lectura:
- HyDE da el mejor recall (+22pts) por $450/mes — buena opción si recall es crítico.
- Expansion da +17pts por $330/mes — sweet spot precio/calidad.
- Decomposition es caro y solo justifica si tienes muchas queries complejas.
- Adaptive da casi el mismo resultado que HyDE/Expansion mezclados, a menor costo — pero requiere más complejidad operativa.
Anti-patrones al combinar técnicas
Anti-patrón 1: aplicar todas las técnicas
El error: "agreguemos rewriting + expansion + HyDE para máxima calidad".
Síntoma: latencia +2 segundos, costo 4x, mejora marginal sobre la mejor técnica individual.
Por qué pasa: las técnicas atacan problemas distintos. Si tu problema dominante es recall (HyDE lo resuelve), agregar rewriting solo agrega latencia sin beneficio.
Cómo prevenir: diagnosticar primero (cápsula 02), aplicar la técnica que ataca el problema dominante. Combinar solo cuando hay evidencia de problemas múltiples.
Anti-patrón 2: combinar sin medir
El error: asumes que dos técnicas combinadas suman mejoras. Rewriting +5% + Expansion +15% = +20% combinado.
Realidad: las técnicas tienen overlap. Combinadas dan típicamente +18% (no +20%) por interacción. A veces combinarlas EMPEORA: una técnica "rompe" lo que la otra arregla.
Cómo prevenir: A/B test cada combinación contra cada técnica individual sobre eval set. Si la combinación no mejora >5% sobre la mejor individual, no vale la complejidad extra.
Anti-patrón 3: optimization donde el problema no es la query
El error: baja precision/recall → asumes problema de query → agregas 4 técnicas de optimization.
Realidad: muchas veces el problema es:
- Chunking inadecuado (M02) — chunks demasiado grandes/pequeños
- Embeddings débiles (M07) — modelo desalineado al dominio
- Falta re-ranking (M04) — top-5 tiene falsos positivos
- Falta hybrid search (M05) — queries con keywords exactos
- Corpus incompleto — la respuesta no está en los documentos
Cómo prevenir: diagnosticar dónde está el problema antes de invertir en query optimization. Si el problema es de re-ranking, agregar HyDE no ayuda.
def diagnose_pipeline_bottleneck(eval_set):
"""Identifica cuál componente del pipeline es el cuello de botella."""
issues = {"missing_data": 0, "retrieval": 0, "reranking": 0, "query": 0}
for item in eval_set:
# ¿La info correcta existe en algún chunk del corpus?
in_corpus = check_corpus(item["expected_text"])
if not in_corpus:
issues["missing_data"] += 1
continue
# ¿Cosine encuentra el doc correcto en top-50?
results = collection.query(query_texts=[item["query"]], n_results=50)
if not any(item["expected_id"] in id_ for id_ in results['ids'][0]):
issues["retrieval"] += 1
continue
# ¿Re-rank encuentra el doc correcto en top-5?
reranked = cross_encoder_rerank(item["query"], results['documents'][0], top_k=5)
if not any(item["expected_id"] in r.original_index for r in reranked):
issues["reranking"] += 1
continue
# Si llegamos hasta acá, el doc está en top-5 con rerank — problema query era el menor
issues["query"] += 1
print("Bottleneck diagnosis:")
for issue, count in issues.items():
print(f" {issue}: {count}/{len(eval_set)}")
Si "retrieval" es alto, query optimization ayuda. Si "reranking" es alto, agregar/mejorar reranker. Si "missing_data" es alto, expandir corpus — ninguna técnica de query lo resuelve.
Trampas y errores comunes
Trampa 1: aplicar query optimization sin diagnóstico previo
Cubierta arriba. Diagnosticar primero.
Trampa 2: copiar técnica de un blog sin medir
El error: ves que "HyDE da +25% recall" en un blog, lo aplicas. Tu mejora real es +5%.
Cómo prevenir: los benchmarks de blogs son sobre datasets/dominios específicos. Medir sobre tu eval set antes de comprometerse.
Trampa 3: optimization que rompe queries simples
El error: activas expansion para todas las queries. Queries simples como "Stripe API documentation" se expanden a 5 versiones, cada una más vaga que la original. Recall sube en queries complejas pero baja en simples.
Cómo prevenir: skip dinámico (visto en cápsulas 03, 04, 05, 06). Queries bien formuladas no necesitan optimization.
Trampa 4: caché de optimization sin invalidar al cambiar prompt
El error: cacheas resultados de query expansion. Cambias el prompt del LLM. Las queries cacheadas siguen usando expansions del prompt viejo.
Cómo prevenir: versioning del prompt en la cache key:
PROMPT_VERSION = "v3"
def cache_key(query: str) -> str:
return f"{PROMPT_VERSION}:{hashlib.md5(query.encode()).hexdigest()}"
Trampa 5: medir solo recall sin precision
El error: activas HyDE, recall sube 20%, deployas.
Síntoma: precision cae 5% (HyDE encontró docs tangenciales). El LLM downstream se confunde con contexto extra. Quality empeora.
Cómo prevenir: medir precision Y recall juntos. Si la combinación no mejora ambas, hay algo mal.
Trampa 6: optimization sin re-ranking
El error: activas expansion + HyDE pero no tienes re-ranking. Los resultados fusionados pasan directo al LLM.
Síntoma: el LLM recibe 10 docs medianamente relevantes en lugar de 5 muy relevantes. Calidad de respuesta es peor que sin optimization.
Cómo prevenir: query optimization + re-ranking trabajan juntos. Optimization mejora retrieval, re-ranking refina los candidatos. Sin re-ranking, optimization puede degradar calidad.
Ejercicio aplicado
Escenario: eres AI Engineer en una plataforma de educación corporativa. Sistema RAG sirve preguntas de empleados sobre cursos de tecnología.
Datos:
- 50K cursos chunkeados, en español
- 20K queries/día
- Pipeline actual: cosine + cross-encoder rerank
Análisis del log:
Categoría % Ejemplos
─────────────────────────────────────────────────────────────
Bien formuladas 30% "¿qué hace useState en React?"
Keyword-style 25% "react useState hook"
Incompletas (chat) 20% "cómo lo configuro?" en chat de Docker
Comparativas 12% "diferencias entre useState y useReducer"
Vagas 8% "ayuda con mi código"
Multi-paso 5% "cómo testeo y deployeo mi app"
Métricas:
- Precision@5: 79%
- Recall@5: 61%
- Stakeholders piden recall@5 ≥ 80% sin pasar 1500ms p95.
Tu trabajo:
- Aplica el decision framework. ¿Qué pipeline diseñas?
- Calcular costo y latencia esperados.
- Plan de validación.
Solución
1. Pipeline propuesto: adaptive con clasificación por LLM
El tráfico es muy variado (6 categorías distintas con %s significativos cada una). Una sola técnica no encaja. Adaptive es la opción correcta.
def adaptive_pipeline(query: str, chat_history: list = None, top_k: int = 5):
"""
Pipeline adaptativo: LLM clasifica y aplica técnica óptima.
"""
# Clasificación con LLM (gpt-4o-mini, ~150ms)
category = classify_query(query, chat_history)
queries_to_search = []
if category == "well_formed":
# Skip optimization
queries_to_search = [query]
elif category == "keyword_style":
# Rewriting estructural
rewritten = rewrite_structural(query).rewritten
queries_to_search = [rewritten]
elif category == "incomplete" and chat_history:
# Rewriting con contexto
rewritten = rewrite_with_context(query, chat_history).rewritten
queries_to_search = [rewritten]
elif category == "comparative":
# Decomposition
decomp = decompose_query(query)
queries_to_search = decomp.sub_queries if decomp.is_decomposable else [query]
elif category == "vague":
# Expansion (queries vagas → múltiples interpretaciones)
expansion = expand_query(query, num_expansions=4)
queries_to_search = [query] + expansion.queries
elif category == "multi_step":
# Decomposition
decomp = decompose_query(query)
queries_to_search = decomp.sub_queries if decomp.is_decomposable else [query]
# Retrieval para todas las queries
if len(queries_to_search) == 1:
results = collection.query(query_texts=queries_to_search, n_results=20)
candidates = results['documents'][0]
else:
# Multiple queries → paralelo + RRF
all_rankings = parallel_retrieve(queries_to_search, top_k=15)
fused = reciprocal_rank_fusion(all_rankings)
top_ids = [doc_id for doc_id, _ in fused[:30]]
candidates = collection.get(ids=top_ids)['documents']
# Re-rank con query original
return cross_encoder_rerank(query, candidates, top_k=top_k)
2. Cálculo de costo y latencia
Latencia ponderada:
| Categoría | % | Latencia | Ponderada |
|---|---|---|---|
| Well-formed (skip) | 30% | 400ms | 120ms |
| Keyword (rewrite) | 25% | 700ms | 175ms |
| Incomplete (rewrite ctx) | 20% | 700ms | 140ms |
| Comparative (decomp) | 12% | 1100ms | 132ms |
| Vague (expansion) | 8% | 850ms | 68ms |
| Multi-step (decomp) | 5% | 1100ms | 55ms |
| Total promedio | 690ms |
Latencia p95 (clasificación + caso peor): ~1300ms. Dentro del límite de 1500ms.
Costo mensual:
queries_per_day = 20_000
days_per_month = 30
total_queries = queries_per_day * days_per_month # 600K
# Cost de clasificación (todas las queries)
classification_cost_per_query = 0.00005 # gpt-4o-mini, prompt corto
classification_total = total_queries * classification_cost_per_query
# Cost de cada técnica
costs_by_technique = {
"skip": 0,
"rewrite": 0.0006,
"rewrite_ctx": 0.0008,
"decomp": 0.0018,
"expansion": 0.0011,
}
distribution = {
"skip": 0.30,
"rewrite": 0.25,
"rewrite_ctx": 0.20,
"decomp": 0.17, # comparative + multi-step
"expansion": 0.08,
}
technique_total = sum(
total_queries * pct * costs_by_technique[tech]
for tech, pct in distribution.items()
)
monthly_cost = classification_total + technique_total
print(f"Classification cost: ${classification_total:.2f}")
print(f"Technique cost: ${technique_total:.2f}")
print(f"Total monthly: ${monthly_cost:.2f}")
Output:
Classification cost: $30.00
Technique cost: $440.40
Total monthly: $470.40
~$470/mes en query optimization. Sostenible para producto educativo corporativo.
Recall esperado:
Categoría % Recall actual Recall con técnica
──────────────────────────────────────────────────────────────────
Well-formed 30% 75% 75% (sin cambio)
Keyword 25% 55% 78% (rewriting)
Incomplete 20% 45% 82% (rewriting ctx)
Comparative 12% 58% 80% (decomposition)
Vague 8% 40% 72% (expansion)
Multi-step 5% 55% 78% (decomposition)
Recall global esperado:
0.30(0.75) + 0.25(0.78) + 0.20(0.82) + 0.12(0.80) + 0.08(0.72) + 0.05(0.78)
= 0.225 + 0.195 + 0.164 + 0.096 + 0.058 + 0.039
= 0.777 (78%) → cerca de target 80%
Si no llega a 80% con esta combinación, considerar agregar HyDE para queries vagas (subir recall en esa categoría).
3. Plan de validación
- Construir eval set ponderado: 100 queries reales distribuidas según las % del log (30 well-formed, 25 keyword, etc.) con ground truth.
- Baseline: medir recall@5 sobre el eval set con pipeline actual.
- Implementar adaptive_pipeline con feature flag.
- A/B test durante 2 semanas: 50% pipeline actual, 50% adaptive.
- Métricas primarias: recall@5 (target ≥80%), precision@5 (no caer >2pts), latency p95.
- Métricas secundarias: distribución de category classification (¿el LLM clasifica bien?), tasa de fallback al pipeline simple.
- Si recall ≥80% y latencia ≤1500ms p95, deployar 100%.
Plan B si recall queda en 75-79%:
- Agregar HyDE para queries vagas (subir esa categoría de 72% a 80%+).
- Mejorar prompt de classification (¿queries comparativas se detectan correctamente?).
- Revisar manualmente sample de queries vagas que no llegaron — ¿son problema de query optimization o de retrieval?
Plan B si latencia >1500ms:
- Bajar
n_resultsdel retrieval inicial. - Reducir paralelismo (de 5 a 3 sub-queries en decomposition).
- Considerar cache LRU para queries comunes (reduce hit rate del classifier).
Resumen y siguiente paso
Lo que aprendiste:
- Cuatro técnicas de query optimization, cada una para problema distinto: expansion (ambigüedad), rewriting (incompleta/keyword), decomposition (compleja), HyDE (recall máximo).
- Decision framework basado en problema dominante (recall vs precision vs queries complejas).
- Patrones híbridos para problemas múltiples: cascada (rewriting + expansion), paralelo (HyDE por sub-query).
- Adaptive con LLM clasificador es la solución cuando el tráfico es muy variado.
- TCO mensual típico para 10K queries/día: $200-600 según técnica. Despreciable para mayoría de productos.
- Anti-patrones: aplicar todas las técnicas, combinar sin medir, optimization donde el problema no es de query.
- Diagnosticar el cuello de botella del pipeline antes de invertir en query optimization.
Checkpoint: antes de cerrar el módulo, deberías poder:
- Aplicar el decision framework para elegir técnica en menos de 10 minutos.
- Diseñar pipelines híbridos que combinan dos o más técnicas justificadamente.
- Calcular TCO mensual de query optimization para tu volumen.
- Diagnosticar si baja precision/recall es problema de query vs retrieval vs rerank.
Siguiente cápsula: 08 — Proyecto Query Optimization Pipeline.
Cierre del módulo: vas a construir un pipeline de query optimization end-to-end con A/B testing entre dos técnicas, medición sobre eval set propio, y reporte comparativo. Es la herramienta que vas a aplicar el día 1 de cualquier proyecto RAG donde sospeches que las queries del usuario son el cuello de botella.
Recursos
- LangChain — Query Construction Guide — Patrones avanzados
- LlamaIndex — Query Pipelines — Implementaciones de referencia
- Pinecone — Query Optimization Series — Tutorial completo
- Anthropic — Contextual Retrieval — Técnica complementaria
- Goodhart's Law — Por qué optimizar lo que mides puede romper lo que no mides
- Microsoft — Query Optimization in RAG — Caso de uso con Azure
Tiempo estimado: 25-30 minutos Siguiente: 08-project-query-optimizer.md