Módulo 3: Query Optimization

Cápsula 02: Por qué la query del usuario casi nunca es la query óptima

Descripción de la cápsula

Hay un mito en RAG: que la query del usuario es input sagrado y tú solo tienes que "buscar mejor". La realidad es exactamente la opuesta. La query que el usuario tipea es el primer punto de falla del pipeline, no el último. Si no la tocas antes de embebir y buscar, estás aceptando un techo de calidad que ningún re-ranker, ningún hybrid search, ningún chunking puede remontar.

Esta cápsula te muestra los tres patrones por los que las queries directas degradan el retrieval — y por qué entender esto es prerrequisito para apreciar las técnicas que siguen (query expansion, rewriting, decomposition, HyDE). Sin este "por qué", esas técnicas suenan a complejidad innecesaria. Con este "por qué", se vuelven obviamente necesarias.

Al finalizar esta cápsula serás capaz de:

  • ✅ Identificar los tres patrones de queries problemáticas: ambiguas, incompletas, mal formuladas
  • ✅ Diagnosticar por qué una query específica del usuario está dando malos resultados
  • ✅ Cuantificar el impacto de cada problema sobre recall y precision
  • ✅ Anticipar qué técnica de query optimization aplicar a cada tipo de problema
  • ✅ Distinguir cuándo es problema de query vs problema de retrieval (no siempre es lo mismo)
  • ✅ Anticipar el error de "el usuario tiene que escribir mejor sus queries" — tú eres responsable, no el usuario

Tiempo estimado: 25-30 minutos


El insight: la query del usuario está optimizada para Google, no para tu RAG

Los usuarios aprendieron a googlear hace 25 años. Tipean queries cortas, llenas de keywords, optimizadas para search engines tradicionales. Tu RAG opera bajo principios distintos: embeddings semánticos que prefieren oraciones completas en lenguaje natural. Lo que el usuario hace por instinto es exactamente lo opuesto a lo que tu sistema necesita.

Lo que el usuario tipea:        "fastapi auth"          (3 tokens, ambiguo)
Lo que tu RAG querría:          "¿Cómo implemento autenticación OAuth2 con
                                 OAuth2PasswordBearer en FastAPI?"        (oración completa, específica)

Recall con la primera:          52%
Recall con la segunda:          87%
Diferencia:                     35 puntos

No puedes enseñar a millones de usuarios a tipear queries "óptimas". Lo que sí puedes es transformar la query del usuario antes de embebirla. Eso es query optimization. Pero antes de aprender las técnicas, hay que entender qué problemas exactos resuelven.


Problema 1: queries ambiguas (múltiples interpretaciones)

Query del usuario: "fastapi auth"

¿Qué quiso decir?

  • "Cómo implementar autenticación OAuth2 en FastAPI"
  • "FastAPI con JWT tokens"
  • "FastAPI con API keys"
  • "FastAPI con basic auth"
  • "FastAPI con sessions cookies"
  • "Cómo testear autenticación en FastAPI"
  • "Comparación de métodos de auth en FastAPI"

Son 7 interpretaciones distintas, cada una con respuestas distintas en tu corpus. Cosine similarity no sabe cuál quiere el usuario — embebe los 2 tokens y devuelve un mix de docs sobre los 7 temas, ninguno con profundidad.

Por qué pasa

Los embeddings codifican el centroide semántico de los tokens. "fastapi auth" → un vector que apunta a "región general de FastAPI + autenticación". Los docs específicos sobre OAuth2 quedan en una sub-región. Los de JWT en otra. Los de basic auth en otra. La query genérica equidista de las tres — devuelve algo de cada una.

Impacto medible

Query: "fastapi auth"
─────────────────────────────────────────────
Top 5 results:
  1. "OAuth2 in FastAPI overview"      (relevante para 1 interpretación)
  2. "JWT tokens with FastAPI"         (relevante para 1 interpretación)
  3. "API keys best practices"         (relevante para 1 interpretación)
  4. "FastAPI security overview"       (genérico)
  5. "Basic auth tutorial"             (relevante para 1 interpretación)

Si el usuario quería OAuth2 específicamente:
  - Doc 1 está OK (overview, no profundidad)
  - Docs 2-5 son ruido para él
  - Recall efectivo: 1/5 = 20%

Patrón general: queries de 1-3 tokens tienen ~40% más probabilidad de ser ambiguas que queries de 7+ tokens.

Técnica que lo resuelve

