REJCOB
← Todo el trabajo

RAG multi-tenant con búsqueda híbrida y harness de evaluación

Un stack de retrieval donde una fuga entre tenants es imposible por construcción y una regresión de calidad rompe el build.

Rol
Arquitectura y diseño
Período
2026
Empresa
Proyecto personal

RLS

cada consulta limitada a un tenant por una política de Postgres

Hybrid

resultados full-text + vector fusionados y luego re-rankeados

CI-gated

set dorado de preguntas evaluado en cada cambio

#Contexto

La mayoría de productos SaaS que agregan una función de 'chatea con tus documentos' enfrentan los mismos dos riesgos: que el contenido de un cliente aparezca en las respuestas de otro, y que la calidad del retrieval se degrade en silencio cuando alguien ajusta el chunking o los prompts. Este diseño trata ambos como problemas de ingeniería con garantías duras, en vez de cosas que se revisan a mano.

#Problema y restricciones

  • El filtrado por tenant hecho en código de aplicación está a un WHERE olvidado de una fuga de datos.
  • La búsqueda puramente vectorial pierde términos exactos — códigos de producto, mensajes de error, nombres — que la búsqueda por keywords encuentra trivialmente.
  • Sin una evaluación repetible, cada cambio en chunking, embeddings o prompts es una apuesta.

#Arquitectura

Questiontenant set per requestFull-texttsvectorVectorpgvectorPostgreSQLRLS per tenantFusion + rerankRRFLLM answerwith citationsEval harnessrecall@k · CI gate
Dos retrievers sobre una tabla Postgres limitada por tenant, fusionados y re-rankeados; un harness de evaluación valida los cambios en CI.

Los documentos se parsean, se dividen según la estructura de encabezados y se guardan en una sola tabla de PostgreSQL con una columna de embedding pgvector y una columna tsvector. Cada fila lleva un ID de tenant, y una política de Row Level Security restringe todas las lecturas y escrituras al tenant fijado en la sesión actual de base de datos — la API lo fija una vez por request, así ninguna consulta puede alcanzar filas de otro tenant. Al consultar, la pregunta pasa por dos retrievers en paralelo: búsqueda full-text y similitud vectorial. Sus rankings se combinan con reciprocal rank fusion y los mejores candidatos pasan por un reranker antes de que el LLM componga una respuesta con citas a los chunks de origen. Un harness de evaluación aparte mantiene un set dorado de preguntas con sus pasajes esperados; reporta recall@k y fidelidad de la respuesta, y corre en CI para que un cambio que baje cualquiera de las dos métricas rompa el pipeline.

#Decisiones clave y tradeoffs

01Row Level Security sobre una colección por tenant

Decisión
Mantener todos los tenants en una tabla y dejar que una política de Postgres fuerce el aislamiento en cada sentencia.
Alternativas
Una colección vectorial o base separada por tenant, o filtros de tenant escritos a mano en cada consulta.
Por qué
El aislamiento pasa a ser una propiedad de la base y no de la disciplina del desarrollador, dar de alta un tenant es un insert en vez de un aprovisionamiento, y los datos relacionales y los vectores comparten una transacción.
Costo de equivocarse
Un solo índice grande sirve a todos los tenants, así que un tenant muy grande afecta a los demás; pasado cierto tamaño ese tenant necesita particionado o su propia instancia.

02Búsqueda híbrida con fusión de rankings

Decisión
Correr retrieval full-text y vectorial en paralelo, combinar con reciprocal rank fusion y luego re-rankear la lista corta.
Alternativas
Solo búsqueda vectorial, o generar varias reescrituras de la pregunta y recuperar para cada una.
Por qué
La búsqueda por keywords y la vectorial fallan en preguntas distintas, así que combinarlas cubre ambas sin una llamada extra al LLM por consulta. La fusión de rankings no necesita calibrar puntajes entre las dos.
Costo de equivocarse
Dos índices que mantener, y el reranker agrega latencia a cada request.

03Evaluación como compuerta de CI

Decisión
Versionar un set dorado de preguntas junto al código y romper el build cuando recall@k o la fidelidad caen bajo un umbral.
Alternativas
Revisiones manuales en una ventana de chat después de cada cambio.
Por qué
Los cambios de retrieval interactúan de formas que nadie predice; un número que se mueve en cada commit convierte el ajuste de opinión en medición.
Costo de equivocarse
El set dorado hay que escribirlo y mantenerlo a mano, y la fidelidad juzgada por LLM cuesta tokens en cada corrida.

#Resultados

0

filas de otro tenant alcanzables — bloqueadas por política, por diseño

1 DB

datos relacionales, full-text y vectores en un solo Postgres

recall@k

y fidelidad medidos por commit

#Qué haría diferente

Lo primero que pondría bajo presión es el índice compartido: RLS filtra después de que el índice vectorial devuelve candidatos, así que un tenant pequeño dentro de una tabla grande puede recibir menos resultados de los pedidos. El siguiente paso es medirlo sobre un dataset desbalanceado y comparar índices parciales por tenant contra particionado.

#Stack

PostgreSQLpgvectorRow Level SecurityOpenAINode.jsTypeScriptRerankerRAG