Ir al contenido

ADR-0004: Stack de RAG

  • Estado: Accepted
  • Fecha: 2026-03-18
  • Responsables de la decisión: Waldemar Szemat

El agente fundamenta cada afirmación clínica en una pequeña base de conocimiento de 30 a 50 tarjetas que cubren resúmenes de interacciones entre medicamentos, barreras de adherencia, puntos de conversación de entrevista motivacional y criterios de escalamiento. Las fuentes de la KB se restringen a material de dominio público o debidamente atribuido: DailyMed (FDA SPL), MedlinePlus (gobierno de EE. UU.) y entradas parafraseadas de la Lista de Medicamentos Esenciales de la WHO. La capa de recuperación no necesita escalado horizontal; necesita ser barata, reproducible y autocontenida dentro de la imagen Docker que distribuimos.

Al mismo tiempo, esta es una implementación de referencia. Tiene que mostrar cuándo un almacén vectorial embebido es la decisión correcta y cuándo una base de datos vectorial gestionada es la decisión correcta. La narrativa es “empieza embebido, documenta la ruta gestionada”.

¿Cómo elegimos un almacén vectorial y un modelo de embeddings que (a) corran a $0 sin cuentas externas en la demo predeterminada, (b) demuestren conciencia de base de datos vectorial gestionada como ruta alternativa, (c) coincidan con la calidad a la que el LLM-como-juez nos exigirá y (d) mantengan reproducibilidad determinista para el arnés de evaluación?

  • Cero servicios externos para la ruta predeterminada de la demo; el almacén vectorial debe funcionar dentro de la imagen Docker
  • Reproducibilidad: la misma KB más el mismo modelo de embeddings más la misma consulta deben producir la misma recuperación, para que el evaluador de fundamentación sea estable
  • Costo: gratuito a escala de demo (50 tarjetas o menos, cientos de consultas por día), con una alternativa gestionada de nivel gratuito documentada
  • Calidad de embeddings: la evaluación del juez penalizará la recuperación débil a través de las compuertas de fidelidad y alucinación; el modelo de embeddings primario debería ser uno reciente y fuerte, con un fallback fuera de línea disponible en el extra de dev/eval cuando no se configura una clave de API
  • Licencia: cada componente con licencia permisiva; los embeddings generados para la KB se distribuyen dentro de la imagen sin costo por consulta en tiempo de ejecución cuando se usa el fallback fuera de línea de dev/eval
  • Chroma embebido (SQLite + HNSW) + Voyage AI voyage-3.5 como embeddings primarios, sentence-transformers BAAI/bge-small-en-v1.5 como fallback fuera de línea de dev/eval en el extra embeddings-local (elegida)
  • Nivel gratuito de Qdrant Cloud + Voyage AI voyage-3.5: servicio gestionado, nivel gratuito generoso, pero dependencia externa
  • FAISS como almacén embebido: alto rendimiento, pero la historia de metadatos es más delgada que la de Chroma
  • Postgres + pgvector: ubicado junto al saver de Postgres de LangGraph, pero añade superficie operativa para una KB de 50 tarjetas
  • OpenAI text-embedding-3-large como modelo de embeddings

Opción elegida: Chroma embebido como almacén vectorial primario, con Voyage AI voyage-3.5 como modelo de embeddings primario y sentence-transformers BAAI/bge-small-en-v1.5 como fallback fuera de línea de dev/eval en el extra embeddings-local. El nivel gratuito de Qdrant Cloud está documentado como la ruta alternativa gestionada; es la respuesta correcta para cualquier lector cuyo caso de uso tenga más de ~50K fragmentos o necesite un dashboard alojado.

Voyage AI da 200 millones de tokens gratuitos en la familia voyage-3.5 a los usuarios nuevos, lo que excede por mucho lo que la KB necesita (el corpus entero de 50 tarjetas se embebe en menos de un millón de tokens). El fallback de sentence-transformers viene en el extra opcional embeddings-local que usan dev, eval y los checkouts locales sin clave, de modo que esas corridas funcionan con cero claves de API externas si el usuario lo prefiere; la imagen de producción instala embeddings-cloud en su lugar y descarta torch, así que cuando no se establece una clave de API de Voyage degrada a recuperación léxica BM25 en lugar del modelo local.