Query expansion (cápsula 03): generar múltiples versiones expandidas de la query original, buscar con cada una, fusionar resultados. Si el usuario tipeó "fastapi auth", expandir a:

  • "FastAPI OAuth2 authentication"
  • "FastAPI JWT authentication"
  • "FastAPI API key authentication"

Y combinar los rankings. Cubre las interpretaciones probables sin requerir que el usuario sea explícito.


Problema 2: queries incompletas (falta contexto crítico)

Query del usuario: "how to deploy"

¿Deployar qué? ¿Dónde? ¿Con qué herramientas? ¿En qué entorno? El usuario asume que el contexto está implícito ("estoy chateando con un bot de FastAPI, obvio que pregunto sobre deploy de FastAPI"). El sistema no tiene ese contexto.

Por qué pasa

Las queries incompletas son resultado de:

  1. Conversaciones previas implícitas: el usuario asume que el sistema "recuerda" el tópico de los mensajes previos.
  2. Contexto del producto: un usuario de Stripe no escribe "Stripe payment integration", escribe "how to charge" asumiendo que el contexto del producto está claro.
  3. Atajo cognitivo: el usuario sabe lo que quiere y omite los términos "obvios" para él.

Impacto medible

Query: "how to deploy"
─────────────────────────────────────────────
Sin contexto, el embedding apunta a "deployment en general".

Top 5 results:
  1. "AWS Lambda deployment guide"     (deploy, pero ¿es lo que quiere?)
  2. "Docker deployment basics"        (deploy, pero ¿es lo que quiere?)
  3. "Kubernetes for Python apps"      (deploy, pero ¿es lo que quiere?)
  4. "CI/CD setup with GitHub Actions" (deploy, pero ¿es lo que quiere?)
  5. "Heroku deployment for beginners" (deploy, pero ¿es lo que quiere?)

Si el usuario está en contexto FastAPI + Docker:
  - Solo doc 2 es relevante
  - Precision efectiva: 1/5 = 20%

Patrón general: queries de 2-4 tokens sin nombres propios o tecnologías específicas tienen ~50% más probabilidad de ser incompletas.

Técnica que lo resuelve

Query rewriting con contexto (cápsula 04): un LLM toma la query del usuario + el contexto disponible (historial de chat, identidad del producto, último tópico) y reescribe a una query completa.

# Sin rewriting
user_query = "how to deploy"
results = retrieve(user_query)  # Recall: 35%

# Con rewriting (LLM tiene contexto del chat)
chat_history = ["I'm building a FastAPI app", "I want to use Docker"]
rewritten = llm_rewrite(user_query, chat_history)
# rewritten = "How to deploy a FastAPI application with Docker"
results = retrieve(rewritten)  # Recall: 87%

Problema 3: queries mal formuladas (keyword style vs natural language)

Query del usuario: "fastapi async performance"

Es estilo keyword — los usuarios entrenados en Google quitan stop words ("how", "is", "a", "the") porque saben que Google los ignora. Pero embeddings funcionan al revés: las palabras "innecesarias" ayudan al modelo a entender la estructura de la pregunta.

Por qué pasa

# Keyword style
"fastapi async performance"
# El modelo embebe estos 3 tokens. Captura: "tema = FastAPI, async, performance"
# No captura: "es una pregunta sobre cómo se relacionan estos conceptos"

# Natural language
"How does FastAPI achieve high performance through async/await?"
# El modelo embebe la oración. Captura: "tema = FastAPI async performance"
# + "intención = explicación de mecanismo causal"
# + "estructura = pregunta de proceso ('how does X achieve Y')"

Los modelos de embeddings modernos (OpenAI text-embedding-3-small, Cohere, etc.) fueron entrenados sobre texto natural, no listas de keywords. Le va mucho mejor con oraciones completas.

Impacto medible

Query keyword:     "fastapi async performance"     →  Recall: 48%
Query natural:     "How does FastAPI achieve high performance with async/await?"
                                                   →  Recall: 72%
Diferencia:        +24 puntos

Técnica que lo resuelve

Query rewriting estructural (cápsula 04): convertir queries keyword en oraciones interrogativas naturales antes de embebir.

Para queries muy técnicas que necesitan aún más contexto: HyDE — Hypothetical Document Embeddings (cápsula 06). En vez de embebir la query, generas una respuesta hipotética con un LLM y embebes esa respuesta. Lo que buscas en el corpus es "documentos similares al tipo de respuesta que un experto daría a esto" — más cercano semánticamente a los docs reales.


Comparación cuantitativa

