Clinical Variant Annotation Agent:用 NAT 並行 ClinVar + gnomAD,三模型比較
TL;DR:一個 variant → 兩個 API 同時打(ClinVar + gnomAD)→ LLM 解讀 → 結構化 ACMG 報告。
MedGemma 直接推論(無 API):9/9 100%;MedGemma via NAT pipeline(有 ClinVar 資料):5/9 56%;gemma4:e4b:5/9 56%。加了真實 API 資料反而拉低了—— ClinVar conflicting evidence 把模型搞混了。
ClinVar 查詢不能靠訓練記憑:API 版本落差與幻覺風險
NeMo Agent Toolkit(NAT)的官方 examples 全是 NLP 場景(RAG、SQL、PII)。生物資訊領域幾乎空白。
本文提交的 nat_clinical_variant_agent 是 NAT ecosystem 第一個 bioinformatics example,同時也是第一個整合 MedGemma 的端對端 notebook。
變異解讀的痛點:
- 臨床遺傳師每天要查 ClinVar、gnomAD、OMIM,手動切換表格
- 每個查詢串行執行:ClinVar ~900 ms + gnomAD ~270 ms = ~1170 ms/變異
- LLM 輔助要「看過」最新 API 結果,不能只靠訓練記憶
NAT 的 parallel_executor 恰好解決前兩點,MedGemma 解決第三點。
並行查詢架構:ClinVar + gnomAD 同時打,節省 270ms/變異
VCF / 單一 variant (gene + HGVS c. notation)
│
▼
┌──────────────────────────────────┐
│ NAT parallel_executor │
│ ┌─────────────────────┐ │
│ │ ClinVar E-utils │ ~900ms │
│ │ esearch + esummary │ │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ gnomAD v4 GraphQL │ ~270ms │
│ └─────────────────────┘ │
│ bottleneck → ~900 ms (ClinVar) │
└──────────────────────────────────┘
│ (兩個 API 結果合併)
▼
MedGemma 4B-it (RTX 3090, 8.01 GB BF16)
• 整合 ClinVar 顯著性 + gnomAD AF
• 輸出:Pathogenic / LP / VUS / LB / Benign
• ACMG evidence codes (⚠️ 需驗證,見 Pitfall #3)
• 23.7 tok/s, ~43s/1024 tokens
│
▼
JSON 結構化報告 (+ 臨床建議)
NAT workflow YAML(workflow_annotate.yml):
workflow:
type: parallel_executor
steps:
- function: clinvar_annotate # 非同步 httpx + esearch/esummary
input_keys: {gene: gene, hgvs: hgvs}
output_key: clinvar_result
- function: gnomad_annotate # GraphQL POST
input_keys: {gene: gene, hgvs: hgvs}
output_key: gnomad_result
全管道(workflow_interpret.yml)再加一個 sequential step:
workflow:
type: sequential_executor
steps:
- function: clinvar_annotate
- function: gnomad_annotate
- function: medgemma_interpret
input_keys:
gene: gene
hgvs: hgvs
consequence: consequence
clinvar_result: clinvar_result
gnomad_result: gnomad_result
9-Variant Benchmark 設計
Ground truth 來自之前的 MedGemma 4B-it GPU benchmark(9 個臨床複雜案例,已由醫學遺傳師確認)。那次測試(2026-03-10)是用 HuggingFace Transformers 直接呼叫 MedGemma,prompt 手動構建,沒有 ClinVar/gnomAD API;本篇補測(2026-04-15,batch_c5_nat_medgemma_benchmark.py)才是完整 NAT pipeline 的正式對比,結果見下表。
| ID | Gene | Consequence | Expected |
|---|---|---|---|
| TTN_truncating | TTN | stop_gained (A-band) | Likely Pathogenic |
| TTN_missense | TTN | missense (I-band) | Likely Benign |
| BRCA1_VUS | BRCA1 | missense (BRCT domain) | VUS/LP (debated) |
| MYH7_HCM | MYH7 | missense (myosin head hotspot) | Pathogenic |
| SCN1A_Dravet | SCN1A | missense (de novo) | Likely Pathogenic |
| TP53_germline | TP53 | missense (R273H, hotspot) | Pathogenic |
| RYR1_compound_het | RYR1 | compound het missense | Likely Pathogenic |
| LMNA_DCM | LMNA | stop_gained | Pathogenic |
| VHL_type2 | VHL | missense (pheochromocytoma) | Pathogenic |
設計難點:TTN 同一基因「截斷 = LP,錯義 = LB」、VHL「錯義→嗜鉻細胞瘤,截斷→腎細胞癌」,這些需要 domain knowledge 才能正確解讀。
實測結果:三個條件的比較
Step 1:並行 API 標注(9 案例)
| 變異 | ClinVar | gnomAD | 並行耗時 |
|---|---|---|---|
| TTN c.48744C>A | found | 基因在,確切變體缺失 | 2063 ms |
| TTN c.32712A>G | found | found (AF=?) | 2038 ms |
| BRCA1 c.5096G>A | found | found | 2011 ms |
| MYH7 c.1208G>A | found | found | 3806 ms |
| SCN1A c.2837T>C | found | 基因在,確切變體缺失 | 2407 ms |
| TP53 c.818G>A | found | found | 1935 ms |
| RYR1 c.14843G>A | found | found | 11957 ms ⚠️ |
| LMNA c.673C>T | found | found | 2134 ms |
| VHL c.499C>T | found | found | 2289 ms |
平均並行標注:3404 ms(ClinVar 為瓶頸,gnomAD 在 ClinVar 回應前已就緒)
RYR1 outlier 11957 ms:gnomAD 對大基因組(RYR1 ~364 kb)首次查詢有 cold start。
Step 2:LLM 解讀比較
| ID | Expected | gemma4:e4b | MedGemma-NAT ← 補測 | MedGemma 直接 (2026-03-10) | gemma4 ✓ | MedGemma-NAT ✓ |
|---|---|---|---|---|---|---|
| TTN_truncating | Likely Pathogenic | Benign | Likely Pathogenic | Pathogenic | ❌ | ✅ |
| TTN_missense | Likely Benign | Benign | Likely Pathogenic | Likely Benign | ✅ | ❌ |
| BRCA1_VUS | VUS/LP (debated) | Benign | Likely Pathogenic | Likely Pathogenic | ❌ | ✅ |
| MYH7_HCM | Pathogenic | Pathogenic | Likely Benign | Pathogenic | ✅ | ❌ |
| SCN1A_Dravet | Likely Pathogenic | Likely Pathogenic | Uncertain Significance | Pathogenic | ✅ | ❌ |
| TP53_germline | Pathogenic | Likely Benign | Likely Pathogenic | Likely Pathogenic | ❌ | ✅ |
| RYR1_compound_het | Likely Pathogenic | Pathogenic | Likely Pathogenic | Likely Pathogenic | ✅ | ✅ |
| LMNA_DCM | Pathogenic | Likely Pathogenic | Likely Pathogenic | Highly likely pathogenic | ✅ | ✅ |
| VHL_type2 | Pathogenic | Uncertain Significance | Likely Benign | Pathogenic | ❌ | ❌ |
gemma4:e4b:5/9(56%)
MedGemma 4B-it via NAT pipeline(補測,2026-04-15):5/9(56%)
MedGemma 4B-it 直接推論(2026-03-10,無 API 資料):9/9(100%)
意外發現:加了 ClinVar 真實資料後,MedGemma 的準確率從 100% 跌到 56%,跟 gemma4:e4b 打平。MYH7_HCM 是最明顯的案例——ClinVar 收錄了多筆 conflicting interpretations(Pathogenic/VUS 混雜),MedGemma 整合後反而給出 Likely Benign。這說明 API 資料的品質和 LLM 的資料整合能力同樣重要。
整體 timing
並行 API 標注(9 案例): avg 3,404 ms/variant
gemma4:e4b 解讀: avg ~17.9 s/variant
全流程(9 案例 gemma4 total): 195.3 s
--- 補測(2026-04-15,`batch_c5_nat_medgemma_benchmark.py`) ---
MedGemma via NAT pipeline: avg 21.0 tok/s
平均單案例推論: avg ~67.3 s/variant
全流程(9 案例 MedGemma-NAT total): 597.7 s
5 個踩坑紀錄:API Breaking Change、gnomAD 欄位消失、prompt 格式
Pitfall #1:gnomAD GraphQL 大基因 cold start
TTN(363,655 bp)和 RYR1(364,289 bp)是人類基因組最大的基因,gnomAD 第一次查詢要 fetch 數萬個 variants 回來。測到 11957 ms(RYR1)。
解法:用 variant_id 直查(需解析 GRCh38 位置),或在第一個案例後 warm up。
# 慢查(下載全基因 variants):
query GeneVariants($geneSymbol: String!) {
gene(gene_symbol: $geneSymbol) { variants { hgvsc ... } }
}
# ✅ 快查(直接 variant ID):
query VariantSearch($variantId: String!) {
variant(dataset: gnomad_r4, variantId: $variantId) {
exome { ac af an } genome { ac af an }
}
}
Pitfall #2:gemma4 thinking mode 讓 content 全空
gemma4:e4b 預設思考模式(thinking tokens)。Ollama OpenAI-compatible API 回傳:
{
"message": {
"content": "", // 空!
"reasoning": "思考過程..." // 在這裡
}
}
若 max_tokens < 2000,thinking 耗盡所有配額,content 為空,解讀失敗。
# ❌ 錯誤:350 tokens 全被 reasoning 吃掉
payload = {"max_tokens": 350, ...}
# ✅ 正確:確保 content 有足夠空間
payload = {"max_tokens": 2000, ...}
raw = msg.get("content") or msg.get("reasoning") or ""
(同 blog post 1 發現的 Gemma 4 thinking bug,見 gap analysis notes)
Pitfall #3:ACMG criterion codes 幻覺(MedGemma 也中招)
MedGemma 4B-it 的「方向」(P/LP/VUS/LB/B)9/9 全對,但 ACMG 具體準則碼有幻覺:
- 編造了
PP6(ACMG/AMP 沒有 PP6,只到 PP5) - 編造了
PM2-A、PM2-B(正式碼沒有這種細分) -
PVS1_Strong是社群擴充符號,非正式 ACMG 碼
解法:在 Prompt 尾端加 ⚠️ Only use ACMG/AMP 2015 official criteria: PVS1, PS1–PS4, PM1–PM6, PP1–PP5, BA1, BS1–BS4, BP1–BP7.,並在 API 層用 InterVar 或 SpliceAI 做 double-check。
Pitfall #4:ClinVar esearch HGVS 需精確比對
ClinVar esearch 接受 hgvs[variant name],但 HGVS notation 若有細微差異就找不到:
# ❌ 找不到(transcript 版本號不符):
"NM_007294.3:c.5096G>A[variant name]" # ClinVar 收錄的是 NM_007294.4
# ✅ 策略:也搜尋 gene name fallback
if not ids:
ids = search_gene_fallback(gene) # 至少知道基因有多少 entries
目前的 annotation_functions.py 已實作 fallback,找不到精確 HGVS 時退而搜尋 gene name。
Pitfall #5:API 資料對模型導入 conflicting evidence 後的判斷干擾
VHL p.Arg167Trp(missense)正確分類是 Pathogenic(Type 2,嗜鉻細胞瘤)。
gemma4:e4b 回傳:Uncertain Significance(補測剛好這次是 US,不同 run 結果略有浮動)。
MedGemma-NAT 回傳:Likely Benign——跟 gemma4 一樣錯。
瀏覽 raw output:ClinVar 對 VHL c.499C>T 收錄了多筆 conflicting interpretations(主要是 Pathogenic 但有少數 VUS submission)。MedGemma 看到混雜資料後,套用「missense 通常比 truncating 危險性低」的直覺,給出 Likely Benign,而非進一步查 VHL genotype-phenotype specificity。
前次純直接推論(無 ClinVar 資料)時,MedGemma 用訓練記憶直接答 Pathogenic,因為訓練資料裡 VHL Type 2 missense 的結論夠強,能覆蓋雜訊。但加入 ClinVar conflicting evidence 後,資訊反而干擾了判斷。
教訓:不是「提供資料 = 更好」,而是「提供高品質 API 資料 = 更好」。
ClinVar 幾筆 VUS submission 就能把騎士級的 P/LP expert consensus 覆蓋。完善方向:加入「少數服從多數」策略(多筆 P/LP submission 趨勢暗示有 consensus),而非直接把所有 ClinVar 記錄雜項塞入 prompt。
失敗案例分析
gemma4:e4b 4 個錯誤案例:
| 案例 | 錯誤方向 | 根因 |
|---|---|---|
| TTN_truncating | B(應為LP) | TTN A-band truncating = DCM,常見誤解 |
| BRCA1_VUS | B(應為VUS/LP) | conflicting ClinVar 證據整合失敗 |
| TP53_germline | LB(應為P) | R273H 在胚系 vs 體系的臨床含義混淆 |
| VHL_type2 | US(應為P) | conflicting ClinVar 覆蓋了 genotype-phenotype 知識 |
MedGemma-NAT 4 個錯誤案例:
| 案例 | 錯誤方向 | 根因 |
|---|---|---|
| TTN_missense | LP(應為LB) | gnomAD found(AF 低)+ ClinVar found,兩者沒有幫助 missense 方向 |
| MYH7_HCM | LB(應為P) | ClinVar 含 conflicting interpretations,MedGemma 做錯整合 |
| SCN1A_Dravet | VUS(應為LP) | 前一個錯誤可能有 context 干擾;ClinVar 資料繁雜 |
| VHL_type2 | LB(應為P) | 同 gemma4,conflicting evidence 蓋過 Type 2 missense 知識 |
共同失敗點:當 ClinVar 有 conflicting interpretations 時,兩個模型都容易被雜訊誤導。 差別在於 gemma4 也缺乏 domain knowledge;MedGemma 有 domain knowledge 但在 API 雜訊面前同樣脆弱。
代碼架構
nat_clinical_variant_agent/
├── src/nat_clinical_variant_agent/
│ ├── annotation_functions.py # ClinVar + gnomAD NAT functions
│ └── medgemma_functions.py # MedGemma 4B-it NAT function
├── annotate.py # CLI(single variant + batch VCF)
├── run_benchmark.py # 本文 benchmark harness
├── ground_truth.json # 9 案例 ground truth
├── workflow_annotate.yml # parallel_executor (API only)
├── workflow_interpret.yml # sequential_executor (full pipeline)
└── pyproject.toml # `nvidia-nat[langchain]>=1.5.0`
核心 API 呼叫(簡化)
# annotation_functions.py — 並行標注
clinvar_cfg = ClinVarConfig()
gnomad_cfg = GnomADConfig()
clinvar_result, gnomad_result = await asyncio.gather(
clinvar_annotate(gene, hgvs, clinvar_cfg),
gnomad_annotate(gene, hgvs, gnomad_cfg),
)
# 瓶頸 = ClinVar ~900 ms,gnomAD 在等待中就完成了
# medgemma_functions.py — MedGemma 解讀
@register_function("medgemma_interpret", config_class=MedGemmaConfig)
async def medgemma_interpret(gene, hgvs, consequence,
clinvar_result, gnomad_result, config):
model, processor = _load_model(config) # singleton, 只載入一次
prompt = _build_prompt(gene, hgvs, consequence, clinvar_result, gnomad_result)
...
return {"interpretation": text, "tok_per_sec": 23.7, ...}
CLI 使用
# 安裝(需 HuggingFace token for MedGemma gated model)
pip install -e ".[dev]"
export HF_TOKEN=hf_...
# 單一變異(全管道)
python annotate.py \
--gene BRCA1 \
--hgvs "NM_007294.4:c.5266dup" \
--consequence frameshift_variant
# 僅 API 標注(不载 MedGemma)
python annotate.py --gene MYH7 \
--hgvs "NM_000257.4:c.1208G>A" --no-interpret
# batch VCF(VEP 標注格式)
python annotate.py --vcf variants.vcf --output report.json
# benchmark(9 ground truth 案例)
python run_benchmark.py
與 Blog 1(PII-Aware RAG)的架構對比
| Blog 1: PII-Aware RAG | Blog 2: Variant Annotation | |
|---|---|---|
| NAT workflow | sequential_executor | parallel_executor → sequential |
| 自訂 function | pii_detect / pii_redact / doc_ingest / rag_search | clinvar_annotate / gnomad_annotate / medgemma_interpret |
| LLM | NIM Llama-3.3-70B (cloud) | MedGemma 4B-it (local GPU) |
| 延遲 | 304 ms (RAG query) | ~900 ms (parallel API) + ~43s (MedGemma) |
| LLM accuracy | 不適用(retrieval精確度) | 9/9 100% (pathogenicity direction) |
| PR target | NeMo-Agent-Toolkit-Examples | NeMo-Agent-Toolkit-Examples |
延伸:加入 OpenCRAVAT / InterVar 雙重驗證
目前架構對 ACMG criterion codes 的 hallucination 問題,建議加入第三步驗證層:
# 擴充 workflow_interpret.yml
steps:
- function: clinvar_annotate # 並行
- function: gnomad_annotate # 並行
- function: medgemma_interpret # MedGemma 解讀
- function: intervar_validate # TODO: InterVar REST API 驗證 criterion codes
input_keys:
gene: gene
hgvs: hgvs
medgemma_criteria: interpretation.criteria_codes
InterVar REST API 目前無公開官方端點,但 ClinGen ACMG Calculator 和 SpliceAI 各有 web API 可整合。
總結
| 指標 | 數值 |
|---|---|
| MedGemma 4B 直接推論(無 API) | 9/9 (100%) 2026-03-10 |
| MedGemma 4B via NAT pipeline(有 ClinVar) | 5/9 (56%) 2026-04-15 補測 |
| gemma4:e4b (general LLM) accuracy | 5/9 (56%) |
| 並行 API 標注延遲 | avg 3.4 s(ClinVar 瓶頸) |
| 無並行時的理論延遲 | avg 3.4 s + 0.27 s = ~3.7 s(節省 ~270 ms/variant) |
| MedGemma VRAM | 8.01 GB BF16(RTX 3090) |
| MedGemma 推論速度 | avg 21.0 tok/s |
| ACMG codes hallucination | ⚠️ 需 InterVar 驗證 |
| 程式碼 | nat_clinical_variant_agent/ |
關鍵洞察:
MedGemma 純直接推論 100% vs gemma4:e4b 56%——差距來自醫學域訓練,不是模型大小。但加入 ClinVar 真實 API 資料後,MedGemma 降到 56%,與通用模型持平。原因:ClinVar 的 conflicting submissions 蓋過了模型的 domain knowledge。不是資料越多越好,是高品質資料才有幫助。
臨床應用建議:variant interpretation pipeline 請用 MedGemma 等醫學專用模型,同時對 ClinVar 多方提交做「主流意見加權」,而非原始餵入所有 submissions。
參考資料
- MedGemma 4B-it (Google)
- NeMo Agent Toolkit
- NCBI ClinVar E-utilities
- gnomAD GraphQL API
- ACMG/AMP 2015 Variant Classification Guidelines
- 本文 code:
nat_clinical_variant_agent/ - 前篇:MedGemma 變異解讀實測
- 前篇:PII-Aware RAG with NAT + Piiranha
Top comments (0)