橘智橘智
FakeOrange
预计阅读时间:6分钟50秒

使用 OpenAI SDK进行实时化学信息分子分析

借助OpenAI库构建动态查询化学信息的功能

0
0


前言


本文属于搬运内容分享,文章末尾附有原文链接。 本文提供了一种使用OpenAI库的思路将化学信息领域的工作流集成,实现客制化操作。其中封装一定的工作流函数以便利化学信息方向的工作需求。更多是提供一种思路,现在主流本地运行大模型框架如 Ollama,vLLM均支持 openai库,所以大家可以尝试更多的开源模型构建属于自己的工作流。


原作者:Abhik Seal


概览流程图


data/78df2c1f-e442-415d-a382-fa7925af0c4b/84bb8422-661c-491f-a665-28690fada937image.png



使用 OpenAI 的函数调用


函数调用旨在让模型(例如 OpenAI 提供的模型)根据用户查询动态执行特定函数。这种方法有几个显著优势:


  • 实时数据访问:该功能克服了知识截止的限制,实现实时动态数据检索和处理,确保 AI 的响应是最新且相关的。


  • 增强的 API 集成:它支持与内部和外部 API 的交互,实现跨不同软件系统的无缝信息交换和功能集成。


  • 个性化互动:函数调用可以通过访问和分析用户数据、偏好和历史记录,提供个性化的响应,带来更具针对性和意义的互动。


  • 高级任务自动化:它可以自动执行复杂或重复性任务,使 AI 成为优化工作流和操作的宝贵工具。


  • 逻辑封装:函数封装特定逻辑和操作,使代码更具模块化、易维护性和可复用性。


接下来我们来看如何在构建分子对话系统时实现这一点。

我定义了两个函数 get_smiles_from_common_namecompute_physchem_properties。实现的目标是输入药物或化合物的通用名称,从 PubChem API 获取其 SMILES 表示式,然后计算某些性质并输出结果给用户。这里不要求 OpenAI 进行计算,而是系统在本地动态完成,这样便可以编写一组函数库以利用其他函数。


import warnings
import json
import requests
from rdkit import Chem
from rdkit.Chem import Descriptors
from openai import OpenAI

# 忽略所有警告
warnings.filterwarnings("ignore")

client = openai.OpenAI(api_key=key)

def get_smiles_from_common_name(common_name):
    """使用通用名称从 PubChem 获取 SMILES"""
    base_url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{common_name}/property/CanonicalSMILES/JSON"
    response = requests.get(base_url)
    if response.status_code == 200:
        data = response.json()
        smiles = data['PropertyTable']['Properties'][0]['CanonicalSMILES']
        return json.dumps({"smiles": smiles}) 
    else:
        return json.dumps({"error": "SMILES not found"})

def compute_physchem_properties(smiles):
    """使用 RDKit 计算基本物理化学性质"""
    mol = Chem.MolFromSmiles(smiles)
    properties = {
        "MolecularWeight": Descriptors.MolWt(mol),
        "LogP": Descriptors.MolLogP(mol),
        "NumHDonors": Descriptors.NumHDonors(mol),
        "NumHAcceptors": Descriptors.NumHAcceptors(mol)
    }
    return json.dumps(properties)

def compare_properties(properties_list):
    """比较多个分子的性质"""
    comparison = {}
    for prop in ["MolecularWeight", "LogP", "NumHDonors", "NumHAcceptors"]:
        comparison[prop] = {item["compound"]: item["properties"][prop] for item in properties_list}
    return json.dumps(comparison, indent=2)



核心代码部分


下面是代码的核心部分。我们定义了 run_conversation 函数,脚本定义了 get_smiles_from_common_namecompute_physchem_properties 函数,并指定为模型可调用的工具。run_conversation 函数设置了模型使用的消息和工具。OpenAI API 调用使用 client.chat.completions.create 并启用函数调用。模型根据对话动态决定何时调用特定函数(如 get_smiles_from_common_namecompute_physchem_properties)。响应处理动态处理函数调用并处理其输出。这是一个相当酷的实用功能。


def run_conversation(content):
    messages = [{"role": "user", "content": content}]
    tools = [
        {
            "name": "get_smiles_from_common_name",
            "description": "使用通用名称从 PubChem 获取 SMILES",
            "parameters": {
                "type": "object",
                "properties": {
                    "common_name": {
                        "type": "string",
                        "description": "分子的通用名称",
                    }
                },
                "required": ["common_name"],
            },
        },
        {
            "name": "compute_physchem_properties",
            "description": "从 SMILES 计算基本物理化学性质",
            "parameters": {
                "type": "object",
                "properties": {
                    "smiles": {
                        "type": "string",
                        "description": "分子的 SMILES 表示式",
                    }
                },
                "required": ["smiles"],
            },
        },
        {
            "name": "compare_properties",
            "description": "比较多个分子的性质",
            "parameters": {
                "type": "object",
                "properties": {
                    "properties_list": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "compound": {"type": "string"},
                                "properties": {"type": "object"}
                            }
                        }
                    }
                },
                "required": ["properties_list"],
            },
        }
    ]

    response = client.chat.completions.create(
        model=config['openai']['model'],
        messages=messages,
        functions=tools,
        function_call="auto",
        temperature=0.1
    )
    response_message = response.choices[0].message
    if hasattr(response_message, 'function_call') and response_message.function_call:
        tool_call = response_message.function_call
        messages.append(response_message)

        available_functions = {
            "get_smiles_from_common_name": get_smiles_from_common_name,
            "compute_physchem_properties": compute_physchem_properties,
            "compare_properties": compare_properties
        }

        function_name = tool_call.name
        function_to_call = available_functions[function_name]
        function_args = json.loads(tool_call.arguments)
        function_response = function_to_call(**function_args)
        messages.append({
            "role": "assistant",
            "name": function_name,
            "content": function_response,
        })

        if function_name == "get_smiles_from_common_name":
            response_dict = json.loads(function_response)
            if "smiles" in response_dict:
                smiles = response_dict["smiles"]
                properties = compute_physchem_properties(smiles)
                messages.append({
                    "role": "assistant",
                    "name": "compute_physchem_properties",
                    "content": json.dumps(properties),
                })

        second_response = client.chat.completions.create(
            model=config['openai']['model'],
            messages=messages,
            stream=True,
            temperature=config['openai']['temperature']
        )
        return second_response

if __name__ == "__main__":
    while True:
        next_question = input("输入你的问题(或键入 'exit' 退出):")
        if next_question.lower() == 'exit':
            break
        response = run_conversation(next_question)
        for chunk in response:
            if hasattr(chunk.choices[0], 'delta'):
                content = getattr(chunk.choices[0].delta, 'content', '')
                if content:
                    print(content, end='', flush=True)


第二个响应使 AI 提供更详细和上下文相关的响应,增强用户互动体验。此代码将函数调用结果集成到最终用户响应中,确保任何通过函数获取或计算的数据都包含在回复中。该代码可用于单个分子、多个分子及其性质比较。下面是一些示例问题和对话。

  • 计算 Navitoclax、Venetoclax 和 Aspirin 的性质。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/6eb9bc5f-0b7c-48f7-aa76-c5f799289339image.png


  • 比较 Navitoclax 和 Venetoclax 的差异。

data/78df2c1f-e442-415d-a382-fa7925af0c4b/2a85a58b-798d-4c0d-8531-35f45c7f5ff0image.png


原文链接

评论