Módulo 3: Query Optimization

Introducción a Query Optimization

Descripción de la cápsula

Query optimization transforma queries del usuario antes de buscar para aumentar recall (encontrar más docs relevantes) y precision (docs más específicos). Queries directas fallan frecuentemente: "fastapi auth" es ambiguo (OAuth2? JWT? Basic auth?), "how to do X" está mal formulado para semantic search.

Este módulo enseña 4 técnicas que mejoran recall +15-25%: (1) Query Expansion (generar queries relacionadas), (2) Query Rewriting (reformular para claridad), (3) Query Decomposition (dividir complejas), (4) HyDE (generar documento hipotético). Cada técnica tiene trade-offs de latency, cost, y mejora.


🎯 Objetivos de Aprendizaje

Al finalizar este módulo, podrás:

  1. ✅ Explicar por qué queries directas son subóptimas (-20-30% recall)
  2. ✅ Implementar query expansion con LLM (+20-30% recall)
  3. ✅ Implementar query rewriting (+10-15% precision)
  4. ✅ Implementar query decomposition (+15-20% precision para complejas)
  5. ✅ Implementar HyDE (+15-25% recall)
  6. ✅ Comparar técnicas con benchmarks
  7. ✅ Seleccionar técnica óptima según query type
  8. ✅ Integrar en proyecto evolutivo (+15-25% recall total)

📐 Por Qué Query Optimization Importa

Problema: Queries directas son ambiguas/incompletas

# Query directa del usuario
user_query = "fastapi auth"

# Problemas:
# 1. Ambigüedad: ¿OAuth2? ¿JWT? ¿Basic auth? ¿API keys?
# 2. Falta contexto: ¿Implementación? ¿Tutorial? ¿Best practices?
# 3. Keyword style: "fastapi auth" vs "How to implement authentication in FastAPI?"

# Semantic search con query directa
results = collection.query(
    query_embeddings=[create_embedding("fastapi auth")],
    n_results=5
)

# Recall: 52% (miss muchos docs relevantes)
# Precision: 68% (algunos docs no son sobre auth específicamente)

Solución: Query optimization

# Técnica 1: Query Expansion
expanded_queries = [
    "How to implement OAuth2 authentication in FastAPI",
    "FastAPI JWT token authentication tutorial",
    "FastAPI security and authentication best practices",
    "Implementing API key authentication in FastAPI"
]

# Buscar con todas las queries y merge resultados
all_results = []
for query in expanded_queries:
    results = collection.query(
        query_embeddings=[create_embedding(query)],
        n_results=10
    )
    all_results.extend(results['documents'][0])

# Deduplicar y rankear
final_results = reciprocal_rank_fusion(all_results)[:5]

# Recall: 77% (+25% vs directa)
# Precision: 78% (+10% vs directa)

🗺️ Roadmap del Módulo

Cápsula 01 (Esta): Introducción

  • Por qué queries directas fallan
  • Overview de 4 técnicas
  • Setup técnico

Cápsula 02: Problemas de Queries Directas

  • Ambigüedad, incompletitud, mal formuladas
  • Impacto en recall: -20-30%

Cápsula 03: Query Expansion

  • Generar múltiples queries con LLM
  • Merge con reciprocal rank fusion
  • Mejora: +20-30% recall

Cápsula 04: Query Rewriting

  • Reformular para claridad
  • Casos: typos, clarificación, natural language
  • Mejora: +10-15% precision

Cápsula 05: Query Decomposition

  • Dividir queries complejas
  • Multi-hop reasoning
  • Mejora: +15-20% precision

Cápsula 06: HyDE

  • Generar documento hipotético
  • Buscar con doc embedding (no query)
  • Mejora: +15-25% recall

Cápsula 07: Comparación de Técnicas

  • Benchmark completo
  • Trade-offs: Recall vs Latency vs Cost
  • Decision matrix

Cápsula 08: Proyecto - Query Optimizer

  • Implementar 4 técnicas
  • Comparar y seleccionar
  • Integrar en RAG

📊 Overview de Query Optimization Techniques

Técnica 1: Query Expansion

Concepto: Generar múltiples queries relacionadas y buscar con todas.

# Input
user_query = "fastapi auth"

# Query expansion (LLM)
expanded = [
    "How to implement OAuth2 authentication in FastAPI",
    "FastAPI JWT token authentication",
    "FastAPI security best practices"
]

# Buscar con todas → Merge resultados
# Mejora: +20-30% recall

Cápsula: 03


Técnica 2: Query Rewriting

Concepto: Reformular query para claridad y completitud.

# Input (ambigua)
user_query = "fastapi auth"

# Query rewriting (LLM)
rewritten = "How to implement authentication in FastAPI using OAuth2 or JWT tokens"

# Buscar con rewritten query (más clara)
# Mejora: +10-15% precision

Cápsula: 04


Técnica 3: Query Decomposition

Concepto: Dividir query compleja en sub-queries simples.

# Input (compleja)
user_query = "Compare FastAPI and Flask authentication approaches"

# Decomposition (LLM)
sub_queries = [
    "How does authentication work in FastAPI?",
    "How does authentication work in Flask?",
    "What are the differences between FastAPI and Flask?"
]

# Buscar cada sub-query → Agregar resultados
# Mejora: +15-20% precision para queries complejas

Cápsula: 05


Técnica 4: HyDE (Hypothetical Document Embeddings)

Concepto: Generar documento hipotético que respondería la query, buscar con su embedding.

# Input
user_query = "How to implement authentication in FastAPI?"