La elección mantiene la demo en vivo a costo cero, da una alternativa limpia de base de datos gestionada para los lectores que la quieran, y usa dos rutas de embeddings que ambas puntúan bien en los benchmarks de recuperación.

  • La demo corre Chroma embebido dentro de la imagen Docker; no se requiere ningún servicio externo para levantarla
  • La fábrica de embedder selecciona Voyage AI si se establece una clave de API de Voyage, y recurre al modelo local de sentence-transformers en caso contrario; una prueba unitaria ejercita ambas ramas
  • El ingest de la KB hace upsert de una fila de Chroma por sub-fragmento e imprime el conteo de filas insertadas; el embedder queda fijado por config, para que el arnés de evaluación corra contra la superficie de recuperación esperada
  • La demo corre fuera de línea: no se requiere ningún servicio externo, lo que mantiene rápida y determinista la ruta de despertar en frío
  • El arnés de evaluación ve una superficie de recuperación determinista (Chroma
    • embeddings fijados + el embedder fijado por config), exactamente lo que el evaluador de fundamentación necesita
  • Voyage AI voyage-3.5 es un modelo de embeddings reciente y fuerte (anunciado el 2025-05-20); el nivel de 200M tokens gratuitos cubre la KB muchas veces
  • El fallback fuera de línea elimina la lectura de “necesita una clave de API” para cualquier lector que quiera clonar y ejecutar
  • Qdrant Cloud como ruta alternativa documentada permite que el proyecto señale conciencia de base de datos vectorial gestionada sin heredar el riesgo de suspensión del nivel gratuito
  • El modelo sentence-transformers de fallback se suma al tamaño del extra embeddings-local que usan dev y eval, no al de la imagen de producción, que instala embeddings-cloud y descarta torch; ese extra compra embeddings fuera de línea sin viaje de ida y vuelta para esas corridas
  • Chroma embebido escala mal más allá de cientos de miles de fragmentos; irrelevante para una KB de 50 tarjetas pero vale la pena señalarlo
  • Dos rutas de embeddings significan dos firmas de recuperación; el embedder fijado por config hace auditable la diferencia, pero los resultados de evaluación deben compararse dentro de una sola ruta de embeddings, no entre ellas
  • El proyecto gana las dependencias chromadb y voyageai
  • La imagen de producción no lleva los pesos de sentence-transformers; se distribuyen solo en el extra embeddings-local para dev y eval, intencional y documentado
  • Una migración futura a Qdrant Cloud es un cambio a nivel de Protocol, no una reescritura: la abstracción del almacén cubre ambos backends

Chroma embebido + Voyage AI primario + bge-small-en-v1.5 fallback

Sección titulada «Chroma embebido + Voyage AI primario + bge-small-en-v1.5 fallback»
  • Buena, porque la ruta predeterminada corre con cero servicios externos
  • Buena, porque el nivel gratuito de 200M tokens de Voyage AI cubre la KB
  • Buena, porque el fallback fuera de línea elimina la lectura de “necesita-una-clave”
  • Buena, porque el arnés de evaluación ve una superficie de recuperación determinista
  • Mala, porque el modelo de fallback agranda el extra embeddings-local de dev y eval, aunque la imagen de producción lo descarta
  • Mala, porque Chroma embebido no escala a cientos de miles de fragmentos
  • Buena, porque el dashboard gestionado y el nivel gratuito (1 GB, sin tarjeta) son generosos
  • Mala, porque la demo dependería de un servicio externo y de la política de cuentas de Qdrant; cada lector tendría que registrarse
  • Conservada como alternativa documentada
  • Buena, porque FAISS es rápido y probado en batalla
  • Mala, porque la ergonomía de metadatos + filtrado es más débil que la de Chroma
  • Buena, porque Postgres ya se usa para el saver de estado de la conversación
  • Mala, porque ubicar juntos el estado de la conversación y el almacenamiento vectorial complica la operación para una KB de 50 tarjetas, y distribuir Postgres para la recuperación contradice la postura embebida por defecto
  • Buena, porque es un modelo de embeddings fuerte y bien conocido
  • Mala, porque forzaría a la demo a requerir una clave de OpenAI solo para la recuperación, y de todos modos no hay un fallback fuera de línea limpio con calidad comparable fuera de sentence-transformers

Embedder y recuperación asimétrica tal como se construyó

Sección titulada «Embedder y recuperación asimétrica tal como se construyó»

Embedder primario: Voyage voyage-3.5, con un fallback local fuera de línea en el extra embeddings-local. La fábrica de embedder está dirigida por clave: resuelve Voyage voyage-3.5 cuando hay una clave de API de Voyage configurada, como en el despliegue en vivo. El fallback local BAAI/bge-small-en-v1.5 viene en el extra opcional embeddings-local que usan dev, eval y los checkouts locales sin clave; la imagen de producción instala embeddings-cloud en lugar de embeddings-local y descarta torch, de modo que no lleva el modelo local. Sin una clave de Voyage, la imagen de producción degrada a recuperación léxica BM25 en lugar del embedder local. El fallback es un modelo liviano, de aproximadamente 130 MB, compatible con CPU en una instancia pequeña, de modo que una corrida de dev o eval también funciona a $0 sin claves externas mientras mantiene una calidad de recuperación fuerte.

La recuperación es asimétrica y consciente de instrucciones. La familia BGE v1.5 está afinada por instrucciones y es asimétrica. El código distribuido lo honra: una consulta se embebe con el prefijo documentado de instrucción de recuperación de BGE (Represent this sentence for searching relevant passages: ); un pasaje se embebe sin prefijo; cada vector se normaliza con L2 para que la búsqueda por producto interno de Chroma se comporte como similitud de coseno. Un modelo simétrico de propósito general (por ejemplo all-MiniLM-L6-v2) no recibe prefijo de instrucción. Usada sin el manejo asimétrico, la calidad de recuperación de BGE se degrada; la capa de recuperación está construida para aplicarlo.

El umbral de similitud de recuperación se distribuye desactivado. Existe un ajuste de similitud mínima de recuperación pero viene desactivado por defecto. En el corpus de KB de un solo dominio un umbral no puede separar una pregunta clínica fuera de corpus por poco de una genuinamente dentro de corpus sin rechazar falsamente esta última. El agente rechaza ante una recuperación de cero coincidencias; una pregunta fuera de corpus por poco se responde contra la tarjeta más cercana. El umbral se deja en su lugar, desactivado, para que un corpus más amplio y temáticamente más diverso pueda habilitarlo más adelante. Ver la ficha del modelo para la limitación completa.

Parte del portafolio de Waldemar Szemat · szemat.pro
GitHub · LinkedIn