PHAROS: Quatro agentes, 60 segundos, um sinal de segurança medicamentosa não detectado de um desastre

Hackathon do Elasticsearch Agent Builder

pharos-blog.png

A Food and Drug Administration (FDA) recebe cerca de dois milhões de relatórios de eventos adversos de medicamentos todos os anos. As empresas farmacêuticas são legalmente obrigadas a detectar sinais de segurança dentro de 15 dias corridos após um relatório sério. Na prática, os analistas de farmacovigilância estão revisando manualmente documentos espalhados pelo Sistema de Relatórios de Eventos Adversos da FDA (FAERS), EudraVigilance, registros eletrônicos de saúde (EHRs) e redes sociais. A detecção leva semanas a meses, e cada sinal consome mais de 40 horas do tempo do analista.

O custo de ser lento não é abstrato. A falha da Merck em detectar sinais cardíacos do Vioxx custou US$ 4,85 bilhões em acordos judiciais. Um único sinal perdido pode gerar multas entre US$ 100 milhões e US$ 1 bilhão. Mas o custo real é ver pacientes tomando medicamentos que deveriam ter sido identificados como risco enquanto ninguém percebe a tempo.

Sou Prajwal Sutar, desenvolvedor independente que passou o último ano processando dados reais por meio de pipelines baseados em grandes modelos de linguagem (LLM), orquestração assíncrona e coordenação de múltiplos agentes. Não consegui encontrar nenhuma ferramenta existente que integre detecção de sinais, geração de relatórios e escalonamento em um único pipeline automatizado. Então, criei um durante o Hackathon do Elasticsearch Agent Builder.

O que o PHAROS faz

O PHAROS (Pharmacovigilance Autonomous Reasoning and Oversight System) extrai relatos de eventos adversos da API FAERS da FDA, executa uma análise estatística padronizada pela OMS para identificar sinais de segurança, gera a documentação regulatória (por exemplo, formulários MedWatch 3500A, seções de PSUR e narrativas de caso) e envia alertas para Slack, Jira e e-mail.

Desde a ingestão de dados até o envio do alerta, tudo leva menos de 60 segundos.

Aqui está como isso se manifesta de ponta a ponta. 50 relatos de eventos adversos para um medicamento fictício chamado CARDIVEX chegam e todos relatam perda súbita de visão concentrados no Japão, Coreia e Índia. Eles são indexados. Em menos de um minuto, o sistema detectou uma razão proporcional de relatos (PRR) de 18,94 para CARDIVEX/perda de visão, identificou o cluster geográfico JP/KR/IN, gerou um formulário MedWatch 3500A e uma seção PSUR, disparou um alerta Slack para #safety-crítico, criou um ticket Jira P1 e enviou um e-mail ao responsável pela segurança. Cada ação é registrada no pharos-audit-log — porque na farmacêutica, se você não registrou, não aconteceu.

Quatro agentes cuidam disso, cada um com uma função distinta.

Por que quatro agentes, e não apenas um

Eu dividi o sistema porque as funções são diferentes o suficiente para que um único agente seja medíocre em todos eles. Monitorar picos de volume não é a mesma habilidade que calcular índices estatísticos, que também não é a mesma que redigir documentos regulatórios, nem decidir quem acionar às 2h. Cada agente recebe um prompt do sistema ajustado às suas tarefas específicas e às configurações de temperatura correspondentes: o ANALYST é executado a 0,0 porque você não quer números PRR criativos. O SCRIBE roda em 0,2 para geração controlada de texto. SENTINEL em 0,1.

O SENTINEL

O SENTINEL monitora o índice pharos-adverse-events em busca de picos de volume. Ele usa o ES|QL para comparar o volume de relatórios dos últimos 7 dias com uma linha de base de 90 dias. Se um medicamento apresentar um salto de 3x, o SENTINEL aciona um fluxo de trabalho do Elastic que dispara o ANALYST. No caso do CARDIVEX, ele detectou um pico de 15x.

O analista

No ANALYST é onde a verdadeira detecção acontece. Ele executa o cálculo PRR da OMS inteiramente em ES|QL — STATS para contagens, EVAL para a matemática da razão e WHERE para limiares — entre pares medicamento-reação. Depois, executa análise temporal com BUCKET(report_date, 1 week) para capturar agrupamentos semanais, agregação geográfica em geo.country_code, e uma busca híbrida BM25 + vetorial denso para encontrar sinais históricos semelhantes. A classificação de gravidade é em níveis: PRR ≥ 5,0 com 5+ casos é CRÍTICO, PRR ≥ 2,0 com 3+ casos é ALTO, e qualquer valor acima de 1,5 vai para MONITORAMENTO. Sinais confirmados são escritos para pharos-signals.

O SCRIBE

O SCRIBE capta sinais confirmados e gera três tipos de documentos: MedWatch 3500A, PSUR Seção VI e uma narrativa de caso. Ele extrai até 100 relatórios de casos de apoio do índice de eventos adversos, produz os documentos e os indexa em pharos-regulatory-reports.

O HERALD

O HERALD é a camada de ação. Sinais críticos geram um alerta no Slack (formatação Block Kit), um ticket P1 no Jira e e-mails para o responsável pela segurança e o vice-presidente de Segurança. Sinais altos geram alertas no Slack, Jira P2 e e-mail para o responsável pela segurança. Sinais de monitoramento são acumulados em um resumo semanal. Um tempo limite de escalonamento de 2 horas reenvia um alerta para o vice-presidente de Segurança caso um sinal crítico não seja reconhecido.

As transferências entre agentes são todas executadas por fluxos de trabalho do Elastic — nove fluxos de trabalho no total, cobrindo coordenação agente a agente, ingestão noturna de FAERS em cron schedule, envio por e-mail via Slack/Jira, logging de auditoria e o timeout de escalonamento.

