大语言模型在药物发现中的位置容易被误解。它的合理定位是「编排者与信息处理者」——把工具串起来、把文献读进来、把结果整理出去;而不是「预测器」——直接回答分子的活性或性质。
LLM 能做与不能做
| 任务 | 适合度 | 说明 |
|---|---|---|
| 文献检索与综述 | 很适合 | 需要人工核对引用 |
| 从文献/专利抽取结构化信息 | 很适合 | 如提取活性数据表 |
| 编排计算工具链 | 很适合 | 调用 RDKit、对接、模型 |
| 写代码与数据分析 | 很适合 | 加速日常工作 |
| 生成实验方案草稿 | 可以 | 必须专家审核 |
| 解释计算结果 | 可以 | 需要提供准确的上下文 |
| 预测分子活性 | 不适合 | 应该调用专门模型 |
| 预测 ADMET 性质 | 不适合 | 同上 |
| 生成有效的 SMILES | 不可靠 | 常生成价键错误的结构 |
| 判断分子相似性 | 不适合 | 应该算指纹 |
| 作为事实来源 | 不适合 | 会编造文献与数据 |
为什么不该让 LLM 直接预测分子性质
# 原因一:【它没有见过足够的定量数据】
# 训练语料中的分子数据大多是文本描述,
# 而非结构化的活性测定值
#
# 原因二:【无法处理精确的结构差异】
# 一个甲基的位置变化可能让活性差 100 倍
# 而 LLM 对 SMILES 的「理解」是 token 级的
#
# 原因三:【会自信地编造】
# 问「这个分子的 logP 是多少」
# → 它会给出一个看似合理的数字
# → 但这个数字没有依据
# → 【比不回答更危险】
#
# 原因四:【有更好的工具】
# logP: RDKit Crippen 计算,秒级,有明确定义
# 活性: 专门训练的 QSAR 模型
# 相似性: 指纹 + Tanimoto
# → 【用确定性工具替代概率性猜测】
#
# 【正确做法】:
# 让 LLM 【调用】这些工具,而非【模仿】它们
# 反例(不要这样做):
# prompt = "这个分子 CC(=O)Nc1ccc(O)cc1 的 logP 是多少?"
#
# 正例:
# 让 LLM 生成并执行代码:
# from rdkit import Chem
# from rdkit.Chem import Crippen
# mol = Chem.MolFromSmiles("CC(=O)Nc1ccc(O)cc1")
# print(Crippen.MolLogP(mol))
编排模式:最有价值的用法
# 让 LLM 作为「协调者」,调用确定性工具
import json
from rdkit import Chem
from rdkit.Chem import Descriptors, Crippen, QED
# ---- 定义工具 ----
def calc_properties(smiles: str) -> dict:
"""计算分子的物化性质"""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return {"error": "无效的 SMILES"}
return {
"MW": round(Descriptors.MolWt(mol), 2),
"logP": round(Crippen.MolLogP(mol), 2),
"TPSA": round(Descriptors.TPSA(mol), 2),
"HBD": Descriptors.NumHDonors(mol),
"HBA": Descriptors.NumHAcceptors(mol),
"RotB": Descriptors.NumRotatableBonds(mol),
"QED": round(QED.qed(mol), 3),
}
def check_alerts(smiles: str) -> dict:
"""检查结构警示"""
from rdkit.Chem import FilterCatalog
params = FilterCatalog.FilterCatalogParams()
params.AddCatalog(FilterCatalog.FilterCatalogParams.FilterCatalogs.PAINS)
catalog = FilterCatalog.FilterCatalog(params)
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return {"error": "无效的 SMILES"}
matches = catalog.GetMatches(mol)
return {"pains_alerts": [m.GetDescription() for m in matches]}
def predict_activity(smiles: str) -> dict:
"""调用训练好的 QSAR 模型"""
# 【关键】:这里调用的是真正训练过的模型
pred, unc = qsar_model.predict_with_uncertainty(smiles)
sim = applicability_check(smiles)
return {"pActivity": float(pred), "uncertainty": float(unc),
"max_train_similarity": float(sim),
"reliable": bool(sim > 0.4)}
TOOLS = {
"calc_properties": calc_properties,
"check_alerts": check_alerts,
"predict_activity": predict_activity,
}
# ---- LLM 决定调用哪些工具,并解释结果 ----
# 用户:「帮我评估这批分子,找出最有希望的」
# LLM:
# 1) 对每个分子调用 calc_properties 与 check_alerts
# 2) 过滤掉有警示、性质超范围的
# 3) 对剩余的调用 predict_activity
# 4) 【整合结果,用自然语言解释权衡】
#
# 【LLM 的价值在第 4 步】:
# 把多维度的数字整理成人能快速理解的判断
# 而所有数字本身来自确定性工具
文献与专利信息抽取
- 这是 LLM 在药物发现中最成熟的应用:从非结构化文本中提取化合物结构、活性数据、实验条件;
- 典型场景:从专利的 Markush 结构与实施例表格中提取化合物与活性;从文献方法部分提取实验条件;
- 必须配套的验证:
- 抽取的 SMILES 必须用 RDKit 验证;
- 数值要检查单位与量级是否合理;
- 抽样人工核对——建立准确率的基线;
- 记录来源(哪篇文献、哪一页、哪个表)以便追溯。
- 化学结构识别(OCSR):从图片中识别化学结构是专门的任务,应该用 DECIMER、MolScribe 等专用工具,而非通用 LLM。
使用 LLM 的纪律
# 1) 【任何事实性输出都要验证】
# 文献引用 → 核对是否真实存在
# 数值 → 用工具重算
# 化学结构 → RDKit 解析
#
# 2) 【提供准确的上下文】
# 与其让它「回忆」,不如把资料给它
# → 检索增强(RAG)比纯生成可靠得多
#
# 3) 【明确它的角色】
# 在提示中说明:
# 「你的任务是调用工具并整理结果,
# 不要凭记忆回答数值问题」
#
# 4) 【保留人的决策权】
# LLM 给出建议与整理,
# 决策由人做出并记录
#
# 5) 【数据安全】
# 【专有的分子结构、靶点信息
# 不应发送到外部 API】
# → 敏感场景用本地部署的模型
# → 或先脱敏
#
# 6) 【记录可追溯】
# 保存提示、输出、调用的工具与结果
# → 决策要能解释与复现
# 【一个实用的自检问题】:
# 「如果 LLM 这次的输出是错的,
# 我会怎么发现?」
# → 答不上来,就说明缺少验证环节
关键要点
- 让 LLM 调用工具,而非模仿工具——logP 用 RDKit 算,活性用 QSAR 模型预测;
- 它会自信地编造数值与文献,这比不回答更危险;
- 最成熟的应用是文献与专利的信息抽取,但抽取结果必须用 RDKit 验证并抽样人工核对;
- 专有分子结构不应发送到外部 API;每个输出都要能回答「错了我怎么发现」。
延伸资源
- 多模态:164《多模态模型》;AI 靶点发现:154《AI 靶点发现模型》;
- 论文阅读框架:170《AI 制药论文阅读框架》;可解释性:168《可解释性 AI》。