DeepRetro 完整教學

本文目的:把 DeepRetro 的「為什麼要用 LLM 做逆合成」、「整個 pipeline 怎麼運作」、「怎麼跑起來然後接進你的 lead optimization 工作流」一次講清楚。重點放在架構理解、實務操作、幻覺防護機制、以及對你場景的適用性評估


1. 專案定位與動機

1.1 一句話總結

DeepRetro 是一個 開源的混合式逆合成規劃工具 (hybrid retrosynthesis planning tool),先讓 AiZynthFinder(template-based MCTS engine)嘗試,失敗時再讓 LLM(Claude / DeepSeek-R1)做 single-step disconnection,然後遞歸處理每個前驅物,直到所有分子都能從商業可購的 building block 合成。

1.2 為什麼需要 LLM 做逆合成

傳統逆合成工具(MCTS-based 如 AiZynthFinder, ASKCOS, Synthia)的局限:

傳統方法的問題DeepRetro 的解法
Template 覆蓋率有限:只能建議 training set 見過的反應類型LLM 見過整個化學文獻,能提出 novel disconnection
失敗就失敗:找不到路徑就回傳空結果AZ 失敗 → LLM 接手,不放棄任何分子
無法解釋推理過程LLM 回覆含 Chain-of-Thought,可以看到每步的推理依據
固定搜尋策略可以透過不同 prompt 版本(V1-V4)調整搜尋偏好
對新骨架無能為力LLM 的 generalization 能力可以處理 training set 之外的結構

1.3 與其他工具的定位差異

工具方法LLM 角色開源
AiZynthFinderTemplate-based MCTS
ASKCOSTemplate + NN
Synthia (Merck)Rules + MCTS
ChemCrowLLM agent + tool use主導但無 recursive
DeepRetroAZ 先行 + LLM fallback + recursive refinement後援 + recursive

DeepRetro 的獨特之處:不是讓 LLM 完全主導(那樣幻覺太多),而是讓 LLM 只在 template engine 失敗時介入,且每個建議都要過 heuristic + ML 雙重幻覺檢測。

1.4 對你的場景意味著什麼

你做 lead optimization,產出新的 analog(類似物)後需要:

  1. 確認 analog 可合成 — DeepRetro 可以快速給出合成路線
  2. 合成步驟不要太多 — AiZynthFinder 的 tree search 天生偏好短路線;max_depth 可調
  3. Building block 要可購買 — AZ 用 ZINC stock 做 in-stock 判斷

2. 安裝指南

2.1 兩種部署方式

模式場景複雜度
Docker(推薦)快速上手、不碰本機環境
本機開發需要改程式碼、debug、整合到管線

2.2 Docker 部署(5 分鐘)

 1# 1. Clone
 2git clone https://github.com/deepforestsci/DeepRetro.git
 3cd DeepRetro
 4
 5# 2. 設定環境變數
 6cp env.example .env
 7# 編輯 .env,填入你的 API key:
 8#   API_KEY=your-backend-api-key         # 用來驗證 API 呼叫
 9#   ANTHROPIC_API_KEY=sk-ant-xxx         # Claude API key
10#   FIREWORKS_API_KEY=xxx                # 如果要用 DeepSeek-R1

必填API_KEY(自訂,用於 X-API-KEY header)+ 至少一個 LLM API key。

1# 3. Build & Start(首次約 10-15 分鐘,會下載 USPTO 模型 ~1.5GB)
2docker-compose up -d --build
3
4# 4. 驗證
5curl -H "X-API-KEY: your-backend-api-key" http://localhost:5000/api/health
6# 預期回覆:{"status": "healthy"}

2.3 本機開發安裝

 1# 需要 Python 3.9 + Conda
 2conda env create -f environment.yml
 3conda activate deepretro
 4
 5# 下載 USPTO 模型
 6mkdir -p aizynthfinder/models
 7python -m aizynthfinder.tools.download_public_data aizynthfinder/models/
 8
 9# 設定 .env(同上)
10cp env.example .env
11# 編輯 .env
12
13# 啟動後端
14python src/api.py
15# 另一個 terminal 啟動前端
16cd viewer && python -m http.server 8000

2.4 環境變數完整清單

變數必要性說明
API_KEY必填後端 API 認證 key
ANTHROPIC_API_KEY必填(用 Claude 時)Anthropic API key
FIREWORKS_API_KEY選填DeepSeek-R1 via Fireworks
OPENAI_API_KEY選填OpenAI 模型
ENABLE_LOGGING選填True / False,控制 structlog
LANGFUSE_SECRET_KEY選填Langfuse LLM observability
AZ_MODELS_PATH選填AiZynthFinder 模型路徑

