DEV Community

JH5
JH5

Posted on

NeMo Agent Toolkit + MedGemma:如何批次處理 VUS、快速產出 ACMG 建議

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_agentNAT 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 結構化報告 (+ 臨床建議)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

全管道(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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

NAT clinical variant annotation — parallel ClinVar+gnomAD, MedGemma 100% vs gemma4 56%

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 }
  }
}
Enter fullscreen mode Exit fullscreen mode

Pitfall #2:gemma4 thinking mode 讓 content 全空

gemma4:e4b 預設思考模式(thinking tokens)。Ollama OpenAI-compatible API 回傳:

{
  "message": {
    "content": "",           // 空!
    "reasoning": "思考過程..."  // 在這裡
  }
}
Enter fullscreen mode Exit fullscreen mode

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 ""
Enter fullscreen mode Exit fullscreen mode

(同 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-APM2-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
Enter fullscreen mode Exit fullscreen mode

目前的 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`
Enter fullscreen mode Exit fullscreen mode

核心 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, ...}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

與 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
Enter fullscreen mode Exit fullscreen mode

InterVar REST API 目前無公開官方端點,但 ClinGen ACMG CalculatorSpliceAI 各有 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。


參考資料

Top comments (0)