2026-09-19 — Retriever: cómo gestionar/escalar y hacerlo multifaceta
Investigación (búsqueda web) para el silo-engine (~/nef/nef-silo) y news-silo.
Objetivo: pasar de “captura por cron” a un retriever gestionable, escalable y de
múltiples facetas.
Fuentes consultadas
- System design de agregador de noticias (ingest → dedup → rank): https://systemdesignschool.io/problems/news-aggregator/solution
- Caso real de un operador solo, self-hosted, 1,800 fuentes / 13 idiomas: https://syfeed.com/blog/an-ai-first-news-platform-run-by-one-person
- Búsqueda híbrida + RRF + rerank + facetas: https://yobitel.com/knowledge-base/hybrid-search · https://learn.microsoft.com/en-ie/azure/search/hybrid-search-overview · https://codelit.io/blog/ai-powered-search-architecture
- Bug de HDBSCAN (el que tumbó el clustering): https://github.com/scikit-learn/scikit-learn/issues/33219 · https://github.com/scikit-learn/scikit-learn/pull/33630
1. Gestión y escala (lo que falta en nef-silo)
Hoy: capturar recorre las 122 fuentes en serie cada 30 min, sin estado por
fuente. Eso no escala ni se puede operar. El patrón estándar:
- Scheduler por fuente, no un barrido uniforme. Guardar por fuente
last_fetch,etag,last_modified,fallos_consecutivos,n_items.- Conditional GET: si el feed responde
304 Not Modified, se salta parseo y downstream. - Backoff exponencial para fuentes muertas; “priority lane” para fuentes que el usuario añade.
- Conditional GET: si el feed responde
- Dedup en dos etapas, en dos momentos (no se sustituyen):
- Antes de fetch: dedup de URL (set exacto + bloom para memoria).
- Después de parsear: fingerprint de contenido (SimHash/MinHash) para colapsar la misma noticia sindicada en URLs distintas. Hoy solo deduplico por URL → se cuelan copias.
- Aislamiento por fuente: un feed roto no debe parar el pipeline (ya lo hace, con errores por fuente) y hay que alertar si una fuente deja de producir (hoy solo se listan errores).
- Métricas operativas (en
estado.json): ingest lag (publicación→captura), éxito de fetch por fuente, tasa de error de parseo, items por tema. Sin esto no hay gestión. - Almacenamiento por dataset: SQLite (corpus/metadata, ya) + índice de vectores (ANN) para similitud + KV/contadores para el feed. No forzar todo a una sola DB.
2. Multifaceta (lo que pide “más completo”)
Faceta = dimensión de filtrado/agrupación. Hoy solo hay tema. Añadir a items:
lang, region, tipo (tuit/nota/telegram), fuente_tipo (ya), y tiempo.
Con eso el home y la búsqueda pueden cortar por cualquiera.
Búsqueda híbrida (el estándar 2026 para RAG/retrieval):
- Rama léxica: BM25 (FTS5 de SQLite sirve).
- Rama densa: embeddings (Qwen3-Embedding-0.6B local, o BGE-M3 multilingüe).
- Fusión: RRF con k=60 (sin normalizar scores; robusto por defecto).
- Rerank: cross-encoder (
bge-reranker-v2-m3) sobre top-50 → top-5. - Filtros de faceta idénticos en ambas ramas (tema, idioma, rango de fechas); pre-filtro cuando la faceta es muy selectiva.
Clustering incremental (en vez de re-cluster completo por corrida): fingerprint + LSH para candidatos + “assign-or-open” (si el candidato más cercano supera umbral, se une al silo; si no, abre uno nuevo). Costo ~constante por artículo. Un job de union-find repara fragmentación en background.
3. Fix inmediato del clustering (ya aplicado)
silo.py reventaba con TypeError: only 0-dimensional arrays can be converted to Python scalars: bug de scikit-learn en traverse_upwards cuando
cluster_selection_epsilon > 0 y hay distancias empatadas (NumPy ≥ 2.4).
Workaround aplicado en news-silo/silo.py: epsilon por defecto 0.0 + copy=True.
Alternativas si se necesita epsilon > 0: pin numpy<2.4 o el paquete hdbscan.
Corrida verificada: 600 textos → 85 silos, 346 outliers (calidad mixta:
varios silos se agrupan por fuente, no por evento; coincide con la nota previa
de que KMeans separa mejor en corpus AI).
4. Roadmap concreto para nef-silo
- Tabla
fuentes(last_fetch/etag/backoff) + conditional GET + métricas. - Facetas en
items(lang,region,tipo) + FTS5. - Fingerprint de contenido (MinHash) además del dedup por URL.
buscar: híbrida BM25+FTS5 + embeddings + RRF(k=60) + rerank.- Clustering incremental (LSH + assign-or-open) al estilo SyFeed; inherit-first para no pagar embeddings de lo ya agrupado.
Nota de arquitectura: el caso SyFeed (un operador, self-hosted) usa Redis para scheduling, worker async, BGE-M3 + Qwen3-30B en vLLM, Meilisearch + Qdrant. Es el norte razonable, pero se llega por pasos: primero scheduler+dedup+métricas, luego híbrida+facetas, luego clustering incremental.