3. 核心架構深入解析

3.1 Pipeline 全景

 1Target SMILES
 2 3 4┌─────────────────────────┐
 5│  rec_run_prithvi()      │  ◄── 遞歸入口
 6│  ┌─────────────────────┐│
 7│  │ 1. is_basic_molecule?││ → 碳原子 < 5 → 直接標記 in_stock
 8│  │ 2. cycle detection   ││ → canonical SMILES 去重
 9│  │ 3. depth >= 50?      ││ → 防止無窮遞歸
10│  │ 4. run_az()          ││ → AiZynthFinder tree search
11│  │    ├─ solved → 回傳  ││
12│  │    └─ not solved ────┼┼─┐
13│  └─────────────────────┘│  │
14│                          │  │
15│  ┌─────────────────────┐│  │
16│  │ 5. llm_pipeline()   ││◄─┘
17│  │    ├─ call_LLM()    ││  → LiteLLM 呼叫(temperature=0.0 起)
18│  │    ├─ split_cot_json ││  → 解析 <cot>/<json> 結構
19│  │    ├─ validate_json  ││  → 提取 data/explanation/confidence
20│  │    ├─ validity_check ││  → RDKit SMILES 驗證
21│  │    ├─ stability_check││  → 化學穩定性(選填)
22│  │    └─ hallucination  ││  → 幻覺評分(選填)
23│  └─────────────────────┘│
24│                          │
25│  ┌─────────────────────┐│
26│  │ 6. 遞歸             ││  → 對每個 validated precursor
27│  │    rec_run_prithvi() ││    重新走步驟 1-6
28│  └─────────────────────┘│
29└─────────────────────────┘
303132┌─────────────────────────┐
33│ add_metadata()          │  → reagent_agent / conditions_agent / literature_agent
34└─────────────────────────┘
353637   JSON result(tree structure)

3.2 遞歸邏輯(rec_run_prithvi())的三個關鍵設計

Cycle detection:用 visited set + canonical SMILES 避免 A → B → A 的無窮迴圈。RDKit 先做 canonicalization 確保同一分子的不同 SMILES 寫法不會繞過 detection。

Depth limitingmax_depth=50,避免過深的路線。實務上,你可能想調到 5-10(大多數可行的合成路線在 5 步以內)。

First-success-break:LLM 可能回傳多條路徑(3-5 條),但遞歸只要找到一條全部 solved 的就 break。這是效率考量。

3.3 LLM 呼叫策略

llm_pipeline() 有一個內建的 retry 機制:

1run = 0.0
2max_run = 1.5 if (stability or hallucination) else 0.6
3while output_pathways == [] and run < max_run:
4    call_LLM(molecule, temperature=run)
5    run += 0.1
  • 從 temperature=0.0 開始(最確定的回覆)
  • 如果失敗,逐步提高 temperature 到 0.6 或 1.5
  • 每次呼叫都做 split → validate → check 的完整流程
  • DeepSeek 第一次失敗後 fallback 到 Claude Opus

3.4 AiZynthFinder 整合細節

  • Stock:使用 ZINC database 做 in-stock 判斷
  • Expansion policy:USPTO(免費)或 Pistachio(商業授權)
  • Filter policy:USPTO filter
  • 基本分子 bypassBASIC_MOLECULES list 含 80+ 常見試劑/溶劑(乙醇、醋酸、Grignard 等),這些直接標記 in_stock=True 不進 tree search

4. Prompt 工程:四個版本的演進

DeepRetro 的 prompt 設計是理解這個專案的核心。四個版本反映了 prompt engineering 在化學領域的最佳實踐演進。

4.1 V1(基礎版 SYS_PROMPT + USER_PROMPT)

  • System prompt 設定 “expert organic chemist specializing in retrosynthesis”
  • 要求 CoT reasoning 在 <thinking> tags 裡
  • 輸出格式:<cot> + <json> 分區
  • JSON 結構:data(SMILES list of lists) + explanation + confidence_scores

4.2 V4(進階版 SYS_PROMPT_V4)

