Módulo 3: Features Esenciales para RAG
Cápsula 02: Metadata Filtering (Where Clauses)
🎯 Objetivo de la cápsula
Entender QUÉ es metadata filtering, por qué es la feature más crítica para RAG en producción, y cómo reduce latency 10x mientras mejora accuracy.
Al finalizar esta cápsula:
- ✅ Explicarás qué es metadata filtering y por qué es esencial
- ✅ Compararás pre-filtering vs post-filtering (trade-offs)
- ✅ Identificarás operadores clave (equality, range, membership, logical)
- ✅ Calcularás impact en performance (latency, accuracy, costos)
Tiempo estimado: 10-12 minutos
🔍 ¿Qué es Metadata Filtering?
Definición
Metadata filtering es capacidad de buscar vectores similares SOLO en subset de documentos que cumplen condiciones específicas (where clauses).
Sin metadata filtering (búsqueda ingenua)
# ❌ Búsqueda en TODO el database
query = "How do I reset my password?"
results = db.query(
query_embedding=embed(query),
k=10
)
# Busca en: 500K documentos (todos)
# Latency: 200ms
# Accuracy: 85% (mucho ruido)
Problema: Busca en documentos irrelevantes (marketing, legal, product specs).
Con metadata filtering (búsqueda inteligente)
# ✅ Búsqueda en subset relevante
query = "How do I reset my password?"
results = db.query(
query_embedding=embed(query),
k=10,
where={"category": "support", "language": "en"}
)
# Busca en: 10K documentos (solo support docs en inglés)
# Latency: 20ms (10x faster)
# Accuracy: 95% (sin ruido)
Ventaja: Search space reducido → Latency menor + Accuracy mayor.
📊 Por qué es crítica para RAG
Problema 1: Relevancia (Accuracy)
Escenario: E-commerce RAG con 1M productos
Query: "Nike Air Max precio"
Sin filtering:
Top 10 results:
1. Nike Air Max (product page) ✅
2. Adidas similar shoe (product page) ❌
3. Nike history article ❌
4. Air conditioning product ❌ (false positive: "air")
5. Max brand electronics ❌ (false positive: "max")
...
Accuracy: 60% (4/10 irrelevantes)
Con filtering (category='shoes'):
Top 10 results:
1. Nike Air Max (product page) ✅
2. Nike Air Max variants ✅
3. Similar Nike shoes ✅
...
Accuracy: 95% (9/10 relevantes)
Ganancia: 35% accuracy improvement.
Problema 2: Latency (Performance)
Benchmark (1M vectores, 1536-dim, HNSW):
| Escenario | Search Space | Latency | vs Baseline |
|---|---|---|---|
| Sin filtering | 1M vectores | 180 ms | 1x |
| Filtering (10% data) | 100K vectores | 18 ms | 10x faster |
| Filtering (1% data) | 10K vectores | 2 ms | 90x faster |
Clave: Latency es proporcional a search space. Filtering reduce search space dramáticamente.
Problema 3: Costos (Compute)
Con Layer 1 (HNSW) arquitectura:
- Sin filtering: HNSW navega 1M vectores → CPU intensive
- Con filtering: HNSW navega 10K vectores → 100x menos CPU
Impact en cloud:
AWS cost (r6i.xlarge, $180/month):
- Sin filtering: CPU usage 80% → Need larger instance ($360/month)
- Con filtering: CPU usage 10% → Can downsize ($90/month)
Savings: $270/month = $3240/year
🏗️ Arquitectura: Pre-filtering vs Post-filtering
Pre-filtering (Filter-then-search)
Proceso:
- Aplicar metadata filter primero (SQL-like where clause)
- Buscar vectores similares SOLO en subset filtrado
- Devolver top-k
Ventaja:
- ✅ Más rápido (search space reducido)
- ✅ Menos CPU (no calcula similitud en docs irrelevantes)
Desventaja:
- ❌ Si subset es vacío → No devuelve resultados
- ❌ Requiere índice de metadata (overhead)
Usado por: Pinecone, Weaviate (default)
Visualización:
1M documentos
↓
[Filter: category='support'] → 10K documentos
↓
[Vector search HNSW] → Top 10 resultados
Post-filtering (Search-then-filter)
Proceso:
- Buscar vectores similares en TODO el database
- Aplicar metadata filter a resultados
- Si <k resultados, buscar más vectores
- Devolver top-k
Ventaja:
- ✅ Siempre devuelve k resultados (no falla si subset vacío)
- ✅ No requiere índice de metadata
Desventaja:
- ❌ Más lento (busca en todo primero)
- ❌ Más CPU (calcula similitud en docs que luego filtra)
Usado por: ChromaDB (default)
Visualización:
1M documentos
↓
[Vector search HNSW] → Top 100 candidates
↓
[Filter: category='support'] → Top 10 resultados filtrados
Comparación: Pre vs Post
| Dimensión | Pre-filtering | Post-filtering |
|---|---|---|
| Latency | 20ms ✅ | 50ms ⚠️ |
| CPU | Bajo ✅ | Alto ❌ |
| Garantía k results | No ❌ | Sí ✅ |
| Requiere índice metadata | Sí ⚠️ | No ✅ |
| Mejor para | High cardinality filters | Low cardinality filters |
Decisión:
- Pre-filtering si filter es selectivo (reduce >50% data)
- Post-filtering si filter es poco selectivo (<10% reduction)
🔧 Operadores de Metadata Filtering
1. Equality (Exact match)
# Single value
where={"category": "support"}
# Busca docs donde category == "support"
# Uso: Categorías, IDs, status
Ejemplo RAG: Solo buscar en documentación técnica (no marketing).
2. Range (Comparaciones)
# Greater than, less than
where={
"timestamp": {"$gt": 1640000000}, # Después de 2021-12-20
"price": {"$lt": 100} # Menor a $100
}
# Operadores: $gt, $gte, $lt, $lte, $ne
Ejemplo RAG: Solo documentos recientes (últimos 30 días).
3. Membership (IN operator)
# Multiple values
where={
"category": {"$in": ["support", "faq", "docs"]}
}
# Busca docs donde category IN ["support", "faq", "docs"]
Ejemplo RAG: Buscar en múltiples categorías relevantes.
4. Logical operators (AND, OR, NOT)
# AND (implícito con múltiples conditions)
where={
"category": "support",
"language": "en",
"status": "published"
}
# OR
where={
"$or": [
{"category": "support"},
{"category": "faq"}
]
}
# NOT
where={
"category": {"$ne": "archived"}
}
# Combinado (complex)
where={
"$and": [
{"category": {"$in": ["support", "faq"]}},
{"language": "en"},
{"timestamp": {"$gt": 1640000000}}
]
}
Ejemplo RAG: Documentos de soporte EN INGLÉS NO archivados DE últimos 30 días.
📊 Benchmark: Impact real de Metadata Filtering
Escenario: Customer Support RAG
Setup:
- Database: 500K documentos
- Support articles: 50K (10%)
- Marketing content: 200K (40%)
- Product specs: 150K (30%)
- Legal/HR docs: 100K (20%)
- Algoritmo: HNSW (ChromaDB)
- Hardware: r6i.xlarge (32 GB RAM)
Query: "How to reset password?"
Test A: Sin metadata filtering
results = db.query(
query_embedding=embed("How to reset password?"),
k=10
)
Resultados:
- Search space: 500K docs
- Latency: 210ms
- Top 10 accuracy: 70% (7/10 relevantes)
- 3 false positives (marketing con "password" mention)
- CPU usage: 60%
Test B: Con metadata filtering (category='support')
results = db.query(
query_embedding=embed("How to reset password?"),
k=10,
where={"category": "support"}
)
Resultados:
- Search space: 50K docs (10x reducción)
- Latency: 22ms (9.5x faster)
- Top 10 accuracy: 95% (9.5/10 relevantes)
- CPU usage: 8%
Ganancia:
- 9.5x latency reduction
- 25% accuracy improvement
- 7.5x CPU reduction
Test C: Con multiple filters (category + language + recency)
results = db.query(
query_embedding=embed("How to reset password?"),
k=10,
where={
"category": "support",
"language": "en",
"timestamp": {"$gt": time.now() - 90_days}
}
)
Resultados:
- Search space: 8K docs (62x reducción)
- Latency: 4ms (52x faster)
- Top 10 accuracy: 98% (9.8/10 relevantes)
- CPU usage: 2%
Ganancia:
- 52x latency reduction
- 28% accuracy improvement
- 30x CPU reduction
Conclusión: Más filters = Mayor speedup + Mayor accuracy (si filters son relevantes).
🎯 Estrategias de Metadata Filtering para RAG
Estrategia 1: Categorical filtering
Uso: Multi-domain RAG (support + sales + product)
# User selecciona categoría en UI
category = user_input # "support"
results = db.query(
query_embedding=embed(query),
where={"category": category}
)
Ventaja: User controla scope explícitamente.
Estrategia 2: Recency filtering
Uso: Documentación técnica (priorizar versiones recientes)
# Solo documentos de últimos 90 días
cutoff = time.now() - 90_days
results = db.query(
query_embedding=embed(query),
where={"timestamp": {"$gte": cutoff}}
)
Ventaja: Evita documentación obsoleta.
Estrategia 3: Multi-tenant filtering
Uso: SaaS RAG (aislar datos por cliente)
# Cada tenant tiene su propio namespace
results = db.query(
query_embedding=embed(query),
where={"tenant_id": current_user.tenant_id}
)
Ventaja: Security + performance (search en subset).
Estrategia 4: Hierarchical filtering
Uso: Documentos con jerarquía (department → team → project)
# Buscar en scope cada vez más amplio si no hay resultados
scopes = [
{"department": "engineering", "team": "backend", "project": "api"},
{"department": "engineering", "team": "backend"},
{"department": "engineering"},
{} # Fallback: buscar en todo
]
for scope in scopes:
results = db.query(
query_embedding=embed(query),
where=scope,
k=10
)
if len(results) >= 5: # Threshold
break
Ventaja: Balance entre relevancia (narrow scope) y coverage (broad scope).
🚫 Anti-patterns comunes
Anti-pattern 1: No usar metadata filtering
# ❌ MAL: Buscar en todo sin filtros
results = db.query(query_embedding=embed(query), k=10)
# ✅ BIEN: Filtrar por contexto relevante
results = db.query(
query_embedding=embed(query),
where={"category": "support"},
k=10
)
Impact: 10x latency + 25% accuracy loss.
Anti-pattern 2: Over-filtering (subset vacío)
# ❌ MAL: Filters muy específicos → 0 resultados
results = db.query(
query_embedding=embed(query),
where={
"category": "support",
"subcategory": "password",
"language": "en",
"region": "US-West",
"version": "2.3.1"
},
k=10
)
# Resultado: 0 docs match → No devuelve nada
Fix: Usar hierarchical filtering (intentar con filters más amplios).
Anti-pattern 3: Filtering en application layer (no en DB)
# ❌ MAL: Fetch todo, filtrar en Python
all_results = db.query(query_embedding=embed(query), k=1000)
filtered = [r for r in all_results if r.metadata['category'] == 'support']
top_10 = filtered[:10]
# ✅ BIEN: Filtrar en DB
results = db.query(
query_embedding=embed(query),
where={"category": "support"},
k=10
)
Impact: 10-50x latency (transfer 1000 docs vs 10), CPU waste.
✅ Checklist de comprensión
Verifica que entendiste esta cápsula:
-
¿Qué es metadata filtering?
- Respuesta: Buscar vectores similares SOLO en subset de docs que cumplen where clauses (e.g., category='support').
-
¿Cuál es diferencia entre pre-filtering y post-filtering?
- Respuesta: Pre-filtering filtra primero (faster), post-filtering busca primero (garantiza k results).
-
¿Cuál es impact típico de metadata filtering en latency?
- Respuesta: 10x speedup típico (200ms → 20ms) cuando filter reduce search space 90%.
-
¿Qué operadores de filtering existen?
- Respuesta: Equality (==), Range ($gt, $lt), Membership ($in), Logical ($and, $or, $not).
-
¿Cuándo NO usar metadata filtering?
- Respuesta: Cuando no tienes metadata relevante, o cuando query es muy general (buscar en todo es correcto).
Si respondiste 4-5/5 correctamente → ✅ Listo para Cápsula 03 (Hybrid Search)
🔗 Conexión con RAG
¿Cómo esto mejora tu RAG system?
Caso A: Customer Support Chatbot
Sin filtering:
User: "How do I reset my password?"
RAG busca en: 500K docs (support + marketing + legal + product)
Latency: 200ms
LLM recibe: 3 support docs + 7 marketing docs con "password"
Response quality: 70% (contaminated context)
Con filtering:
User: "How do I reset my password?"
RAG busca en: 50K docs (solo support)
Latency: 20ms (10x faster)
LLM recibe: 10 support docs relevantes
Response quality: 95%
Ganancia: 10x latency + 25% quality improvement.
Caso B: Multi-tenant SaaS RAG
Sin filtering (INSEGURO):
Tenant A query: "Show me sales data"
RAG busca en: Todos los tenants (data leakage risk!)
Con filtering (SEGURO):
Tenant A query: "Show me sales data"
RAG busca en: where={"tenant_id": "tenant_a"}
Isolation garantizado
Crítico: Security compliance (GDPR, SOC2).
🚀 Siguiente paso
Metadata filtering es feature #1 más crítica. Ahora aprenderás feature #2: Hybrid Search.
Próxima cápsula: 03 - Hybrid Search (Keyword + Semantic)
Aprenderás:
- Por qué pure semantic search falla en queries específicos
- Cómo combinar BM25 (keyword) + vector search (semantic)
- Ranking strategies (RRF, weighted fusion)
- Impact en accuracy (75% → 92%)
Clave: Metadata filtering reduce search space. Hybrid search mejora ranking dentro de ese space.
Tiempo de lectura: 10-12 minutos
Siguiente: 03-hybrid-search.md