From 603304e10f411b05495caa3a6cfd758477708da5 Mon Sep 17 00:00:00 2001 From: lzy <949777411@qq.com> Date: Wed, 2 Apr 2025 10:17:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0=E9=9B=B6?= =?UTF-8?q?=E6=95=A3=E8=83=BD=E7=94=A8=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + __pycache__/mattergen_wrapper.cpython-310.pyc | Bin 0 -> 960 bytes __pycache__/mattergen_wrapper.cpython-312.pyc | Bin 0 -> 1263 bytes agent_test.py | 401 +++++++++++ api_key.py | 8 + execute_tool_copy.py | 270 ++++++++ mattergen_wrapper.py | 26 + test_tools.py | 190 ++++++ tools_for_ms/__init__.py | 16 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 821 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 916 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1005 bytes .../__pycache__/api_key.cpython-310.pyc | Bin 0 -> 278 bytes .../__pycache__/basic_tools.cpython-310.pyc | Bin 0 -> 2759 bytes .../__pycache__/basic_tools.cpython-312.pyc | Bin 0 -> 3976 bytes .../__pycache__/llm_tools.cpython-310.pyc | Bin 0 -> 5211 bytes .../__pycache__/llm_tools.cpython-311.pyc | Bin 0 -> 7852 bytes .../__pycache__/llm_tools.cpython-312.pyc | Bin 0 -> 7572 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 3215 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 4683 bytes tools_for_ms/basic_tools.py | 123 ++++ tools_for_ms/llm_tools.py | 213 ++++++ tools_for_ms/services_tools/Configs.py | 23 + .../__pycache__/Configs.cpython-310.pyc | Bin 0 -> 884 bytes .../__pycache__/Configs.cpython-312.pyc | Bin 0 -> 769 bytes .../cif_visualization_tools.cpython-310.pyc | Bin 0 -> 4012 bytes .../error_handlers.cpython-310.pyc | Bin 0 -> 1724 bytes .../error_handlers.cpython-312.pyc | Bin 0 -> 2591 bytes .../fairchem_service_tools.cpython-310.pyc | Bin 0 -> 7305 bytes ...airchem_service_tools_test.cpython-310.pyc | Bin 0 -> 8247 bytes .../fairchem_tools.cpython-310.pyc | Bin 0 -> 7289 bytes .../fairchem_tools.cpython-312.pyc | Bin 0 -> 9541 bytes .../mattergen_service.cpython-310.pyc | Bin 0 -> 11094 bytes .../mattergen_tools.cpython-310.pyc | Bin 0 -> 12358 bytes .../mattergen_tools.cpython-312.pyc | Bin 0 -> 15297 bytes .../mattersim_tools.cpython-310.pyc | Bin 0 -> 1958 bytes .../mp_service_tools.cpython-310.pyc | Bin 0 -> 22234 bytes .../__pycache__/mp_tools.cpython-310.pyc | Bin 0 -> 14774 bytes .../__pycache__/mp_tools.cpython-312.pyc | Bin 0 -> 26366 bytes .../oqmd_service_tools.cpython-310.pyc | Bin 0 -> 3016 bytes .../__pycache__/oqmd_tools.cpython-310.pyc | Bin 0 -> 3008 bytes .../__pycache__/oqmd_tools.cpython-312.pyc | Bin 0 -> 5499 bytes .../__pycache__/search_dify.cpython-310.pyc | Bin 0 -> 1682 bytes .../__pycache__/search_dify.cpython-312.pyc | Bin 0 -> 2352 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 3315 bytes tools_for_ms/services_tools/error_handlers.py | 49 ++ tools_for_ms/services_tools/fairchem_tools.py | 386 +++++++++++ .../services_tools/mattergen_tools.py | 412 +++++++++++ .../services_tools/mattersim_tools.py | 65 ++ tools_for_ms/services_tools/mp_tools.py | 644 ++++++++++++++++++ tools_for_ms/services_tools/oqmd_tools.py | 96 +++ tools_for_ms/services_tools/search_dify.py | 80 +++ tools_for_ms/utils.py | 101 +++ 53 files changed, 3111 insertions(+) create mode 100644 .gitignore create mode 100644 __pycache__/mattergen_wrapper.cpython-310.pyc create mode 100644 __pycache__/mattergen_wrapper.cpython-312.pyc create mode 100644 agent_test.py create mode 100644 api_key.py create mode 100644 execute_tool_copy.py create mode 100644 mattergen_wrapper.py create mode 100644 test_tools.py create mode 100644 tools_for_ms/__init__.py create mode 100644 tools_for_ms/__pycache__/__init__.cpython-310.pyc create mode 100644 tools_for_ms/__pycache__/__init__.cpython-311.pyc create mode 100644 tools_for_ms/__pycache__/__init__.cpython-312.pyc create mode 100644 tools_for_ms/__pycache__/api_key.cpython-310.pyc create mode 100644 tools_for_ms/__pycache__/basic_tools.cpython-310.pyc create mode 100644 tools_for_ms/__pycache__/basic_tools.cpython-312.pyc create mode 100644 tools_for_ms/__pycache__/llm_tools.cpython-310.pyc create mode 100644 tools_for_ms/__pycache__/llm_tools.cpython-311.pyc create mode 100644 tools_for_ms/__pycache__/llm_tools.cpython-312.pyc create mode 100644 tools_for_ms/__pycache__/utils.cpython-310.pyc create mode 100644 tools_for_ms/__pycache__/utils.cpython-312.pyc create mode 100644 tools_for_ms/basic_tools.py create mode 100644 tools_for_ms/llm_tools.py create mode 100644 tools_for_ms/services_tools/Configs.py create mode 100644 tools_for_ms/services_tools/__pycache__/Configs.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/Configs.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/cif_visualization_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/error_handlers.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/error_handlers.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/fairchem_service_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/fairchem_service_tools_test.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/fairchem_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/fairchem_tools.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mattergen_service.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mattergen_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mattergen_tools.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mattersim_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mp_service_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mp_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/mp_tools.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/oqmd_service_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/oqmd_tools.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/oqmd_tools.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/search_dify.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/search_dify.cpython-312.pyc create mode 100644 tools_for_ms/services_tools/__pycache__/utils.cpython-310.pyc create mode 100644 tools_for_ms/services_tools/error_handlers.py create mode 100644 tools_for_ms/services_tools/fairchem_tools.py create mode 100644 tools_for_ms/services_tools/mattergen_tools.py create mode 100644 tools_for_ms/services_tools/mattersim_tools.py create mode 100644 tools_for_ms/services_tools/mp_tools.py create mode 100644 tools_for_ms/services_tools/oqmd_tools.py create mode 100644 tools_for_ms/services_tools/search_dify.py create mode 100644 tools_for_ms/utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81685b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ + +/.venv +# 忽略整个mattergen目录中的所有更改 +mattergen/ +model_agent_test.py +pyproject.toml +/pretrained_models +/mcp-python-sdk diff --git a/__pycache__/mattergen_wrapper.cpython-310.pyc b/__pycache__/mattergen_wrapper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..976e44bd981563ae722b40213ada0a27b5efec27 GIT binary patch literal 960 zcmZ`%QEL-H5Z=99l1rMzSld*o4<~|<2NMx|DIyjdq+n}k1Ih^=*V}FG>hA5iyJ@*R ziT(@`toY(z@h9x7Px=!C!P&D71@-ps+nJr&nQ!JswA)Pt&-48s)6W%zzFFnsW33P2 zQ(plH3J}4>VK~6$?gS3(6;|PH;9^9bqw2?6iFN9oV&a}UL4#sAZ4yiyrw&GQA8u6H z5^n`9Y@^j!n|FdvDc9L0-VM5??6J#yIan^`2D`#nf)$LOq3pVg=xXs2qo4=Tx^#^+ zY41Jy;Jm}YV2v!BNUX za+c|o(?Rn)v=Hs~v2n*>GL1zjIBGydDkCkVRpNy5DR4?a=}aO>r^iwNLtVhh+D^&g zG0h$&G=7zfG}GJJp#anBcyBb>+uYtAZG}5ew?LfwJ~(7$HtVMs(AhNhwlWgMYQ-ImMJ1L=T=ajjBmWPF2a& z-1y65rcBB)ycF7jLt aw{Wl0!A_+Gl3iWgfz`zh_TD(ZTD9NRYZ{{f literal 0 HcmV?d00001 diff --git a/__pycache__/mattergen_wrapper.cpython-312.pyc b/__pycache__/mattergen_wrapper.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e82e1d701806ac96af7f748d2d908ebaffd5e674 GIT binary patch literal 1263 zcmZ`&O-$TI6n&^G(ee>S;X5ap0 zng;N7?aGt(uQI?t!s)Nl5!m+;zy^3=gNHmRL^djR$(HyohcZ!Y1p$^;id_Bchx>~8 zsxQ3PFb7XyY7cmAu{`C0bck#nqZ2EQ9L8U^9>RM69s*tHFNGCSwW~lt|TNBBwVD@9P8 zR!&y=D)TmYl0ABTbQP___wu)}iYCmp?~Fx1NG<-k)+%+A1XGK|UMIv>=DV4d(D-KH zVIFR^aGF|K%+Ik!+$_VijiV!SYAodf2FrO6wF}C8p80WPC2rGd7q=XT$!VT4DNdOx+6{vx%I6!HB0Q$7aA{2S0vfpl zGtF_9gAnsu=5c+82U$6YQr;U?LHy2>^MgQS5NW-?b6}YeL>&lDa8LU{h6Dd zj1YP%$w=AP09F15y$fT#GMpOTQ17W5)qB;=h3%_bSGS!l=l->aZ-4ej<>AouE%On` zceHiw&fxmsU3B;JJs9tmb!2pvT32awmC3F$!@p+(P`QED)#p7EwBctUBjd56?^YYX zqx Dict[str, Any]: + """ + 获取 qwq 模型的响应(返回字典格式) + + Args: + messages: 消息列表 + + Returns: + 字典格式的响应 + """ + completion = self.qwq_client.chat.completions.create( + model=self.qwq_model_name, + messages=messages, + tools=self.tools, + temperature=0.6, + tool_choice='auto' + ) + # qwq 返回的是对象,需要转换为字典 + return completion.model_dump() + + def get_gpt_response(self, messages: List[Dict[str, Any]]) -> Any: + """ + 获取 gpt 模型的响应(返回对象格式) + + Args: + messages: 消息列表 + + Returns: + 对象格式的响应 + """ + completion = self.gpt_client.chat.completions.create( + model=self.gpt_model_name, + messages=messages, + tools=self.tools, + tool_choice="auto", + temperature=0.6, + ) + # gpt 返回的是对象,直接返回 + return completion + + def extract_tool_calls_from_qwq(self, response: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]: + """ + 从 qwq 响应中提取工具调用信息 + + Args: + response: qwq 响应字典 + + Returns: + 工具调用列表,如果没有则返回 None + """ + assistant_message = response['choices'][0]['message'] + console.print("assistant_message",assistant_message) + return assistant_message.get('tool_calls') + + def extract_tool_calls_from_gpt(self, response: Any) -> Optional[List[Any]]: + """ + 从 gpt 响应中提取工具调用信息 + + Args: + response: gpt 响应对象 + + Returns: + 工具调用列表,如果没有则返回 None + """ + if hasattr(response.choices[0].message, 'tool_calls'): + return response.choices[0].message.tool_calls + return None + + def extract_content_from_qwq(self, response: Dict[str, Any]) -> str: + """ + 从 qwq 响应中提取内容 + + Args: + response: qwq 响应字典 + + Returns: + 内容字符串 + """ + content = response['choices'][0]['message'].get('content') + return content if content is not None else "" + + def extract_content_from_gpt(self, response: Any) -> str: + """ + 从 gpt 响应中提取内容 + + Args: + response: gpt 响应对象 + + Returns: + 内容字符串 + """ + content = response.choices[0].message.content + return content if content is not None else "" + + def extract_finish_reason_from_qwq(self, response: Dict[str, Any]) -> str: + """ + 从 qwq 响应中提取完成原因 + + Args: + response: qwq 响应字典 + + Returns: + 完成原因 + """ + return response['choices'][0]['finish_reason'] + + def extract_finish_reason_from_gpt(self, response: Any) -> str: + """ + 从 gpt 响应中提取完成原因 + + Args: + response: gpt 响应对象 + + 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: + # 异步调用工具函数 + tool_result = await tool_function(**tool_arguments) + return tool_result + except Exception as e: + return f"工具调用错误: {str(e)}" + else: + return f"未找到工具: {tool_name}" + + async def chat_with_qwq(self, messages: List[Dict[str, Any]], max_turns: int = 5) -> str: + """ + 与 qwq 模型对话,支持工具调用 + + Args: + messages: 初始消息列表 + max_turns: 最大对话轮数 + + Returns: + 最终回答 + """ + + current_messages = messages.copy() + turn = 0 + + while turn < max_turns: + turn += 1 + console.print(f"\n[bold cyan]第 {turn} 轮 QWQ 对话[/bold cyan]") + console.print("message",current_messages) + # 获取 qwq 响应 + response = self.get_qwq_response(current_messages) + assistant_message = response['choices'][0]['message'] + if assistant_message['content'] is None: + assistant_message['content'] = "" + console.print("message", assistant_message) + # 将助手消息添加到上下文 + current_messages.append(assistant_message) + + # 提取内容和工具调用 + content = self.extract_content_from_qwq(response) + tool_calls = self.extract_tool_calls_from_qwq(response) + finish_reason = self.extract_finish_reason_from_qwq(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", + "name": tool_call_name, + "content": tool_result + }) + + return "达到最大对话轮数限制" + + async def chat_with_gpt(self, messages: List[Dict[str, Any]], max_turns: int = 5) -> str: + """ + 与 gpt 模型对话,支持工具调用 + + Args: + messages: 初始消息列表 + max_turns: 最大对话轮数 + + Returns: + 最终回答 + """ + current_messages = messages.copy() + turn = 0 + + while turn < max_turns: + turn += 1 + console.print(f"\n[bold magenta]第 {turn} 轮 GPT 对话[/bold magenta]") + + # 获取 gpt 响应 + response = self.get_gpt_response(current_messages) + assistant_message = response.choices[0].message + + # 将助手消息添加到上下文 + current_messages.append(assistant_message.model_dump()) + + # 提取内容和工具调用 + content = self.extract_content_from_gpt(response) + tool_calls = self.extract_tool_calls_from_gpt(response) + finish_reason = self.extract_finish_reason_from_gpt(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 chat_with_both_models(self, user_input: str, system_prompt: str = "你是一个有用的助手。") -> Dict[str, str]: + """ + 同时与两个模型对话,比较它们的回答 + + Args: + user_input: 用户输入 + system_prompt: 系统提示 + + Returns: + 包含两个模型回答的字典 + """ + messages = [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_input} + ] + + # 并行调用两个模型 + qwq_task = asyncio.create_task(self.chat_with_qwq(messages)) + gpt_task = asyncio.create_task(self.chat_with_gpt(messages)) + + # 等待两个任务完成 + qwq_response, gpt_response = await asyncio.gather(qwq_task, gpt_task) + + return { + "qwq": qwq_response, + "gpt": gpt_response + } + + +async def main(): + """主函数""" + # 创建双模型代理 + agent = DualModelAgent() + + while True: + # 获取用户输入 + user_input = input("\n请输入问题(输入 'exit' 退出): ") + #user_input='现在的时间' + if user_input.lower() == 'exit': + break + + # 选择模型 + model_choice = input("选择模型 (1: QWQ, 2: GPT, 3: 两者): ") + + + try: + if model_choice == '1': + # 使用 QWQ 模型 + messages = [ + {"role": "system", "content": "你是一个有用的助手。"}, + {"role": "user", "content": user_input} + ] + response = await agent.chat_with_qwq(messages) + console.print(f"\n[bold cyan]QWQ 最终回答:[/bold cyan] {response}") + + elif model_choice == '2': + # 使用 GPT 模型 + messages = [ + {"role": "system", "content": "你是一个有用的助手。"}, + {"role": "user", "content": user_input} + ] + response = await agent.chat_with_gpt(messages) + console.print(f"\n[bold magenta]GPT 最终回答:[/bold magenta] {response}") + + elif model_choice == '3': + # 同时使用两个模型 + responses = await agent.chat_with_both_models(user_input) + console.print(f"\n[bold cyan]QWQ 最终回答:[/bold cyan] {responses['qwq']}") + console.print(f"\n[bold magenta]GPT 最终回答:[/bold magenta] {responses['gpt']}") + + else: + console.print("[bold red]无效的选择,请输入 1、2 或 3[/bold red]") + + except Exception as e: + console.print(f"[bold red]错误:[/bold red] {str(e)}") + + + + +if __name__ == "__main__": + + asyncio.run(main()) + #from tools_for_ms.services_tools.mp_service_tools import search_material_property_from_material_project + #asyncio.run(search_material_property_from_material_project('Fe2O3')) + + # 测试工具函数 + + # tool_name = 'get_crystal_structures_from_materials_project' + # result = asyncio.run(test_tool(tool_name)) + # print(result) + + #pass + +# 知识检索API的接口 数据库 diff --git a/api_key.py b/api_key.py new file mode 100644 index 0000000..b2d86aa --- /dev/null +++ b/api_key.py @@ -0,0 +1,8 @@ +OPENAI_API_KEY='sk-oYh3Xrhg8oDY2gW02c966f31C84449Ad86F9Cd9dF6E64a8d' +OPENAI_API_URL='https://vip.apiyi.com/v1' + +#OPENAI_API_KEY='gpustack_56f0adc61a865d22_c61cdbf601fa2cb95979d417618060e6' +#OPENAI_API_URL='http://192.168.191.100:5080/v1' + + + diff --git a/execute_tool_copy.py b/execute_tool_copy.py new file mode 100644 index 0000000..ed2a9ba --- /dev/null +++ b/execute_tool_copy.py @@ -0,0 +1,270 @@ +import json +import asyncio +import concurrent.futures +from tools_for_ms.llm_tools import * +import threading +# Create a lock for file writing +file_lock = threading.Lock() +from mysql.connector import pooling + + +connection_pool = pooling.MySQLConnectionPool( + pool_name="mypool", + pool_size=32, + pool_reset_session=True, + host='localhost', + user='metadata_mat_papers', + password='siat-mic', + database='metadata_mat_papers' + ) +def process_retrieval_from_knowledge_base(data): + doi = data.get('doi') + mp_id = data.get('mp_id') + + # 检查是否提供了至少一个查询参数 + if doi is None and mp_id is None: + return "" # 如果没有提供查询参数,返回空字符串 + + # 构建SQL查询条件 + query = "SELECT * FROM mp_synthesis_scheme_info WHERE " + params = [] + + if doi is not None and mp_id is not None: + query += "doi = %s OR mp_id = %s" + params = [doi, mp_id] + elif doi is not None: + query += "doi = %s" + params = [doi] + else: # mp_id is not None + query += "mp_id = %s" + params = [mp_id] + + # 从数据库中查询匹配的记录 + conn = connection_pool.get_connection() + try: + cursor = conn.cursor(dictionary=True) + try: + cursor.execute(query, params) + result = cursor.fetchone() # 获取第一个匹配的记录 + finally: + cursor.close() + finally: + conn.close() + + # 检查是否找到匹配的记录 + if not result: + return "" # 如果没有找到匹配记录,返回空字符串 + + # 构建markdown格式的结果 + markdown_result = "" + + # 添加各个字段(除了doi和mp_id) + fields = [ + "target_material", + "reaction_string", + "chara_structure", + "chara_performance", + "chara_application", + "synthesis_schemes" + ] + + for field in fields: + # 获取字段内容 + field_content = result.get(field, "") + # 只有当字段内容不为空时才添加该字段 + if field_content and field_content.strip(): + markdown_result += f"\n## {field}\n{field_content}\n\n" + + return markdown_result # 直接返回markdown文本 +async def execute_tool_from_dict(input_dict: dict): + """ + 从字典中提取工具函数名称和参数,并执行相应的工具函数 + + Args: + input_dict: 字典,例如: + {"name": "search_material_property_from_material_project", + "arguments": "{\"formula\": \"Th3Pd5\", \"is_stable\": \"true\"}"} + + Returns: + 工具函数的执行结果,如果工具函数不存在则返回错误信息 + """ + try: + # 解析输入字符串为字典 + # input_dict = json.loads(input_str) + + # 提取函数名和参数 + func_name = input_dict.get("name") + arguments_data = input_dict.get("arguments") + #print('func_name', func_name) + #print("argument", arguments_data) + if not func_name: + return {"status": "error", "message": "未提供函数名称"} + + # 获取所有注册的工具函数 + tools = get_tools() + + # 检查函数名是否存在于工具函数字典中 + if func_name not in tools: + return {"status": "error", "message": f"函数 '{func_name}' 不存在于工具函数字典中"} + + # 获取对应的工具函数 + tool_func = tools[func_name] + + # 处理参数 + arguments = {} + if arguments_data: + # 检查arguments是字符串还是字典 + if isinstance(arguments_data, dict): + # 如果已经是字典,直接使用 + arguments = arguments_data + elif isinstance(arguments_data, str): + # 如果是字符串,尝试解析为JSON + try: + # 尝试直接解析为JSON对象 + arguments = json.loads(arguments_data) + except json.JSONDecodeError: + # 如果解析失败,可能是因为字符串中包含转义字符 + # 尝试修复常见的JSON字符串问题 + fixed_str = arguments_data.replace('\\"', '"').replace('\\\\', '\\') + try: + arguments = json.loads(fixed_str) + except json.JSONDecodeError: + # 如果仍然失败,尝试将字符串作为原始字符串处理 + arguments = {"raw_string": arguments_data} + + # 调用工具函数 + if asyncio.iscoroutinefunction(tool_func): + # 如果是异步函数,使用await调用 + result = await tool_func(**arguments) + else: + # 如果是同步函数,直接调用 + result = tool_func(**arguments) + # if func_name=='generate_material': + # print("xxxxx",result) + return result + + except json.JSONDecodeError as e: + return {"status": "error", "message": f"JSON解析错误: {str(e)}"} + except Exception as e: + return {"status": "error", "message": f"执行过程中出错: {str(e)}"} + + + + +# # 示例用法 +# if __name__ == "__main__": +# # 示例输入 +# input_str = '{"name": "search_material_property_from_material_project", "arguments": "{\"formula\": \"Th3Pd5\", \"is_stable\": \"true\"}"}' + +# # 调用函数 +# result = asyncio.run(execute_tool_from_string(input_str)) +# print(result) + + +def worker(data, output_file_path): + + try: + # rich.console.Console().print(tools_schema) + # print(tools_schema) + func_contents = data["function_calls"] + func_results = [] + formatted_results = [] # 新增一个列表来存储格式化后的结果 + for func in func_contents: + if func.get("name") == 'retrieval_from_knowledge_base': + func_name = func.get("name") + arguments_data = func.get("arguments") + # print('func_name', func_name) + # print("argument", arguments_data) + result = process_retrieval_from_knowledge_base(data) + func_results.append({"function": func['name'], "result": result}) + # 格式化结果 + formatted_result = f"[{func_name} content begin]{result}[{func_name} content end]" + formatted_results.append(formatted_result) + else: + result = asyncio.run(execute_tool_from_dict(func)) + func_results.append({"function": func['name'], "result": result}) + # 格式化结果 + func_name = func.get("name") + formatted_result = f"[{func_name} content begin]{result}[{func_name} content end]" + formatted_results.append(formatted_result) + + # 将所有格式化后的结果连接起来 + final_result = "\n\n\n".join(formatted_results) + data['obeservation']=final_result + # print("#"*50,"start","#"*50) + # print(data['obeservation']) + # print("#"*50,'end',"#"*50) + #return final_result # 返回格式化后的结果,而不是固定消息 + + + with file_lock: + with jsonlines.open(output_file_path, mode='a') as writer: + writer.write(data) # obeservation . data + return f"Processed successfully" + + except Exception as e: + return f"Error processing: {str(e)}" + + + +def main(datas, output_file_path, max_workers=1): + import random + from tqdm import tqdm + import os + from mysql.connector import pooling, Error + + # 创建进度条 + pbar = tqdm(total=len(datas), desc="Processing papers") + + # 创建一个线程池 + with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: + # 提交任务到执行器 + future_to_path = {} + for path in datas: + future = executor.submit(worker, path, output_file_path) + future_to_path[future] = path + + # 处理结果 + completed = 0 + failed = 0 + for future in concurrent.futures.as_completed(future_to_path): + path = future_to_path[future] + try: + result = future.result() + if "successfully" in result: + completed += 1 + else: + failed += 1 + # 更新进度条 + pbar.update(1) + # 每100个文件更新一次统计信息 + if (completed + failed) % 100 == 0: + pbar.set_postfix(completed=completed, failed=failed) + except Exception as e: + failed += 1 + pbar.update(1) + print(f"\nWorker for {path} generated an exception: {e}") + + pbar.close() + print(f"Processing complete. Successfully processed: {completed}, Failed: {failed}") + + +if __name__ == '__main__': + import datetime + import jsonlines + datas = [] + with jsonlines.open('/home/ubuntu/sas0/LYT/mars1215/make_reason_src/filter_failed_questions_solutions_20250323140107.jsonl') as reader: + for obj in reader: + datas.append(obj) + + print(len(datas)) + # print() + output_file = f"./filter_ok_questions_solutions_agent_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.jsonl" + main(datas, output_file,max_workers=8) + + # print("开始测试 process_retrieval_from_knowledge_base 函数...") + # data={'doi':'10.1016_s0025-5408(01)00495-0','mp_id':None} + # result = process_retrieval_from_knowledge_base(data) + # print("函数执行结果:") + # print(result) + # print("测试完成") diff --git a/mattergen_wrapper.py b/mattergen_wrapper.py new file mode 100644 index 0000000..6bbe6de --- /dev/null +++ b/mattergen_wrapper.py @@ -0,0 +1,26 @@ +""" +This is a wrapper module that provides access to the mattergen modules +by modifying the Python path at runtime. +""" +import sys +import os +from pathlib import Path + +# Add the mattergen directory to the Python path +mattergen_dir = os.path.join(os.path.dirname(__file__), 'mattergen') +sys.path.insert(0, mattergen_dir) + +# Import the necessary modules from the mattergen package +try: + from mattergen import generator + from mattergen.common.data import chemgraph + from mattergen.common.data.types import TargetProperty + from mattergen.common.utils.eval_utils import MatterGenCheckpointInfo + from mattergen.common.utils.data_classes import PRETRAINED_MODEL_NAME +except ImportError as e: + print(f"Error importing mattergen modules: {e}") + print(f"Python path: {sys.path}") + raise + +# Re-export the modules +__all__ = ['generator', 'chemgraph', 'TargetProperty', 'MatterGenCheckpointInfo', 'PRETRAINED_MODEL_NAME'] diff --git a/test_tools.py b/test_tools.py new file mode 100644 index 0000000..c57688b --- /dev/null +++ b/test_tools.py @@ -0,0 +1,190 @@ + +import asyncio + + +async def test_tool(tool_name: str) -> str: + """ + 测试指定的工具函数是否能正常被调用 + + Args: + tool_name: 工具函数的名称 + + Returns: + 测试结果信息 + """ + try: + print(f"开始测试工具: {tool_name}") + + if tool_name == "get_current_time": + from tools_for_ms.basic_tools import get_current_time + result = await get_current_time(timezone="Asia/Shanghai") + + elif tool_name == "search_online": + from tools_for_ms.basic_tools import search_online + #from tools_for_ms.basic_tools import search_online + result = await search_online(query="material science", num_results=2) + + elif tool_name == "search_material_property_from_material_project": + from tools_for_ms.services_tools.mp_tools import search_material_property_from_material_project + result = await search_material_property_from_material_project(formula="Fe2O3") + + elif tool_name == "get_crystal_structures_from_materials_project": + from tools_for_ms.services_tools.mp_tools import get_crystal_structures_from_materials_project + result = await get_crystal_structures_from_materials_project( + formulas=["Fe2O3"]) + elif tool_name == "get_mpid_from_formula": + from tools_for_ms.query_tools.mp_tools import get_mpid_from_formula + result = await get_mpid_from_formula(['Fe2O3']) + elif tool_name == "optimize_crystal_structure": + from tools_for_ms.services_tools.fairchem_tools import optimize_crystal_structure + # 使用一个简单的CIF字符串作为测试输入 + simple_cif = """ + data_simple + _cell_length_a 4.0 + _cell_length_b 4.0 + _cell_length_c 4.0 + _cell_angle_alpha 90 + _cell_angle_beta 90 + _cell_angle_gamma 90 + _symmetry_space_group_name_H-M 'P 1' + loop_ + _atom_site_label + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + Si 0.0 0.0 0.0 + O 0.25 0.25 0.25 + """ + result = await optimize_crystal_structure(content=simple_cif, input_format="cif") + + elif tool_name == "generate_material": + from tools_for_ms.services_tools.mattergen_tools import generate_material + # 使用简单的属性约束进行测试 + result = await generate_material(properties={'dft_mag_density': 0.15}, batch_size=2, num_batches=1) + + elif tool_name == "fetch_chemical_composition_from_OQMD": + from tools_for_ms.services_tools.oqmd_tools import fetch_chemical_composition_from_OQMD + result = await fetch_chemical_composition_from_OQMD(composition="Fe2O3") + + elif tool_name == "retrieval_from_knowledge_base": + from tools_for_ms.query_tools.search_dify import retrieval_from_knowledge_base + result = await retrieval_from_knowledge_base(query="CsPbBr3", topk=3) + + elif tool_name == "predict_properties": + from tools_for_ms.services_tools.mattersim_tools import predict_properties + # 使用一个简单的硅钻石结构CIF字符串作为测试输入 + _cif = """ + # generated using pymatgen +data_CsPbBr3 +_symmetry_space_group_name_H-M 'P 1' +_cell_length_a 8.37036600 +_cell_length_b 8.42533500 +_cell_length_c 12.01129500 +_cell_angle_alpha 90.00000000 +_cell_angle_beta 90.00000000 +_cell_angle_gamma 90.00000000 +_symmetry_Int_Tables_number 1 +_chemical_formula_structural CsPbBr3 +_chemical_formula_sum 'Cs4 Pb4 Br12' +_cell_volume 847.07421031 +_cell_formula_units_Z 4 +loop_ + _symmetry_equiv_pos_site_id + _symmetry_equiv_pos_as_xyz + 1 'x, y, z' +loop_ + _atom_site_type_symbol + _atom_site_label + _atom_site_symmetry_multiplicity + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + Cs Cs0 1 0.50831300 0.46818500 0.25000000 1 + Cs Cs1 1 0.00831300 0.03181500 0.75000000 1 + Cs Cs2 1 0.99168700 0.96818500 0.25000000 1 + Cs Cs3 1 0.49168700 0.53181500 0.75000000 1 + Pb Pb4 1 0.50000000 0.00000000 0.50000000 1 + Pb Pb5 1 0.00000000 0.50000000 0.00000000 1 + Pb Pb6 1 0.00000000 0.50000000 0.50000000 1 + Pb Pb7 1 0.50000000 0.00000000 0.00000000 1 + Br Br8 1 0.54824500 0.99370800 0.75000000 1 + Br Br9 1 0.04824500 0.50629200 0.25000000 1 + Br Br10 1 0.79480800 0.20538600 0.02568800 1 + Br Br11 1 0.20519200 0.79461400 0.97431200 1 + Br Br12 1 0.29480800 0.29461400 0.97431200 1 + Br Br13 1 0.70519200 0.70538600 0.02568800 1 + Br Br14 1 0.29480800 0.29461400 0.52568800 1 + Br Br15 1 0.70519200 0.70538600 0.47431200 1 + Br Br16 1 0.20519200 0.79461400 0.52568800 1 + Br Br17 1 0.79480800 0.20538600 0.47431200 1 + Br Br18 1 0.95175500 0.49370800 0.75000000 1 + Br Br19 1 0.45175500 0.00629200 0.25000000 1 + + """ + result = await predict_properties(cif_content=_cif) + +# elif tool_name == "visualize_cif": +# from tools_for_ms.services_tools.cif_visualization_tools import visualize_cif +# # 使用一个简单的CIF字符串作为测试输入 +# simple_cif = """ +# data_CdEu2NEu +# _chemical_formula_structural CdEu2NEu +# _chemical_formula_sum "Cd1 Eu3 N1" +# _cell_length_a 5.114863465543178 +# _cell_length_b 5.110721509244114 +# _cell_length_c 5.113552093505859 +# _cell_angle_alpha 90.02261043268513 +# _cell_angle_beta 90.00946914658029 +# _cell_angle_gamma 89.99314499504335 + +# _space_group_name_H-M_alt "P 1" +# _space_group_IT_number 1 + +# loop_ +# _space_group_symop_operation_xyz +# 'x, y, z' + +# loop_ +# _atom_site_type_symbol +# _atom_site_label +# _atom_site_symmetry_multiplicity +# _atom_site_fract_x +# _atom_site_fract_y +# _atom_site_fract_z +# _atom_site_occupancy +# Cd Cd1 1.0 0.6641489863395691 0.6804293394088744 0.3527604341506958 1.0000 +# Eu Eu1 1.0 0.1641521006822586 0.18045939505100247 0.35262206196784973 1.0000 +# Eu Eu2 1.0 0.16385404765605927 0.6803322434425354 0.8526210784912109 1.0000 +# N N1 1.0 0.16389326751232147 0.1804375052452087 0.8527467250823975 1.0000 +# Eu Eu3 1.0 0.664197564125061 0.1803932040929794 0.8526203036308289 1.0000 +# """ +# result = await visualize_cif(cif_content=simple_cif) + +# else: +# return f"未知工具: {tool_name}" + + print(f"工具 {tool_name} 测试完成") + return f"工具 {tool_name} 测试成功,返回结果类型: {type(result)},返回的结果{result}" + + except Exception as e: + import traceback + error_details = traceback.format_exc() + return f"工具 {tool_name} 测试失败: {str(e)}\n{error_details}" + + +if __name__ == "__main__": + + # 查询工具 + # search_material_property_from_material_project 在material project中通过化学式查询材料性质 ✅ + # get_crystal_structures_from_materials_project 在material project中通过化学式查询晶体性质✅ + # fetch_chemical_composition_from_OQMD 在OQMD中通过化学式查询获取化学组成✅ + # search_online + # 生成内容的工具 + # optimize_crystal_structure 使用fairchem 优化晶体结构✅ + # predict_properties 使用mattersim 预测晶体性质 ✅ + # generate_material 使用matter 预测晶体性质✅ + # 测试 MatterSim 工具 + tool_name ='search_material_property_from_material_project' + result = asyncio.run(test_tool(tool_name)) + print(result) diff --git a/tools_for_ms/__init__.py b/tools_for_ms/__init__.py new file mode 100644 index 0000000..301e67b --- /dev/null +++ b/tools_for_ms/__init__.py @@ -0,0 +1,16 @@ +""" +Tools package for LLM function calling. + +This package provides utilities for defining, registering, and managing LLM tools. +""" + +from .llm_tools import llm_tool, get_tools, get_tool_schemas +from .basic_tools import * +from .services_tools.oqmd_tools import fetch_chemical_composition_from_OQMD +from .services_tools.mp_tools import search_material_property_from_material_project,get_crystal_structures_from_materials_project +from .services_tools.search_dify import retrieval_from_knowledge_base +from .services_tools.fairchem_tools import optimize_crystal_structure +#from .services_tools.mattergen_tools import generate_material +#from .services_tools.mattersim_tools import predict_properties + +__all__ = ["llm_tool", "get_tools", "get_tool_schemas"] diff --git a/tools_for_ms/__pycache__/__init__.cpython-310.pyc b/tools_for_ms/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e43ae5043200f827c9a99450244f91da5f1997c GIT binary patch literal 821 zcmZXSL2uJA6vv%3Ym=-Ul|VvFnv?^VBGzz$(}Xy1+(3w)axtFmq-J)U*-occ;zMxb z%#E*o*?woi|J28qQzdS$x^8EM9)00VxV0`}av-%Yx^xZG+4v3*wFzp903>An4 zER=x?i%<=Ufr^SK@UekBSon7&hiX)e`q+VtRZ=7>Ez*8Hl!t0ujC+41C+es;3Q&bu z_%$f*vG_fjC7Vy_LThQWhR`cg@vPJ)J3qh3%C@FX=sKfBN>NwyG+ivkU#>BFB^b9^ z>x2|efagDDyc9LyPcy?SVI4R9g48Tiq$U-t`t;5pI!}KDaLd`Ci=|Z9$GW8AuJ>#= zy>+prORk8W1zqq2%;S2pu37ZvfQi$1xefb>1)Ndt{Hmx z;oiP#cDo~=FZRgs9@!jinLsww@4Vn8svp2tAFDE JUC4kUcDjBY3I0?OsF=;z>beSCK6q<`QNn=`?OKsY#~N1;PKq zvtGpirWc`%N4@NAU~hZ!W!mDlAeec)Pw{AB;u}s7qVNsMkLG7z~vXCY;2{X`Hffq;z8-o8ADp`id$pn^ge1;av{PoQ zlPRvRkHjQnCS!As9U2$-E~q;rW|J{-e>iX?iINj#fQib;NGnNRzd3r^^NPM-wnSZZzhj9l_T8Px8#> zg9iu0fjFBDt`;*O1mH+SALwQ#{Qv*} literal 0 HcmV?d00001 diff --git a/tools_for_ms/__pycache__/__init__.cpython-312.pyc b/tools_for_ms/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa1c0177b1eaef1e000a77842ccee1534fc8882a GIT binary patch literal 1005 zcmZ{izi-qq6vyq{mAlK`m9`+Ewkl*`IYcikz|t}x5Cbg;sZ$m!b)1~39S7Sfx|10A zE7;i>_)C~rBRVm`1gcP_PCV!C_;Cm;a^8FXgg!fGzQC{7 z=>%SHP=X?q;KUPHdXXm^QA0MPCU&ugSbFc(R#RM%t*BMoOQJ11QAhfbU;7tCSN5V_ zZMQ^UE=S9?-4+++O0-hj9kD9cqBV>%lz4A(bZP7lx|3J_fl|T*MNE(4j0LIE!QS3} zkd`^MT;&0ch2VKM^!wG-z_AmcpF zz^l_+S9Iuq!SMfsM%5NV5*MpF8MC!zs+GA(3>`5Un*pxy4cM-y%+e8YK@RN^s$`)I zG@){mY9-0jXZsHZUNtl<)&R=b^#LF>T`+COQ|iy~b4G2o?Fv#oHWpZ8bxCchnfaSG zE{nsVkuA-v=Ij{oy6#b~UJ90Ej2y;>0kEM8%O#(%f78P0YQ}P=p}AijLU6OtEaBA7 zn+0X3$th2t3@%qKLJ~y@sXBAbjBbF6y6Lth*s3m6=X9pxx?%d5Q^+#aT)mLV)YL5p zUs*Vp#q6~0@^u`tv4G5EO?W!iZpN#v1t5(%=+35BYIN5zR~HyiA8rK{n3e1OA2NKN zsqY|A0A`Yqcjy#cbbcosDapd}u*_{4-oAAZipe;Xu{PV17GeF{1csAH6A}XBX$ct? zrL@prF@n`b?QcR!+?PiW%=JwEdgzVDr&hxN02f*w`= L-PbL>4&VF){=7{# literal 0 HcmV?d00001 diff --git a/tools_for_ms/__pycache__/api_key.cpython-310.pyc b/tools_for_ms/__pycache__/api_key.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef305242431d62e8266e1716e51f6cc1624e1876 GIT binary patch literal 278 zcmd1j<>g`kf=G!Y>3KlLsX`H>mM z5k(p47WpobM(N=OM#+|DW@*NT&K4#nCYFvV7G`di&MB5DZf34#CW#g)RT3E`B?ZM+ z`ub&=1$v1EnU$G($@#hZWrlv5Oi_IP0j_?Ip7D+Wp7GwUktqDoAfJ^CMIiTpiC@n8 zp~b01#rip!RT+tSm8FS!`r-LS*#$X?$*KAQMfq8&$tC&)l_eSZc~G8_eo20QPO(1F g*7)qyO1*;0TO2mI`6;D2sdgZ9i&=mK3j+ru0B5pFIRF3v literal 0 HcmV?d00001 diff --git a/tools_for_ms/__pycache__/basic_tools.cpython-310.pyc b/tools_for_ms/__pycache__/basic_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb27a78d83050821ce680b4cf973319fb3576c30 GIT binary patch literal 2759 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 T>0fg;2)x$K7cD<6qwe)Tna8q0 literal 0 HcmV?d00001 diff --git a/tools_for_ms/__pycache__/llm_tools.cpython-312.pyc b/tools_for_ms/__pycache__/llm_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddcebe5e7ef24c498f95199f84d7af140ca3fba2 GIT binary patch literal 7572 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 Wy-cmWi|LxP*9cVe_QzOJW&Afd6IP1= literal 0 HcmV?d00001 diff --git a/tools_for_ms/__pycache__/utils.cpython-310.pyc b/tools_for_ms/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99addb5ade06e021c6d178b8e9a6ab819e6fd99d GIT binary patch literal 3215 zcmZWrTW=f372ch_aJjsQ)ZI;DD{YmSK}EH70u+JJSdytoutf=yT-$u;V#OI!D=oR( z*`;g|Qei+w5fpXXrveQGL@N3c6lfk3p#3HDTDy+>6O20PIkP0CsJqy+Gv7ILJJ$(v zIa`6})6{>OcXEpIFYH`=Oz7N%FZvx2s!-KeYRO-%sj^RM1pdU=T6#@ak*E1a%dDBQ zt^28#RkKuuk<&D@&(vDxV}%;je4-jJ=@k`p`C(JmynqWEb3sZL&D_+Rdnr zq8A?>Iyd2q=2S&t3Ld3K?kY^9DkW5VN@_YYn0ZBEDXPyUxFK-}x;+cVbriW)Go*m%19-fzYZh#L-yg-W*&jUUr=O6CLk4U^QkRR>gV-hb8@bMl# zA@Sh>KH0;sNPJ{~e^26X&^IMMI>6tWQ^0gr=`}FjwPR8nlV+F_R$t3iVXv+`^Xn_l z?aBi%fyUbV!fu6*8+y(jI}jOQSE{$x*H)@qA_EN8reWZ<b>Z&f?aG$OCNpk#>U%8yKVJtAPken88hu}-RVo3~?Hs z&qT4`0?pL}3L*Hp!lz+*NnX7-yA!n7Y-hXEjytm-{CI2D?;gzJ*&?SAaHkc`c4E(u z%He@9lMTHMgCk5vmZGbc%HM@%X`D~Nk0XzN9|^hikl58RkfAaDiA8%W+lat#Ttetw>$^$ubNkSu)Bn?P)3 zl1!@bY8O82tHVS}3M=X-C^S<4!8RUs5x(dukXYGOsd}jFlF$OG!KcG#Q1Vm<^JyKl z?AO2l^7$YBwCc52)}H_UFaQ4J%NKw9LXhZ2NfX(AoH-rti=z85a?ZWa@u(O>sON0w zoRTSw?H~?r2(#{c40I)RA+<74NJ-^Cg4Hi*0~H|(P4L^qEt9cm{RmZ~6QbIx4zKxN zegkFG=rb}XdrkBwVS?v(c)O04>_4a#G@sPWC(!7=8!NGTtR9g=vP%x34cY-+QR1nP zzMvifu2a2-8z+RCH1$+F(hjv#15h*k0kztIn;j$mZBK-1Lr(=GeR zfO(8V{aC#RhIydS%#r?U(0uZLs6JFm*$%4Z#XmlK_SNI(e|_@ekH3+Yc=pwApZ)Vx zX!DDAL@MOoz8f>hjOogwn7i}583`K|6>RUadR!vHT#zv=wA<{!E9Ak)0b6&u+lp{d z=y4XUw1vXaM_{7i9k)$Ed5NYO=K-&lGE%M5EF#kh!Ih~DOX<5}yvf>(!#*HmvdC+; z8I^94;w<#tIy*Nqu2^TXwu<*IMrS!lg>UrqA}7%(K0I3u=ek1a7xD{a|Lg zH8V}8rk6jQUj1--6MY8KXv>X7ih`jVtyt(Q)upxW7%ba_Y+T-qIdfaf(s?*!2t&>< zjJoee;AzQdC}_gCRav35f+BG z3~38W?7~6JB9Ym4>wBGWF@Ord*Kq_9sXFB60IG+M1NcWc!7q^91F{91wEBz7$weCO z0-@W)h%!WKpYaG1yqKh3A*mdgB6ltIOS!p z&q4O@gmQl{9iBIu19DZ(qo2bEFCfexWi6vXFi79c()nDnaNFkBbUz2aMUvsd5 zQX1yJgdhJ25(-3^@j(bnBvsK052)LYy}B%yqS)_30Z4Ak9B;=W4T98{_i}#k9SsMth zNaVJ-t!j8FBtj~QECtI};2|84ee<6dy!{$4?A3|ppiAbcv$e(K|Kc$Tgegm|I zurWgWX^QZSVJ5=*S*=fpO%cw|QOJNK!%S&G@LnrfZc(|B-)cbpNMx=ek(Ibx#{8^J zG)cB8gUDT_{dUnTIYbNmtde7j6>Tu*6zx}$zXaslZso=LU2~Mj@sNys9tv}#aWxc+ z3SsCoeIY39fH7@H1b*I^tw zj9rIu7BMBFONVg)#+lcrw1}a^G99KwC#PJ8snB6uI!vVwQ>DX{>M+$hOpOjxro*_4 zm|C$;hbh-#>T?({rvqS44aNJa0K;g+T;bk**fKK4ca075L%kQkD2!6{61GI*yburZ z7o|CD1$M9R?AYj_ZvtC^Aw|V_EEH8A67$3+U{8#W4Pg_o)L8r?whT;6jPYaVM$ccs zmT6Uu^Km&g3xde}c%ES;3?+2dk*E7&N!jFue?H%UFx+Vs2*_05H1JhE3kHkbN z%*O?FI{5B8&icTS&i^kTya{^pQzKmNg4A zDyoc~JTF9}F;yU8OyPOiM)*!3*a){MyU4JNkP1So2!ojl#{^X-J}lP~Qcp+&Aw((d z?XrjTG-ZyEo~b~He5DGm;wR|OjjbEv9ou%}ON%crb8BTQ_B8#{-KMq;^-leE)0xGg zS~?XM;2+O_i=fps!e*eC?NLK@cbV3yMj^Ie$LlKHnew+*-Q(#Mg`bzw_kny<@H~W zgrb9^Uw-)hUza}q>efftpmchS*j5N}d_oRmmvC9(_Z*)OiMWDH<^6n*<6$u~8B=4O zm<@zO5?G~Xb(;MZP0vHg&%@glavY5^u{m54gm96I2KjX-V4C=YLSpzLdSETNe)`(! z3}?$!)bCUrOH~~EtYU1ddgsJg>cm*aeQ?L!nR0h#4z%qYcrkV0#c#}vjm;v4VYOSr zX5!;o0L^!a|6>-SR6>ph^w&Y|Q@gZD&~Fu)b=RgAafNE-iebSp!@!Dt&jQ-RKJnrx zQbL3%NL=|#fh#u5nD>c+p-4Yf60>d)+1sWV4Si3wyv&5~m0`4g?#C3sRECRY(+Q z)SjH@lznR-Ii2NE0D0l3yboj%WobLpde7B#<51dlWQomijvdaO;@oQ|ZcOjAol3Qx z+PaV&KcDmmlIO%E=icU|EQ_2KJB}kM$B~S!`W|ts&eVaRJ!?6MnXUJ^@85Mc zZ8W8wEq~%#{+(rD?y+(J)O@?!(rrhd+Rfcw+-1R>(MFt)B6MVn|}phSo0gmK9_E4m=Gl!|3ydC&l; z3bAtC2>7aXrmzDF%^^>Na!Yu~1CQTN|eMG$F`v`zP!lEM znsOjZegjx%;HUf!$RfJuDqF2usakDVX}D4PLDhRzAJn~9cYE@q;LYGiv750^rqka3 zKa{7Nd}-I{;>-8SD_8qh`d5cnhBw^F_A}}7?=6mG=(>Bf^=H1fd^>b~imuO;m1kN! zU)U-$?y*d*_Yq^Pab%55l_gt(+>M!<=Q8!rJTlSMj;sy2n=&f1n1&q}3IkQh5rtBfm&U z7zk$7xj5M_iBFh|&xt}*4F$A<9=i%dD4cSCImpD8uo*Jhuy%*qcK~K{*OsfqwT#qG zS3nq$Po83jwWJ{e?>qci5L31b9Y<2FE(bor? TDbu523fankaq=xrJCgqZyjqcG literal 0 HcmV?d00001 diff --git a/tools_for_ms/basic_tools.py b/tools_for_ms/basic_tools.py new file mode 100644 index 0000000..cf9e48b --- /dev/null +++ b/tools_for_ms/basic_tools.py @@ -0,0 +1,123 @@ + +import asyncio +from datetime import datetime +from typing import Annotated, Dict, Any +import pytz + +from langchain_community.utilities import SearxSearchWrapper + +from .llm_tools import llm_tool + +# Json Schema 将函数转化为大模型能够理解的格式(因为大模型训练时,调用函数相关的数据使用Json Schema的格式) +#1. 使用@tool装饰器装饰函数。 +#2. 使用Annotated为参数添加描述。 +#3. 完善函数的docstring以明确工具的功能。 让模型在调用函数时能清楚每个模块的功能 + +# @tool +# def online_search( +# query: Annotated[str, "The search term to find scientific content in English"] +# ) -> str: +# """Searches scientific information on the Internet and returns results in English.""" +# search = SearxSearchWrapper( +# searx_host="http://192.168.191.101:40032/", +# categories=["science"], +# k=20 +# ) + +# return search.run(query, language='es', num_results=2) + + +@llm_tool(name="get_current_time", description="Get current date and time in specified timezone") +async def get_current_time(timezone: str = "UTC") -> str: + """Returns the current date and time in the specified timezone. + + Args: + timezone: Timezone name (e.g., UTC, Asia/Shanghai, America/New_York) + + Returns: + Formatted date and time string + """ + try: + tz = pytz.timezone(timezone) + current_time = datetime.now(tz) + return f"The current {timezone} time is: {current_time.strftime('%Y-%m-%d %H:%M:%S %Z')}" + except pytz.exceptions.UnknownTimeZoneError: + return f"Unknown timezone: {timezone}. Please use a valid timezone such as 'UTC', 'Asia/Shanghai', etc." + +@llm_tool(name="search_online", description="Search scientific information online and return results as a string") +async def search_online( + query: Annotated[str, "Search term"], + num_results: Annotated[int, "Number of results (1-20)"] = 5 +) -> str: + """ + Searches for scientific information online and returns results as a formatted string. + + Args: + query: Search term for scientific content + num_results: Number of results to return (1-20) + + Returns: + Formatted string with search results (titles, snippets, links) + """ + # 确保 num_results 是整数 + try: + num_results = int(num_results) + except (TypeError, ValueError): + num_results = 5 + + # Parameter validation + if num_results < 1: + num_results = 1 + elif num_results > 20: + num_results = 20 + + # Initialize search wrapper + search = SearxSearchWrapper( + searx_host="http://192.168.191.101:40032/", + categories=["science",], + k=num_results + ) + + # Execute search in a separate thread to avoid blocking the event loop + # since SearxSearchWrapper doesn't have native async support + loop = asyncio.get_event_loop() + raw_results = await loop.run_in_executor( + None, + lambda: search.results(query, language=['en','zh'], num_results=num_results) + ) + + # Transform results into structured format + formatted_results = [] + for result in raw_results: + formatted_results.append({ + "title": result.get("title", ""), + "snippet": result.get("snippet", ""), + "link": result.get("link", ""), + "source": result.get("source", "") + }) + + # Convert the results to a formatted string + result_str = f"Search Results for '{query}' ({len(formatted_results)} items):\n\n" + + for i, result in enumerate(formatted_results, 1): + result_str += f"Result {i}:\n" + result_str += f"Title: {result['title']}\n" + result_str += f"Summary: {result['snippet']}\n" + result_str += f"Link: {result['link']}\n" + result_str += f"Source: {result['source']}\n\n" + + return result_str + + +# 让大模型可以根据函数名,直接调用函数 +# tool_map = { +# "online_search": online_search, +# "get_current_time": get_current_time, +# } + + + + +#####要用时得加修饰符@tool;为了实现异步,不用@tool +#tools = [online_search,get_current_time] +#tools_json_shcema = [convert_to_openai_function_format(tool.args_schema.model_json_schema()) for tool in tools] diff --git a/tools_for_ms/llm_tools.py b/tools_for_ms/llm_tools.py new file mode 100644 index 0000000..e261bb5 --- /dev/null +++ b/tools_for_ms/llm_tools.py @@ -0,0 +1,213 @@ +""" +LLM Tools Module + +This module provides decorators and utilities for defining, registering, and managing LLM tools. +It allows marking functions as LLM tools, generating JSON schemas for them, and retrieving +registered tools for use with LLM APIs. + +""" + +import asyncio +import inspect +import json +from functools import wraps +from typing import Any, Callable, Dict, List, Optional, get_type_hints, get_origin, get_args +import docstring_parser +from pydantic import BaseModel, create_model, Field + +# Registry to store all registered tools +_TOOL_REGISTRY = {} + +def llm_tool(name: Optional[str] = None, description: Optional[str] = None): + """ + Decorator to mark a function as an LLM tool. + + This decorator registers the function as an LLM tool, generates a JSON schema for it, + and makes it available for retrieval through the get_tools function. + + Args: + name: Optional custom name for the tool. If not provided, the function name will be used. + description: Optional custom description for the tool. If not provided, the function's + docstring will be used. + + Returns: + The decorated function with additional attributes for LLM tool functionality. + + Example: + @llm_tool(name="weather_lookup", description="Get current weather for a location") + def get_weather(location: str, units: str = "metric") -> Dict[str, Any]: + '''Get weather information for a specific location.''' + # Implementation... + return {"temperature": 22.5, "conditions": "sunny"} + """ + # Handle case when decorator is used without parentheses: @llm_tool + if callable(name): + func = name + name = None + description = None + return _llm_tool_impl(func, name, description) + + # Handle case when decorator is used with parentheses: @llm_tool() or @llm_tool(name="xyz") + def decorator(func: Callable) -> Callable: + return _llm_tool_impl(func, name, description) + + return decorator + +def _llm_tool_impl(func: Callable, name: Optional[str] = None, description: Optional[str] = None) -> Callable: + """Implementation of the llm_tool decorator.""" + # Get function signature and docstring + sig = inspect.signature(func) + doc = inspect.getdoc(func) or "" + parsed_doc = docstring_parser.parse(doc) + + # Determine tool name + tool_name = name or func.__name__ + + # Determine tool description + tool_description = description or doc + + # Create parameter properties for JSON schema + properties = {} + required = [] + + for param_name, param in sig.parameters.items(): + # Skip self parameter for methods + if param_name == "self": + continue + + param_type = param.annotation + param_default = None if param.default is inspect.Parameter.empty else param.default + param_required = param.default is inspect.Parameter.empty + + # Get parameter description from docstring if available + param_desc = "" + for param_doc in parsed_doc.params: + if param_doc.arg_name == param_name: + param_desc = param_doc.description + break + + # Handle Annotated types + if get_origin(param_type) is not None and get_origin(param_type).__name__ == "Annotated": + args = get_args(param_type) + param_type = args[0] # The actual type + if len(args) > 1 and isinstance(args[1], str): + param_desc = args[1] # The description + + # Create property for parameter + param_schema = { + "type": _get_json_type(param_type), + "description": param_desc, + "title": param_name.replace("_", " ").title() + } + + # Add default value if available + if param_default is not None: + param_schema["default"] = param_default + + properties[param_name] = param_schema + + # Add to required list if no default value + if param_required: + required.append(param_name) + + # Create JSON schema + schema = { + "type": "function", + "function": { + "name": tool_name, + "description": tool_description, + "parameters": { + "type": "object", + "properties": properties, + "required": required + } + } + } + + # Create Pydantic model for args schema + field_definitions = {} + for param_name, param in sig.parameters.items(): + if param_name == "self": + continue + + param_type = param.annotation + param_default = ... if param.default is inspect.Parameter.empty else param.default + + # Handle Annotated types + if get_origin(param_type) is not None and get_origin(param_type).__name__ == "Annotated": + args = get_args(param_type) + param_type = args[0] + description = args[1] if len(args) > 1 and isinstance(args[1], str) else "" + field_definitions[param_name] = (param_type, Field(default=param_default, description=description)) + else: + field_definitions[param_name] = (param_type, Field(default=param_default)) + + # Create args schema model + model_name = f"{tool_name.title().replace('_', '')}Schema" + args_schema = create_model(model_name, **field_definitions) + + # 根据原始函数是否是异步函数来创建相应类型的包装函数 + if asyncio.iscoroutinefunction(func): + @wraps(func) + async def wrapper(*args, **kwargs): + return await func(*args, **kwargs) + else: + @wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + # Attach metadata to function + wrapper.is_llm_tool = True + wrapper.tool_name = tool_name + wrapper.tool_description = tool_description + wrapper.json_schema = schema + wrapper.args_schema = args_schema + + # Register the tool + _TOOL_REGISTRY[tool_name] = wrapper + + return wrapper + +def get_tools() -> Dict[str, Callable]: + """ + Get all registered LLM tools. + + Returns: + A dictionary mapping tool names to their corresponding functions. + """ + return _TOOL_REGISTRY + +def get_tool_schemas() -> List[Dict[str, Any]]: + """ + Get JSON schemas for all registered LLM tools. + + Returns: + A list of JSON schemas for all registered tools, suitable for use with LLM APIs. + """ + return [tool.json_schema for tool in _TOOL_REGISTRY.values()] + +def _get_json_type(python_type: Any) -> str: + """ + Convert Python type to JSON schema type. + + Args: + python_type: Python type annotation + + Returns: + Corresponding JSON schema type as string + """ + if python_type is str: + return "string" + elif python_type is int: + return "integer" + elif python_type is float: + return "number" + elif python_type is bool: + return "boolean" + elif python_type is list or python_type is List: + return "array" + elif python_type is dict or python_type is Dict: + return "object" + else: + # Default to string for complex types + return "string" diff --git a/tools_for_ms/services_tools/Configs.py b/tools_for_ms/services_tools/Configs.py new file mode 100644 index 0000000..84e986a --- /dev/null +++ b/tools_for_ms/services_tools/Configs.py @@ -0,0 +1,23 @@ +MP_API_KEY='PMASAg256b814q3OaSRWeVc7MKx4mlKI' +MP_ENDPOINT='https://api.materialsproject.org/' +MP_TOPK = 3 +LOCAL_MP_PROPERTY_ROOT='/home/ubuntu/sas0/LYT/paper_dataset/mp_cif/Props/' + + + +# Proxy +HTTP_PROXY='http://192.168.168.1:20171' #'http://127.0.0.1:7897' #192.168.191.101:20171 +HTTPS_PROXY='http://192.168.168.1:20171'#'http://127.0.0.1:7897' + + +FAIRCHEM_MODEL_PATH='/home/ubuntu/50T/lzy/mars-mcp/pretrained_models/fairchem_ckpt/eqV2_86M_omat_mp_salex.pt' +FMAX=0.05 + + +MATTERGENMODEL_ROOT='/home/ubuntu/50T/lzy/mars-mcp/pretrained_models/mattergen_ckpt' +MATTERGENMODEL_RESULT_PATH='results/' + +DIFY_ROOT_URL='http://192.168.191.101:6080' +DIFY_API_KEY='app-IKZrS1RqIyurPSzR73mz6XSA' + +VIZ_CIF_OUTPUT_ROOT='/home/ubuntu/50T/lzy/mars-mcp/outputs/cif_visualization' diff --git a/tools_for_ms/services_tools/__pycache__/Configs.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/Configs.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1663d1cfffa763ad6dca15ab6e8ba49c99764164 GIT binary patch literal 884 zcmb7D-EPw`6i$D(b=@i1$*Su9J1RK{jB8RNyArgjG_7TVxAvlWkXdRm)!3rsit% z)X=;^d$4sZ9vU}&T-ui}`ABLFb-8zGJ<_i-Q?kKWU1+=%!bd@IQs zQWac9AqB~0vWOEPuxNtOg$EdBlp}&;ENBcuhNk`u*gRqaq&rJ$$*dl>8fv?{@}pK$ z?WpP5KXeC84+QBQ2r(DC;gkW!Y0R;|pag{xp@9H1?DOdr4bk)iivhhpZJ}=4M3F2R zNm+pddfQ;}{L{y!-M>kX|5rgW$?oTLkxQf%PDK*LvX5sE`#4k^YEwOGH@nUBIc99v z7((u<&b4uuaNA9t&R&?d&s{C;JYGbU#4M2#^3c$YFA^O1DUSVUF_w2%HEpEX1{&%Y z*_NE^h5IZt+v(r#$cs~CeA3^&f%YW z>!$`pJ!62ZiDyr|ysUeLKanpf4?p{lA50uYfk3l}qfh`r`HerN0_9oIixx9~E*k7E gtGt%4BMIp7@_!LKGL)5aAs=fpmVSG&T&NU(0eF2B9smFU literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/Configs.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/Configs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a648e7bdf338e3e4a47f39479c134293c3dad7b GIT binary patch literal 769 zcmaJ<&rjPh6i#TNC2gl^)h@8Zc9_J3sFOlVLDQy)2FThpQ4)Sdnyk>6kdZXOPA8gO zy1!!D{saD&T{u=bP1`Au2&wl>i^LzeYsc;OnPwsE`{S3qW%*bD3 z@4*dE!57A1EXH9i)|s(poSc=TBk$y`S!dRobLOmqQ?QCo(VBPWEpEiUDy7dgMKnaW z-e?}TgthZk)i(44wBKJ>Iu~n!-;t9Sr*RySO#tl3s|9w9uxI-u!r=$hk82@z!LJ;R zoh)R2)J@o^*Mw&4Zfw?hVO>Z%;4};n7#c5XI>0~ z&_O;x=lgZoYAP_KF<}rvV*BW#7RBzjubExGP2T;Z+Tcy#C!?v>O3;r$gi(xbZ-5*) zl_c~irs=zA0Q+ZA4Em>tuAw)GNm9g!4E>mZWbxrUHiVkM3!6>8#V60~C|Z#_7B&R^ zTpkUvW+eLhYLGM!4Y5b-$tfBXH5qoKqqIb^)N5<1+%wYxC8nx%Qf}8YHK^(8A@#_x zaZjq-qO5Q2N(xlew$z21Xzr%@ZACmxtBPovlD;GL?v8X-HPeNMo075DHSexf+Vb`h z%-rCf-c8F>dY{)NbCZ3V+xGvmswP)l?q_i^TCS@=#O}Nsya6QMAm$Hqeaf#xm$LtkToy^XU^BJ?J XZ1Lq4v+(25^0+doEPpQJ$5iVNYP#;< literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/cif_visualization_tools.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/cif_visualization_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8936a5decc55ba757a8bc322b35148c7cc068c0e GIT binary patch literal 4012 zcmbssTZ|OPae8KUcXn@gZ|{8^U>0LrUJ33X7$-Ve4hV7%i3BK|jakPT&27)^%`vmH z^vrO#tJw&f$B~%hkWC;C;Os?;4T>?yQVgeZw*d1NsZVNF(bW5MwiVl> zJ2_$$UBfO6rfV}dmp(dOJ9}~W-l5u!ON$?WIDhBU#owH)-Tr;;)a`%XJxgp?W28%; z++V!+$^4zqU)V9Z`Gp-N_{uOne{XK_!Zk~Ci>B?G#>o8K)!L0;16l3z{l#BQ#g;)e zVP6JOWj~jwnSh6fjJ!v5z+|Fp7}^+Hu4=ArInaw9bcvU91Cr=&WAuPV9G$>S>n=y% z#i({+t?1fhSp@blu9&8!y0&dDqhO%pwY^yt_N4BAb@;%m!|G4=?;oa#V}|2trcu__ zoH6RX2BeSN{}XRJBT~7bSvqaOtH!1+AXLr4!RQ5-hKUXvEaaHJ^+yJT`Rxh#oC1Ki z(3CLBee$GILFH~&s3ITDqP+r^ekfE#Ec+r3tr0E@I1E^XW6IUgA>p7P3$9pt5eXFu z3FQQix{@!QK$nH9;vpd{90m>*>F3hHm39J62$`7oZ>B*Sp4zR`%^uM(6P)b!J&Rww zxA4KqC$`d;$KQ?x!e-uH_|>gJjx*s+b^?FiJG4-}Te~|0Ub*o8XY=>o3&c0PHQ=!{ zccFIqgH;~2v!`n3|IpBzueril%*-s!-KxENHgM3T+c#_XPjbmQ*n8ti0TzW1f`{N- zlmx*Q#`!^D3Cq*cOsXRILRCB?%pwHXxKu%hAdtxLo&bXvlx>2ub^_+udla-rV?8u@b2$e%O!rA>6)3=oEScUje+M3GWQ> zN)#j}{V49j-MHt9cn;(o9mc)D=SSa)PmIaK9X$f>>=ynHxh=4_RzJq{^II6qi_ilN zp$CxnKu72S%!_!sPQ2{L~88i8pY6>l*KNdvF?WWWMNjjrUg9 zxIKP+R;(nT*X<|jbBrT?`l?t-22_5+PXbk|8>;rrg71UBuk~B;Cj1QN6soTCB`&Mg zkK<>rKtST_*0a!d9+A6lSM@1|BM4&$AD;Wm>fD3?A?vw)Ww{^X1w|7>8Ht7+Z*aLyRL}2Xer&4SgzZ5AqMO$Mqabbjw>983b@xdyL9j z*|4rlqy+|7LAj+V3+A`;=da&~MM^zg(h$+GVJt)B5HbodU~zsG)Hyar>l9oL8?6s; z3O1T)R)USz2bw7wc%bWshEUFz1vH9|kZ!hGq@kQ`+Jqt}Ba%&w@)-bS@yM2X|5;8K z%st2EI(Czi<7#HQk)Q|DUKnOxPrvb}Q*Uzg@G6?+=rGIA`lR8ws(qxKRGoTq?=Kee z#%53%2qauInEebZuNF0T3{J@=mS<|24ODS;t2kW4dMJG*ewsl13Rj*Kk3f7(*vbZA3Q%ywrr3 z16%~YilrUVu|XUXXR>9!YkAx@3N-E-`7u{DwUX_*pn!HvhwZ?5nYII7&28z>uvdWE z?novD2C|AOHxS1-rw}iusKgeE8vGG_UtyaVd60k%ipFWE2-3h-yxF$WeuuChfVQk` zO$8NcoE-}jz=|5O8RoAe!cydQ;lINJW45Iacq3lH^#*oq9Ud^tr2z{nwk=k!IKYZB zhuKNBoB=4}ju|=K;iZ~0!18>(B;!S9Fy3D*(Kg~09-i9>;+zox?P3fmXx&$`tcYe68jP%9AuawcDRAoH<|n?aH;mG(SeKi>cqxAMOFQ8y`Zg1=Gw6yz)cS zOc3WcKR3JZ*3H#*Thg^Nre_I5l75=bYnL!TUC;4lLT0L&y#9{}-q zJT4wciJ}ya$o~mPWB*FF^&#cB_#m^MMpPBsIaMWZuyy?m>;ynV1cw=HO`X6Ix&5r;8 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/error_handlers.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/error_handlers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7843a782afd6ad57c0353c2bfb181fe931003333 GIT binary patch literal 1724 zcmai!&u`pB6vt=AUaxnv+icR5wm}s0F_Y=U9PMGP|%;x2Is)?*_yx~6M(3vMBso1x`e+}bE| z`yqBq?;uuS)(T=4C(DLgW<@^6Y+m3MUTKqysBN+mE3cSt^@PY6t3c$;36Uy01Cj9) zBIB$Eks32^q58z&YI!!5i?L`rAEwfaI?nZ0xfLZckf{vfsOj8l%`RNN(V8pI#gX)U z*>w7HCJbJ`k_4Wd@%$M-D(}OWqdHdAcNZ3JUc2k_p59w0%9xG%%Dfu*a^BZ#jyK3< z){GYeK{#JqMfjRv&F| zuKn@rm*CyM|Lxw^*6!oq=G9mtJ((udj~Q1MCkuV?nz~BjMss;PZrL-bVIfy68nLhfh z<9VG^N+Yk!fwN3$H)d(5#}!I%r(QUU6e(q~4{jrfq|kZG4p7w*w$Hz3rpJ2E96V260wd7|W>}IXvzl8&n|NTaB z5u(8U6m&VezD#>tKkq#LY=nMJdmO=Ur#zAW<`&biO+2H`v)UXfkl{^?@D`=)<#bQ$ zRnKYjJeX6dF2JIkYUi6DckXTOKl*BK>(dd}*$=!hfNcFIo2O8nA5q;o+@7<&aLA@d zr?O2Rvh9DewtN4F5!K}T5x?8xzU0iw1Q|(u3e?mHs>36wkT55D<&fw_7}Y1l6m+WC z_MldJfgpNj$$l@0I{B^8w@nrGeFv8+b-28q-F-0ufxM)2ajVkd-%?!C5l0)Xh{%$l nPdlb%rfMdr{bqhc-_qA4(KIHiST$_pGO3YiYucW+FWLV9YA&$} literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/error_handlers.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/error_handlers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e097feb4427c1295061d588c191675708907a17b GIT binary patch literal 2591 zcmd5-?QaxC7@vK)y_epVyH=nekp(LDB)#KB2q|D_3d+TTm{x*k!e-r_yB^uwJ7(ud zuU1n`l{S&c2MBK(&(IHhTtkoeL5zQbi8*48x{^pD(AH0FijnZincY2FvGSSy@XXBf zduDc?-#qi1??a&gAm3&0pXsazfIsM<{t0ep{s+>z0923!Dx%q}&_Z8@aYji$3tk7RXBemgWGkHO4X9q&p!%Q(24OJ8t|IOf zulk4iWN1kwK#;~Ikp_Y+TM`KpBwXa9p@DsY&I0bsp$_q#0+#i(*qsO@bOUE_0cUc0 zhuD+o>}@}o=n8b@bSx{lLoDK$mf5=3$jCS*D=|e6%+Rez87p+4xA*Y=eg)>~+$d{t zoqEyY_hl4(NO970EaBMx=3}HeO<08>$q;5)l}U1{M`aC>mL-MfDI?zf}FJ9F;R^u)x}jqj`m1IxHzNJ>tHmY_l` zXEep7*#0#=Ryv+PM$CEU0>HMLno({LoFQ8rWCq!BrouV^12A6?Iqt3Z7Fv}ZWUc^} z8)UD5FZlW)GoT2f{Glj|d?Xb4i5X}hI~DB&I>Z4^Y!{=vJ1TyH2 z+KVDIik^&ZLKJv`n1{pQSAOjr%X;>dx@G}ss?C4XW{w)vRG(lmq*4&X1*9wP6(tY= zk1Hib28-+D_~Lr9LjjQkTrX}g2U*HYc8NJsF*3brVv1BZR69gO1*gnff>Zq#ugX|1bBJdQ%XEui%zrkTcVCuRuT^}T zUK;1j&Wcd#uZ1^1du+9kEF8A(pRe9ObA9I8r_&RsT)vu*$XbR})qkq)DQvBD**f`n zs>|6%@}~_;aJF&GFt=PiS?a4r;*YVAa z)%3Ow`}0u2P!;V<l8Hj(Gl(jfCE#K{qo*B}rRrsQ>ElCOmOl+~xBUHqt zkKcJDQiQ4nqL%>CQ$=*cW#EV_)#XRN-Hy_~isEEQ_0{;2m|2ctm^qeb_`iaHS^qm& z^BZWq<6k|p@@N0*TmID-cmC*a9TFb!JhNxyg9m^#vq6CoE=FeoX-wYisctZjmK*2k za5hY#RW?+@W>>Y@Jn+?ri=d)2tGBwLYE;|G#}+{e2WlmcR+rgdjy<3Y+6nvv)5>sN literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/fairchem_service_tools.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/fairchem_service_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc519c6671579f0027d49f4e3e22c4cb5d3fee83 GIT binary patch literal 7305 zcmb_h?Qo|S6ZgerW6XY0)hYmzKrUWLyMIXi&P|z)!di!33mWbzQZAV z2T8yHu4~D$rimt*aor})I3b%!9nU0jGMQ=X*6E+o57&=1Dd$r@Wjf77PM&A)4m=7- zW-?PqIPC2{zujl|dD-7Co#A1LfzQH!{daA*z%c)VFY!+XUj7ok?gYm$I-|1|W3vj& zVwtlzg`>Z`!ox3b33ftB*hwWx+XX9Si;76g2`g<&iWK68lp&B$S{Zv-8HT!)F?4H0 z7cVe+x>sU!$r`oCl(7&qu8ad_$eOSxl}V_}=)=|#`>1l1wvSlH?BmLDmNCY6P8cV5 zS!D{ON3EyqQ_3kSF=jn&Pb6eF@2J$CiN-(sr!O* zRzGD-LhWo-(4W?)p(d+8V@&HZl|60D8fU5;m48-$4qDF{!}^SVT0irUOHZUH^q^Pl z{YiwVe@>sdpSZ^=&+FWECVTc*=q*{+7q5H7tk*7YZe%%MAcm&fp5s`4(yY}TV)!Fhzp`~{bK}P9wT;Uge|5b*71Xb+ zyl6C_1I;s?`VEcLVK~HZHn2jopqvIJnia1}j9;;VT1U3Ex^5Y2&8Qnhvs8l+2S=T_ z-q0#WjX2H5Dqve}s!Mv!tlmJSQ6#bIIkpQ0_MFdG%&PWInqeee^ECBRVN+7wmTemz zX{j!DrP99Cx@H^drO#iHWqJ0hJU=U`6_~7Q8TFdCt!hxcm@DUtrEW9 zt9a_&0d<4qK)Kj>PNmXpX!Qz6%9r4Oo@$=Y6^r?$LNO1ohs)*V@*qrROceg- zbLH~#{NiGOh+Kr$_aO=);*eAf-9JWgz!>rI=KIE22odpl#K-GLlm=)ONB1MjA!4Ca zC@zFEkC#jH`SL21lWc0Z5akU0O zBCE&64#E3JiOjyZRGwd)4;-Zou{ckcD4$zcTv`g2vFBQm-HXL?VQC?N%ohp^rBYBH z0ErC0w76JW2*nEX#ihdXAB9Agk1G>ba=<8&>Ep`8l^g(xY#&!9uH*nnWc)ZJKFV_c zibd9s&m}%eKco;@KR%cE^ans9>&KOeD>(oXSwF5!T*(2D$og?*;z}NdY|8Q`B>f;6 z6$@p^Q-Q_f&y z{o^AJfJD}hiyeaZjZ%!PUoPj%`7mDMWgr8kMOu$hi7Qq-u>Nv!IfTSV8~}-|9~UbY z58wkJk@e$Z@$z!NVv+UZ`o~8c0Ew(07mLH=kStH|6ypmonibFISL>|`veM|oLjl{@ z;Oo}m;Q`BUvLvaq*O~T7or9ALYja%&_&KKw7Hg4+B_FOyEPXddG7GYbCITMSRX3XX!#PZhDsOaG4$=8fQQHnEXQTo3@fo?>%bF6par9I~{w^*I+ zFz5*ASi10#|K?bi>u@_)6pEK?Wf8TXkx(1!oM0$)$)@b}oR zfR5MUDN4A(gpT(TI9>uAZ`>1i(t6U9I&d6|9Pb9;^+Y%ET0&1;4@=YuQ_UGraPMbd z`}9BG-h10x$85`}V58MbA=e9vSIimjtxX+L;dWPMof~)xx8*m)bx8T(w8fn3q&maP* z;R|?9A{dxH=eT~dS+~sktK=!DASdy03Lj6yBby}8VHvfDp6!N8d!{9He?Ng7-6hi~ zgzghOWE39{_3Ir^j0^;uI|XeVcLJ<0vZp>11&K{kFBl2Dpoq|PZ%P%6&*+nYf(T!l zN*Yk`AWeX!*bckH?m|NFz`8oyv;1_I2U~J_LQm?chkRG)2s?=me~TuQ4!@JqMQUAs zN8E+v!odYdYJ-v8PD>+i2!UT+IIIHUfS zyYu`ynFWd3yWhdjM_<^2^LPr*DtL7Ehlx=&>u|uK=QWyoHnM3l0s#ML&G6KqT|Zbc z@;tPW^Y|D=DTyBHsS(KMu|jYS<5gvJ>10jol=B`gi0nppwAN72^*(P46Ib4+ZKlwD$t_JoJTT zQ<-+#WABAV>+&5io=S~}_gGy4!Z7Kj(7a&aL?>}e>xe|GZZF~W36{srVGK)&UnXnPUvIa$xh}ViF7h=uzI?ic`ZXz$2fJlPd@%O zY+n1{`Ik?A`Szp#czgeQ_oJOlSiiFR*KkiFRBi1pLH8tQ@KJ(C;EWsXzmvAM4Y}%A zmU9R0ZDiAxJ*aSCPq67uRUs|cgX@Ey$G{$i7Xb9OXGNNsnUV3L2ZU*je9ds-*zL+^ zn=bZ;H%w=ZufAMRm&#YNvS!sBxX0PHvn$fMb6XDfd=dPr)>@Ia)7vppiiCWre06_iZ$rFMc!wxvI?rTmm3B4#I+d5d|AeT%E|xT)M1VMF0|cq9dvfn#0*Qjiri zbVE1Nmi~1HnAk*<0W`#s1R<#-0R|Gk{#p6B!~^VTi_#m-sen*W=FD$7|dOM z5)xVtsxd$)b5t@|6U~GsOn(TgfEB{Ey-Twv$szVw{KSxp{>tEz6gH-e`fZaqbw3Fg zzTnHj0WyV$7I^cIH(KDmKt<@;C)abYFxS!}j2irD0mejn;ZOA-5pl^yJSgB-X-M9% z#o(bxRD(YhsFdvbqdlnUAd3r_RvM=jBy(gwehRi8dcLCDNRJegFGC|i-z7)zftI4o znfg+|kI;#z5n^nMa%CABeDMMh{HA5R02X3g0*?&%40;VFQjyJYkUDv+!*@)iK|L^} zzY#K!IM-0>K>kQ5hqV$X@FF)gIx#XmBwXNF;rE;X&&M3g{T?#*XB^KxmV~k2{Q+t3 BLk<7{ literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/fairchem_service_tools_test.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/fairchem_service_tools_test.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf0efedc7b135cf08e17ba9c3404ed38796105b9 GIT binary patch literal 8247 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 BK)nC} literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/fairchem_tools.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/fairchem_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5806247002d8a2ce470534fdd4cc498d4bd55807 GIT binary patch literal 9541 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 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/mattergen_service.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/mattergen_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d6391bba435bfc1c4fe3809f929354730125603 GIT binary patch literal 11094 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 Ws;iNjP`;W49K3xEVGiLGzWg6ZSG_<0 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/mattergen_tools.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/mattergen_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8aedab149937f7c8ae8049669adc0224951a21f7 GIT binary patch literal 15297 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 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/mp_service_tools.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/mp_service_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ba2bfe266efe713c50caeb9b50221cbfd3fd607 GIT binary patch literal 22234 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 Z05krX?~jxoK_T#2>pl51Ihg$I{{pzYzfJ%E literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/mp_tools.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/mp_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..020fb00e267756e5bcd0050e8057464a352be898 GIT binary patch literal 14774 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 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/mp_tools.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/mp_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fc112d31354b6d03156ed75e86f4b70c5e12e4b GIT binary patch literal 26366 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 KOix7FRQ|tSA;BB~ literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/oqmd_service_tools.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/oqmd_service_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f2d4b701e229c8b5923f1a55faef413f8e4ee01 GIT binary patch literal 3016 zcmbtW&2JmW6`vU{m%Gak$+B!oPNHmLCo$VttXrokgrbd`M2UgKQY*EBw=jqmXGpEQ zAN1_dlordv4RbYM6zHXa0Sf4)|3U9P_tN7uw;l@L+GBHY`(~D^=)?jFbQkmW=Dpwh zn3*?|)awp{Z}Tr-_Ws&H=x=H;`;}qv9+dQRs3@X1L|)Ni59=}UO3;$fh{|3WtGQBW zMisB3`$lL*wr4}XOwG`Vs$Nz1E1|Q47@E{r{TO=ZE;j-o z9{8b4ec`*^guA|bBI(XZx=PYjlA3eRPg#>lBlaUEtCXc39`uz4S)CEe-gR0YrVC%-3|8L&F{VKeiCf3t<4)vX>uk8Jnmo>g6TI0Z57I&prpKn(62GYIr;)U zAtU@4XA1%yU`lA|5x#+LA!>Y#e)q2t$;pJ|_!t+IQ7JD?O2-JJymVmjrYNg+jA{8E z;@5;p%`yHAE855qmE53}F&UM6Xk_N)iAAkrJgVds{!wmD?7VVdOq^qMj4_HWMWJ?K zkD>s7l{&}x19W$5WaSqBJ*|%H+&ZWM<;NpO)N_Z{#)OY^=b%CB_n=1`(4TvNpf?Az zjTN+nMmV?WJkYhSFAzPKW2hINl;1@o`~F>xa~^PhliMd;ba4q4v$-umc8rysGgM{0 zePa9l9k8sb{ks7E)$$tuXLd!Ln=FX)AYB{dF~pBvTtTCHUY{(|DsZXZ1#yusjWN{A zW88xseM}VnV2Q5KOONoVx{Kmf1BvCMe_%9fU<7NlGPy)w0@TJBym&%Xr0C0v^A4=o zsPU^tTrS!n9wPe6KPA8H0t3AJRIzCg62`ImP4&{wgDUI^x>G({PWkC7kb0}|}N@zVWEG!?JA689zIHo!^jd&WLz-E8qi=6!{KnUT9!GtzXWp&Xakm1XS}>_ZSQ zf$N!Z4eAXrwH(Bh9cC9k-7v-ES+EY;ecrcTs;A# z(S?PTrr+-~Xv--)7W$s0h;2U%r8V7wsjTe#DV!2s?5(ourm_V5lJu!(bLP{w+L!9K zMV{FW!Z7w_g|mL>cbF`9!^9U}RV3}m=l5tbjHUhIVTTo0zZoXI9^ZMrmizB$L$r7ueZ9!j%Qgv2^IOwv&1c}5EFBF}+WNYH7k zmy74@UvIOJMJj-1@fz{!aAfy_n9;UA8kOn1=ja2X<@g1qi_osb><@^)3!|?#TKh@F zT7$hoEC#K2zO&N`v!j-JU!{6IqO=8P_Ce5LseW~(EtM_WQ(8e<>mP9o!0J%a&!8d? zorZxg;(2W0#gc{1l7kIZ@B*GE4Prpg!K=6i#legCB57dqu;3sd=ip_KZNLm5JS5MY zvH`q=eE-i2?=0d=K${016VOgZ&$r}R^PH@x1%e~bm%$>>>_aZ}Q~rh;l=jja#bzQw zqL*Kq;;65f4Vahq$s04}rXIs4{iIlKtKC-5z;>HAVTNB(mGWIjbEf)%lCypoC3Fxn exQ-z*t58|Uf|%5bpIF!-4k6$R`8Of+&;A2wZC@4u literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/oqmd_tools.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/oqmd_tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74c4847b7cb083955e0a7032e6156bd194999e45 GIT binary patch literal 3008 zcmbtW%WoUU8J`(0m%Gb{WJR_fNt8|O#AX|db?dZ%P^3|us4lz|G@OvL*bsr(%`92ci3JqsF6RAxzwb5k z&BWEJgW&t3^!LtRY6$&R4Q9V04Bms1d=3>w6bEQOZ}C3XW3pd>mIOvv+%ICaRtU_n zv|rMFBd|hy--dpXnt>CR_shCp3Y=ZU(5S+yr+B}{%G9DZbsiB~W;UCnl{Q&Hw94k% z2CY%+b7P42=PBMn^|_~@3A$Csvid&rdctq_f}Ob6g$Zj%a6jtYybXPOGm2v22}Yk1 zpjWFw5H>{|2hhhqkeAv_v<{oCLl*iiFKD*nup1}7@Z+f2=5g4({i{ztNH@0>l^X&O z_r1WSp77ju%w5l&aJn;`uHtkRr=~pc5>_YDh`f-=GG$4N`&}hLmS>pK%QwbKDCDpg zca90oGbu#m?Vqdl~Pu8m{=7$*yT zKv(}AD0@J?1JtlfUU`g9-W_YSq&0f&I~uvu@g6e&tJ}_4$3E6LuL91kT!Kb6cyC~r==G|F4k@tG*gfb!?(P%cloG|H9f_{AuF zRIjIhb6oiT?_{y=Xq-*nNj7v(q0Xd=4flUc6*TY>ahGv%;tKB|V2Rcz{o$by-DIQD==b~U@%@ml$Gp>+HycLB=f!KV)%F+T;HJ<0xTZWh7W6W-{pJ_RX|>D8gX9>+ytf zNe-{2&L`tkyx~gRm5AE_C$T4uL(bai;)~S#7XLOScdw+R?n*-$F0m_%+A7$Gz+M2; zQ{yVsTcB#uk0?7%&wt1{Bzh$qE|uH~8*aM%f@z)XQ{p(O>o^xLx)UAU?c9YvOWa-M zM4(vFw*%M)rr+(l}H^$s&ktqI!Uq|e?%UWvpY4N~!am@GKC3m73FxFp_ zMHSU!S>8-eqSlSThkYUo9VX;F_xyx4Vc#|rxa@lgKd<8%ut++;@q!-H0=RktNTUsb zm8RG2GH8nlJQljrQj<+D2&6UMfr%^~dI_8op6{)q>L#)P^pbR`v^n!=Q|(K2+ahVU z{UC@uS>mi4cr7N2?I8AqEQ`1qdi)-Z`;oLiJZ`c4>NkV9(}CSw)J~AbQS3)@?xWq^ z?Hz4`)>zK(=I3R~R~d&!uh-|ey7XnC#{+4$a+g?>!vu!`)RRU6?;`2IE5vU#)yqXX zhu4}cV4-rLnZHJ46^`tIA2Hh0N24@Zck}_#e0)jCBD5_r`xD~t!|2<08;5bo8oh&F zBzlc^eze;N(vyaIUnM#oVbXvz`@nCpM8CR{hRPOscfEVUEdZ%PNj`&$JalRXzJTYk zg%=AJHVY0mRKW{)p45l|JqNGh3KRz~;tQmP$-|t20H1@Gfwu-Lfbft!bBYGg67th; z7T#IJt1veYG$x=;M$fn8S^bNvckH=vbCtXEs zsC9etwoJIG$FMm+&ckdqo9g-3Z1M)I@XM-FwrelWR5ui#_46>My@0_r44zqo%0d?S Uq>}%{!VYl=0b9tw37LQP9|SvM6#xJL literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/oqmd_tools.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/oqmd_tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38158d15d8e40068f85fd2b8fdc7d2d2780c2adc GIT binary patch literal 5499 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 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/search_dify.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/search_dify.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..860f10a69b3696eb0674083be1c1d30be1b29c03 GIT binary patch literal 1682 zcma)6-EUMy6u)!7_p20C5>b-|W6UPf?b1k~QIszeLPAI&A?(Y|?VZ`}rT1f!iI}|FOqhHMUvvrtmN4?9QvZ)i2+LUUWM3&MsIOB?YO+TwX;{UY zr~5|9KoT*qjty*{lkp%<6BB1h3TKH~)^QH&XS5kqN|Ov8n3U^s(z`%2UEe2ayl$KRoZQI|~Fo>iuy zZ%qc_36J0kvBvF)#P7rY&durWuS=bUyX}Pso$Kd2m#%d0eBHixyK`x-JAc3X{jJsI zv+ai$@-kNg+b2AON#szs%G@yMDPVwWpT~sOxXQxnB-g=XOan(idC{^k5DfnTU-T!4 z(+H!cjAbTWldc1QSY42t3f3CPMGdLX6HYB?O|`zAq2qExZYT{Er*Y<-+SD4_aiytO zq^7~tMXjOZY)fwHOmFCdDP}C14ZVe$X2V=e;hZbMYO0Zf@jweDx^*oCF@TE!t8?-$y$x5kpOFqbepHq;SOSZd$yMNCpt8~n~rY&@1zLE*wE_Zi|uPSx{oe&ZY*@ZebAfx z3eNT(U0%Ka!)WJv>!s7VhcBJB=V#mJe}edTS~vIaeKLKzvpCmVy4?O|z7M!FeW(59 zSuh7z08H)KmBefPg9*l}V}(L-$JXKEw(Y~k9mV0|NO5f2$o7%KRI%Vp*ldeWqR6h0 zsBY{gHYIeZK4@2~p6l2mp$f;NFo0Cc?TXn%NZop(t!MXy0V4t1a->!zNunL#=C~I+ zlWtJqQr)n}oh`+!qj`<%ZcvR`L`A;w93@dT3?gFrAtrpC69!X+Mu}0&#axXeLfxY( zJSBx26U4@_%2fe^8_e}d7&AT)2d)FhEy#Nt+?zgOHny1!Pa$+-A8-j<8grRP4stDW zLQ3*Fmt9QX5-1FCE6~$7#Vkeqae^@br*FeN7R9A^@yhD*^4L&5OOqF6QUZ~W7+|S} z5rZ3(lZ@*Eg%fcdq_~b&r7l?AD6DE;Xyb@06~cIG-${og?~^MLqhM5!L7}oqT#2bi z-w-1;xdM3VGxQ2qiL(KqrW|_~tj8Vz$@B-7;HH0(%<~;?P!2gFKT32f*nRd%VIuTN zAs&wd78gDoIa2WIwSuTdkyQ?<0P7w06 zI%ujg(${oDM*Xh;Bbyr(T~Se1F;Q9pshZE%Or$^4(*yD**_@WwxM^9w?FN>`jpr{U zk7x?)<_EZ8N43Cl!#;rqq*&RlL`eYX5Nx6Eg5aqawKSrdQ09eF5xfhW!hxuYmPOx7 v7HAwKG`u24lYJjS2F4!QDe8740&hwt$|4hb7Rj=>nYy+~HFs~6)wO>Cm>(~S literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/search_dify.cpython-312.pyc b/tools_for_ms/services_tools/__pycache__/search_dify.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f8933c3ccc84492ff5ebbb5a25d368ff7c78a70 GIT binary patch literal 2352 zcma(SZEO@pboTakZ}0Z5J#2wqKhn}R;1Sy6K#Xlvgcg-FQ4>uJjmc)aGi~?Sk8^gH z(&o<859CZRQc+r@Xh8)`L5TXJQKKgOG4T&tO)iTj8o9gHKOW)_qyBK_u1k&4#M$h; zH}Ac9@6G#|`63bt0Ww|-{4~-d0`MbUctco0ZhkLh8=!cxf>w&=l1AG7lAP)l^2IkNOCOvm&R!_Y zUM`+GT0DNDbZ)9}@oe$<;nIUWH4T@0(m{CK z_1k88zsnP24mOmPkPzJf(11Tn{+nMBhX(*dgU)1q&}UO_Wt;@3NZ6r&)|X`=mtj;U z188ue0E4q!)*l2m>&d449<~ZIz6_i3!_WnBeubOmGF*yZmGH1EKLWA=JLs)bl|u1W zRhS{E^OBn7?TE+C@Loa~VAp!oRMf*(;i_a-$Ox&rRgQXK^>z|%3K7A=v*}nA2vBIIZ zdn-6qSE;PtwjwRSGlh%Or3+`TFTD5j$43fhpQ+Yq;pmH{bC=uutVf1+EmBPu_OCKF8zCK%g_44xJm&o4bk7kzMeQSI1)ag~Dhd*33S~zm3aCDxOe)061 zd;5Q!e7ZPyc=^ms;pHP$hl`Wv3c2TrI&uW!Q{m7;#p=YCQQJ=UCKAb>ZQaQ`@9IwW zB)gMalf8Fty=!aYKr*3>%66BIEK43i)K8Ny_850O@rrqlb7?I3dHt{8GS~fW`I%vB*^+K^+p3L+(TpFuH zE3I0W(@Ys!E<1v3H@N4Zf~r>&YuT76rNO}D82~QpU=6RMn7?uaVyPOTzs0IztcHYU z*wG}ycQoRNSVbfno9aQt6$#ZCHeCi;VWO4RtT%ptFfnTCDB%n_hV3MFY<(=DO^hdW z8CzYtl1|XO*^-7$Ea_H)n~d^C1LvLDb^OYM`sV3-rtX=mJ3q3}|9R)vbr0u+G7n-K-iVxtpFYOZB*T_Puvt@W+bJEwNe^`6Izb=!zC0n{~3Z<*RM zlU=;6E5EjFTAGsP9$#GBm0#bKZ)ncPTJmdKrk|L4Vs6vo+RZm2f%PGxryfL`t_qF6 zT^H+r0Y0wd`!%uIjv4vn#+inzwe8;s?SGUxLjKd*LBjD&tZx%DyL)3F3$FMWf?r{S zeVy!;aHOx5y%J+6-pEmUD@XB8mf)@+NxG~WlH>+fu8ugfOkbFIjT?}yaYIqfYGnmT z?+vRXR;4oW1|otx$>avT_tnDmmBE`Sk);Jm6GaWsmr|1O#tH*F#yj99K>^-Qrr(9T zNlQ8!x)<*xOOjDwVKS9jhGDLOnrlG(4s5svYOjIFccASDCYTT2o{#qY%CSD*4PhtK zao#Ee0v0+d;4|eHy1E%8>@sDS`+dM4&P{y9#(v<%+(7Pumj+%K__ATgWp%OPzQypa PZ}{D14)`1Ky~ONalSQ~D literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/__pycache__/utils.cpython-310.pyc b/tools_for_ms/services_tools/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afac9a32ffe20cef2d652369fc7dca0b2ec0aadc GIT binary patch literal 3315 zcmZWrTW=f372Z3SOH!gFOP15b7Lckn(~2r`0b0XoEzwlO*rEiBhNFF1tae7!%FA7L zc4%9KR2Z;P1O?pYsXz;%k&3J~p#h(&Ub6_TU*N4i@gHZARFBu|{jhC4Ci;R(RK+B{|CLSy91T8&M+)1KsDwWCpb!Vx^cN2DHZmY-rxIK5X;WXluhrGwb zB)&4Y*;r~VtTk#*Es41w@GEosyc&h?UQ0utSN)(G#Lf#io6@YP!d~y(o7z^S(YX1FPF zdxTpO&kP{M-nC>#70-xJWJm0WLk#+-ahM zG3SIm)N)feYa8CuM#H;N-vSfpG`=UCPS^9hp?8<=y9H>jH?ME3H<~R`fCd_qZW6}) zg$}lGbI{yc-?$-i(8QDOUE!>@S{vTRt@V$$gtNgTw`Tz>0@E+bo>YI z*jl66Sbx3Au}&C=$!MSMdDwaVR&(j5cc$ZcI0a?ELGR3na@KLFR;zDrqGCij>){6h zO;f3tL7&Zf?N+^Yx=%_2MmhHB7dt*n7kH9HcSG(LvsssK*KX8XqL|IN-3#tg{(oEt zhG$$q2ZbTmi3-jSEGIhH(CCj3ZCC||+X>r!qYB3Bn?1g>@bhNH6t?HZeusLVa6Hsv zFT!%s^M2j)qd|)eL%f|ZqGDoL0?h>p0T=vSVQ<3nQoQ`d;%?HRi@oh$%zKL;{H(PY z_4gO?Y^k@CFt3v?rj*?aK|#O+Ko=n_L}|6VFRW~T^DtM6QO;Hj)mGU>C{||Jd0b)^ z#RU||DrTe5KnOkMOpJS;AIAyzF=V8k$MV=-knLbY2H{j;<0#4~Ca}TWi4vc)Nd%@) zpeETgiWwBR`s?FmrMD0>LEtngp50MqwOJKj{nUqbbyAZ(g`E!L720h0U>T2EgD=IU zxUvU<{6N{$x;981J_A0JXip5VrryKtee?S-pZ(#FYhm12fA+;+|MSV0&;R}pp{4It zbWt3-o!4WLnDFnX-fJ>?AraFE4Y>EZrD6$lJK@QD!V030g07@4q*g`>si^F2SpAeX zP!Xcs1{!B9&Bj&$O^YB5O;a7!fYfy7$J%0Y_Z>1%ke)YSj|2%f}rP@uA>$32k&uK+<^YsUu`Ae*w3I`RHZ12&4 zS2STQOP3b<4Z0te$AII2ZTQUZqyh$YLq^j^EEI+ufrvY~>&FC?muZ?Zmat}}Ak`|( z;ud;cpfZtOD!D6W+BBvN_5lHureQm#MA9P1XgBf$`r632VnAi874MvO*6R12wJO*3 zvaPU`eSmwj*iYcceu@HZFLdOdN@eR=woHBR7}=KJg+bDdG|dK=F?6JjrdqFN%rUj7 zo>T3I+PCZ~^a|h`JxCXHX-ScXP(bH7)}+*>G`M*J6gkmO)WfV59ExZs+M$|PxV4u% zh6GOxVj@+DeWU@EfCx}89Op;DHWRVG=)FhcA=h}ELnkF+xI`lt(1Z%*p z3Mf^XZT7WlwO__~E~6X7^J{3-=DwirvVQ;w35y(%*|g$KNu7v zdXLJmJE(UcmH2IF?N7{aEzEZo=E>ar>XrGmEAyMk8HlPKpNkv;L+^CBFdEI3_5L(0 z+lJs=-QE;G4mAufBR2XQZ`d7}pfV+Lo=1`Zl3f$(>@tcL2x0O4F07TMN4vXE{FsM< z%$s6jScOcG-J%)ZnnfNW)J>^FnQn!ZsZD0EEYe)U`El!V`Lp=|Qz4T{!M>VZBz}p^ bPu~DxD7IxoJ}=7G(aPG`g_*oP;r!@-Um<+0 literal 0 HcmV?d00001 diff --git a/tools_for_ms/services_tools/error_handlers.py b/tools_for_ms/services_tools/error_handlers.py new file mode 100644 index 0000000..9669eb6 --- /dev/null +++ b/tools_for_ms/services_tools/error_handlers.py @@ -0,0 +1,49 @@ +""" +Author: Yutang LI +Institution: SIAT-MIC +Contact: yt.li2@siat.ac.cn +""" + +from fastapi import HTTPException +from typing import Any, Dict +import logging + +logger = logging.getLogger(__name__) + +class APIError(HTTPException): + """自定义API错误类""" + def __init__(self, status_code: int, detail: Any = None): + super().__init__(status_code=status_code, detail=detail) + logger.error(f"API Error: {status_code} - {detail}") + +def handle_minio_error(e: Exception) -> Dict[str, str]: + """处理MinIO相关错误""" + logger.error(f"MinIO operation failed: {str(e)}") + return { + "status": "error", + "data": f"MinIO operation failed: {str(e)}" + } + +def handle_http_error(e: Exception) -> Dict[str, str]: + """处理HTTP请求错误""" + logger.error(f"HTTP request failed: {str(e)}") + return { + "status": "error", + "data": f"HTTP request failed: {str(e)}" + } + +def handle_validation_error(e: Exception) -> Dict[str, str]: + """处理数据验证错误""" + logger.error(f"Validation failed: {str(e)}") + return { + "status": "error", + "data": f"Validation failed: {str(e)}" + } + +def handle_general_error(e: Exception) -> Dict[str, str]: + """处理通用错误""" + logger.error(f"Unexpected error: {str(e)}") + return { + "status": "error", + "data": f"Unexpected error: {str(e)}" + } diff --git a/tools_for_ms/services_tools/fairchem_tools.py b/tools_for_ms/services_tools/fairchem_tools.py new file mode 100644 index 0000000..423ab2a --- /dev/null +++ b/tools_for_ms/services_tools/fairchem_tools.py @@ -0,0 +1,386 @@ +# 输入content-> 转换content生成atom(ase能处理的格式)->用matgen生成优化后的结构-再生成对称性cif。 + +from io import StringIO +import sys +import tempfile +from ase.io import read, write +from ase.optimize import FIRE +from ase.filters import FrechetCellFilter +import os + +from .. import llm_tool +os.environ["PYTHONWARNINGS"] = "ignore" + +# 或者更精细的控制 +os.environ["PYTHONWARNINGS"] = "ignore::DeprecationWarning" + +from typing import Optional +import logging +from pymatgen.core.structure import Structure +from .error_handlers import handle_general_error +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer +logger = logging.getLogger(__name__) +from pymatgen.io.cif import CifWriter +from ase.atoms import Atoms +from .Configs import* +calc = None + +# def init_model(): +# """初始化FairChem模型""" +# global calc +# try: +# from fairchem.core import OCPCalculator +# calc = OCPCalculator(checkpoint_path= FAIRCHEM_MODEL_PATH) +# logger.info("FairChem model initialized successfully") +# except Exception as e: +# logger.error(f"Failed to initialize FairChem model: {str(e)}") +# raise +# init_model() +# # 格式转化 +# def convert_structure(input_format: str, content: str) -> Optional[Atoms]: + +# '''example: +# input_format = "xyz" cif vasp 等等 +# content = """5 +# H2O molecule with an extra oxygen and hydrogen +# O 0.0 0.0 0.0 +# H 0.0 0.0 0.9 +# H 0.0 0.9 0.0 +# O 1.0 0.0 0.0 +# H 1.0 0.0 0.9 +# return Atoms(symbols='OH2OH', pbc=False) +# """ +# ''' + +# """将输入内容转换为Atoms对象""" +# try: +# with tempfile.NamedTemporaryFile(suffix=f".{input_format}", mode="w", delete=False) as tmp_file: +# tmp_file.write(content) +# tmp_path = tmp_file.name + +# atoms = read(tmp_path) +# os.unlink(tmp_path) +# return atoms +# except Exception as e: +# logger.error(f"Failed to convert structure: {str(e)}") +# return None + +# def generate_symmetry_cif(structure: Structure) -> str: +# """生成对称性CIF""" +# analyzer = SpacegroupAnalyzer(structure) +# structure = analyzer.get_refined_structure() + +# with tempfile.NamedTemporaryFile(suffix=".cif", mode="w+", delete=False) as tmp_file: +# cif_writer = CifWriter(structure, symprec=0.1, refine_struct=True) +# cif_writer.write_file(tmp_file.name) +# tmp_file.seek(0) +# return tmp_file.read() + + +# def optimize_structure(atoms: Atoms, output_format: str): +# """优化晶体结构""" +# atoms.calc = calc +# try: +# import io +# # from contextlib import redirect_stdout + +# # # 创建StringIO对象捕获输出 +# # f = io.StringIO() +# # dyn = FIRE(FrechetCellFilter(atoms)) + +# # # 同时捕获并输出到控制台 +# # with redirect_stdout(f): +# # dyn.run(fmax=FMAX) +# # # 获取捕获的日志 +# # optimization_log = f.getvalue() + +# temp_output = StringIO() +# # 保存原始的stdout +# original_stdout = sys.stdout +# # 重定向stdout到StringIO对象 +# sys.stdout = temp_output +# dyn = FIRE(FrechetCellFilter(atoms)) +# dyn.run(fmax=FMAX) +# sys.stdout = original_stdout +# output_string = temp_output.getvalue() + +# temp_output.close() +# optimization_log = output_string + + + + + + + +# # 同时输出到控制台 + +# total_energy = atoms.get_potential_energy() + +# except Exception as e: +# return handle_general_error(e) + +# #atoms.get_potential_energy() 函数解析 +# # atoms.get_potential_energy() 是 ASE (Atomic Simulation Environment) 中 Atoms 对象的一个方法,用于获取原子系统的势能(或总能量)。 + +# # 功能与用途 +# # 获取能量 :返回原子系统的计算总能量,通常以电子伏特 (eV) 为单位。 +# # 用途 : +# # 评估结构稳定性(能量越低的结构通常越稳定) +# # 计算反应能垒和反应能 +# # 分析能量随结构变化的趋势 +# # 作为结构优化的目标函数 +# # 计算分子或材料的吸附能、形成能等 +# # 工作原理 +# # 计算引擎依赖 : +# # 该方法不会自行计算能量,而是从附加到 Atoms 对象的计算器 (calculator) 获取能量 +# # 需要先给 Atoms 对象设置一个计算器(如 VASP、Quantum ESPRESSO、GPAW 等) +# # 执行机制 : +# # 如果能量已计算过且原子结构未改变,则返回缓存值 +# # 否则会触发计算器执行能量计算 + +# # 处理对称性 +# if output_format == "cif": +# optimized_structure = Structure.from_ase_atoms(atoms) +# content = generate_symmetry_cif(optimized_structure) +# #print('xxx',content) +# #print('yyy',total_energy) +# # 格式化返回结果 +# format_result = f""" +# The following is the optimized crystal structure information: +# ### Optimization Results (using FIRE(eqV2_86M) algorithm): +# **Total Energy: {total_energy} eV** + +# #### Optimizing Log: +# ```text +# {optimization_log} +# ``` +# ### Optimized {output_format.upper()} Content: +# ```{content} +# {optimized_structure[:300]} +# ``` +# """ +# print("output_log",format_result) + +input_format = "cif" # generated using pymatgen +content = """ +data_H2O +_symmetry_space_group_name_H-M 'P 1' +_cell_length_a 7.60356659 +_cell_length_b 7.60356659 +_cell_length_c 7.14296200 +_cell_angle_alpha 90.00000000 +_cell_angle_beta 90.00000000 +_cell_angle_gamma 120.00000516 +_symmetry_Int_Tables_number 1 +_chemical_formula_structural H2O +_chemical_formula_sum 'H24 O12' +_cell_volume 357.63799926 +_cell_formula_units_Z 12 +loop_ + _symmetry_equiv_pos_site_id + _symmetry_equiv_pos_as_xyz + 1 'x, y, z' +loop_ + _atom_site_type_symbol + _atom_site_label + _atom_site_symmetry_multiplicity + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + H H0 1 0.33082300 0.33082300 0.69642800 1 + H H1 1 0.66917700 0.00000000 0.69642800 1 + H H2 1 0.00000000 0.66917700 0.69642800 1 + H H3 1 0.66917700 0.66917700 0.19642800 1 + H H4 1 0.33082300 0.00000000 0.19642800 1 + H H5 1 0.00000000 0.33082300 0.19642800 1 + H H6 1 0.45234700 0.45234700 0.51064600 1 + H H7 1 0.54765300 0.00000000 0.51064600 1 + H H8 1 0.00000000 0.54765300 0.51064600 1 + H H9 1 0.54765300 0.54765300 0.01064600 1 + H H10 1 0.45234700 0.00000000 0.01064600 1 + H H11 1 0.00000000 0.45234700 0.01064600 1 + H H12 1 0.78617100 0.66371600 0.47884700 1 + H H13 1 0.33628400 0.12245500 0.47884700 1 + H H14 1 0.87754500 0.21382900 0.47884700 1 + H H15 1 0.66371600 0.78617100 0.47884700 1 + H H16 1 0.12245500 0.33628400 0.47884700 1 + H H17 1 0.21382900 0.87754500 0.47884700 1 + H H18 1 0.21382900 0.33628400 0.97884700 1 + H H19 1 0.66371600 0.87754500 0.97884700 1 + H H20 1 0.12245500 0.78617100 0.97884700 1 + H H21 1 0.33628400 0.21382900 0.97884700 1 + H H22 1 0.87754500 0.66371600 0.97884700 1 + H H23 1 0.78617100 0.12245500 0.97884700 1 + O O24 1 0.32664200 0.32664200 0.55565800 1 + O O25 1 0.67335800 0.00000000 0.55565800 1 + O O26 1 0.00000000 0.67335800 0.55565800 1 + O O27 1 0.67335800 0.67335800 0.05565800 1 + O O28 1 0.32664200 0.00000000 0.05565800 1 + O O29 1 0.00000000 0.32664200 0.05565800 1 + O O30 1 0.66060500 0.66060500 0.42957500 1 + O O31 1 0.33939500 0.00000000 0.42957500 1 + O O32 1 0.00000000 0.33939500 0.42957500 1 + O O33 1 0.33939500 0.33939500 0.92957500 1 + O O34 1 0.66060500 0.00000000 0.92957500 1 + O O35 1 0.00000000 0.66060500 0.92957500 1 +""" +# atoms=convert_structure(input_format=input_format,content=content) +# optimize_structure(atoms=atoms,output_format='cif') + +# 添加新的异步LLM工具,包装optimize_structure功能 +import asyncio +from io import StringIO +import sys +import tempfile +from ase.io import read, write +from ase.optimize import FIRE +from ase.filters import FrechetCellFilter +import os +from typing import Optional, Dict, Any +from ase.atoms import Atoms +from pymatgen.core.structure import Structure +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer +from pymatgen.io.cif import CifWriter +import logging + +logger = logging.getLogger(__name__) + +# 初始化FairChem模型 +calc = None + +def init_model(): + """初始化FairChem模型""" + global calc + if calc is not None: + return + + try: + from fairchem.core import OCPCalculator + from tools_for_ms.services_tools.Configs import FAIRCHEM_MODEL_PATH + calc = OCPCalculator(checkpoint_path=FAIRCHEM_MODEL_PATH) + logger.info("FairChem model initialized successfully") + except Exception as e: + logger.error(f"Failed to initialize FairChem model: {str(e)}") + raise + +def convert_structure(input_format: str, content: str) -> Optional[Atoms]: + """将输入内容转换为Atoms对象""" + try: + with tempfile.NamedTemporaryFile(suffix=f".{input_format}", mode="w", delete=False) as tmp_file: + tmp_file.write(content) + tmp_path = tmp_file.name + + atoms = read(tmp_path) + os.unlink(tmp_path) + return atoms + except Exception as e: + logger.error(f"Failed to convert structure: {str(e)}") + return None + +def generate_symmetry_cif(structure: Structure) -> str: + """生成对称性CIF""" + analyzer = SpacegroupAnalyzer(structure) + structure_refined = analyzer.get_refined_structure() + + with tempfile.NamedTemporaryFile(suffix=".cif", mode="w+", delete=False) as tmp_file: + cif_writer = CifWriter(structure_refined, symprec=0.1, refine_struct=True) + cif_writer.write_file(tmp_file.name) + tmp_file.seek(0) + return tmp_file.read() + +def optimize_structure(atoms: Atoms, output_format: str) -> Dict[str, Any]: + """优化晶体结构""" + atoms.calc = calc + + try: + # 捕获优化过程的输出 + temp_output = StringIO() + original_stdout = sys.stdout + sys.stdout = temp_output + + # 执行优化 + from tools_for_ms.services_tools.Configs import FMAX + dyn = FIRE(FrechetCellFilter(atoms)) + dyn.run(fmax=FMAX) + + # 恢复标准输出并获取日志 + sys.stdout = original_stdout + optimization_log = temp_output.getvalue() + temp_output.close() + + # 获取总能量 + total_energy = atoms.get_potential_energy() + + # 处理优化后的结构 + if output_format == "cif": + optimized_structure = Structure.from_ase_atoms(atoms) + content = generate_symmetry_cif(optimized_structure) + else: + with tempfile.NamedTemporaryFile(suffix=f".{output_format}", mode="w+", delete=False) as tmp_file: + write(tmp_file.name, atoms) + tmp_file.seek(0) + content = tmp_file.read() + + # 格式化返回结果 + format_result = f""" +The following is the optimized crystal structure information: +### Optimization Results (using FIRE(eqV2_86M) algorithm): +**Total Energy: {total_energy} eV** + +#### Optimizing Log: +```text +{optimization_log} +``` +### Optimized {output_format.upper()} Content: +``` +{content[:300]} +``` +""" + + # return { + # "total_energy": total_energy, + # "optimization_log": optimization_log, + # "content": content, + # "formatted_result": format_result + # } + return format_result + except Exception as e: + logger.error(f"Failed to optimize structure: {str(e)}") + raise e + +@llm_tool(name="optimize_crystal_structure", + description="Optimize crystal structure using FairChem model") +async def optimize_crystal_structure( + content: str, + input_format: str = "cif", + output_format: str = "cif" +) -> Dict[str, Any]: + """ + Optimize crystal structure using FairChem model. + + Args: + content: Crystal structure content string + input_format: Input format (cif, xyz, vasp) + output_format: Output format (cif, xyz, vasp) + + Returns: + Optimized structure with energy and optimization log + """ + # 确保模型已初始化 + if calc is None: + init_model() + + # 使用asyncio.to_thread异步执行可能阻塞的操作 + def run_optimization(): + # 转换结构 + atoms = convert_structure(input_format, content) + if atoms is None: + raise ValueError(f"无法转换输入的{input_format}格式内容,请检查格式是否正确") + + # 优化结构 + return optimize_structure(atoms, output_format) + + # 直接返回结果或抛出异常 + return await asyncio.to_thread(run_optimization) diff --git a/tools_for_ms/services_tools/mattergen_tools.py b/tools_for_ms/services_tools/mattergen_tools.py new file mode 100644 index 0000000..dc66dd9 --- /dev/null +++ b/tools_for_ms/services_tools/mattergen_tools.py @@ -0,0 +1,412 @@ +""" +Author: Yutang LI +Institution: SIAT-MIC +Contact: yt.li2@siat.ac.cn +""" +import ast +import json +import logging +import tempfile +import os +import datetime +import asyncio +import zipfile +import shutil +from pathlib import Path +from typing import Literal, Dict, Any, Tuple, Union, Optional, List +from ase.optimize import FIRE +from ase.filters import FrechetCellFilter +from ase.atoms import Atoms +from ase.io import read, write +from pymatgen.core.structure import Structure +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer +from pymatgen.io.cif import CifWriter +# Use our wrapper module instead of direct imports +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))) +#import mattergen_wrapper + +# Access the modules through the wrapper +# The generator module is re-exported as an attribute, not a submodule +from mattergen_wrapper import generator +CrystalGenerator = generator.CrystalGenerator +from mattergen.common.data.types import TargetProperty +from mattergen.common.utils.eval_utils import MatterGenCheckpointInfo +from mattergen.common.utils.data_classes import ( + PRETRAINED_MODEL_NAME, + MatterGenCheckpointInfo, +) +logger = logging.getLogger(__name__) +from tools_for_ms.services_tools.Configs import * +from tools_for_ms.llm_tools import llm_tool + +import json +import re + + +def format_cif_content(content): + """ + Format CIF content by removing unnecessary headers and organizing each CIF file. + + Args: + content: String containing CIF content, possibly with PK headers + + Returns: + Formatted string with each CIF file properly labeled and formatted + """ + # 如果内容为空,直接返回空字符串 + if not content or content.strip() == '': + return '' + + # 删除从PK开始到第一个_chemical_formula_structural之前的所有内容 + content = re.sub(r'PK.*?(?=_chemical_formula_structural)', '', content, flags=re.DOTALL) + + # 删除从PK开始到字符串结束且没有_chemical_formula_structural的内容 + content = re.sub(r'PK[^_]*$', '', content, flags=re.DOTALL) + content = re.sub(r'PK.*?(?!.*_chemical_formula_structural)$', '', content, flags=re.DOTALL) + + # 使用_chemical_formula_structural作为分隔符来分割不同的CIF文件 + # 但我们需要保留这个字段在每个CIF文件中 + cif_blocks = [] + + # 查找所有_chemical_formula_structural的位置 + formula_positions = [m.start() for m in re.finditer(r'_chemical_formula_structural', content)] + + # 如果没有找到任何_chemical_formula_structural,返回空字符串 + if not formula_positions: + return '' + + # 分割CIF块 + for i in range(len(formula_positions)): + start_pos = formula_positions[i] + # 如果是最后一个块,结束位置是字符串末尾 + end_pos = formula_positions[i+1] if i < len(formula_positions)-1 else len(content) + + cif_block = content[start_pos:end_pos].strip() + + # 提取formula值 + formula_match = re.search(r'_chemical_formula_structural\s+(\S+)', cif_block) + if formula_match: + formula = formula_match.group(1) + cif_blocks.append((formula, cif_block)) + + # 格式化输出 + result = [] + for i, (formula, cif_content) in enumerate(cif_blocks, 1): + formatted = f"[cif {i} begin]\ndata_{formula}\n{cif_content}\n[cif {i} end]" + result.append(formatted) + + return "\n".join(result) + + + +def convert_values(data_str): + # 将字符串转换为字典 + try: + data = json.loads(data_str) + except json.JSONDecodeError: + return data_str # 如果无法解析为JSON,返回原字符串 + + return data + + +def preprocess_property(property_name: str, property_value: Union[str, float, int]) -> Tuple[str, Any]: + """ + Preprocess a property value based on its name, converting it to the appropriate type. + + Args: + property_name: Name of the property + property_value: Value of the property (can be string, float, or int) + + Returns: + Tuple of (property_name, processed_value) + + Raises: + ValueError: If the property value is invalid for the given property name + """ + valid_properties = [ + "dft_mag_density", "dft_bulk_modulus", "dft_shear_modulus", + "energy_above_hull", "formation_energy_per_atom", "space_group", + "hhi_score", "ml_bulk_modulus", "chemical_system", "dft_band_gap" + ] + + if property_name not in valid_properties: + raise ValueError(f"Invalid property_name: {property_name}. Must be one of: {', '.join(valid_properties)}") + + # Process property_value if it's a string + if isinstance(property_value, str): + try: + # Try to convert string to float for numeric properties + if property_name != "chemical_system": + property_value = float(property_value) + except ValueError: + # If conversion fails, keep as string (for chemical_system) + pass + + # Handle special cases for properties that need specific types + if property_name == "chemical_system": + if isinstance(property_value, (int, float)): + logger.warning(f"Converting numeric property_value {property_value} to string for chemical_system property") + property_value = str(property_value) + elif property_name == "space_group" : + space_group = property_value + if space_group < 1 or space_group > 230: + raise ValueError(f"Invalid space_group value: {space_group}. Must be an integer between 1 and 230.") + + return property_name, property_value + + +def main( + output_path: str, + pretrained_name: PRETRAINED_MODEL_NAME | None = None, + model_path: str | None = None, + batch_size: int = 2, + num_batches: int = 1, + config_overrides: list[str] | None = None, + checkpoint_epoch: Literal["best", "last"] | int = "last", + properties_to_condition_on: TargetProperty | None = None, + sampling_config_path: str | None = None, + sampling_config_name: str = "default", + sampling_config_overrides: list[str] | None = None, + record_trajectories: bool = True, + diffusion_guidance_factor: float | None = None, + strict_checkpoint_loading: bool = True, + target_compositions: list[dict[str, int]] | None = None, +): + """ + Evaluate diffusion model against molecular metrics. + + Args: + model_path: Path to DiffusionLightningModule checkpoint directory. + output_path: Path to output directory. + config_overrides: Overrides for the model config, e.g., `model.num_layers=3 model.hidden_dim=128`. + properties_to_condition_on: Property value to draw conditional sampling with respect to. When this value is an empty dictionary (default), unconditional samples are drawn. + sampling_config_path: Path to the sampling config file. (default: None, in which case we use `DEFAULT_SAMPLING_CONFIG_PATH` from explorers.common.utils.utils.py) + sampling_config_name: Name of the sampling config (corresponds to `{sampling_config_path}/{sampling_config_name}.yaml` on disk). (default: default) + sampling_config_overrides: Overrides for the sampling config, e.g., `condition_loader_partial.batch_size=32`. + load_epoch: Epoch to load from the checkpoint. If None, the best epoch is loaded. (default: None) + record: Whether to record the trajectories of the generated structures. (default: True) + strict_checkpoint_loading: Whether to raise an exception when not all parameters from the checkpoint can be matched to the model. + target_compositions: List of dictionaries with target compositions to condition on. Each dictionary should have the form `{element: number_of_atoms}`. If None, the target compositions are not conditioned on. + Only supported for models trained for crystal structure prediction (CSP) (default: None) + + NOTE: When specifying dictionary values via the CLI, make sure there is no whitespace between the key and value, e.g., `--properties_to_condition_on={key1:value1}`. + """ + assert ( + pretrained_name is not None or model_path is not None + ), "Either pretrained_name or model_path must be provided." + assert ( + pretrained_name is None or model_path is None + ), "Only one of pretrained_name or model_path can be provided." + + if not os.path.exists(output_path): + os.makedirs(output_path) + + sampling_config_overrides = sampling_config_overrides or [] + config_overrides = config_overrides or [] + properties_to_condition_on = properties_to_condition_on or {} + target_compositions = target_compositions or [] + + if pretrained_name is not None: + checkpoint_info = MatterGenCheckpointInfo.from_hf_hub( + pretrained_name, config_overrides=config_overrides + ) + else: + checkpoint_info = MatterGenCheckpointInfo( + model_path=Path(model_path).resolve(), + load_epoch=checkpoint_epoch, + config_overrides=config_overrides, + strict_checkpoint_loading=strict_checkpoint_loading, + ) + _sampling_config_path = Path(sampling_config_path) if sampling_config_path is not None else None + generator = CrystalGenerator( + checkpoint_info=checkpoint_info, + properties_to_condition_on=properties_to_condition_on, + batch_size=batch_size, + num_batches=num_batches, + sampling_config_name=sampling_config_name, + sampling_config_path=_sampling_config_path, + sampling_config_overrides=sampling_config_overrides, + record_trajectories=record_trajectories, + diffusion_guidance_factor=( + diffusion_guidance_factor if diffusion_guidance_factor is not None else 0.0 + ), + target_compositions_dict=target_compositions, + ) + generator.generate(output_dir=Path(output_path)) + + +@llm_tool(name="generate_material", description="Generate crystal structures with optional property constraints") +async def generate_material( + properties: Optional[Dict[str, Union[float, str, Dict[str, Union[float, str]]]]] = None, + batch_size: int = 2, + num_batches: int = 1, + diffusion_guidance_factor: float = 2.0 +) -> str: + """ + Generate crystal structures with optional property constraints. + + This unified function can generate materials in three modes: + 1. Unconditional generation (no properties specified) + 2. Single property conditional generation (one property specified) + 3. Multi-property conditional generation (multiple properties specified) + + Args: + properties: Optional property constraints. Can be: + - None or empty dict for unconditional generation + - Dict with single key-value pair for single property conditioning + - Dict with multiple key-value pairs for multi-property conditioning + Valid property names include: "dft_band_gap", "chemical_system", etc. + batch_size: Number of structures per batch + num_batches: Number of batches to generate + diffusion_guidance_factor: Controls adherence to target properties + + Returns: + Descriptive text with generated crystal structures in CIF format + """ + # Use the configured results directory + output_dir = MATTERGENMODEL_RESULT_PATH + + # Handle string input if provided + if isinstance(properties, str): + try: + properties = json.loads(properties) + except json.JSONDecodeError: + raise ValueError(f"Invalid properties JSON string: {properties}") + + # Default to empty dict if None + properties = properties or {} + + # Process properties based on generation mode + if not properties: + # Unconditional generation + model_path = os.path.join(MATTERGENMODEL_ROOT, "mattergen_base") + properties_to_condition_on = None + generation_type = "unconditional" + property_description = "unconditionally" + else: + # Conditional generation (single or multi-property) + properties_to_condition_on = {} + + # Process each property + for property_name, property_value in properties.items(): + _, processed_value = preprocess_property(property_name, property_value) + properties_to_condition_on[property_name] = processed_value + + # Determine which model to use based on properties + if len(properties) == 1: + # Single property conditioning + property_name = list(properties.keys())[0] + property_to_model = { + "dft_mag_density": "dft_mag_density", + "dft_bulk_modulus": "dft_bulk_modulus", + "dft_shear_modulus": "dft_shear_modulus", + "energy_above_hull": "energy_above_hull", + "formation_energy_per_atom": "formation_energy_per_atom", + "space_group": "space_group", + "hhi_score": "hhi_score", + "ml_bulk_modulus": "ml_bulk_modulus", + "chemical_system": "chemical_system", + "dft_band_gap": "dft_band_gap" + } + model_dir = property_to_model.get(property_name, property_name) + generation_type = "single_property" + property_description = f"conditioned on {property_name} = {properties[property_name]}" + else: + # Multi-property conditioning + property_keys = set(properties.keys()) + if property_keys == {"dft_mag_density", "hhi_score"}: + model_dir = "dft_mag_density_hhi_score" + elif property_keys == {"chemical_system", "energy_above_hull"}: + model_dir = "chemical_system_energy_above_hull" + else: + # If no specific multi-property model exists, use the first property's model + first_property = list(properties.keys())[0] + model_dir = first_property + generation_type = "multi_property" + property_description = f"conditioned on multiple properties: {', '.join([f'{name} = {value}' for name, value in properties.items()])}" + + # Construct the full model path + model_path = os.path.join(MATTERGENMODEL_ROOT, model_dir) + + # Check if the model directory exists + if not os.path.exists(model_path): + # Fallback to base model if specific model doesn't exist + logger.warning(f"Model directory for {model_dir} not found. Using base model instead.") + model_path = os.path.join(MATTERGENMODEL_ROOT, "mattergen_base") + + # Call the main function with appropriate parameters + main( + output_path=output_dir, + model_path=model_path, + batch_size=batch_size, + num_batches=num_batches, + properties_to_condition_on=properties_to_condition_on, + record_trajectories=True, + diffusion_guidance_factor=diffusion_guidance_factor if properties else 0.0 + ) + + # Create a dictionary to store the file contents + result_dict = {} + + # Define file paths + cif_zip_path = os.path.join(output_dir, "generated_crystals_cif.zip") + xyz_file_path = os.path.join(output_dir, "generated_crystals.extxyz") + trajectories_zip_path = os.path.join(output_dir, "generated_trajectories.zip") + + # Read the CIF zip file + if os.path.exists(cif_zip_path): + with open(cif_zip_path, 'rb') as f: + result_dict['cif_content'] = f.read() + + # Create a descriptive prompt based on generation type + if generation_type == "unconditional": + title = "Generated Material Structures" + description = "These structures were generated unconditionally, meaning no specific properties were targeted." + elif generation_type == "single_property": + property_name = list(properties.keys())[0] + property_value = properties[property_name] + title = f"Generated Material Structures Conditioned on {property_name} = {property_value}" + description = f"These structures were generated with property conditioning, targeting a {property_name} value of {property_value}." + else: # multi_property + title = "Generated Material Structures Conditioned on Multiple Properties" + description = "These structures were generated with multi-property conditioning, targeting the specified property values." + + # Create the full prompt + prompt = f""" +# {title} + +This data contains {batch_size * num_batches} crystal structures generated by the MatterGen model, {property_description}. + + {'' if generation_type == 'unconditional' else f''' + A diffusion guidance factor of {diffusion_guidance_factor} was used, which controls how strongly + the generation adheres to the specified property values. Higher values produce samples that more + closely match the target properties but may reduce diversity. + '''} + +## CIF Files (Crystallographic Information Files) + +- Standard format for crystallographic structures +- Contains unit cell parameters, atomic positions, and symmetry information +- Used by crystallographic software and visualization tools + +``` +{format_cif_content(result_dict.get('cif_content', b'').decode('utf-8', errors='replace') if isinstance(result_dict.get('cif_content', b''), bytes) else str(result_dict.get('cif_content', '')))} +``` + +{description} +You can use these structures for materials discovery, property prediction, or further analysis. +""" + + # Clean up the files (delete them after reading) + try: + if os.path.exists(cif_zip_path): + os.remove(cif_zip_path) + if os.path.exists(xyz_file_path): + os.remove(xyz_file_path) + if os.path.exists(trajectories_zip_path): + os.remove(trajectories_zip_path) + except Exception as e: + logger.warning(f"Error cleaning up files: {e}") + return prompt diff --git a/tools_for_ms/services_tools/mattersim_tools.py b/tools_for_ms/services_tools/mattersim_tools.py new file mode 100644 index 0000000..9559d62 --- /dev/null +++ b/tools_for_ms/services_tools/mattersim_tools.py @@ -0,0 +1,65 @@ +from ..llm_tools import llm_tool +import torch +import numpy as np +from ase.units import GPa +from mattersim.forcefield import MatterSimCalculator +import asyncio +from .fairchem_tools import convert_structure + +@llm_tool( + name="predict_properties", + description="Predict energy, forces, and stress of crystal structures based on CIF string", +) +async def predict_properties(cif_content: str) -> str: + """ + Use MatterSim to predict energy, forces, and stress of crystal structures. + + Args: + cif_content: Crystal structure string in CIF format + + Returns: + String containing prediction results + """ + # 使用asyncio.to_thread异步执行可能阻塞的操作 + def run_prediction(): + # 使用 convert_structure 函数将 CIF 字符串转换为 Atoms 对象 + structure = convert_structure("cif", cif_content) + if structure is None: + return "Unable to parse CIF string. Please check if the format is correct." + + # 设置设备 + device = "cuda" if torch.cuda.is_available() else "cpu" + + # 使用 MatterSimCalculator 计算属性 + structure.calc = MatterSimCalculator(device=device) + + # 直接获取能量、力和应力 + energy = structure.get_potential_energy() + forces = structure.get_forces() + stresses = structure.get_stress(voigt=False) + + # 计算每原子能量 + num_atoms = len(structure) + energy_per_atom = energy / num_atoms + + # 计算应力(GPa和eV/A^3格式) + stresses_ev_a3 = stresses + stresses_gpa = stresses / GPa + + # 构建返回的提示信息 + prompt = f""" +## {structure.get_chemical_formula()} Crystal Structure Property Prediction Results + +Prediction results using the provided CIF structure: + +- Total Energy (eV): {energy} +- Energy per Atom (eV/atom): {energy_per_atom:.4f} +- Forces (eV/Angstrom): {forces[0]} # Forces on the first atom +- Stress (GPa): {stresses_gpa[0][0]} # First component of the stress tensor +- Stress (eV/A^3): {stresses_ev_a3[0][0]} # First component of the stress tensor + +""" + return prompt + + # 异步执行预测操作 + return await asyncio.to_thread(run_prediction) diff --git a/tools_for_ms/services_tools/mp_tools.py b/tools_for_ms/services_tools/mp_tools.py new file mode 100644 index 0000000..5fcc6d1 --- /dev/null +++ b/tools_for_ms/services_tools/mp_tools.py @@ -0,0 +1,644 @@ +""" +Materials Project API Service Tools + +This module provides functions for querying the Materials Project database, +processing search results, and formatting responses. It includes a LLM tool +for integration with large language models. + +Author: Yutang LI +Institution: SIAT-MIC +Contact: yt.li2@siat.ac.cn +""" + +import glob +import json +import asyncio +import logging +import datetime +import os +from multiprocessing import Process, Manager +from typing import Dict, Any, List, Optional + +from mp_api.client import MPRester +from pymatgen.core import Structure +from pymatgen.symmetry.analyzer import SpacegroupAnalyzer +from pymatgen.io.cif import CifWriter +from ..services_tools import Configs +from ..utils import settings, handle_minio_upload +from .error_handlers import handle_general_error +from ..llm_tools import llm_tool +def read_cif_txt_file(file_path): + """Read the markdown file and return its content.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return f.read() + except Exception as e: + print(f"Error reading file {file_path}: {e}") + return None + +def get_extra_cif_info(path: str, fields_name: list): + """Extract specific fields from the CIF description.""" + basic_fields = ['formula_pretty', 'chemsys', 'composition', 'elements', 'symmetry', 'nsites', 'volume', 'density'] + energy_electronic_fields = ['formation_energy_per_atom', 'energy_above_hull', 'is_stable', 'efermi', 'cbm', 'vbm', 'band_gap', 'is_gap_direct'] + metal_magentic_fields = ['is_metal', 'is_magnetic', "ordering", 'total_magnetization', 'num_magnetic_sites'] + # metal_magentic_fields = ['is_metal', 'is_magnetic', "ordering", 'total_magnetization', 'total_magnetization_normalized_vol', 'total_magnetization_normalized_formula_units', 'num_magnetic_sites', 'num_unique_magnetic_sites', 'types_of_magnetic_species', "decomposes_to"] + + selected_fields = [] + if fields_name[0] == 'all_fields': + selected_fields = basic_fields + energy_electronic_fields + metal_magentic_fields + # selected_fields = energy_electronic_fields + metal_magentic_fields + else: + for field in fields_name: + selected_fields.extend(locals().get(field, [])) + + with open(path, 'r') as f: + docs = json.load(f) + + new_docs = {} + for field_name in selected_fields: + new_docs[field_name] = docs.get(field_name, '') + + # new_docs['structure'] = {"lattice": docs['structure']['lattice']} + return new_docs + +def remove_symmetry_equiv_xyz(cif_content): + """ + Remove symmetry operations section from CIF file content + + Args: + cif_content: CIF file content string + + Returns: + Cleaned CIF content string + """ + lines = cif_content.split('\n') + output_lines = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + + # 检测循环开始 + if line == 'loop_': + # 查看下一行,检查是否是对称性循环 + next_lines = [] + j = i + 1 + while j < len(lines) and lines[j].strip().startswith('_'): + next_lines.append(lines[j].strip()) + j += 1 + + # 检查是否包含对称性操作标签 + if any('_symmetry_equiv_pos_as_xyz' in tag for tag in next_lines): + # 跳过整个循环块 + while i < len(lines): + if i + 1 >= len(lines): + break + + next_line = lines[i + 1].strip() + # 检查是否到达下一个循环或数据块 + if next_line == 'loop_' or next_line.startswith('data_'): + break + + # 检查是否到达原子位置部分 + if next_line.startswith('_atom_site_'): + break + + i += 1 + else: + # 不是对称性循环,保留loop_行 + output_lines.append(lines[i]) + else: + # 非循环开始行,直接保留 + output_lines.append(lines[i]) + + i += 1 + + return '\n'.join(output_lines) + +logger = logging.getLogger(__name__) + +def parse_bool(param: str) -> bool | None: + """ + Parse a string parameter into a boolean value. + + Args: + param: String parameter to parse (e.g., "true", "false") + + Returns: + Boolean value if param is not empty, None otherwise + """ + if not param: + return None + return param.lower() == 'true' + +def parse_list(param: str) -> List[str] | None: + """ + Parse a comma-separated string into a list of strings. + + Args: + param: Comma-separated string (e.g., "Li,Fe,O") + + Returns: + List of strings if param is not empty, None otherwise + """ + if not param: + return None + return param.split(',') + +def parse_tuple(param: str) -> tuple[float, float] | None: + """ + Parse a comma-separated string into a tuple of two float values. + + Used for range parameters like band_gap, density, etc. + + Args: + param: Comma-separated string of two numbers (e.g., "0,3.5") + + Returns: + Tuple of two float values if param is valid, None otherwise + """ + if not param: + return None + try: + values = param.split(',') + return (float(values[0]), float(values[1])) + except (ValueError, IndexError): + return None + +def parse_search_parameters(query_params: Dict[str, str]) -> Dict[str, Any]: + """ + Parse search parameters from query parameters. + + Converts string query parameters into appropriate types for the Materials Project API. + """ + return { + 'band_gap': parse_tuple(query_params.get('band_gap')), + 'chemsys': parse_list(query_params.get('chemsys')), + 'crystal_system': parse_list(query_params.get('crystal_system')), + 'density': parse_tuple(query_params.get('density')), + 'formation_energy': parse_tuple(query_params.get('formation_energy')), + 'elements': parse_list(query_params.get('elements')), + 'exclude_elements': parse_list(query_params.get('exclude_elements')), + 'formula': parse_list(query_params.get('formula')), + 'is_gap_direct': parse_bool(query_params.get('is_gap_direct')), + 'is_metal': parse_bool(query_params.get('is_metal')), + 'is_stable': parse_bool(query_params.get('is_stable')), + 'magnetic_ordering': query_params.get('magnetic_ordering'), + 'material_ids': parse_list(query_params.get('material_ids')), + 'total_energy': parse_tuple(query_params.get('total_energy')), + 'num_elements': parse_tuple(query_params.get('num_elements')), + 'volume': parse_tuple(query_params.get('volume')), + 'chunk_size': int(query_params.get('chunk_size', '5')) + } + +def process_search_results(docs: List[Dict[str, Any]]) -> List[Dict[str, Any]] | str: + """ + Process search results from the Materials Project API. + + Extracts relevant fields from each document and formats them into a consistent structure. + + Returns: + List of processed documents or error message string if an exception occurs + """ + try: + fields = [ + 'formula_pretty', 'nsites', 'nelements', 'material_id', 'chemsys', + 'volume', 'density', 'density_atomic', 'cbm', 'vbm', 'band_gap', + 'is_gap_direct', 'is_stable', 'formation_energy_per_atom', + 'energy_above_hull', 'is_metal', 'total_magnetization', 'efermi', + 'is_magnetic', 'ordering', 'bulk_modulus', 'shear_modulus', + 'universal_anisotropy', 'theoretical' + ] + + res = [] + for doc in docs: + try: + new_docs = {} + for field_name in fields: + new_docs[field_name] = doc.get(field_name, '') + res.append(new_docs) + except Exception as e: + # logger.warning(f"Error processing document: {str(e)}") + continue + return res + except Exception as e: + error_msg = f"Error in process_search_results: {str(e)}" + # logger.error(error_msg) + import traceback + # logger.error(traceback.format_exc()) + return error_msg + + +def _search_worker(queue, api_key, **kwargs): + """ + Worker function for executing Materials Project API searches. + + Runs in a separate process to perform the actual API call and puts results in the queue. + + Args: + queue: Multiprocessing queue for returning results + api_key: Materials Project API key + **kwargs: Search parameters to pass to the API + """ + try: + import os + import traceback + os.environ['HTTP_PROXY'] = Configs.HTTP_PROXY or '' + os.environ['HTTPS_PROXY'] = Configs.HTTPS_PROXY or '' + + + # 初始化 MPRester 客户端 + with MPRester(api_key) as mpr: + # print(f"MPRester initialized with endpoint:") + + + # print("Executing search...") + result = mpr.materials.summary.search(**kwargs) + # print(f"Search completed, result type: {type(result)}") + + # 检查结果 + if result: + # print(f"Number of results: {len(result)}") + # print(f"First result type: {type(result[0])}") + + # 尝试使用更安全的方式处理结果 + processed_results = [] + for doc in result: + try: + # 尝试使用 model_dump 方法 + processed_doc = doc.model_dump() + processed_results.append(processed_doc) + except AttributeError: + # 如果没有 model_dump 方法,尝试使用 dict 方法 + try: + processed_doc = doc.dict() + processed_results.append(processed_doc) + except AttributeError: + # 如果没有 dict 方法,尝试直接转换为字典 + if hasattr(doc, "__dict__"): + processed_doc = doc.__dict__ + # 移除可能导致序列化问题的特殊属性 + if "_sa_instance_state" in processed_doc: + del processed_doc["_sa_instance_state"] + processed_results.append(processed_doc) + else: + # 最后的尝试,直接使用 doc + processed_results.append(doc) + + # print(f"Processed {len(processed_results)} results") + queue.put(processed_results) + else: + # print("No results found") + queue.put([]) + except Exception as e: + # print(f"Error in _search_worker: {str(e)}") + # print(traceback.format_exc()) + queue.put(e) + + +async def execute_search(search_args: Dict[str, Any], timeout: int = 120) -> List[Dict[str, Any]] | str: + """ + Execute a search against the Materials Project API. + + Runs the search in a separate process to handle potential timeouts and returns the results. + + Args: + search_args: Dictionary of search parameters + timeout: Maximum time in seconds to wait for the search to complete + + Returns: + List of document dictionaries from the search results or error message string if an exception occurs + """ + # print(f"Starting execute_search with args: {search_args}") + + # 确保 formula 参数是列表类型 + if 'formula' in search_args and isinstance(search_args['formula'], str): + search_args['formula'] = [search_args['formula']] + # print(f"Converted formula to list in execute_search: {search_args['formula']}") + + manager = Manager() + queue = manager.Queue() + + try: + p = Process(target=_search_worker, args=(queue, Configs.MP_API_KEY), kwargs=search_args) + + p.start() + + # logger.info(f"Started worker process with PID: {p.pid}") + # print(f"Waiting for process {p.pid} to complete (timeout: {timeout}s)...") + p.join(timeout=timeout) + + if p.is_alive(): + # logger.warning(f"Terminating worker process {p.pid} due to timeout") + # print(f"Process {p.pid} timed out, terminating...") + p.terminate() + p.join() + error_msg = f"Request timed out after {timeout} seconds" + return error_msg + + # print("Process completed, retrieving results from queue...") + try: + if queue.empty(): + # logger.warning("Queue is empty after process completion") + # print("Warning: Queue is empty after process completion") + pass + else: + # logger.info("Queue contains data, retrieving...") + # print("Queue contains data, retrieving...") + pass + + result = queue.get(timeout=timeout) + # print(f"Result type: {type(result)}") + + if isinstance(result, Exception): + # logger.error(f"Error in search worker: {str(result)}") + # print(f"Error in search worker: {str(result)}") + # 尝试获取更详细的错误信息 + if hasattr(result, "__traceback__"): + import traceback + tb_str = ''.join(traceback.format_exception(None, result, result.__traceback__)) + # print(f"Error traceback: {tb_str}") + return f"Error in search worker: {str(result)}" + + if isinstance(result, list): + # print(f"Successfully retrieved {len(result)} documents") + # logger.info(f"Successfully retrieved {len(result)} documents") + pass + else: + # print(f"Result is not a list, but {type(result)}") + pass + + return result + + except queue.Empty: + error_msg = "Failed to retrieve data from queue (timeout)" + # logger.error(error_msg) + # print(error_msg) + return error_msg + except Exception as e: + error_msg = f"Error in execute_search: {str(e)}" + # logger.error(error_msg) + # print(error_msg) + import traceback + # print(traceback.format_exc()) + return error_msg + +@llm_tool(name="search_material_property_from_material_project", description="Search materials in Materials Project database by formula and properties") +async def search_material_property_from_material_project( + formula: str | list[str], + chemsys: Optional[str | list[str] | None] = None, + crystal_system: Optional[str | list[str] | None] = None, + is_gap_direct: Optional[bool | None] = None, + is_stable: Optional[bool | None] = None, + ) -> str: + """ + Search materials in Materials Project database. + + Args: + formula: Chemical formula(s) (e.g., "Fe2O3" or ["ABO3", "Si*"]) + chemsys: Chemical system(s) (e.g., "Li-Fe-O") + crystal_system: Crystal system(s) (e.g., "Cubic") + is_gap_direct: Filter for direct band gap materials + is_stable: Filter for thermodynamically stable materials + Returns: + JSON formatted material properties data + """ + # print(f"search_material_property_from_material_project called with formula: {formula}, type: {type(formula)}") + + # 验证晶系参数 + VALID_CRYSTAL_SYSTEMS = ['Triclinic', 'Monoclinic', 'Orthorhombic', 'Tetragonal', 'Trigonal', 'Hexagonal', 'Cubic'] + + # 验证晶系参数是否有效 + if crystal_system is not None: + if isinstance(crystal_system, str): + if crystal_system not in VALID_CRYSTAL_SYSTEMS: + return "Input should be 'Triclinic', 'Monoclinic', 'Orthorhombic', 'Tetragonal', 'Trigonal', 'Hexagonal' or 'Cubic'" + elif isinstance(crystal_system, list): + for cs in crystal_system: + if cs not in VALID_CRYSTAL_SYSTEMS: + return "Input should be 'Triclinic', 'Monoclinic', 'Orthorhombic', 'Tetragonal', 'Trigonal', 'Hexagonal' or 'Cubic'" + + # 确保 formula 是列表类型 + if isinstance(formula, str): + formula = [formula] + # print(f"Converted formula to list: {formula}") + + + + params = { + "chemsys": chemsys, + "crystal_system": crystal_system, + "formula": formula, + "is_gap_direct": is_gap_direct, + "is_stable": is_stable, + "chunk_size": 5, + + } + + # Filter out None values + params = {k: v for k, v in params.items() if v is not None} + # print("Parameters after filtering:", params) + mp_id_list = await get_mpid_from_formula(formula=formula) + try: + res=[] + # Execute search against Materials Project API + #docs = await execute_search(params) + # for mp_id in id_list: + for mp_id in mp_id_list: + crystal_props = get_extra_cif_info(Configs.LOCAL_MP_PROPERTY_ROOT+f"{mp_id}.json", ['all_fields']) + res.append(crystal_props) + + + + #res = process_search_results(docs) + # print(f"Processed {len(res)} results") + + if len(res) == 0: + # print("No results found") + return "No results found, please try again." + + # Format response with top results + # print(f"Formatting top {Configs.MP_TOPK} results") + try: + # 创建包含索引的JSON结果 + formatted_results = [] + for i, item in enumerate(res[:Configs.MP_TOPK], 1): + formatted_result = f"[property {i} begin]\n" + formatted_result += json.dumps(item, indent=2) + formatted_result += f"\n[property {i} end]\n\n" + formatted_results.append(formatted_result) + + # 将所有结果合并为一个字符串 + res_chunk = "\n\n".join(formatted_results) + res_template = f""" +Here are the search results from the Materials Project database: +Due to length limitations, only the top {Configs.MP_TOPK} results are shown below:\n +{res_chunk} +If you need more results, please modify your search criteria or try different query parameters. +""" + # print("Successfully formatted results") + return res_template + except Exception as format_error: + # print(f"Error formatting results: {str(format_error)}") + import traceback + # print(traceback.format_exc()) + return str(format_error) + + except Exception as e: + # print(f"Error in search_material_property_from_material_project: {str(e)}") + import traceback + # print(traceback.format_exc()) + return str(e) + + +@llm_tool(name="get_crystal_structures_from_materials_project", description="Get symmetrized crystal structures CIF data from Materials Project database by chemical formula") +async def get_crystal_structures_from_materials_project( + formulas: list[str], + conventional_unit_cell: bool = True, + symprec: float = 0.1 +) -> str: + """ + Get crystal structures from Materials Project database by chemical formula and apply symmetrization. + + Args: + formulas: List of chemical formulas (e.g., ["Fe2O3", "SiO2", "TiO2"]) + conventional_unit_cell: Whether to return conventional unit cell (True) or primitive cell (False) + symprec: Precision parameter for symmetrization + + Returns: + Formatted text containing symmetrized CIF data + """ + # 确保 formulas 是列表类型 + # if isinstance(formulas, str): + # formulas = [formulas] + + # try: + # # 构建搜索参数 + # search_args = { + # "formula": formulas, + # "fields": ["material_id",] + # } + + # # 使用execute_search函数查询晶体结构信息 + # docs = await execute_search(search_args, timeout=60) + + # if isinstance(docs, str): + # # 如果返回的是字符串,说明发生了错误 + # return f"获取晶体结构时出错: {docs}" + + # if not docs: + # return "未找到指定化学式的晶体结构数据。" + + # 处理结果 + # result = {} + # for i, doc in enumerate(docs): + # try: + # # 获取材料ID和结构 + # material_id = doc.get('material_id') + # structure_data = doc.get('structure') + + # if not structure_data: + # continue + + # # 将结构数据转换为pymatgen Structure对象 + result={} + mp_id_list=await get_mpid_from_formula(formula=formulas) + + + for i,mp_id in enumerate(mp_id_list): + cif_file = glob.glob(f"/home/ubuntu/sas0/LYT/paper_dataset/mp_cif/MPDatasets/{mp_id}.cif")[0] + #print('111',cif_file) + structure = Structure.from_file(cif_file) + # 如果需要常规单元格 + if conventional_unit_cell: + structure = SpacegroupAnalyzer(structure).get_conventional_standard_structure() + + # 对结构进行对称化处理 + sga = SpacegroupAnalyzer(structure, symprec=symprec) + symmetrized_structure = sga.get_refined_structure() + + # 使用CifWriter生成CIF数据 + cif_writer = CifWriter(symmetrized_structure, symprec=symprec, refine_struct=True) + cif_data = str(cif_writer) + + # 删除CIF文件中的对称性操作部分 + cif_data = remove_symmetry_equiv_xyz(cif_data) + cif_data=cif_data.replace('# generated using pymatgen',"") + # 生成一个唯一的键 + formula = structure.composition.reduced_formula + key = f"{formula}_{i}" + + result[key] = cif_data + + # 只保留前Configs.MP_TOPK个结果 + if len(result) >= Configs.MP_TOPK: + break + + # except Exception as e: + # continue + + # 格式化响应 + try: + prompt = f""" +# Materials Project Symmetrized Crystal Structure Data + +Below are symmetrized crystal structure data for {len(result)} materials from the Materials Project database, in CIF (Crystallographic Information File) format. +These structures have been analyzed and optimized for symmetry using SpacegroupAnalyzer with precision parameter symprec={symprec}.\n +""" + + for i, (key, cif_data) in enumerate(result.items(), 1): + prompt += f"[cif {i} begin]\n" + prompt += cif_data + prompt += f"\n[cif {i} end]\n\n" + + prompt += """ +## Usage Instructions + +1. You can copy the above CIF data and save it as .cif files +2. Open these files with crystal structure visualization software (such as VESTA, Mercury, Avogadro, etc.) +3. These structures can be used for further material analysis, simulation, or visualization + +CIF files contain complete structural information of crystals, including cell parameters, atomic coordinates, symmetry, etc. +Symmetrization helps identify and optimize crystal symmetry, making the structure more standardized and accurate. +""" + return prompt + + except Exception as format_error: + import traceback + return str(format_error) + + +@llm_tool(name="get_mpid_from_formula", description="Get material IDs (mpid) from Materials Project database by chemical formula") +async def get_mpid_from_formula(formula: str) -> str: + """ + Get material IDs (mpid) from Materials Project database by chemical formula. + Returns mpids for the lowest energy structures. + + Args: + formula: Chemical formula (e.g., "Fe2O3") + + Returns: + Formatted text containing material IDs + """ + # 确保 formula 是列表类型,因为 _search_by_formula_worker 需要列表输入 + + os.environ['HTTP_PROXY'] = Configs.HTTP_PROXY or '' + os.environ['HTTPS_PROXY'] = Configs.HTTPS_PROXY or '' + id_list = [] + with MPRester(Configs.MP_API_KEY) as mpr: + docs = mpr.materials.summary.search(formula=formula)#这里设定搜索条件id list =[]for doc in docs:#获取材料索引号id list.append(doc.material id) + for doc in docs: + id_list.append(doc.material_id) + return id_list + # cif_description_list= [] + # cif_information_list=[] + # crystal_props_list=[] + # #print("mp_id",id_list) + # for mp_id in id_list: + # cif_description = read_cif_txt_file('/home/ubuntu/sas0/LYT/paper_dataset/mp_cif/Text-bond/{}.txt'.format(mp_id)) + # cif_information = read_cif_txt_file('/home/ubuntu/sas0/LYT/paper_dataset/mp_cif/Symmetry_MPDatasets/{}_symmetrized.cif'.format(mp_id)) + # cif_information = cif_information.replace('# generated using pymatgen\n', '') + + # crystal_props = get_extra_cif_info("/home/ubuntu/sas0/LYT/paper_dataset/mp_cif/Props/{}.json".format(mp_id), ['all_fields']) + # cif_description_list.append(cif_description) + # cif_information_list.append(cif_information) diff --git a/tools_for_ms/services_tools/oqmd_tools.py b/tools_for_ms/services_tools/oqmd_tools.py new file mode 100644 index 0000000..7b13ce1 --- /dev/null +++ b/tools_for_ms/services_tools/oqmd_tools.py @@ -0,0 +1,96 @@ +import logging +import httpx +import pandas as pd +from bs4 import BeautifulSoup +from io import StringIO +from typing import Annotated + +from ..llm_tools import llm_tool +from ..llm_tools import * + +logger = logging.getLogger(__name__) + +@llm_tool(name="fetch_chemical_composition_from_OQMD", description="Fetch material data for a chemical composition from OQMD database") +async def fetch_chemical_composition_from_OQMD ( + composition: Annotated[str, "Chemical formula (e.g., Fe2O3, LiFePO4)"] +) -> str: + """ + Fetch material data for a chemical composition from OQMD database. + + Args: + composition: Chemical formula (e.g., Fe2O3, LiFePO4) + + Returns: + Formatted text with material information and property tables + """ + # Fetch data from OQMD + url = f"https://www.oqmd.org/materials/composition/{composition}" + try: + async with httpx.AsyncClient(timeout=100.0) as client: + response = await client.get(url) + response.raise_for_status() + + # Validate response content + if not response.text or len(response.text) < 100: + raise ValueError("Invalid response content from OQMD API") + + # Parse HTML data + html = response.text + soup = BeautifulSoup(html, 'html.parser') + + # Parse basic data + basic_data = [] + h1_element = soup.find('h1') + if h1_element: + basic_data.append(h1_element.text.strip()) + else: + basic_data.append(f"Material: {composition}") + + for script in soup.find_all('p'): + if script: + combined_text = "" + for element in script.contents: + if hasattr(element, 'name') and element.name == 'a' and 'href' in element.attrs: + url = "https://www.oqmd.org" + element['href'] + combined_text += f"[{element.text.strip()}]({url}) " + elif hasattr(element, 'text'): + combined_text += element.text.strip() + " " + else: + combined_text += str(element).strip() + " " + basic_data.append(combined_text.strip()) + + # Parse table data + table_data = "" + table = soup.find('table') + if table: + try: + df = pd.read_html(StringIO(str(table)))[0] + df = df.fillna('') + df = df.replace([float('inf'), float('-inf')], '') + table_data = df.to_markdown(index=False) + except Exception as e: + logger.error(f"Error parsing table: {str(e)}") + table_data = "Error parsing table data" + + # Integrate data into a single text + combined_text = "\n\n".join(basic_data) + if table_data: + combined_text += "\n\n## Material Properties Table\n\n" + table_data + + return combined_text + + except httpx.HTTPStatusError as e: + logger.error(f"OQMD API request failed: {str(e)}") + return f"Error: OQMD API request failed - {str(e)}" + except httpx.TimeoutException: + logger.error("OQMD API request timed out") + return "Error: OQMD API request timed out" + except httpx.NetworkError as e: + logger.error(f"Network error occurred: {str(e)}") + return f"Error: Network error occurred - {str(e)}" + except ValueError as e: + logger.error(f"Invalid response content: {str(e)}") + return f"Error: Invalid response content - {str(e)}" + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + return f"Error: Unexpected error occurred - {str(e)}" diff --git a/tools_for_ms/services_tools/search_dify.py b/tools_for_ms/services_tools/search_dify.py new file mode 100644 index 0000000..13028f9 --- /dev/null +++ b/tools_for_ms/services_tools/search_dify.py @@ -0,0 +1,80 @@ +import asyncio +import json +from .Configs import DIFY_API_KEY +import requests +import codecs +from ..llm_tools import llm_tool + +@llm_tool( + name="retrieval_from_knowledge_base", + description="Retrieve information from local materials science literature knowledge base" +) +async def retrieval_from_knowledge_base(query: str, topk: int = 3) -> str: + """ + 检索本地材料科学文献知识库中的相关信息 + + 输入: + query: 查询字符串,如材料名称"CsPbBr3" + topk: 返回结果数量,默认3条 + + 输出: + 包含文档ID、标题和相关性分数的字典 + """ + # 设置Dify API的URL端点 + url = 'http://192.168.191.101:6080/v1/chat-messages' + + # 配置请求头,包含API密钥和内容类型 + headers = { + 'Authorization': f'Bearer {DIFY_API_KEY}', # 使用配置文件中的API密钥 + 'Content-Type': 'application/json' + } + + # 准备请求数据 + data = { + "inputs": {"topK": topk}, # 设置返回的最大结果数量 + "query": query, # 设置查询字符串 + "response_mode": "blocking", # 使用阻塞模式,等待并获取完整响应 + "conversation_id": "", # 不使用会话ID,每次都是独立查询 + "user": "abc-123" # 用户标识符 + } + + try: + # 发送POST请求到Dify API并获取响应 + # 设置较长的超时时间(1111秒)以处理可能的长时间响应 + response = requests.post(url, headers=headers, json=data, timeout=1111) + + # 获取响应文本 + response_text = response.text + useful_results = [] # 初始化结果列表(当前未使用) + + # 解码响应文本中的Unicode转义序列 + response_text = codecs.decode(response_text, 'unicode_escape') + print(response_text) # 打印完整响应用于调试 + + # 将响应文本解析为JSON对象 + result_json = json.loads(response_text) + + # 从响应中提取元数据 + metadata = result_json.get("metadata", {}) + + # 构建包含关键信息的结果字典 + useful_info = { + "id": metadata.get("document_id"), # 文档ID + "title": result_json.get("title"), # 文档标题 + "content": None, # 内容字段设为空,注意:原字典使用'answer'字段存储内容 + "metadata": None, # 元数据字段设为空 + "embedding": None, # 嵌入向量字段设为空 + "score": metadata.get("score") # 相关性分数 + } + + # 返回提取的有用信息 + return useful_info + + except Exception as e: + # 捕获并处理所有可能的异常,返回错误信息 + return f"错误: {str(e)}" + +# 当脚本直接运行时的测试代码 +if __name__ == "__main__": + # 使用示例查询"CsPbBr3"测试函数 + print(asyncio.run(retrieval_from_knowledge_base('CsPbBr3'))) diff --git a/tools_for_ms/utils.py b/tools_for_ms/utils.py new file mode 100644 index 0000000..955f409 --- /dev/null +++ b/tools_for_ms/utils.py @@ -0,0 +1,101 @@ + + +import os +import boto3 +import logging +from typing import Optional +from pydantic import Field +from pydantic_settings import BaseSettings + +logger = logging.getLogger(__name__) + +class Settings(BaseSettings): + # Material Project + mp_api_key: Optional[str] = Field(None, env="MP_API_KEY") + mp_endpoint: Optional[str] = Field(None, env="MP_ENDPOINT") + mp_topk: Optional[int] = Field(3, env="MP_TOPK") + + # Proxy + http_proxy: Optional[str] = Field(None, env="HTTP_PROXY") + https_proxy: Optional[str] = Field(None, env="HTTPS_PROXY") + + # FairChem + fairchem_model_path: Optional[str] = Field(None, env="FAIRCHEM_MODEL_PATH") + fmax: Optional[float] = Field(0.05, env="FMAX") + + # MinIO + minio_endpoint: Optional[str] = Field(None, env="MINIO_ENDPOINT") + internal_minio_endpoint: Optional[str] = Field(None, env="INTERNAL_MINIO_ENDPOINT") + minio_access_key: Optional[str] = Field(None, env="MINIO_ACCESS_KEY") + minio_secret_key: Optional[str] = Field(None, env="MINIO_SECRET_KEY") + minio_bucket: Optional[str] = Field("mars-toolkit", env="MINIO_BUCKET") + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + +def get_minio_client(settings: Settings): + """获取MinIO客户端""" + return boto3.client( + 's3', + endpoint_url=settings.internal_minio_endpoint or settings.minio_endpoint, + aws_access_key_id=settings.minio_access_key, + aws_secret_access_key=settings.minio_secret_key + ) + +def handle_minio_upload(file_path: str, file_name: str) -> str: + """统一处理MinIO上传""" + try: + client = get_minio_client(settings) + client.upload_file(file_path, settings.minio_bucket, file_name, ExtraArgs={"ACL": "private"}) + + # 生成预签名 URL + url = client.generate_presigned_url( + 'get_object', + Params={'Bucket': settings.minio_bucket, 'Key': file_name}, + ExpiresIn=3600 + ) + return url.replace(settings.internal_minio_endpoint or "", settings.minio_endpoint) + except Exception as e: + from tools_for_ms.services_tools.error_handlers import handle_minio_error + return handle_minio_error(e) + +def setup_logging(): + parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + log_file_path = os.path.join(parent_dir, 'mars_toolkit.log') + """配置日志记录""" + logging.config.dictConfig({ + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { + 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + 'datefmt': '%Y-%m-%d %H:%M:%S' + }, + }, + 'handlers': { + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'standard' + }, + 'file': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': log_file_path, + 'maxBytes': 10485760, # 10MB + 'backupCount': 5, + 'formatter': 'standard' + } + }, + 'loggers': { + '': { + 'handlers': ['console', 'file'], + 'level': 'INFO', + 'propagate': True + } + } + }) + +# 初始化配置 +settings = Settings()