Módulo 3: Features Esenciales para RAG

Cápsula 03: Hybrid Search (Keyword + Semantic)

🎯 Objetivo de la cápsula

Entender POR QUÉ pure semantic search falla en ciertos queries, cómo hybrid search (BM25 + vector) mejora accuracy 15-20%, y cuándo usar cada estrategia.

Al finalizar esta cápsula:

  • ✅ Explicarás limitaciones de pure semantic search
  • ✅ Entenderás cómo hybrid search combina keyword + semantic
  • ✅ Compararás ranking strategies (RRF, weighted fusion)
  • ✅ Decidirás cuándo usar hybrid vs pure semantic

Tiempo estimado: 10-12 minutos


🔍 Problema: Pure Semantic Search falla en queries específicos

¿Qué es semantic search?

Semantic search = Buscar por significado (embeddings) en lugar de palabras exactas.

Ejemplo:

Query: "automobile repair"
Embedding: [0.2, 0.5, 0.8, ...]

Top results (semantic):
1. "Car maintenance guide" ✅ (similar meaning)
2. "Vehicle troubleshooting" ✅ (similar meaning)
3. "How to fix your vehicle" ✅ (similar meaning)

Ventaja: Encuentra documentos conceptualmente similares (no solo exact keyword match).

Pero... semantic search falla en queries específicos

Problema 1: Nombres propios

Query: "GPT-4 API documentation"
Embedding: [0.1, 0.7, 0.3, ...]

Pure semantic results:
1. "OpenAI models overview" ⚠️ (menciona GPT-3, Claude)
2. "API integration guide" ⚠️ (general, no GPT-4)
3. "Language model comparison" ⚠️ (GPT-3 vs PaLM)

Expected:
1. "GPT-4 API reference" ← Falta porque "GPT-4" no dominó embedding

Por qué falla: "GPT-4" es nombre propio (token específico), pero embedding captura concepto general "language model API".

Problema 2: IDs, códigos, números exactos

Query: "Invoice #INV-2024-001234"

Pure semantic results:
1. "Invoice management guide"2. "Billing documentation"3. "Payment processing" ❌

Expected:
1. Invoice #INV-2024-001234 ← Falta porque embedding no captura ID exacto

Por qué falla: Embeddings no están diseñados para capturar strings exactos (son representaciones semánticas).

Problema 3: Queries con typos o variantes exactas

Query: "PostgreSQL"

Pure semantic results:
1. "Database management" ✅ (general)
2. "SQL tutorial" ✅ (general)
3. "MySQL guide" ⚠️ (diferente DB!)

Falta:
- "PostgreSQL installation" ← Debería ser #1

Por qué falla: Embedding de "PostgreSQL" y "MySQL" son similares (ambos son SQL databases).


🔀 Solución: Hybrid Search (BM25 + Vector)

¿Qué es hybrid search?

Hybrid search = Combinar keyword search (BM25) + semantic search (vector) para obtener best of both worlds.

Keyword search (BM25):

  • Encuentra exact keyword matches
  • Excelente para nombres propios, IDs, códigos
  • Pésimo para sinónimos, conceptos

Semantic search (Vector):

  • Encuentra similar concepts
  • Excelente para queries conceptuales
  • Pésimo para exact matches

Hybrid = Keyword + Semantic:

  • Combina resultados de ambos
  • Rank usando fusion algorithm (RRF, weighted)

Arquitectura de Hybrid Search

User Query: "GPT-4 API documentation"
        │
        ├─────────────────┬─────────────────┐
        ↓                 ↓                 ↓
  [Keyword Search]  [Semantic Search]
    (BM25)            (Vector HNSW)
        │                 │
  1. GPT-4 docs      1. OpenAI API guide
  2. API reference   2. LLM integration
  3. OpenAI guide    3. GPT-3 docs
        │                 │
        └─────────────────┴─────────────────┐
                          ↓
                   [Fusion Algorithm]
                   (RRF or Weighted)
                          ↓
                   Merged Top-10:
                   1. GPT-4 API docs ✅
                   2. OpenAI API guide ✅
                   3. GPT-4 reference ✅

