timmers's picture
v2: clinical decision support repositioning + multi-turn chat + 5 PT-BR examples + canonical lookup
98da1ab verified
Raw
History Blame
12.1 kB
"""Araras-Gemma4 — HF Space (Gradio + ZeroGPU)
Clinical decision support copilot for Brazilian rare diseases.
For licensed healthcare professionals.
Submission: Gemma 4 Good Hackathon (Kaggle, May 2026).
"""
import os, re, unicodedata, json
import gradio as gr
import spaces
from llama_cpp import Llama
from huggingface_hub import hf_hub_download
MODEL_REPO = "Raras-AI/araras-gemma4-e4b-v4-gguf"
MODEL_FILE = "araras-gemma4-e4b-v4-Q4_K_M.gguf"
# ─── Canonical ORPHA resolver (subset, 26 CEAF diseases) ───────────────
CANONICAL = [
(["esclerose multipla","multiple sclerosis","neuromielite optica","mogad","desmielinizante"], "ORPHA:802", "esclerose-multipla", "Esclerose Múltipla", ["betainterferona-1a","natalizumabe","fingolimode","ocrelizumabe"]),
(["doenca falciforme","anemia falciforme","hbss","sickle cell","drepanocitose","falciforme"], "ORPHA:232", "doenca-falciforme", "Doença Falciforme", ["hidroxiureia","deferasirox","L-glutamina"]),
(["ame tipo 1","sma tipo 1","spinal muscular atrophy type 1","werdnig","ame tipo i"], "ORPHA:70", "atrofia-muscular-espinhal-5q", "AME Tipo 1 (Werdnig-Hoffmann)", ["nusinersena","onasemnogeno-abeparvoveque"]),
(["atrofia muscular espinhal","ame 5q","sma 5q","spinal muscular atrophy"], "ORPHA:83330", "atrofia-muscular-espinhal-5q", "Atrofia Muscular Espinhal 5q", ["nusinersena","risdiplam","onasemnogeno-abeparvoveque"]),
(["hipertensao arterial pulmonar","hipertensao pulmonar","pulmonary hypertension"], "ORPHA:182090", "hipertensao-pulmonar", "Hipertensão Arterial Pulmonar", ["sildenafila","bosentana","ambrisentana","macitentana"]),
(["fibrose cistica","cystic fibrosis","mucoviscidose","cftr"], "ORPHA:586", "fibrose-cistica", "Fibrose Cística", ["dornase-alfa","tobramicina inalatória","ivacaftor","lumacaftor"]),
(["fenilcetonuria","pku","phenylketonuria","fenilalaninemia"], "ORPHA:716", "fenilcetonuria", "Fenilcetonúria", ["fórmula metabólica isenta de fenilalanina","sapropterina"]),
(["doenca de wilson","wilson disease","kayser"], "ORPHA:905", "doenca-de-wilson", "Doença de Wilson", ["d-penicilamina","trientina","zinco-acetato"]),
(["mucopolissacaridose tipo i","mps i","mps 1","hurler","iduronidase"], "ORPHA:579", "mucopolissacaridose-tipo-i", "MPS I (Hurler)", ["laronidase"]),
(["mucopolissacaridose tipo ii","mps ii","mps 2","hunter","iduronato"], "ORPHA:580", "mucopolissacaridose-tipo-ii", "MPS II (Hunter)", ["idursulfase-alfa"]),
(["mucopolissacaridose tipo vi","mps vi","mps 6","maroteaux","arilsulfatase b"], "ORPHA:583", "mucopolissacaridose-tipo-vi", "MPS VI (Maroteaux-Lamy)", ["galsulfase"]),
(["scid","imunodeficiencia combinada","severe combined immunodeficiency"], "ORPHA:183660", None, "SCID", ["TCTH","IVIg"]),
(["doenca de gaucher","gaucher disease","glucocerebrosidase"], "ORPHA:355", "doenca-de-gaucher", "Doença de Gaucher", ["imiglucerase","alfa-velaglicerase","alfa-taliglicerase"]),
(["doenca de pompe","pompe disease","alfa-glicosidase"], "ORPHA:365", "doenca-de-pompe", "Doença de Pompe", ["alfa-alglicosidase"]),
(["doenca de fabry","fabry disease","alfa-galactosidase"], "ORPHA:324", "doenca-de-fabry", "Doença de Fabry", ["alfa-agalsidase","beta-agalsidase","migalastate"]),
(["hemoglobinuria paroxistica noturna","hpn","paroxysmal nocturnal"], "ORPHA:447", "hemoglobinuria-paroxistica-noturna", "Hemoglobinúria Paroxística Noturna", ["eculizumabe","ravulizumabe"]),
(["esclerose lateral amiotrofica","ela","amyotrophic lateral sclerosis"], "ORPHA:803", "esclerose-lateral-amiotrofica", "Esclerose Lateral Amiotrófica", ["riluzol","edaravone"]),
]
def norm(s):
s = (s or "").lower()
s = unicodedata.normalize("NFD", s)
return "".join(c for c in s if unicodedata.category(c) != "Mn")
def resolve(text):
"""Find disease mentions in model output, return list of dicts."""
text_n = norm(text)
found, seen = [], set()
# Per-line parse first
for line in text.split("\n"):
line_n = norm(line)
for kws, orpha, slug, name, drugs in CANONICAL:
if orpha in seen: continue
if any(k in line_n for k in kws):
found.append({"orpha": orpha, "name_pt": name, "pcdt_slug": slug, "ceaf_drugs": drugs})
seen.add(orpha); break
# Fallback whole-text
if not found:
for kws, orpha, slug, name, drugs in CANONICAL:
if orpha in seen: continue
if any(k in text_n for k in kws):
found.append({"orpha": orpha, "name_pt": name, "pcdt_slug": slug, "ceaf_drugs": drugs})
seen.add(orpha)
return found[:3]
_llm = None
def get_llm():
global _llm
if _llm is None:
path = hf_hub_download(MODEL_REPO, MODEL_FILE)
_llm = Llama(model_path=path, n_ctx=8192, n_gpu_layers=-1, verbose=False)
return _llm
SYS = """Você é ARARAS, copiloto de apoio à decisão clínica em doenças raras para profissionais de saúde no Brasil.
NÃO substitui diagnóstico — apoia o(a) médico(a) na consulta.
FORMATO OBRIGATÓRIO:
- TEXTO SIMPLES. PROIBIDO LaTeX/$\\mathbf$/fórmulas matemáticas.
- Respostas CURTAS — até 8 linhas por turno.
- Lista numerada com até 3 hipóteses (nome PT-BR + 1 linha justificativa).
- NUNCA invente código ORPHA ou Portaria — apenas escreva o nome canônico em PT-BR.
- Máximo 2 emojis por resposta.
- Para perguntas de exames/encaminhamento/CEAF: responda direto.
DOENÇAS RARAS COMUNS NO BRASIL (priorizar):
Doença de Gaucher, Pompe, Fabry, Mucopolissacaridoses I/II/VI, Doença Falciforme,
AME 5q/tipo 1, Esclerose Múltipla, Hipertensão Pulmonar, Fibrose Cística, PKU,
Wilson, SCID, HPN, ELA, ESPECIALMENTE com sinais clássicos:
- Hepatoesplenomegalia + citopenias + Erlenmeyer = Gaucher
- Fácies grosseira + hepatosplenomegalia = MPS
- Hipotonia profunda bebê + arreflexia = AME tipo 1
- Surtos remitentes + Lhermitte = EM
- Crises álgicas + HbSS = Falciforme"""
@spaces.GPU(duration=90)
def araras_infer(case_text, history):
"""Main inference — case_text is the latest user turn; history is conversation history."""
if not case_text.strip():
return "", history, "", ""
llm = get_llm()
# Build messages from history + new
messages = [{"role": "system", "content": SYS}]
for h in history:
if isinstance(h, dict):
messages.append({"role": h["role"], "content": h["content"]})
messages.append({"role": "user", "content": case_text})
resp = llm.create_chat_completion(
messages=messages,
temperature=0.5, top_p=0.95, top_k=64, repeat_penalty=1.15, max_tokens=700,
)
raw = resp["choices"][0]["message"]["content"]
# Resolve ORPHAs
orphas = resolve(raw)
orpha_md = ""
sus_md = ""
if orphas:
orpha_md = "**🧬 Diagnósticos reconhecidos** (top-3):\n\n" + "\n".join(
f"- **{o['name_pt']}** (`{o['orpha']}`)" for o in orphas
)
top = orphas[0]
if top.get("pcdt_slug"):
pcdt_url = f"https://www.gov.br/conitec/pt-br/midias/protocolos/{top['pcdt_slug']}.pdf"
sus_md = (f"### 🇧🇷 Conduta SUS sugerida ({top['name_pt']})\n\n"
f"**📜 PCDT do MS**: [`{top['pcdt_slug']}`]({pcdt_url})\n\n"
f"**💊 CEAF dispensa**: {', '.join(top['ceaf_drugs'])}\n\n"
f"**🏥 Próximos passos**: solicitar exames confirmatórios; encaminhar ao centro de referência regional.")
# Append to history (Gradio chat format)
new_history = history + [
{"role": "user", "content": case_text},
{"role": "assistant", "content": raw},
]
return "", new_history, orpha_md, sus_md
# ─── Pre-loaded clinical examples ──────────────────────────────────────
EXAMPLES = [
"Lactente feminino, 4 meses, natural de Petrolina (PE), pais primos. Quadro de hipotonia profunda generalizada desde o nascimento, ausência de sustentação cefálica, choro fraco, dificuldade para mamar, e abolição dos reflexos osteotendíneos profundos. SpO2 95% em ar ambiente.",
"Adolescente masculino, 15 anos, da zona rural de Conceição do Coité (BA), pardo. Internado há 4 horas com dor torácica intensa e dispneia. História: triagem neonatal positiva HbSS, crises álgicas vaso-oclusivas frequentes desde os 5 anos. Hemograma: Hb 6.8, retic 12%, leuco 18k. Saturação 89%.",
"Mulher 28 anos, UBS Cruzeiro do Sul (AC). Fraqueza no braço esquerdo há 3 semanas + episódio prévio há 2 anos de diplopia que resolveu sozinha. Exame: paresia leve MSE, hiperreflexia, sinal de Lhermitte positivo. Nega febre.",
"Recém-nascido masculino, 12 dias de vida, Manaus (AM). Triagem neonatal (teste do pezinho) com fenilalanina sérica 18 mg/dL (normal <2). Pais consanguíneos, primeira filha. Sem sintomas no momento.",
"Menino de 8 anos, hepatoesplenomegalia progressiva, opacidade corneana bilateral, atraso cognitivo importante, fácies grosseira, irmã mais velha com mesmo quadro (falecida aos 12 anos).",
]
# ─── UI ────────────────────────────────────────────────────────────────
with gr.Blocks(title="Araras-Gemma4 — Copiloto Clínico de Doenças Raras", theme=gr.themes.Soft()) as demo:
gr.Markdown("""# 🇧🇷 Araras-Gemma4
### Copiloto offline de apoio à decisão clínica em doenças raras — para profissionais de saúde no Brasil
**Para médicos, enfermeiros, agentes comunitários** atendendo nas UBSs e ambulatórios do SUS.
NÃO é diagnóstico — é apoio à decisão.
🤖 Gemma 4 E4B (4.5B params, quantizado Q4) · 🇧🇷 PT-BR · 🛜 roda offline no celular · Apache 2.0
🔗 [Model](https://huggingface.co/Raras-AI/araras-gemma4-e4b-v4-gguf) · [Code](https://github.com/rarasAI/araras-gemma4) · [RareBench-BR](https://github.com/rarasAI/rarebench-br) · [raras.org](https://raras.org)
""")
with gr.Row():
with gr.Column(scale=3):
chatbot = gr.Chatbot(
label="Conversa com o(a) profissional de saúde",
type="messages",
height=500,
avatar_images=("👨‍⚕️", "🤖"),
)
msg = gr.Textbox(
label="Descreva o caso ou pergunte sobre conduta, exames, CEAF…",
placeholder="Ex: Lactente 4 meses com hipotonia profunda + arreflexia em UBS sem 4G…",
lines=3,
)
with gr.Row():
submit = gr.Button("Enviar", variant="primary", scale=2)
clear = gr.Button("Nova consulta", scale=1)
with gr.Column(scale=2):
orpha_out = gr.Markdown(label="ORPHAs reconhecidos")
sus_out = gr.Markdown(label="Conduta SUS")
gr.Examples(
examples=EXAMPLES,
inputs=msg,
label="📋 Casos de exemplo (clique pra testar)",
)
submit.click(araras_infer, inputs=[msg, chatbot], outputs=[msg, chatbot, orpha_out, sus_out])
msg.submit(araras_infer, inputs=[msg, chatbot], outputs=[msg, chatbot, orpha_out, sus_out])
clear.click(lambda: ("", [], "", ""), outputs=[msg, chatbot, orpha_out, sus_out])
gr.Markdown("""---
### ⚠️ Aviso clínico
Araras é **software de apoio à decisão clínica** (SaMD per ANVISA Res. 657/2022 e 830/2023), destinado a **profissionais de saúde habilitados**. Não realiza diagnósticos nem prescreve. Toda recomendação é informativa e ancorada em fontes públicas verificáveis (gov.br/conitec, bvsms, PubMed). O profissional de saúde é sempre quem decide.
### 🎯 Submissão
Gemma 4 Good Hackathon · Google DeepMind / Kaggle · Maio 2026 · Tracks: Main + Health & Sciences + Digital Equity + Ollama + llama.cpp
— Dimas, fundador da [Raras.org](https://raras.org), paciente raro (distonia mioclônica, ORPHA:36899)
""")
demo.queue().launch()