Spaces:
Sleeping
Sleeping
File size: 12,091 Bytes
98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 98da1ab a1c9731 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | """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()
|