ProblemaFrecuencia (% queries reales)Recall sin fixRecall con fixTécnica de fix
Ambigua30-40%~50%~80%Query expansion
Incompleta20-30%~40%~85%Query rewriting con contexto
Mal formulada30-40%~60%~80%Query rewriting estructural
Combinación de problemas10-20%~30%~75%Múltiples técnicas en pipeline

Lectura clave: ~70-80% de las queries reales tienen al menos uno de estos problemas. Pasar de ignorarlos a remediarlos transforma la calidad del sistema, típicamente +25-35 puntos de recall.


El error costoso: culpar al usuario

Cuando los stakeholders ven baja precision, la primera reacción a veces es:

"Los usuarios no saben tipear queries específicas. Necesitamos educarlos."

Esto es trampa por tres razones:

  1. No escala. Tienes miles o millones de usuarios. No vas a entrenarlos.
  2. No es problema del usuario. Los usuarios están bien tipeando como tipean. El sistema tiene que adaptarse a ellos, no al revés.
  3. Esconde el problema real. Mientras culpas al usuario, tu pipeline sigue subóptimo. Cuando un competidor implementa query optimization, te come el mercado.

El framing correcto: la query del usuario es input. Optimizarla antes de procesarla es responsabilidad del pipeline, igual que validar inputs en una API REST. Nadie le dice al cliente de un endpoint REST "aprende a hacer mejor el JSON" — tú sanitizas y validas. Mismo principio acá.


Cómo diagnosticar qué problema afecta más a tu sistema

Antes de aplicar técnicas de query optimization a ciegas, mídelo:

# diagnose_query_quality.py
from collections import Counter

def categorize_query(query: str, llm_classifier=None) -> str:
    """
    Clasifica una query en uno de los patrones problemáticos.
    Para precisión, usar un LLM (gpt-4o-mini) como clasificador.
    """
    # Heurísticas simples
    word_count = len(query.split())

    if word_count < 4:
        # Probablemente ambigua o incompleta
        if any(name in query.lower() for name in PRODUCT_NAMES):
            return "ambiguous"  # tiene producto pero falta detalle
        return "incomplete"

    # Detección de keyword style: ratio de keywords vs stop words
    stop_words_count = sum(1 for w in query.lower().split() if w in {"how", "is", "the", "a", "what", "when", "where"})
    if stop_words_count == 0 and word_count <= 6:
        return "keyword_style"

    return "well_formed"


def diagnose_query_problems(query_log: list[str]) -> dict:
    """
    Sobre un sample de queries reales, ¿qué proporción tiene cada problema?
    """
    counts = Counter()
    for q in query_log:
        category = categorize_query(q)
        counts[category] += 1

    total = len(query_log)
    return {
        cat: f"{count} ({count/total:.0%})"
        for cat, count in counts.items()
    }


# Sobre 1000 queries reales del log de producción
diagnosis = diagnose_query_problems(production_queries[:1000])
print(diagnosis)

Output típico:

{
    "incomplete": "412 (41%)",
    "keyword_style": "287 (29%)",
    "ambiguous": "208 (21%)",
    "well_formed": "93 (9%)",
}

Interpretación: 91% de las queries tienen problemas. Las más frecuentes son incompletas (41%) y keyword-style (29%). Tu prioridad de optimización debería ser query rewriting — atacaría 70% de las queries problemáticas.


Trampas y errores comunes

Trampa 1: aplicar query optimization sin diagnóstico

El error: copias un blog que dice "implementa HyDE", lo agregas al pipeline.

Síntoma: HyDE agrega 800ms de latencia, costo extra de LLM, y mejora marginal porque tu problema dominante era queries incompletas, no mal formuladas.

Cómo prevenir: diagnosticar primero (ver script arriba). Aplicar la técnica que ataca el problema dominante.

Trampa 2: query optimization es panacea

El error: asumes que con query optimization no necesitas re-ranking, ni hybrid search, ni chunking decente.

Realidad: son complementarios. Query optimization mejora el input del retrieval. Re-ranking refina el output. Chunking define qué unidades se buscan. Hybrid search complementa los embeddings con keywords.

Cómo prevenir: medir cada componente sobre eval set. Query optimization es típicamente la mejora de mayor impacto si tu pipeline base es semantic + cosine, pero rara vez resuelve todo.

Trampa 3: optimizar queries que ya estaban bien

El error: aplicas query rewriting a TODAS las queries, incluyendo las bien formuladas.

Síntoma: queries que ya eran buenas pasan por LLM rewriting innecesariamente, agregando 200-500ms y costo. A veces el LLM "rewrite" cambia una query buena a una peor.

Cómo prevenir: clasificar primero, optimizar solo las problemáticas. Queries bien formuladas pasan directo al retrieval.