比 V1 多了:

  1. Initial validation:要求 LLM 先驗證 SMILES 有效性
  2. 四階段結構化分析
    • structural_decomposition → 骨架、官能基、立體中心
    • disconnection_analysis → 策略性斷鍵、transforms、stereochemical strategy
    • practical_evaluation → 商業可得性、反應條件、製程考量
    • proposal_refinement → 排序、文獻驗證、failure mode
  3. 每階段都有 “wait” 反思步驟:強制 LLM 回顧自己的分析
  4. Edge case handling:複雜分子、簡單分子、特殊結構的處理指南
  5. Quality checks:SMILES 有效性、解釋完整性、分數校準
  6. Confidence score 公式:feasibility 33% + implementation 33% + strategic value 34%

4.3 DeepSeek 專用 prompt

  • 使用 <think> 而非 <cot> tags(符合 DeepSeek-R1 原生格式)
  • max_completion_tokens 加倍到 16384
  • V4 版本不用 system prompt(DeepSeek 風格),全部塞進 user prompt

4.4 7-Member Ring Addon

偵測到 7 元環時自動注入範例:

1Examples of some 7-membered rings retrosynthesis:
21. ClC(C(OC)=C1)=CC2=C1[C@@H]3... when broken down gives ...

這解決了 LLM 對非典型環系統容易幻覺的問題。


5. 幻覺防護機制(Hallucination Guard)

這是 DeepRetro 最有價值的技術貢獻。LLM 做逆合成最大的問題就是「看起來合理但化學上不可能」的幻覺。

5.1 Heuristic Checker(hallucination_checker.py)

核心函數 calculate_hallucination_score(reactant_smiles, product_smiles) 做 5 項檢查:

檢查項目扣分規則原理
Atom count mismatch每個 mismatch 的原子 -5 分(上限 100)反應不能無中生有原子
Ring size change每個環變化 -25 分(上限 50)環系統不應隨意增減
Substituent position swap每次 swap -60 分(上限 100)LLM 常把 ortho/meta/para 搞混
Aromaticity shift-40 分芳香性不應大幅改變
Unnecessary bond formation每條多餘鍵 -5 分(上限 30)反應物不該比產物多鍵

Score 100 → 0 分制:

  • = 80:low severity → 保留

  • 40-79:medium severity → 保留
  • 20-39:high severity → 過濾掉
  • < 20:critical severity → 過濾掉

5.2 ML Classifier(hallucination_classifier.py)

  • 預訓練模型(model_out/model.joblib,scikit-learn)
  • 使用 domain features 做二元分類
  • 與 heuristic checker 互補

5.3 實務意義

對你做 lead optimization analog 的逆合成:

  • 不需要手動 review 每條 LLM 建議的路線
  • 明顯不合理的(substituent 換位、原子數不對)自動過濾
  • 保留 severity low/medium 的路線給化學家做最終判斷

6. Web GUI 互動操作

6.1 啟動

1# Docker 用戶:已自動啟動
2# 本機開發:
3cd viewer && python -m http.server 8000
4# 瀏覽器開 http://localhost:8000

6.2 主要功能

Smart Retrosynthesis

  1. 在 SMILES 欄位輸入目標分子
  2. 選擇 LLM 模型(Claude 4 Sonnet / Claude 3.7 / DeepSeek-R1)
  3. 勾選 Advanced Prompt / Stability Check / Hallucination Check
  4. 點 “Analyze”
  5. 結果以 D3.js tree 呈現,每個節點是一個分子

Partial Rerun(核心功能)

  • 對 tree 中任一步驟不滿意 → 點擊該步驟 → 選擇 “Rerun from here”
  • 系統會保留左側(upstream)的路徑,只重新規劃右側(downstream)
  • 使用者可以指定不同的 LLM 或設定

File Management

  • 上傳 / 下載 JSON 格式的路徑結果
  • 可以儲存 → 修改 → 重新載入 → partial rerun

6.3 Advanced Settings

 1{
 2  "llm_models": {
 3    "claude4": {
 4      "internal_name": "claude-4-sonnet-20250514",
 5      "display_name": "Claude 4 Sonnet",
 6      "supports_advanced_prompt": true,
 7      "supports_stability_check": true,
 8      "supports_hallucination_check": true
 9    }
10  },
11  "az_models": {
12    "USPTO": { "display_name": "USPTO (Free)", "requires_permissions": false }
13  }
14}

7. API 使用指南

7.1 基本呼叫

 1# 完整逆合成
 2curl -X POST http://localhost:5000/api/retrosynthesis \
 3  -H "Content-Type: application/json" \
 4  -H "X-API-KEY: your-api-key" \
 5  -d '{
 6    "smiles": "CC(C)(C)OC(=O)N[C@@H](CC1=CC=CC=C1)C(=O)O",
 7    "model_type": "claude4",
 8    "advanced_prompt": true,
 9    "model_version": "USPTO",
