PHAROS: 4 agentes, 60 segundos y 1 señal de seguridad farmacológica no detectada al borde del desastre

Hackatón de Elasticsearch Agent Builder

pharos-blog.png

La FDA recibe aproximadamente dos millones de reportes de eventos adversos por medicamentos cada año. Las compañías farmacéuticas están legalmente obligadas a detectar señales de seguridad dentro de los 15 días calendario posteriores a un reporte grave. En la práctica, los analistas de farmacovigilancia revisan manualmente documentos dispersos en el Sistema de Reportes de Eventos Adversos de la FDA (FAERS), EudraVigilance, registros electrónicos de salud (EHRs) y redes sociales. La detección toma semanas o meses, y cada señal consume más de 40 horas de tiempo de los analistas.

El costo de ser lento no es abstracto. El hecho de que Merck no captara las señales cardíacas de Vioxx costó $4.85 mil millones en acuerdos. Una sola señal perdida puede desencadenar multas de entre $100 millones y $1 mil millones. Pero el costo real son los pacientes que toman medicamentos que deberían haber sido marcados mientras nadie se dio cuenta lo suficientemente rápido.

Soy Prajwal Sutar, un desarrollador independiente que el último año ha impulsado datos reales a través de la ingesta de canalizaciones basados en modelos de lenguaje grande (LLM), la orquestación asíncrona y la coordinación de múltiples agentes. No encontré ni una sola herramienta existente que conecte la detección de señales, la generación de reportes y la escalada en una sola canalización automatizada. Así que construí uno durante el Hackathon de creación de agentes de Elasticsearch.

Qué hace PHAROS

PHAROS (Pharmacovigilance Autonomous Reasoning and Oversight System) extrae reportes de eventos adversos de la API FAERS de la FDA, ejecuta análisis estadísticos estándar de la OMS para encontrar señales de seguridad, genera la documentación regulatoria real (p. ej.: formularios MedWatch 3500A, secciones PSUR y narrativas de casos) y envía alertas a Slack, Jira y por correo electrónico.

Desde la ingesta de datos hasta el envío de la alerta, transcurren menos de 60 segundos.

Así es como se ve de principio a fin. Llegan 50 reportes de eventos adversos para un medicamento ficticio llamado CARDIVEX donde todos reportan pérdida repentina de la visión agrupada en Japón, Corea e India. Se indexan. En un minuto, el sistema detectó una proporción de reporte proporcional (PRR) de 18.94 para CARDIVEX/pérdida de visión, identificó el clúster geográfico JP/KR/IN, generó un formulario MedWatch 3500A y una sección PSUR, disparó una alerta de Slack a #safety-critical, creó un ticket Jira P1 y envió un correo electrónico al oficial de seguridad. Cada acción registrada en pharos-audit-log, porque en la industria farmacéutica, si no queda registrado, no sucedió.

Cuatro agentes se encargan de esto, cada uno con un trabajo distinto.

¿Por qué cuatro agentes y no solo uno?

Dividí el sistema porque las tareas son lo suficientemente diferentes como para que un solo agente fuera mediocre en todas. Monitorear los picos de volumen no es la misma habilidad que calcular índices estadísticos, y no es lo mismo que redactar documentos regulatorios, ni lo mismo que decidir a quién alertar a las 2 a. m. Cada agente recibe un prompt del sistema ajustado a su tarea específica y ajustes de temperatura que coinciden: ANALYST se ejecuta a 0.0 porque no quieres números PRR creativos. SCRIBE se ejecuta a 0.2 para la generación de texto controlada. SENTINEL a 0.1.

El centinela

SENTINEL observa el índice de eventos adversos de pharos (pharos-adverse-events) para detectar picos de volumen. Utiliza ES|QL para comparar los últimos 7 días de volumen de reportes con una línea base de 90 días. Si un medicamento muestra un salto de 3x, SENTINEL dispara un flujo de trabajo de Elastic que inicia ANALYST. En la ejecución de CARDIVEX, detectó un pico de 15x.