# Generar documento hipotético (LLM)
hypothetical_doc = """
To implement authentication in FastAPI, you can use OAuth2 with JWT tokens.
First, install python-jose and passlib libraries.
Then, create a User model with password hashing...
"""

# Buscar con embedding del documento (no query)
doc_embedding = create_embedding(hypothetical_doc)
results = collection.query(query_embeddings=[doc_embedding])

# Mejora: +15-25% recall (doc embeddings > query embeddings)

Cápsula: 06


🛠️ Setup Técnico

No requiere instalación nueva:

# Ya tenemos OpenAI API (Módulo 1)
# Ya tenemos ChromaDB (Módulo 1)
# Ya tenemos LangChain (Módulo 2)

# Verificar setup
python -c "from openai import OpenAI; print('✅ OpenAI ready')"
python -c "import chromadb; print('✅ ChromaDB ready')"

Configuración:

# .env (ya existe del Módulo 1)
OPENAI_API_KEY=sk-proj-...

# Usar GPT-3.5-turbo para query optimization (balance cost/calidad)

📊 Mejora Esperada por Técnica

Baseline (Queries Directas):

# Sin query optimization
user_query = "fastapi auth"
results = rag_system.query(user_query)

# Métricas baseline:
# - Recall@50: 52%
# - Precision@5: 78% (del Módulo 2 con chunking optimizado)

Query Expansion (+20-30% recall):

# Con expansion
expanded_queries = expand_query(user_query)  # Genera 3-5 queries
results = search_with_multiple_queries(expanded_queries)

# Mejora esperada:
# - Recall@50: 67% (+15% vs baseline)
# - Precision@5: 80% (+2%)
# - Latency: +600ms (LLM call + multiple searches)
# - Cost: +$0.001/query

Query Rewriting (+10-15% precision):

# Con rewriting
rewritten_query = rewrite_query(user_query)  # Clarifica
results = rag_system.query(rewritten_query)

# Mejora esperada:
# - Recall@50: 55% (+3%)
# - Precision@5: 88% (+10% vs baseline)
# - Latency: +500ms (LLM call)
# - Cost: +$0.0008/query

HyDE (+15-25% recall):

# Con HyDE
hypothetical_doc = generate_hypothetical_document(user_query)
doc_embedding = create_embedding(hypothetical_doc)
results = collection.query(query_embeddings=[doc_embedding])

# Mejora esperada:
# - Recall@50: 70% (+18% vs baseline)
# - Precision@5: 82% (+4%)
# - Latency: +700ms (LLM generation + embedding)
# - Cost: +$0.0015/query

🔗 Conexión con Proyecto Evolutivo

Módulo 1 (Baseline):

# retrieval.py (Módulo 1)
def retrieve(query: str, top_k: int = 5):
    """Query directa sin optimización"""
    query_embedding = create_embedding(query)
    return collection.query(query_embeddings=[query_embedding], n_results=top_k)

Métricas: Recall 52%


Módulo 2 (Chunking optimizado):

# indexing.py (Módulo 2)
# Mejora chunking → Mejor calidad de docs → Precision 78%

Métricas: Precision 78% (+10%), Recall 57% (+5%)


Módulo 3 (Query optimization):

# retrieval.py (Módulo 3 - actualizado)
def retrieve(query: str, top_k: int = 5, optimize_query: bool = True):
    """Retrieval con query optimization"""
    
    if optimize_query:
        # Query expansion
        expanded_queries = expand_query(query)
        
        # Buscar con todas las queries
        all_results = []
        for q in expanded_queries:
            q_embedding = create_embedding(q)
            results = collection.query(query_embeddings=[q_embedding], n_results=10)
            all_results.append(results)
        
        # Merge con reciprocal rank fusion
        final_results = reciprocal_rank_fusion(all_results)[:top_k]
        return final_results
    else:
        # Fallback a query directa
        query_embedding = create_embedding(query)
        return collection.query(query_embeddings=[query_embedding], n_results=top_k)

Métricas optimizadas: Precision 80% (+2%), Recall 72% (+15%)


Módulo 8 (Final):

# advanced_rag_system.py (Módulo 8)
class AdvancedRAGSystem:
    def __init__(self, query_optimization: str = "expansion"):
        # Módulo 2: Chunking optimizado
        self.chunker = RecursiveChunker()
        
        # Módulo 3: Query optimization
        if query_optimization == "expansion":
            self.query_optimizer = QueryExpansion()
        elif query_optimization == "hyde":
            self.query_optimizer = HyDE()
        # ... módulos 4-7

Métricas finales (acumulado): Precision 93%, Recall 77%


🎯 Resumen

Conceptos clave:

  • Queries directas son subóptimas: Ambiguas, incompletas, mal formuladas (-20-30% recall)
  • 4 técnicas de optimization: Expansion (recall), Rewriting (precision), Decomposition (complejas), HyDE (recall máximo)
  • Trade-offs: Latency (+500-800ms), Cost (+$0.001-0.002/query), Recall (+15-30%)
  • Mejora combinada: +15-25% recall typical, +10-15% precision
  • Decision depende de: Query type, latency budget, cost constraints

Qué sigue:

Cápsula 02 desglosa problemas específicos de queries directas con ejemplos concretos y métricas de impacto.


📚 Recursos Adicionales

  1. Query Understanding for RAG - Research paper
  2. Multi-Query Retrieval - LangChain docs
  3. HyDE Original Paper - Hypothetical Document Embeddings
  4. Query Expansion Techniques - Pinecone guide
  5. Query Decomposition - LlamaIndex blog

Creado: Febrero 6, 2026
Versión: 1.0