Clave: Keyword captura "GPT-4" exacto, Semantic captura concepto "API documentation".


📊 Benchmark: Pure Semantic vs Hybrid

Escenario: Technical Documentation RAG

Setup:

  • Database: 100K technical docs
  • Queries: 1000 test queries (mix de conceptual + specific)
  • Metric: MRR (Mean Reciprocal Rank) + NDCG@10

Test A: Pure Semantic Search

results = db.query(
    query_embedding=embed(query),
    k=10
)

Resultados:

  • Conceptual queries: 92% accuracy ✅
  • Specific queries (names, IDs): 68% accuracy ❌
  • Overall: 78% accuracy

Fallos típicos:

  • "GPT-4 docs" → Devuelve GPT-3 docs
  • "React 18 features" → Devuelve React general
  • "Invoice #12345" → No encuentra

Test B: Pure Keyword Search (BM25)

results = bm25_search(query, k=10)

Resultados:

  • Specific queries: 95% accuracy ✅
  • Conceptual queries: 55% accuracy ❌
  • Overall: 72% accuracy

Fallos típicos:

  • "automobile repair" → No encuentra "car maintenance" (sinónimo)
  • "password reset" → No encuentra "credential recovery" (concepto similar)

Test C: Hybrid Search (BM25 + Vector)

# Keyword results
keyword_results = bm25_search(query, k=20)

# Semantic results
semantic_results = db.query(
    query_embedding=embed(query),
    k=20
)

# Fusion (RRF)
final_results = reciprocal_rank_fusion(
    [keyword_results, semantic_results],
    k=10
)

Resultados:

  • Conceptual queries: 91% accuracy ✅ (casi igual a pure semantic)
  • Specific queries: 94% accuracy ✅ (casi igual a pure keyword)
  • Overall: 92% accuracy

Ganancia: 14% improvement vs pure semantic, 20% vs pure keyword.


🔧 Fusion Algorithms

1. Reciprocal Rank Fusion (RRF)

Definición: Asignar score basado en posición (rank) en cada lista.

Fórmula:

RRF_score(doc) = Σ (1 / (k + rank_i))

donde:
- k = constant (típicamente 60)
- rank_i = posición de doc en lista i (1-indexed)

Ejemplo:

Keyword results:
1. Doc A (rank=1)
2. Doc B (rank=2)
3. Doc C (rank=3)

Semantic results:
1. Doc B (rank=1)
2. Doc D (rank=2)
3. Doc A (rank=3)

RRF scores:
Doc A: 1/(60+1) + 1/(60+3) = 0.0164 + 0.0159 = 0.0323
Doc B: 1/(60+2) + 1/(60+1) = 0.0161 + 0.0164 = 0.0325 ← Highest
Doc C: 1/(60+3) + 0 = 0.0159
Doc D: 0 + 1/(60+2) = 0.0161

Final ranking: B, A, D, C

Ventajas:

  • ✅ No requiere normalizar scores (solo ranks)
  • ✅ Robusto a outliers (un score muy alto no domina)
  • ✅ Simple de implementar

Desventajas:

  • ❌ No considera magnitude de scores (solo posición)
  • ❌ Puede penalizar docs con score alto pero rank bajo

Usado por: Weaviate (default)

2. Weighted Score Fusion

Definición: Combinar scores normalizados con pesos.

Fórmula:

Final_score(doc) = α × keyword_score + (1-α) × semantic_score

donde:
- α = peso para keyword (típicamente 0.3-0.7)
- keyword_score, semantic_score = normalizados [0,1]

Ejemplo:

Keyword results:
Doc A: score=0.9
Doc B: score=0.7
Doc C: score=0.5

Semantic results:
Doc A: score=0.6
Doc B: score=0.9
Doc D: score=0.8

Weighted (α=0.5):
Doc A: 0.5×0.9 + 0.5×0.6 = 0.75
Doc B: 0.5×0.7 + 0.5×0.9 = 0.80 ← Highest
Doc D: 0.5×0 + 0.5×0.8 = 0.40
Doc C: 0.5×0.5 + 0.5×0 = 0.25