El analista

ANALYST es donde realmente se lleva a cabo la detección. Realiza el cálculo del PRR de la OMS íntegramente en ES|QL en todos los pares de medicamentos y reacciones: STATS para los recuentos, EVAL para los cálculos de la relación y WHERE para los umbrales. Luego, realiza un análisis temporal con BUCKET(report_date, 1 semana) para detectar agrupaciones semanales, una agregación geográfica en geo.country_code y una búsqueda híbrida BM25 + vector denso para encontrar señales históricas similares. La clasificación de gravedad tiene varios niveles: un PRR ≥ 5.0 con 5 o más casos es CRÍTICO, un PRR ≥ 2.0 con 3 o más casos es ALTO, y cualquier valor por encima de 1.5 pasa a MONITOREO. Las señales confirmadas se registran en pharos-signals.

El escriba

SCRIBE capta señales confirmadas y genera tres tipos de documentos: MedWatch 3500A, PSUR Sección VI y una narrativa de caso. Obtiene hasta 100 reportes de casos de apoyo del índice de eventos adversos y produce los documentos y los indexa en pharos-regulatory-reports.

El heraldo

HERALD es la capa de acción. Las señales CRÍTICAS reciben una alerta de Slack (formato del kit de bloqueo), un boleto de Jira P1 y correos electrónicos al oficial de seguridad y al vicepresidente de seguridad. Las señales ALTAS se envían por Slack, Jira P2 y por correo electrónico al oficial de seguridad. Las señales de MONITOREO se acumulan en un resumen semanal. Un tiempo de espera escalado de 2 horas vuelve a alertar al VP de seguridad si una señal CRÍTICA no se reconoce.

Todas las transferencias entre agentes se realizan a través de flujos de trabajo de Elastic: un total de nueve flujos de trabajo que abarcan la coordinación entre agentes, la ingesta nocturna de FAERS según un cron schedule, el envío a través de Slack/Jira/correo electrónico, los logs de auditoría y el tiempo de espera para la escalada de incidencias.

Mantener las estadísticas dentro de Elasticsearch

Tomé la decisión deliberada de mantener el cálculo PRR dentro de ES|QL en lugar de extraer datos a Python. Al entrar, asumí que necesitaría pandas para el trabajo estadístico. Me equivoqué.

La fórmula completa PRR de la OMS, el conteo, los cálculos de proporción, los umbrales y el agrupamiento temporal se ejecutan como consultas ES|QL. Los agentes llaman a las herramientas de ES|QL, razonan sobre los resultados y escriben: sin pandas, sin computación externa y sin cuellos de botella en la transferencia de datos. Las estadísticas escalan con el clúster.

ES|QL es menos flexible que pandas para análisis arbitrarios. Pero para la fórmula de la OMS y las agregaciones semanales de BUCKET (cubeta), maneja el trabajo de manera limpia. Eliminar esa capa intermedia de Python simplificó la arquitectura más de lo que esperaba. Los agentes solo consultan y razonan, y hay un lugar menos donde las cosas pueden fallar.

El diseño del índice que lo hace funcionar

PHAROS funciona con cuatro índices Serverless de Elasticsearch, y el principal, pharos-adverse-events, es donde pasé más tiempo diseñando.

Cuenta con un analizador clinical_text_analyzerpersonalizado con stemming de tipo snowball para la búsqueda narrativa, un analizador drug_name_analyzer con tokenizador de palabras clave para la coincidencia exacta de medicamentos, campos dense_vector (1536 dimensiones) para las incrustaciones narrativas, geo_point para la agrupación geográfica y mapeos anidados para las reacciones. El diseño del índice admite todas las consultas que necesitan los agentes: búsqueda narrativa difusa, búsqueda exacta de medicamentos, agregación geográfica y similitud semántica. Los otros tres índices son más sencillos: pharos-signals almacena las señales detectadas con puntuaciones PRR y la cadena de razonamiento del analista, pharos-regulatory-reports contiene los reportes generados y pharos-audit-log registra con marca de tiempo cada acción de los agentes.

