2026/8/24 11:09:08

基于AI Agent与Skills体系构建自动化药物发现计算平台

基于AI Agent与Skills体系构建自动化药物发现计算平台 在药物发现领域传统的人工主导、工具分散的研发模式正面临效率瓶颈。一个完整的药物发现项目从靶点识别到先导化合物优化往往涉及分子对接、ADMET预测、合成路线设计等数十个环节每个环节都需要切换不同的专业软件和数据库数据流转不畅专家经验难以沉淀。近期我们团队在“书生国智科探挑战赛”生命科学赛道中基于“面向药物发现计算全流程的Agent和Skills体系”这一理念构建了名为“E-Drug-Lab Scientist”的智能体系统旨在通过AI Agent技术整合碎片化的计算工具实现药物发现流程的自动化与智能化。本文将完整拆解该项目的架构设计、核心实现与落地经验为希望将AI Agent应用于生命科学领域的开发者提供一套可复现的实战方案。1. 项目背景与核心概念解析1.1 药物发现的计算全流程挑战现代药物发现是一个漫长、昂贵且高风险的过程。其计算密集型环节通常包括靶点识别与验证 从基因组、蛋白质组数据中筛选潜在药物作用靶点。虚拟筛选 从百万级化合物库中通过分子对接、药效团模型等方法快速筛选出可能与靶点结合的苗头化合物。先导化合物优化 对苗头化合物进行结构修饰通过计算评估其活性、选择性、类药性如Lipinski五规则等。ADMET预测 评估化合物的吸收、分布、代谢、排泄和毒性性质这是临床前失败的主要原因。合成可行性分析 设计或评估化合物的合成路线。每个环节都依赖特定的计算工具如AutoDock Vina, RDKit, Schrödinger Suite, SwissADME和数据库如PDB, PubChem, ChEMBL。研究人员需要在不同界面、命令行和文件格式之间频繁切换导致流程断裂、效率低下且可重复性差。1.2 什么是AI Agent与Skills体系AI Agent智能体在此语境下指的是一个能够感知环境如用户指令、文件数据、进行决策规划任务步骤、执行动作调用工具并达成特定目标的自主程序。它不同于简单的聊天机器人具备规划、记忆和工具使用能力。Skills技能是Agent可调用的、封装了特定功能的原子化工具。一个Skill可以是一个Python函数、一个API调用、一个命令行工具的封装或是一段复杂的计算流程。Agent与Skills体系的核心思想是“大脑”与“手脚”的分离。一个强大的“大脑”Agent LLM负责理解复杂任务、进行全局规划和决策而众多专业的“手脚”Skills则负责执行具体的计算任务。通过让Agent动态地组合和调用Skills可以构建出能够处理复杂、多步骤工作流的智能系统。E-Drug-Lab Scientist便是这样一个大脑。它被赋予了一个药物化学家的“角色”其目标是完成用户提出的药物发现任务。它自身不进行具体的计算而是通过调用一系列预先封装好的药物计算Skills来完成任务。2. 环境准备与核心技术栈2.1 基础环境说明本项目是一个集成系统环境搭建涉及多个层面。以下是我们的核心环境配置读者可根据自身资源调整。操作系统 Ubuntu 20.04 LTS 或更高版本推荐 macOS 也可用于开发测试。部分计算软件对Linux支持更佳。Python环境 Python 3.9。强烈建议使用Conda管理环境以解决科学计算包依赖冲突。核心AI框架 LangChain / LlamaIndex。我们选用LangChain作为Agent框架因其在工具调用、工作流编排上生态成熟。也可选用AutoGen、Semantic Kernel等。大语言模型LLM OpenAI GPT-4 API 或 本地部署的 Llama 3.1、Qwen2.5 等开源模型。Agent的“大脑”能力直接取决于LLM的性能。药物计算软件与库RDKit 化学信息学核心库用于分子处理、描述符计算、子结构搜索等。AutoDock Vina 用于分子对接。Open Babel 化学文件格式转换。SwissADME/ProTox-II 可通过其Web API或本地化模型进行ADMET预测。2.2 项目结构预览在开始编码前规划一个清晰的项目结构至关重要。e-drug-lab-scientist/ ├── agent_core/ # Agent核心逻辑 │ ├── __init__.py │ ├── drug_agent.py # 药物发现Agent主类 │ └── prompts/ # 系统提示词模板 │ └── drug_scientist.txt ├── skills/ # 技能库 │ ├── __init__.py │ ├── base_skill.py # 技能基类 │ ├── chem_skill.py # 化学处理技能 (RDKit) │ ├── docking_skill.py # 分子对接技能 (Vina) │ ├── admet_skill.py # ADMET预测技能 │ └── data_fetch_skill.py # 数据获取技能 (PubChem) ├── workflows/ # 预定义工作流 │ └── virtual_screening_workflow.py ├── config/ # 配置文件 │ └── settings.yaml ├── data/ # 临时数据、输入输出文件 ├── tests/ # 单元测试 └── requirements.txt # Python依赖列表3. 核心架构构建药物发现Skills库Skills是系统的基石。每个Skill都应遵循单一职责原则具有明确的输入、输出和错误处理。3.1 定义Skill基类我们首先定义一个基类确保所有技能具有统一的接口。# skills/base_skill.py from abc import ABC, abstractmethod from typing import Any, Dict, Optional import logging class BaseSkill(ABC): 所有药物发现技能的抽象基类。 def __init__(self, name: str, description: str): self.name name self.description description self.logger logging.getLogger(__name__) abstractmethod def execute(self, **kwargs) - Dict[str, Any]: 执行技能的核心方法。 返回一个字典至少包含 {success: bool, result: Any, message: str} pass def validate_input(self, **kwargs) - bool: 验证输入参数。可被具体技能重写。 # 基础验证逻辑 return True def _format_result(self, success: bool, result: Any, message: str ) - Dict[str, Any]: 统一格式化技能执行结果。 return { skill_name: self.name, success: success, result: result, message: message }3.2 实现化学信息学SkillRDKit这是一个最基础的技能用于处理分子对象。# skills/chem_skill.py from rdkit import Chem from rdkit.Chem import Descriptors, Draw from .base_skill import BaseSkill import json from typing import List class ChemInfoSkill(BaseSkill): 化学信息处理技能。 def __init__(self): super().__init__( namechem_info, description处理化学分子信息。功能1. 从SMILES解析分子 2. 计算分子量 3. 生成分子图像 4. 计算类药性规则。 ) def execute(self, smiles: str, action: str, **kwargs) - Dict[str, Any]: 执行化学信息操作。 Args: smiles: 分子的SMILES字符串。 action: 执行的操作可选 parse, weight, depict, druglike。 Returns: 包含操作结果的字典。 try: mol Chem.MolFromSmiles(smiles) if mol is None: return self._format_result(False, None, 无效的SMILES字符串。) if action parse: result {smiles: smiles, num_atoms: mol.GetNumAtoms()} message 分子解析成功。 elif action weight: mol_weight Descriptors.MolWt(mol) result {molecular_weight: round(mol_weight, 2)} message f分子量计算完成: {result[molecular_weight]} g/mol。 elif action depict: # 生成分子二维图像并保存 img_path fdata/{smiles[:10]}_depict.png Draw.MolToFile(mol, img_path, size(300, 300)) result {image_path: img_path} message f分子图像已保存至 {img_path}。 elif action druglike: # 简化版类药性评估 mw Descriptors.MolWt(mol) logp Descriptors.MolLogP(mol) hbd Descriptors.NumHDonors(mol) hba Descriptors.NumHAcceptors(mol) passes_rule_of_five (mw 500 and logp 5 and hbd 5 and hba 10) result { mw: round(mw, 2), logp: round(logp, 2), hbd: hbd, hba: hba, passes_rule_of_five: passes_rule_of_five } message 类药性五规则评估完成。 else: return self._format_result(False, None, f不支持的操作: {action}。) return self._format_result(True, result, message) except Exception as e: self.logger.error(fChemInfoSkill 执行失败: {e}) return self._format_result(False, None, f技能执行异常: {str(e)})3.3 实现分子对接SkillAutoDock Vina封装此技能封装了命令行工具需要确保Vina已正确安装并配置在系统路径中。# skills/docking_skill.py import subprocess import tempfile import os from .base_skill import BaseSkill class DockingSkill(BaseSkill): 分子对接技能封装AutoDock Vina。 def __init__(self, vina_path: str vina): super().__init__( namemolecular_docking, description执行分子对接计算。需要受体pdbqt文件、配体pdbqt文件以及对接盒中心坐标和大小。 ) self.vina_path vina_path def execute(self, receptor_pdbqt: str, ligand_pdbqt: str, center_x: float, center_y: float, center_z: float, size_x: float 20, size_y: float 20, size_z: float 20, exhaustiveness: int 8, **kwargs) - Dict[str, Any]: 执行对接。 Args: receptor_pdbqt: 受体文件路径。 ligand_pdbqt: 配体文件路径。 center_x, center_y, center_z: 对接盒中心坐标。 size_x, size_y, size_z: 对接盒大小。 exhaustiveness: 搜索详尽度。 Returns: 包含对接结果文件路径和对接得分的字典。 # 创建临时目录存放输出 with tempfile.TemporaryDirectory() as tmpdir: output_pdbqt os.path.join(tmpdir, docked_output.pdbqt) log_path os.path.join(tmpdir, vina_log.txt) # 构建Vina命令 cmd [ self.vina_path, --receptor, receptor_pdbqt, --ligand, ligand_pdbqt, --center_x, str(center_x), --center_y, str(center_y), --center_z, str(center_z), --size_x, str(size_x), --size_y, str(size_y), --size_z, str(size_z), --exhaustiveness, str(exhaustiveness), --out, output_pdbqt, --log, log_path ] try: self.logger.info(f执行命令: { .join(cmd)}) result subprocess.run(cmd, capture_outputTrue, textTrue, checkTrue) # 解析输出日志提取对接得分简化处理 affinity_scores [] with open(log_path, r) as f: for line in f: if Affinity in line: parts line.split() if len(parts) 1: try: affinity_scores.append(float(parts[1])) except ValueError: pass # 将结果文件复制到持久化目录示例 final_output_path fdata/docking_result_{os.path.basename(ligand_pdbqt)} import shutil shutil.copy(output_pdbqt, final_output_path) return self._format_result( True, { output_file: final_output_path, affinity_scores: affinity_scores, best_affinity: min(affinity_scores) if affinity_scores else None, log_file: log_path }, f分子对接完成。最佳结合亲和力: {min(affinity_scores) if affinity_scores else N/A} kcal/mol。 ) except subprocess.CalledProcessError as e: self.logger.error(fVina进程错误: {e.stderr}) return self._format_result(False, None, f对接计算失败: {e.stderr}) except FileNotFoundError: return self._format_result(False, None, f未找到Vina可执行文件请检查路径: {self.vina_path})4. 构建药物发现智能体Agent有了Skills我们需要一个“大脑”来调度它们。这里使用LangChain来构建Agent。4.1 定义系统提示词提示词是Agent的“角色设定”和“工作说明书”至关重要。# agent_core/prompts/drug_scientist.txt 你是一位专业的计算药物化学家AI助手名为E-Drug-Lab Scientist。 你的核心任务是利用一系列专业的计算工具Skills帮助用户完成药物发现相关的计算任务。 ## 你的能力 你拥有以下技能请根据用户问题判断是否需要使用以及使用哪个技能 1. chem_info: 处理化学分子。可以解析SMILES、计算分子量、绘制结构图、评估类药性。 2. molecular_docking: 执行分子对接。需要受体、配体文件和对接盒参数。 3. fetch_pubchem: 从PubChem数据库查询化合物信息。 4. predict_admet: 预测化合物的ADMET性质需联网调用相关API。 ## 工作原则 1. 当用户给出一个任务时首先明确任务目标。 2. 规划完成任务所需的步骤并按顺序调用技能。 3. 每次调用技能时必须提供该技能所需的所有参数。 4. 一个技能的执行结果可以作为下一个技能的输入。 5. 如果用户请求不明确主动询问缺少的信息如SMILES、受体文件路径等。 6. 最终将各个技能的结果整合成一份清晰、专业的报告回复给用户。 ## 输出格式 请用以下JSON格式思考和规划你的行动 { “thought”: “分析用户请求规划步骤。”, “action”: “要调用的技能名称如果是最终答案则为 ‘final’。”, “action_input”: {技能所需的参数字典} } 现在开始请帮助用户解决药物发现计算问题。4.2 实现Agent主类我们将Skills封装为LangChain Tools并创建Agent。# agent_core/drug_agent.py import os from typing import List, Dict, Any from langchain.agents import AgentExecutor, create_react_agent from langchain_core.prompts import PromptTemplate from langchain_core.tools import BaseTool, Tool from langchain_openai import ChatOpenAI # 假设使用OpenAI模型也可替换为其他LLM from skills.chem_skill import ChemInfoSkill from skills.docking_skill import DockingSkill # ... 导入其他技能 class DrugDiscoveryAgent: 药物发现智能体。 def __init__(self, llm_model: str gpt-4, temperature: float 0.1, openai_api_key: str None): 初始化Agent。 Args: llm_model: 使用的LLM模型名称。 temperature: 模型温度。 openai_api_key: OpenAI API密钥。 self.llm ChatOpenAI(modelllm_model, temperaturetemperature, api_keyopenai_api_key or os.getenv(OPENAI_API_KEY)) self.skills self._load_skills() self.tools self._create_tools() self.agent_executor self._create_agent_executor() def _load_skills(self) - Dict[str, BaseSkill]: 实例化所有可用技能。 skills {} chem_skill ChemInfoSkill() docking_skill DockingSkill(vina_path/path/to/vina) # 请修改为实际路径 # 实例化其他技能... skills[chem_skill.name] chem_skill skills[docking_skill.name] docking_skill # ... return skills def _create_tools(self) - List[BaseTool]: 将技能包装成LangChain Tool。 tools [] def create_tool_wrapper(skill_name: str): skill self.skills[skill_name] def skill_function(**kwargs): # 调用技能的执行方法 return skill.execute(**kwargs) return skill_function # 为chem_info技能创建Tool chem_tool Tool( namechem_info, funccreate_tool_wrapper(chem_info), descriptionChemInfoSkill.__doc__ # 使用技能的文档字符串 ) tools.append(chem_tool) # 为docking技能创建Tool docking_tool Tool( namemolecular_docking, funccreate_tool_wrapper(molecular_docking), descriptionDockingSkill.__doc__ ) tools.append(docking_tool) # 添加其他工具的包装... return tools def _create_agent_executor(self) - AgentExecutor: 创建并配置Agent执行器。 # 读取系统提示词 with open(agent_core/prompts/drug_scientist.txt, r) as f: system_prompt f.read() prompt PromptTemplate.from_template( system_prompt \n\n用户问题: {input}\n\n请开始你的思考和行动。 ) # 使用ReAct范式创建Agent agent create_react_agent(llmself.llm, toolsself.tools, promptprompt) agent_executor AgentExecutor(agentagent, toolsself.tools, verboseTrue, handle_parsing_errorsTrue) return agent_executor def run(self, query: str) - Dict[str, Any]: 运行Agent处理用户查询。 Args: query: 用户的问题或指令。 Returns: Agent的执行结果。 try: response self.agent_executor.invoke({input: query}) return response except Exception as e: return {error: str(e), output: Agent执行过程中出现错误。}5. 完整实战虚拟筛选工作流现在我们将上述组件串联起来实现一个从化合物库筛选到对接评估的自动化流程。5.1 工作流脚本我们创建一个独立的工作流脚本它本身也可以被看作一个更高级的“Skill”。# workflows/virtual_screening_workflow.py import pandas as pd from agent_core.drug_agent import DrugDiscoveryAgent import logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) def virtual_screening_workflow(smiles_list: List[str], receptor_file: str, center: tuple, size: tuple (20,20,20)): 虚拟筛选工作流先进行类药性过滤再对通过的化合物进行分子对接。 Args: smiles_list: 待筛选化合物的SMILES列表。 receptor_file: 受体蛋白的pdbqt文件路径。 center: 对接盒中心坐标 (x, y, z)。 size: 对接盒大小 (x, y, z)。 Returns: 包含筛选和对接结果的DataFrame。 agent DrugDiscoveryAgent() results [] for idx, smiles in enumerate(smiles_list): logger.info(f处理化合物 {idx1}/{len(smiles_list)}: {smiles[:20]}...) compound_result {smiles: smiles, passed_filter: False, docking_score: None} # 步骤1: 类药性过滤 filter_query f请评估SMILES为 {smiles} 的化合物的类药性使用druglike操作。 filter_response agent.run(filter_query) # 解析Agent返回结果这里简化实际需解析Agent的output # 假设Agent直接返回了技能执行结果 if passes_rule_of_five in str(filter_response): # 这里需要根据Agent的实际返回结构进行解析以下为示例逻辑 if True in str(filter_response): compound_result[passed_filter] True logger.info(f 化合物 {idx1} 通过类药性筛选。) else: compound_result[passed_filter] False logger.info(f 化合物 {idx1} 未通过类药性筛选。) results.append(compound_result) continue # 跳过对接 else: logger.warning(f 化合物 {idx1} 类药性评估失败。) results.append(compound_result) continue # 步骤2: 分子对接 (需先将SMILES转化为pdbqt文件此处省略转换步骤) # 假设已有一个函数 smiles_to_pdbqt(smiles) 返回配体文件路径 ligand_file fdata/ligand_{idx}.pdbqt # 假设文件已存在 center_x, center_y, center_z center size_x, size_y, size_z size docking_query (f请对受体文件 {receptor_file} 和配体文件 {ligand_file} f进行分子对接。对接盒中心为 ({center_x}, {center_y}, {center_z}) f大小为 ({size_x}, {size_y}, {size_z})。) docking_response agent.run(docking_query) # 解析对接得分 if best_affinity in str(docking_response): # 同样需要根据实际返回解析此处为示例 import re match re.search(rbest_affinity[\]:\s*([-\d.]), str(docking_response)) if match: compound_result[docking_score] float(match.group(1)) logger.info(f 对接完成得分: {compound_result[docking_score]}) results.append(compound_result) # 生成结果表格并按对接得分排序 df pd.DataFrame(results) df_sorted df[df[docking_score].notna()].sort_values(bydocking_score) return df_sorted if __name__ __main__: # 示例用法 test_smiles [CC(O)OC1CCCCC1C(O)O, C1CCC(CC1)CO, CN1CNC2C1C(O)N(C(O)N2C)C] # 阿司匹林、苯甲醛、咖啡因 receptor data/1abc_receptor.pdbqt box_center (10.5, 22.3, 15.8) result_df virtual_screening_workflow(test_smiles, receptor, box_center) print(虚拟筛选结果) print(result_df.to_string()) # 保存结果 result_df.to_csv(data/virtual_screening_results.csv, indexFalse)5.2 运行与验证环境激活与依赖安装conda create -n edruglab python3.9 conda activate edruglab pip install -r requirements.txt # 包含 langchain, rdkit, pandas, openai等 # 单独安装AutoDock Vina并确保其在系统PATH中配置API密钥与环境变量export OPENAI_API_KEYyour-api-key-here # 或在config/settings.yaml中配置准备输入数据将受体蛋白处理为data/1abc_receptor.pdbqt。将配体SMILES列表准备好。执行工作流python workflows/virtual_screening_workflow.py你将在控制台看到Agent的思考过程、技能调用日志并最终生成一个CSV结果文件。6. 常见问题与排查思路在开发和运行此类AI Agent系统时会遇到一些典型问题。问题现象可能原因排查思路与解决方案Agent无法理解任务或调用错误的Skill1. 系统提示词不够清晰。2. Skill的描述Tool description不准确。3. LLM能力不足或温度参数过高。1. 细化提示词明确Agent的角色、可用工具和输出格式。2. 为每个Tool编写精确、无歧义的描述说明输入输出。3. 尝试更强大的LLM如GPT-4并将temperature调低如0.1。Skill执行失败如Vina报错1. 外部软件未安装或路径错误。2. 输入参数格式错误或文件不存在。3. 环境依赖缺失。1. 在Skill的__init__中检查命令行工具路径使用subprocess.run的checkTrue捕获错误。2. 在Skill的execute方法开头增加严格的参数验证和文件存在性检查。3. 确保Python环境包含所有必要的库如RDKit。工作流执行速度慢1. LLM API调用延迟。2. 分子对接等计算本身耗时。3. 串行处理大量化合物。1. 对于固定流程可考虑将Agent规划部分缓存或转为硬编码工作流。2. 对接计算无法避免但可优化参数如降低exhaustiveness。3. 对独立的化合物处理任务使用多进程或异步。Agent陷入循环或无法结束1. ReAct Agent的停止条件不明确。2. Skill返回的结果格式让Agent误解。1. 在提示词中强调“最终答案用‘final’动作”。2. 设置AgentExecutor的max_iterations参数防止无限循环。3. 确保Skill返回的结果结构清晰易于解析。化学文件格式转换问题SMILES到PDBQT等格式转换失败。使用Open Babel或RDKit进行可靠的格式转换并将转换过程封装成一个独立的Skill做好错误处理。7. 最佳实践与工程建议将AI Agent应用于生产级药物发现流程需要超越“跑通Demo”的工程化思维。技能设计的原子化与复用性每个Skill应只做一件事并做好。例如“计算分子量”和“绘制分子结构”应分为两个Skill而不是一个“处理分子”的庞然大物。Skill的输入输出应尽量标准化如使用SMILES、文件路径、浮点数便于串联。提示词工程与Agent角色设定提示词是Agent的“灵魂”。需要反复迭代加入具体的约束如“你必须使用我提供的工具”、“在给出最终答案前请总结所有步骤的结果”。为不同的任务类型如虚拟筛选、ADMET预测、合成路线分析设计不同的“专家角色”提示词而不是用一个通用Agent处理所有问题。状态管理与记忆复杂的多轮交互需要Agent记住上下文。可以利用LangChain的ConversationBufferMemory或更高级的向量存储记忆。对于工作流应将关键中间结果如对接得分、筛选状态持久化到数据库或文件中支持断点续跑和结果追溯。错误处理与鲁棒性在每个Skill内部进行完备的异常捕获和日志记录。Agent层面应能处理Skill失败的情况并尝试替代方案或给用户明确的错误反馈而不是直接崩溃。安全与可解释性非常重要涉及分子生成或修饰时必须加入毒性、反应性预测等安全过滤Skill避免生成危险化合物。Agent的决策过程应可追溯。保存完整的ReAct轨迹Thought-Action-Observation循环便于研究人员审查AI的“思考”过程这对于科学计算至关重要。性能优化对耗时的计算Skill如对接考虑使用任务队列如Celery异步执行避免阻塞Agent主线程。对于频繁查询的数据库或API引入缓存机制。部署与集成将整个系统封装为Web服务如FastAPI提供RESTful API方便与其他实验室信息管理系统LIMS或计算平台集成。使用Docker容器化部署确保计算环境的一致性特别是对于AutoDock Vina这类有复杂依赖的软件。通过“E-Drug-Lab Scientist”项目的实践我们验证了AI Agent与Skills体系在整合药物发现计算全流程上的巨大潜力。它不仅是工具的简单串联更是通过一个可推理、可规划的“大脑”将专家的决策逻辑部分自动化。未来的方向包括引入更专业的计算Skill如自由能微扰、药效团建模、实现多Agent协作让一个Agent负责对接另一个负责ADMET评估并相互辩论以及利用检索增强生成RAG技术构建专属的药物知识库让Agent的决策更加精准可靠。对于开发者而言从封装好第一个可靠的Skill开始逐步构建起属于自己研究领域的智能体生态将是通往下一代智能研发基础设施的关键一步。