Final ranking: B, A, D, C

Ventajas:

  • ✅ Considera magnitude de scores
  • ✅ Flexible (ajustar α según query type)
  • ✅ Intuitivo

Desventajas:

  • ❌ Requiere normalizar scores (BM25 y cosine similarity tienen ranges distintos)
  • ❌ Sensible a outliers

Usado por: Pinecone (custom), Elasticsearch

RRF vs Weighted: ¿Cuál elegir?

CriterioRRFWeighted
Simplicidad✅ Simple⚠️ Requiere normalización
Robustez✅ Robusto a outliers❌ Sensible
Flexibilidad❌ Fixed (no ajustable)✅ Ajustable (α)
Performance✅ Similar✅ Similar
RecomendadoDefault (Weaviate)Custom tuning (Pinecone)

Recomendación: Empezar con RRF (simple, robusto). Ajustar a weighted si necesitas control fino.


🎯 Cuándo usar Hybrid vs Pure Semantic

Decision Tree

┌─────────────────────────────────────────┐
│ ¿Tu query contiene nombres propios,     │
│ IDs, códigos, o términos muy            │
│ específicos?                             │
└─────────────────────────────────────────┘
                  │
       ┌──────────┴──────────┐
       │                     │
      Sí                    No
       │                     │
       ↓                     ↓
┌──────────────┐      ┌─────────────────┐
│ Hybrid Search│      │ Pure Semantic   │
│ (BM25 + Vec) │      │ (Vector only)   │
└──────────────┘      └─────────────────┘

Ejemplos:            Ejemplos:
- "GPT-4 docs"       - "car repair"
- "Invoice #123"     - "fix bug"
- "React 18"         - "improve performance"
- "user_id: abc"     - "reduce latency"

Casos de uso: Hybrid Search

1. Technical Documentation

Query: "Kubernetes 1.28 ingress"
→ Hybrid captura "Kubernetes 1.28" exacto + concepto "ingress"

2. E-commerce Product Search

Query: "Nike Air Max red size 10"
→ Hybrid captura "Nike Air Max" exacto + concepto "red shoes"

3. Legal Document Search

Query: "Case #2024-CV-12345"
→ Hybrid captura case number exacto

4. Code Search

Query: "def authenticate_user()"
→ Hybrid captura function name exacto

Casos de uso: Pure Semantic

1. Conceptual Queries

Query: "How to improve database performance?"
→ Semantic captura concepto (optimización, índices, caching)

2. Ambiguous Queries

Query: "fix connection issues"
→ Semantic captura múltiples interpretaciones (network, DB, API)

3. Synonyms Expected

Query: "automobile maintenance"
→ Semantic encuentra "car repair", "vehicle service"

🏭 Hybrid Search en Vector Databases

Weaviate (Hybrid Search nativo)

# Weaviate soporta hybrid search out-of-the-box
results = client.query.get("Document", ["content"])\
    .with_hybrid(
        query="GPT-4 API documentation",
        alpha=0.5  # 0=pure keyword, 1=pure vector
    )\
    .with_limit(10)\
    .do()

# Fusion: RRF (default)

Ventaja: Built-in, no custom code.

Pinecone + Keyword Search (Custom)

# Pinecone no tiene hybrid nativo
# Opción 1: Combinar con Elasticsearch

# 1. Keyword search en Elasticsearch
keyword_results = es.search(
    index="docs",
    body={"query": {"match": {"content": query}}}
)

# 2. Semantic search en Pinecone
semantic_results = pinecone_index.query(
    vector=embed(query),
    top_k=20
)

# 3. Fusion manual
final_results = weighted_fusion(keyword_results, semantic_results, alpha=0.5)

Desventaja: Requiere 2 sistemas (Pinecone + ES).

ChromaDB + BM25 (Custom implementation)

# ChromaDB no tiene hybrid nativo
# Implementar BM25 manual