El problema poco glamuroso que casi hizo colapsar la canalización

Conseguir que los LLMs devolvieran JSON estructurado de forma fiable fue la lucha que no esperaba.

Cuando le pides JSON a un LLM, obtienes JSON envuelto en tres párrafos de explicación, o JSON dentro de cercas de código markdown, o un preámbulo conversacional seguido de JSON, seguido de un resumen útil. Los agentes se entregan datos estructurados entre sí, por lo que cada respuesta se debe parsear de forma limpia. No importa lo buena que sea tu detección de señales si SCRIBE no puede leer de forma confiable la salida del ANALYST.

Pasé mucho tiempo ajustando los mensajes del sistema y terminé escribiendo una función de extracción de JSON que maneja JSON sin procesar, cercas de código markdown y JSON enterrado dentro del lenguaje natural. No es un trabajo muy interesante, pero es el tipo de cosa que determina si una canalización de agentes múltiples realmente se ejecuta o si solo se ve bien en las demostraciones.

Lo que arreglaría primero

El cálculo de PRR es actualmente una estimación puntual. Un sistema de farmacovigilancia de producción necesita límites de confianza de chi-cuadrado y puntuación IC bayesiana. El modelo de datos ya tiene un campo ic_score configurado. Está usando una aproximación en lugar del cálculo bayesiano adecuado. Eso es lo primero que cambiaría con más tiempo.

El sistema también trata "visión borrosa" y "pérdida de visión" como eventos separados. El siguiente paso inmediato es agrupar las reacciones teniendo en cuenta la ontología de MedDRA, para que el sistema pueda detectar señales entre términos relacionados en lugar de tratar cada cadena de texto como algo independiente. Después de eso, incorporaría los datos de EudraVigilance junto con FAERS para la correlación intercontinental.

El punto más amplio

2 millones de reportes de eventos adversos llegan al escritorio de alguien cada año, y la respuesta actual es que más analistas realizan más revisiones manuales. PHAROS es un argumento de que la respuesta son agentes que analizan las estadísticas de la OMS, generan la documentación y escalan la información a la persona adecuada, y todo antes de que el analista prenda su computadora.

PHAROS es open source bajo MIT. Si trabajas en farmacovigilancia o asuntos regulatorios y quieres comparar esto con datos reales, me gustaría saber de ti.

Prajwal Sutar

Desarrollador independiente ,

Prajwal Sutar es un desarrollador independiente especializado en sistemas de inteligencia artificial y canalizaciones de datos a gran escala.

GitHub · Demo · LinkedIn

El momento del lanzamiento de cualquiera de las características o funcionalidades descritas en esta publicación queda a exclusivo criterio de Elastic. Es posible que algunas características o funcionalidades que no estén disponibles en este momento no se lancen a tiempo o no se lancen en absoluto.

En esta publicación del blog, es posible que hayamos usado o nos hayamos referido a herramientas de AI generativa de terceros, que son propiedad de sus respectivos propietarios y están gestionadas por ellos. Elastic no tiene ningún control sobre las herramientas de terceros y no tenemos ninguna responsabilidad por su contenido, operación o uso, ni por ninguna pérdida o daño que pueda surgir de tu uso de dichas herramientas. Ten cuidado al usar herramientas de AI con información personal, sensible o confidencial. Cualquier dato que envíes puede usarse para el entrenamiento de la AI u otros fines. No se garantiza que la información que proporciones se mantenga segura o confidencial. Debes familiarizarte con las prácticas de privacidad y los términos de uso de cualquier herramienta de IA generativa antes de usarla. 

Elastic, Elasticsearch y las marcas asociadas son marcas comerciales, logotipos o marcas comerciales registradas de Elasticsearch B.V. en los Estados Unidos y otros países. Todos los demás nombres de empresas y productos son marcas comerciales, logotipos o marcas comerciales registradas de sus respectivos dueños.