10    "stability_flag": true,
11    "hallucination_check": true
12  }'

7.2 回傳結構

 1{
 2  "steps": [
 3    {
 4      "step": "1",
 5      "reactants": ["SMILES1", "SMILES2"],
 6      "products": [{"smiles": "target_SMILES"}],
 7      "reagents": ["SMILES_reagent"],
 8      "conditions": {"temperature": "25C", "solvent": "THF"},
 9      "reactionmetrics": [{"closestliterature": "..."}]
10    }
11  ],
12  "dependencies": {"1": ["2", "3"]}
13}

7.3 Partial Rerun

1curl -X POST http://localhost:5000/api/partial_rerun \
2  -H "Content-Type: application/json" \
3  -H "X-API-KEY: your-api-key" \
4  -d '{
5    "smiles": "original_target",
6    "steps": 3,
7    "model_type": "claude4"
8  }'

系統會:

  1. partial.json 讀取上次結果
  2. 找到 step 3 及其所有 downstream steps
  3. 移除這些 steps
  4. 對 step 3 的 product 重新做 retrosynthesis
  5. 重新編號 + 接回原本的 upstream steps

8. 與你的 Lead Optimization 管線整合

8.1 典型工作流

 1你的 Lead Optimization Pipeline
 2 3 4 新 analog SMILES 列表
 5 6 7 ┌────────────────────────┐
 8 │ DeepRetro batch script │
 9 │ for smiles in list:    │
10 │   POST /retrosynthesis │
11 │   結果存 JSON          │
12 └────────────────────────┘
131415 篩選條件:
16 ├── 合成步驟 <= 5
17 ├── 所有 leaf 分子在 stock
18 └── hallucination score >= 80
192021 化學家 review(GUI partial rerun)
222324 選定合成路線 → 實驗室

8.2 Python 整合範例

 1import requests, json
 2
 3API_URL = "http://localhost:5000/api/retrosynthesis"
 4API_KEY = "your-api-key"
 5
 6def analyze_analog(smiles: str) -> dict:
 7    """對單一 analog 做逆合成分析"""
 8    resp = requests.post(API_URL, json={
 9        "smiles": smiles,
10        "model_type": "claude4",
11        "advanced_prompt": True,
12        "model_version": "USPTO",
13        "stability_flag": True,
14        "hallucination_check": True,
15    }, headers={"X-API-KEY": API_KEY})
16    return resp.json()
17
18def count_steps(result: dict) -> int:
19    """計算合成步驟數"""
20    return len(result.get("steps", []))
21
22def all_in_stock(result: dict) -> bool:
23    """檢查所有末端分子是否在 stock"""
24    # 找出 dependency tree 中的 leaf nodes
25    deps = result.get("dependencies", {})
26    all_steps = {s["step"] for s in result.get("steps", [])}
27    downstream = set()
28    for v_list in deps.values():
29        downstream.update(v_list)
30    leaf_steps = all_steps - set(deps.keys())
31    # leaf steps 的 reactants 就是 building blocks
32    return True  # 需要對接 stock API 做實際確認
33
34# 批次分析
35analogs = ["CCO", "CC(=O)Oc1ccccc1C(=O)O", "..."]
36for smiles in analogs:
37    result = analyze_analog(smiles)
38    steps = count_steps(result)
39    print(f"{smiles}: {steps} steps")

8.3 Building Block 可購性的限制與解法

DeepRetro 使用 ZINC stock 判斷 in-stock,但 ZINC 是一個學術資料庫,不等於「你的供應商明天就能出貨」。

現實中的補充方案

供應商資料庫API特色
eMoleculesREST API聚合多家供應商,含價格
MolPortREST API大量 in-stock building blocks
Enamine REAL下載 / API2.5B+ 分子可合成
Sigma-Aldrich手動查詢最常見試劑

你可以在 DeepRetro 的 is_basic_molecule() 和 stock 判斷後面加一層:

1# 在 format_output() 後加
2for step in result["steps"]:
3    for reactant in step["reactants"]:
4        # 對接你的供應商 API
5        availability = check_supplier(reactant)
6        step["supplier_availability"] = availability

9. Protecting Group 處理

9.1 機制

DeepRetro 有一個精巧的 protecting group detection + masking 機制:

1PG_MAP = {
2    "OMe": ("OC", "$"),       # Methoxy → $
3    "OBn": ("COCc1ccccc1", "%"),  # Benzyl ether → %
4    "OEt": ("COC", "&"),      # Ethoxy → &
5}