Mantendo as estatísticas dentro do Elasticsearch

Fiz uma escolha deliberada de manter o cálculo PRR dentro do ES|QL em vez de transferir dados para o Python. Ao começar, achei que precisaria do pandas para o trabalho estatístico. Eu estava errado.

A fórmula completa de PRR da OMS, contagem, matemática de razão, limiares e segmentação temporal, tudo roda como consultas ES|QL. Os agentes chamam ferramentas ES|QL, raciocinam sobre os resultados e retornam — sem pandas, sem computação externa e sem gargalo de transferência de dados. As estatísticas escalam com o cluster.

ES|QL é menos flexível que o pandas para análises arbitrárias. Mas, para a fórmula da OMS e agregações semanais BUCKET, ele lida com o trabalho de forma impecável. Eliminar essa camada intermediária de Python simplificou a arquitetura mais do que eu esperava — os agentes apenas consultam e raciocinam, e há um ponto a menos para as coisas darem errado.

O design do índice que faz tudo funcionar

O PHAROS roda com quatro índices Serverless do Elasticsearch, e o principal, pharos-adverse-events, foi onde dediquei mais tempo ao design.

Ele possui um clinical_text_analyzer personalizado com snowball stemming para busca narrativa, um drug_name_analyzer com tokenizador de palavras-chave para correspondência exata de medicamentos, campos dense_vector (1.536 dimensões) para embeddings narrativos, geo_point para clustering geográfico e mapeamentos nested para reações. Todas as consultas de que os agentes precisam — busca narrativa difusa, busca exata de medicamentos, agregação geográfica, similaridade semântica — são suportadas pelo design do índice. Os outros três índices são mais diretos: pharos-signals armazena sinais detectados com pontuações de PRR e a cadeia de raciocínio do analista, pharos-regulatory-reports mantém relatórios gerados, e pharos-audit-log registra o timestamp de cada ação do agente.

O problema pouco glamouroso que quase interrompeu o pipeline

Fazer com que os LLMs retornem JSON estruturado de forma confiável foi o desafio que eu não esperava.

Você pede JSON a um LLM e recebe JSON envolto em três parágrafos de explicação, ou JSON dentro de blocos de código Markdown, ou um preâmbulo conversacional seguido de JSON e um resumo útil. Os agentes trocam dados estruturados entre si, então cada resposta precisa ser analisada corretamente. Não importa quão boa seja a sua detecção de sinal se a saída do ANALYST não puder ser lida de forma confiável pelo SCRIBE.

Passei muito tempo ajustando prompts do sistema e acabei escrevendo uma função de extração de JSON que lida com JSON bruto, cercas de código Markdown e JSON enterrado dentro da linguagem natural. Não é um trabalho interessante, mas é o tipo de coisa que determina se um pipeline multiagente realmente executa ou apenas faz boas demonstrações.

O que eu corrigiria primeiro

O cálculo do PRR é atualmente uma estimativa pontual. Um sistema de farmacovigilância de produção precisa de intervalos de confiança do qui-quadrado e pontuação IC bayesiana. O modelo de dados já possui um campo ic_score configurado — ele está usando uma aproximação em vez do cálculo bayesiano adequado. Essa é a primeira coisa que eu mudaria com mais tempo.

O sistema também trata "visão turva" e "perda de visão" como eventos separados. A próxima etapa imediata é o agrupamento de reações orientado pela ontologia MedDRA, para que o sistema possa capturar sinais entre termos relacionados, em vez de tratar cada string como independente. Depois disso, eu incorporaria dados do EudraVigilance junto com os do FAERS para correlação entre continentes.

O ponto mais amplo

2 milhões de relatórios de eventos adversos chegam à mesa de alguém todos os anos, e a resposta atual é mais analistas fazendo mais revisões manuais. O PHAROS argumenta que a resposta são agentes que executam as estatísticas da OMS, geram a documentação e encaminham para a pessoa certa — tudo antes de o analista abrir o computador.

O PHAROS é open source sob no MIT. Se você trabalha com farmacovigilância ou assuntos regulatórios e quer executar isso com dados reais, gostaria de falar com você.

Prajwal Sutar

Desenvolvedor Independente ,

Prajwal Sutar é um desenvolvedor independente focado em sistemas de IA e pipelines de dados em grande escala.

GitHub · Demonstração · LinkedIn

O lançamento e o tempo de amadurecimento de todos os recursos ou funcionalidades descritos neste artigo permanecem a exclusivo critério da Elastic. Os recursos ou funcionalidades não disponíveis no momento poderão não ser entregues ou não chegarem no prazo previsto.

Neste post do blog, podemos ter usado ou feito referência a ferramentas de IA generativa de terceiros, que pertencem a seus respectivos proprietários e são operadas por eles. A Elastic não tem nenhum controle sobre as ferramentas de terceiros e não temos qualquer responsabilidade por seu conteúdo, operação ou uso, nem por qualquer perda ou dano que possa surgir do uso de tais ferramentas. Tenha cuidado ao usar ferramentas de IA com informações pessoais, sensíveis ou confidenciais. Os dados que você enviar poderão ser usados para treinamento de IA ou outros fins. Não há garantia de que as informações fornecidas serão mantidas seguras ou confidenciais. Você deve se familiarizar com as práticas de privacidade e os termos de uso de qualquer ferramenta de IA generativa antes de usá-la. 

Elastic, Elasticsearch e marcas associadas são marcas comerciais, logotipos ou marcas registradas do Elasticsearch B.V. nos Estados Unidos e em outros países. Todos os outros nomes de empresas e produtos são marcas comerciais, logotipos ou marcas registradas de seus respectivos proprietários.