Files
multi_mcp/test_tools/agent_test.py
2025-05-09 14:16:33 +08:00

438 lines
16 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
from api_key import *
from openai import OpenAI
import json
from typing import Dict, List, Any, Union, Optional
from rich.console import Console
import sys
sys.path.append('/home/ubuntu/sas0/lzy/multi_mcp_server')
from sci_mcp import *
# 获取工具
all_tools_schemas = get_all_tool_schemas()
tools = get_all_tools()
chemistry_tools = get_domain_tools("chemistry")
console = Console()
#console.print(all_tools_schemas)
class ModelAgent:
"""
只支持 gpt-4o 模型的代理类
处理返回值格式并提供统一的工具调用接口
"""
def __init__(self, model_name: str = "gpt-4o"):
"""
初始化模型客户端
Args:
model_name: 模型名称
"""
# 初始化客户端
self.client = OpenAI(
api_key=OPENAI_API_KEY,
base_url=OPENAI_API_URL,
)
# 模型名称
self.model_name = model_name
# 定义工具列表
self.tools = all_tools_schemas
def get_response(self, messages: List[Dict[str, Any]]) -> Any:
"""
获取模型的响应
Args:
messages: 消息列表
Returns:
响应对象
"""
completion = self.client.chat.completions.create(
model=self.model_name,
messages=messages,
tools=self.tools,
tool_choice="auto",
temperature=0.6,
)
return completion
def extract_tool_calls(self, response: Any) -> Optional[List[Any]]:
"""
从响应中提取工具调用信息
Args:
response: 响应对象
Returns:
工具调用列表,如果没有则返回 None
"""
if hasattr(response.choices[0].message, 'tool_calls'):
return response.choices[0].message.tool_calls
return None
def extract_content(self, response: Any) -> str:
"""
从响应中提取内容
Args:
response: 响应对象
Returns:
内容字符串
"""
content = response.choices[0].message.content
return content if content is not None else ""
def extract_finish_reason(self, response: Any) -> str:
"""
从响应中提取完成原因
Args:
response: 响应对象
Returns:
完成原因
"""
return response.choices[0].finish_reason
async def call_tool(self, tool_name: str, tool_arguments: Dict[str, Any]) -> str:
"""
调用工具函数,支持同步和异步函数
Args:
tool_name: 工具名称
tool_arguments: 工具参数
Returns:
工具执行结果
"""
if tool_name in tools:
tool_function = tools[tool_name]
try:
# 检查函数是同步的还是异步的
import asyncio
import inspect
if asyncio.iscoroutinefunction(tool_function) or inspect.isawaitable(tool_function):
# 异步调用工具函数
tool_result = await tool_function(**tool_arguments)
else:
# 同步调用工具函数
tool_result = tool_function(**tool_arguments)
return tool_result
except Exception as e:
return f"工具调用错误: {str(e)}"
else:
return f"未找到工具: {tool_name}"
async def chat(self, messages: List[Dict[str, Any]], max_turns: int = 5) -> str:
"""
与模型对话,支持工具调用
Args:
messages: 初始消息列表
max_turns: 最大对话轮数
Returns:
最终回答
"""
current_messages = messages.copy()
turn = 0
while turn < max_turns:
turn += 1
console.print(f"\n[bold magenta]第 {turn} 轮对话[/bold magenta]")
# 获取响应
response = self.get_response(current_messages)
assistant_message = response.choices[0].message
# 将助手消息添加到上下文
current_messages.append(assistant_message.model_dump())
# 提取内容和工具调用
content = self.extract_content(response)
tool_calls = self.extract_tool_calls(response)
finish_reason = self.extract_finish_reason(response)
console.print(f"[green]助手回复:[/green] {content}")
# 如果没有工具调用或已完成,返回内容
if tool_calls is None or finish_reason != "tool_calls":
return content
# 处理工具调用
for tool_call in tool_calls:
tool_call_name = tool_call.function.name
tool_call_arguments = json.loads(tool_call.function.arguments)
console.print(f"[yellow]调用工具:[/yellow] {tool_call_name}")
console.print(f"[yellow]工具参数:[/yellow] {tool_call_arguments}")
# 执行工具调用
tool_result = await self.call_tool(tool_call_name, tool_call_arguments)
console.print(f"[blue]工具结果:[/blue] {tool_result}")
# 添加工具结果到上下文
current_messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_call_name,
"content": tool_result
})
return "达到最大对话轮数限制"
async def main():
"""主函数"""
# 创建模型代理
agent = ModelAgent()
while True:
# 获取用户输入
user_input = input("\n请输入问题(输入 'exit' 退出): ")
if user_input.lower() == 'exit':
break
try:
# 使用 GPT-4o 模型
messages = [
{"role": "system", "content": "你是一个有用的助手。"},
{"role": "user", "content": user_input}
]
response = await agent.chat(messages)
console.print(f"\n[bold magenta]最终回答:[/bold magenta] {response}")
except Exception as e:
console.print(f"[bold red]错误:[/bold red] {str(e)}")
# 为每个工具函数生成的问题列表
def get_tool_questions():
"""
获取为每个工具函数生成的问题列表
这些问题设计为能够引导大模型调用相应的工具函数,但不直接命令模型调用特定工具
Returns:
包含工具名称和对应问题的字典
"""
questions = {
# 材料科学相关工具函数
"search_crystal_structures_from_materials_project": [
"我想了解氧化铁(Fe2O3)的晶体结构,能帮我找一下相关信息吗?",
"锂离子电池中常用的LiFePO4材料有什么样的晶体结构",
"能否帮我查询一下钙钛矿(CaTiO3)的晶体结构数据?"
],
"search_material_property_from_material_project": [
"二氧化钛(TiO2)有哪些重要的物理和化学性质?",
"我正在研究锂电池材料能告诉我LiCoO2的主要性质吗",
"硅(Si)的带隙和电子性质是什么?能帮我查一下详细数据吗?"
],
"query_material_from_OQMD": [
"能帮我从OQMD数据库中查询一下铝合金(Al-Cu)的形成能吗?",
"我想了解镍基超合金在OQMD数据库中的热力学稳定性数据",
"OQMD数据库中有关于锌氧化物(ZnO)的什么信息?"
],
"retrieval_from_knowledge_base": [
"有关高温超导体的最新研究进展是什么?",
"能否从材料科学知识库中找到关于石墨烯应用的信息?",
"我想了解钙钛矿太阳能电池的工作原理和效率限制"
],
"predict_properties": [
"这个化学式为Li2FeSiO4的材料可能有什么样的电子性质",
"能预测一下Na3V2(PO4)3这种材料的离子导电性吗",
"如果我设计一个新的钙钛矿结构,能预测它的稳定性和带隙吗?"
],
"generate_material": [
"能生成一种可能具有铁磁性的新材料结构吗?"
],
"optimize_crystal_structure": [
"我有一个CIF文件HEu2H3EuH2EuH5.cif能帮我优化一下使其更稳定吗"
],
"calculate_density": [
"我有一个CIF文件HEu2H3EuH2EuH5.cif能计算一下它的密度吗"
],
"get_element_composition": [
"我有一个CIF文件HEu2H3EuH2EuH5.cif能分析一下它的元素组成吗"
],
"calculate_symmetry": [
"我有一个CIF文件HEu2H3EuH2EuH5.cif能分析一下它的对称性和空间群吗"
],
# 化学相关工具函数
"search_pubchem_advanced": [
"阿司匹林的分子结构和性质是什么?"
],
"calculate_molecular_properties": [
"对乙酰氨基酚的SMILES是CC(=O)NC1=CC=C(C=C1)O计算它的物理化学性质"
],
"calculate_drug_likeness": [
"布洛芬的SMILES是CC(C)CC1=CC=C(C=C1)C(C)C(=O)O能计算它的药物性吗"
],
"calculate_topological_descriptors": [
"咖啡因的SMILES是CN1C=NC2=C1C(=O)N(C(=O)N2C)C能计算它的拓扑描述符吗"
],
"generate_molecular_fingerprints": [
"尼古丁的SMILES是CN1CCCC1C2=CN=CC=C2能为它生成Morgan指纹吗"
],
"calculate_molecular_similarity": [
"阿司匹林的SMILES是CC(=O)OC1=CC=CC=C1C(=O)O对乙酰氨基酚的SMILES是CC(=O)NC1=CC=C(C=C1)O它们的分子相似性如何"
],
"analyze_molecular_structure": [
"苯甲酸的SMILES是C1=CC=C(C=C1)C(=O)O能分析它的结构特征吗"
],
"generate_molecular_conformer": [
"甲基苯并噻唑的SMILES是CC1=NC2=CC=CC=C2S1能生成它的3D构象吗"
],
"identify_scaffolds": [
"奎宁的SMILES是COC1=CC2=C(C=CN=C2C=C1)C(C3CC4CCN3CC4C=C)O它的核心骨架是什么"
],
"convert_between_chemical_formats": [
"将乙醇的SMILESCCO转换为InChI格式"
],
"standardize_molecule": [
"将四环素的SMILESCC1C2C(C(=O)C3(C(CC4C(C3C(=O)C2C(=O)C(=C1O)C(=O)N)O)(C(=O)CO4)O)O)N(C)C标准化处理"
],
"enumerate_stereoisomers": [
"2-丁醇的SMILES是CCC(C)O它可能有哪些立体异构体,不要单纯靠你自身的知识,如果不确定可以使用工具。"
],
"perform_substructure_search": [
"在阿莫西林的SMILESCC1(C(N2C(S1)C(C2=O)NC(=O)C(C3=CC=C(C=C3)O)N)C(=O)O)C中搜索羧酸基团"
],
# RXN工具函数的测试问题
"predict_reaction_outcome": [
"我正在研究乙酸和乙醇的酯化反应想知道这个反应的产物是什么。反应物的SMILES表示法是'CC(=O)O.CCO'。能帮我预测一下这个反应最可能的结果吗?"
],
"predict_reaction_batch": [
"我在实验室中设计了三个酯化反应系列想同时了解它们的可能产物。这三个反应的SMILES分别是'CC(=O)O.CCO'(乙酸和乙醇)、'CC(=O)O.CCCO'(乙酸和丙醇)和'CC(=O)O.CCCCO'(乙酸和丁醇)。能否一次性预测这些反应的结果?"
],
"predict_reaction_topn": [
"我在研究丙烯醛和甲胺的反应机理这个反应可能有多种产物路径。反应物的SMILES是'C=CC=O.CN'。能帮我分析出最可能的前3种产物及它们的相对可能性吗"
],
"predict_retrosynthesis": [
"我需要为实验室合成阿司匹林但不确定最佳的合成路线。阿司匹林的SMILES是'CC(=O)OC1=CC=CC=C1C(=O)O'。能帮我分析一下可能的合成路径,将其分解为更简单的前体化合物吗?"
],
"predict_biocatalytic_retrosynthesis": [
"我们实验室正在研究绿色化学合成方法想知道是否可以使用酶催化方式合成这个含溴的芳香化合物SMILES: 'OC1C(O)C=C(Br)C=C1')。能帮我分析可能的生物催化合成路径吗?"
],
"predict_reaction_properties": [
"我正在研究这个有机反应的机理:'CC(C)S.CN(C)C=O.Fc1cccnc1F.O=C([O-])[O-].[K+].[K+]>>CC(C)Sc1ncccc1F'特别想了解反应中的原子映射关系。能帮我分析一下反应前后各原子的对应关系吗我需要atom-mapping属性。"
],
"extract_reaction_actions": [
"我从一篇有机合成文献中找到了这段实验步骤:'To a stirred solution of 7-(difluoromethylsulfonyl)-4-fluoro-indan-1-one (110 mg, 0.42 mmol) in methanol (4 mL) was added sodium borohydride (24 mg, 0.62 mmol). The reaction mixture was stirred at ambient temperature for 1 hour.' 能帮我将这段文本转换为结构化的反应步骤吗?这样我可以更清晰地理解每个操作。"
]
}
return questions
# 测试特定工具函数的问题
async def test_tool_with_question(question_index: int = 0):
"""
使用预设问题测试特定工具函数
Args:
question_index: 问题索引默认为0
"""
# 获取所有工具问题
all_questions = get_tool_questions()
# 创建工具名称到问题的映射
tool_questions = {}
for tool_name, questions in all_questions.items():
if questions:
tool_questions[tool_name] = questions[min(question_index, len(questions)-1)]
# 打印可用的工具和问题
console.print("[bold]可用的工具和问题:[/bold]")
for i, (tool_name, question) in enumerate(tool_questions.items(), 1):
console.print(f"{i}. [cyan]{tool_name}[/cyan]: {question}")
# 选择要测试的工具
choice = input("\n请选择要测试的工具编号(输入'all'测试所有工具): ")
agent = ModelAgent()
if choice.lower() == 'all':
# 测试所有工具
for tool_name, question in tool_questions.items():
console.print(f"\n[bold]测试工具: [cyan]{tool_name}[/cyan][/bold]")
console.print(f"问题: {question}")
messages = [
{"role": "system", "content": "你是一个有用的助手。"},
{"role": "user", "content": question}
]
try:
response = await agent.chat(messages)
#console.print(f"[green]回答:[/green] {response[:200]}...")
except Exception as e:
console.print(f"[bold red]错误:[/bold red] {str(e)}")
else:
try:
index = int(choice) - 1
if 0 <= index < len(tool_questions):
tool_name = list(tool_questions.keys())[index]
question = tool_questions[tool_name]
console.print(f"\n[bold]测试工具: [cyan]{tool_name}[/cyan][/bold]")
console.print(f"问题: {question}")
messages = [
{"role": "system", "content": "你是一个有用的助手。"},
{"role": "user", "content": question+'如果你不确定答案,请使用工具'}
]
response = await agent.chat(messages)
#console.print(f"[green]回答:[/green] {response}")
else:
console.print("[bold red]无效的选择[/bold red]")
except ValueError:
console.print("[bold red]请输入有效的数字[/bold red]")
if __name__ == "__main__":
# 取消注释以运行主函数
# asyncio.run(main())
# 取消注释以测试工具函数问题
asyncio.run(test_tool_with_question())
# pass
# 知识检索API的接口 数据库