def smart_pipeline(query: str):
    category = categorize_query(query)
    
    if category == "well_formed":
        return retrieve(query)  # skip optimization
    elif category == "ambiguous":
        return retrieve_with_expansion(query)
    elif category == "incomplete":
        return retrieve_with_rewriting(query, context)
    elif category == "keyword_style":
        return retrieve_with_rewriting(query, style="natural")

Trampa 4: rewriting que cambia la intención del usuario

El error: un LLM "rewrites" "why FastAPI is slow" a "How to make FastAPI faster". Cambió la pregunta.

Síntoma: el sistema responde "para hacer FastAPI más rápido, hace X" cuando el usuario quería entender por qué a veces es lento.

Cómo prevenir: prompts conservadores en query rewriting — "expandir la query, NO cambiar su intención". Y validación humana sobre eval set.

Trampa 5: pasar queries en otros idiomas a un rewriter en inglés

El error: tu rewriter usa GPT-4 con prompt en inglés. Recibe una query en español. El LLM la "rewrites" en inglés.

Síntoma: la query traducida no matchea bien con docs en español. Recall cae en queries multilingües.

Cómo prevenir: prompt en el mismo idioma de la query, o instrucción explícita "preserve the query language".

Trampa 6: olvidar el costo agregado

El error: agregas query expansion (5 queries por query original) + LLM rewriting (1 LLM call) + HyDE (1 LLM call). Cada query del usuario ahora hace 7 llamadas downstream.

Síntoma: costos se multiplican 7x. Latencia del primer hit del usuario sube de 200ms a 2.5 segundos.

Cómo prevenir: estimar costo total antes de combinar técnicas. Cada técnica tiene que justificarse independientemente sobre el eval set.


Ejercicio aplicado

Escenario: eres AI Engineer en una empresa de soporte técnico para herramientas de DevOps. Datos:

  • Sistema RAG en producción con cosine + cross-encoder rerank
  • Precision@5 actual: 78%
  • Recall@5 actual: 62%
  • Sin query optimization
  • 5,000 queries/día

Análisis del log de queries (1000 sample):

Categoría:        Cantidad    Ejemplos
─────────────────────────────────────────────────────────
incomplete        510 (51%)   "fix this error", "how to deploy", "auth not working"
keyword_style     320 (32%)   "kubernetes pod restart", "docker compose env"
ambiguous         140 (14%)   "ssl error", "timeout"
well_formed        30 (3%)    "How do I configure Helm chart values for staging?"

Tu trabajo:

  1. Diagnostica cuál es el problema dominante.
  2. Propone 2-3 técnicas de query optimization en orden de prioridad, justificando con los datos.
  3. Estima el costo extra y el impacto esperado.
Solución

1. Diagnóstico

Los datos muestran:

  • Recall (62%) más bajo que precision (78%) — el sistema no está encontrando los docs correctos. Eso es lo que primero hay que atacar.
  • 51% de queries son incompletas — esto es el problema dominante. Queries como "fix this error", "how to deploy" sin contexto.
  • 32% son keyword style — segundo problema más frecuente.
  • Solo 3% son well-formed — el sistema está optimizado para queries que casi nadie tipea.

Diagnóstico final: el cuello de botella es la calidad de la query, no el retrieval ni el re-ranking. Aún con cross-encoder, si la query original es incompleta, el retrieval recupera docs sobre temas equivocados, y no hay re-ranker que rescate eso.

2. Técnicas propuestas en orden de prioridad

Prioridad 1 — Query rewriting con contexto del chat (resuelve ~51% del problema):

# El usuario suele estar en una conversación con el bot
# Usar el historial reciente para reescribir queries incompletas

REWRITER_PROMPT = """The user is in a DevOps support chat. Their previous messages:
{chat_history}

Their current query: "{user_query}"

If the query is incomplete (missing tools, technologies, or context), rewrite it
as a complete question. If it's already complete, return it unchanged.
Preserve the user's intent. Output only the rewritten query."""

def rewrite_query_with_context(user_query, chat_history):
    response = openai.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": REWRITER_PROMPT.format(
                chat_history="\n".join(chat_history[-3:]),
                user_query=user_query
            )},
        ],
        temperature=0.0
    )
    return response.choices[0].message.content.strip()

# Aplicar solo a queries detectadas como "incomplete"
if categorize(query) == "incomplete":
    query = rewrite_query_with_context(query, chat_history)

Impacto esperado: queries incompletas pasan de recall ~40% a ~85%. Con 51% del tráfico en esta categoría, eso es:

  • 0.51 × 0.45 = +23 puntos de recall global