from rank_bm25 import BM25Okapi

# 1. Index documents con BM25
corpus = [doc['content'] for doc in documents]
tokenized_corpus = [doc.split() for doc in corpus]
bm25 = BM25Okapi(tokenized_corpus)

# 2. Keyword search
tokenized_query = query.split()
keyword_scores = bm25.get_scores(tokenized_query)

# 3. Semantic search
semantic_results = collection.query(
    query_embeddings=[embed(query)],
    n_results=20
)

# 4. Fusion (RRF)
final_results = rrf_fusion(keyword_scores, semantic_results)

Desventaja: Requiere implementación custom.


📊 Impact en Accuracy: Ejemplo real

Escenario: API Documentation RAG

Dataset: 50K API documentation pages (OpenAI, Anthropic, AWS, Azure)

Test queries (100):

  • 50 conceptual: "How to handle rate limits?"
  • 50 specific: "GPT-4 turbo pricing"

Pure Semantic

Conceptual queries: 94% accuracy
Specific queries: 72% accuracy ← Falla en "GPT-4 turbo"
Overall: 83%

Hybrid (α=0.5)

Conceptual queries: 92% accuracy (similar)
Specific queries: 96% accuracy ← Mejora 24%
Overall: 94%

Ganancia: 11% overall, 24% en specific queries.


✅ Checklist de comprensión

Verifica que entendiste esta cápsula:

  • ¿Por qué pure semantic search falla en "GPT-4 API docs"?

    • Respuesta: Embedding captura concepto general "API documentation" pero no prioriza nombre exacto "GPT-4". Keyword search captura "GPT-4" exacto.
  • ¿Qué es hybrid search?

    • Respuesta: Combinar keyword search (BM25) + semantic search (vector) usando fusion algorithm (RRF o weighted).
  • ¿Qué es RRF?

    • Respuesta: Reciprocal Rank Fusion. Asigna score basado en posición (rank) en cada lista. Formula: 1/(60+rank).
  • ¿Cuándo usar hybrid vs pure semantic?

    • Respuesta: Hybrid si query tiene nombres propios, IDs, códigos. Pure semantic si query es conceptual/ambiguo.
  • ¿Cuál es accuracy improvement típico de hybrid?

    • Respuesta: 15-20% overall, 20-30% en specific queries.

Si respondiste 4-5/5 correctamente → ✅ Listo para Cápsula 04 (Multi-tenancy)


🔗 Conexión con RAG

¿Cómo hybrid search mejora tu RAG?

Caso A: Technical Docs Chatbot

Pure semantic:

User: "Show me Kubernetes 1.28 networking changes"
RAG: Devuelve Kubernetes networking general (mix de versiones)
LLM: Response contaminada con info de versiones viejas

Hybrid:

User: "Show me Kubernetes 1.28 networking changes"
RAG: Devuelve docs específicos de v1.28 (keyword match) + networking concepts
LLM: Response precisa y actualizada

Caso B: E-commerce Search

Pure semantic:

User: "Nike Air Jordan 1 Retro High OG"
RAG: Devuelve Nike shoes general (Jordan 1, 3, 4...)

Hybrid:

User: "Nike Air Jordan 1 Retro High OG"
RAG: Devuelve exactamente "Jordan 1 Retro High OG" (keyword match exacto)

🚀 Siguiente paso

Ya conoces las 2 features más críticas: Metadata filtering y Hybrid search. Ahora aprenderás feature #3 para SaaS: Multi-tenancy.

Próxima cápsula: 04 - Multi-tenancy (Aislamiento de datos)

Aprenderás:

  • Estrategias de multi-tenancy (collection per tenant, metadata filtering, namespace)
  • Security considerations (data leakage prevention)
  • Performance trade-offs (scale horizontal vs vertical)
  • Cuándo es crítico (SaaS, enterprise RAG)

Clave: Multi-tenancy es esencial para RAG SaaS (aislar datos por cliente).


Tiempo de lectura: 10-12 minutos
Siguiente: 04-multi-tenancy.md