啟用 use_protecting_group_feature=True 後:

  1. 偵測 SMILES 中的 OMe/OBn/OEt patterns
  2. 替換為 $/%/& 符號
  3. 在 prompt 中注入 PROTECTING_GROUP_CONTEXT
  4. LLM 會在建議中考慮去保護步驟

9.2 對 lead optimization 的意義

你的 analog 如果有保護基(做 selective modification 時常見),這個功能會讓 LLM:

  • 建議正確的去保護條件(HCl/MeOH for OMe, H2/Pd-C for OBn)
  • 考慮正交去保護策略(orthogonal deprotection)
  • 不會把保護基當作要合成的 target

10. 進階配置與調校

10.1 控制合成步驟數

修改 rec_prithvi.py 中的 max_depth

1# 預設 50 步——太多了,實務上改成 5-10
2res, solved = rec_run_prithvi(
3    molecule=smiles,
4    max_depth=8,  # 限制 8 步以內
5    ...
6)

10.2 模型選擇策略

場景推薦模型理由
最高品質Claude 4 Sonnet + Advanced Prompt最好的 CoT 結構化分析
預算有限DeepSeek-R1 (Fireworks)成本較低但品質堪用
快速初篩Claude 4 Sonnet(無 Advanced Prompt)較短 prompt = 較低延遲
困難分子Claude 4 + Advanced Prompt + Stability + Hallucination全部開啟,retry 次數多

10.3 Langfuse 觀測

設定 LANGFUSE_* 環境變數後,所有 LLM 呼叫自動追蹤:

  • Token 用量
  • 延遲
  • CoT 內容
  • 成功/失敗率

對管線化使用非常有用——可以看哪些分子類型容易導致 LLM 失敗。

10.4 Caching 機制

@cache_results decorator 做了兩件事:

  1. 相同 SMILES + 相同模型 → 不重複呼叫 LLM
  2. Cache 存在 cache_api/ 目錄

注意:如果你改了 prompt 或模型設定,記得用 API 清除 cache:

1curl -X POST http://localhost:5000/api/clear_molecule_cache \
2  -H "X-API-KEY: your-key" \
3  -d '{"molecule": "SMILES_STRING"}'

11. 限制、風險與展望

11.1 已知限制

限制影響Workaround
ZINC stock 不等於現實庫存leaf 分子可能買不到對接 eMolecules / MolPort API
USPTO 模型覆蓋率只含 ~50K 反應,對新反應類型無能為力使用 Pistachio(需商業授權)
LLM 成本Claude Opus 每次呼叫約 $0.05-0.15用 cache、降低 max_depth、改用 DeepSeek
不支援多步 one-pot每步都是獨立反應後處理合併
Python 3.9 綁定Conda environment.yml 指定 3.9容器化避開
無 batch API每次只分析一個分子自己包一個 for loop
Hallucination checker 有盲點Heuristic 規則無法涵蓋所有化學知識ML classifier 補充

11.2 資安考量

  • ast.literal_eval() 解析 LLM 回覆 — 比 eval() 安全但仍有邊界 case
  • pickle cache — 本地使用無風險,但不要用網路接收的 cache 檔
  • CORS 全開 — production 部署必須限縮
  • API Key 認證 — 只是 simple token,production 建議加 JWT / OAuth

11.3 未來發展方向(從 commit history 推測)

  • Hallucination ML classifier 持續強化(近期 5+ commits)
  • Pipeline checks 模組化(deepretro/algorithms/pipeline_checks.py
  • deepretro/ package 重構:更 Pythonic 的 API、完整的 type hints
  • 可能增加 reaction classification 功能(reaction_prediction/

11.4 與 DeepRetro 論文的對應

論文標題:DeepRetro: Retrosynthetic Pathway Discovery using Iterative LLM Reasoning(arXiv:2507.07060)

引文 BibTeX:

 1@misc{sathyanarayana2025deepretro,
 2  title={DeepRetro: Retrosynthetic Pathway Discovery using Iterative LLM Reasoning},
 3  author={Shreyas V. Sathyanarayana and Rahil Shah and Sharanabasava D. Hiremath
 4          and Rishikesh Panda and Rahul Jana and Riya Singh
 5          and Rida Irfan and Ashwin Murali and Bharath Ramsundar},
 6  year={2025},
 7  eprint={2507.07060},
 8  archivePrefix={arXiv},
 9  primaryClass={q-bio.QM}
10}