Costo extra: 1 LLM call (~$0.0001) por query incompleta = ~$15-25/mes para 5K queries/día.

Prioridad 2 — Query rewriting estructural para keyword style (resuelve ~32%):

KEYWORD_TO_NATURAL_PROMPT = """Convert this keyword-style query into a complete natural-language question.
Preserve all keywords. Do not add information that wasn't in the original.

Keyword query: {user_query}
Natural question:"""

def keyword_to_natural(user_query):
    if categorize(query) == "keyword_style":
        return llm_rewrite(user_query, KEYWORD_TO_NATURAL_PROMPT)
    return user_query

# Ejemplo:
# Input:  "kubernetes pod restart"
# Output: "How do I restart a Kubernetes pod?"

Impacto esperado: keyword queries pasan de recall ~60% a ~80%. Con 32% del tráfico:

  • 0.32 × 0.20 = +6 puntos de recall global

Costo extra: 1 LLM call por query keyword-style = ~$10/mes adicional.

Prioridad 3 — Query expansion para queries ambiguas (resuelve ~14%):

Solo aplicar a las marcadas como ambiguous. Generar 3 versiones expandidas, buscar con cada una, fusionar con RRF.

Impacto esperado: +3 puntos de recall global.

Costo extra: 3 retrieval calls + 1 LLM expansion = mayor pero solo aplica al 14%.

3. Estimación combinada

TécnicaMejora recallCosto mensualROI
Rewriting con contexto+23 puntos$25Excelente
Rewriting estructural+6 puntos$10Bueno
Query expansion+3 puntos$20Marginal

Mejora total esperada: recall@5 de 62% → 94% (+32 puntos).

Latencia extra: ~200-400ms por query optimizada (LLM call).

Plan de rollout:

  • Sprint 1: implementar rewriting con contexto. Validar sobre eval set. Si recall mejora significativamente, deployar.
  • Sprint 2: agregar rewriting estructural. Validar nuevamente.
  • Sprint 3: evaluar si query expansion para queries ambiguas vale el costo extra. Si las primeras dos llevan recall a >90%, posiblemente no.

Métrica de protección: después de cada sprint, verificar que precision NO cayó. Una query "rewrites mal" puede mejorar recall pero meter docs irrelevantes que bajan precision.


Resumen y siguiente paso

Lo que aprendiste:

  • La query del usuario es un input que necesita procesamiento, no un sagrado intocable.
  • Tres patrones dominantes: ambiguas (múltiples interpretaciones), incompletas (falta contexto), mal formuladas (keyword vs natural).
  • ~70-90% de queries reales tienen al menos uno de estos problemas. Vale la pena diagnosticar y atacar.
  • Cada problema tiene técnica específica que lo resuelve: expansion (ambiguas), rewriting con contexto (incompletas), rewriting estructural (mal formuladas), HyDE (queries muy técnicas).
  • Diagnosticar primero, optimizar después. Aplicar técnicas a ciegas agrega complejidad sin garantizar mejora.
  • Culpar al usuario por escribir mal queries es trampa — el sistema es responsable de adaptarse al usuario.
  • Optimizar queries solo cuando son problemáticas (skip dinámico) ahorra costo y latencia.

Checkpoint: antes de avanzar, deberías poder:

  • Clasificar una query dada en uno de los tres patrones problemáticos.
  • Predecir qué técnica de optimization atacaría mejor un problema dominante en un log de queries.
  • Diagnosticar si baja precision/recall es por queries malas vs por retrieval malo.

Siguiente cápsula: 03 — Query Expansion.

Acabas de identificar los problemas. La cápsula 03 cubre la primera técnica de fix: query expansion. Tomar una query original y generar múltiples versiones que cubren las distintas interpretaciones probables, después fusionar los resultados de cada una. Es la herramienta principal contra queries ambiguas, y tu primer experimento concreto de query optimization.


Recursos

  1. Stanford NLP — Query Reformulation — Foundational sobre query expansion y reformulation
  2. Anthropic — Contextual Retrieval — Técnica relacionada que mejora retrieval contextualizando chunks
  3. Pinecone — Query Optimization Guide — Tutorial práctico
  4. LangChain — Query Transformation — Implementación con LangChain
  5. Microsoft — Improving Retrieval Quality with Query Optimization — Caso de uso con Azure
  6. HyDE Paper — Precise Zero-Shot Dense Retrieval without Relevance Labels — Paper original de HyDE (preview de cápsula 06)

Tiempo estimado: 25-30 minutos Siguiente: 03-query-expansion.md