From 3f293a20106b5dbef27f2c192b4fe04279c012eb Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sat, 19 Apr 2025 18:37:48 +0800 Subject: [PATCH 01/17] =?UTF-8?q?=E4=B8=BA=E5=B7=A5=E5=85=B7=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E5=8A=A0=E5=85=A5=E4=BA=86=E4=B8=80=E7=82=B9=E7=82=B9?= =?UTF-8?q?=E5=8F=AF=E6=8B=93=E5=B1=95=E6=80=A7=EF=BC=88=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | Bin 574 -> 586 bytes src/do_tool/tool_can_use/base_tool.py | 18 +++++- .../tool_can_use/compare_numbers_tool.py | 18 +++--- src/do_tool/tool_can_use/get_time_date.py | 16 ++--- src/do_tool/tool_can_use/letter_count_tool.py | 56 ++++++++++++++++++ src/do_tool/tool_use.py | 2 +- 6 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 src/do_tool/tool_can_use/letter_count_tool.py diff --git a/requirements.txt b/requirements.txt index 45fb7e6e52e77aea95193b9fc7b9d5e9530410e6..42e7c31995d172894489dec6adcfb111686759eb 100644 GIT binary patch delta 20 bcmdnTa*Abx9TN{P0~bRMLn%W6Lm~qJHmU?x delta 7 OcmX@bvX5nh9TNZx`2v9e diff --git a/src/do_tool/tool_can_use/base_tool.py b/src/do_tool/tool_can_use/base_tool.py index b1edf805..d1cb6a8c 100644 --- a/src/do_tool/tool_can_use/base_tool.py +++ b/src/do_tool/tool_can_use/base_tool.py @@ -1,4 +1,5 @@ from typing import Dict, List, Any, Optional, Type +from abc import ABC, abstractmethod import inspect import importlib import pkgutil @@ -11,7 +12,7 @@ logger = get_module_logger("base_tool") TOOL_REGISTRY = {} -class BaseTool: +class BaseTool(ABC): """所有工具的基类""" # 工具名称,子类必须重写 @@ -36,6 +37,7 @@ class BaseTool: "function": {"name": cls.name, "description": cls.description, "parameters": cls.parameters}, } + @abstractmethod async def execute(self, function_args: Dict[str, Any], message_txt: str = "") -> Dict[str, Any]: """执行工具函数 @@ -111,3 +113,17 @@ def get_tool_instance(tool_name: str) -> Optional[BaseTool]: if not tool_class: return None return tool_class() + +def run_lua_code(lua_code: str): + """兼容Lua代码运行(小工具) + + Args: + lua_code (str): Lua代码 + + Returns: + _LuaTable: Lua运行时的全局变量 + """ + from lupa import LuaRuntime + lua = LuaRuntime(unpack_returned_tuples=True) + lua.execute(lua_code) + return lua.globals() \ No newline at end of file diff --git a/src/do_tool/tool_can_use/compare_numbers_tool.py b/src/do_tool/tool_can_use/compare_numbers_tool.py index 48cee515..ab96eb18 100644 --- a/src/do_tool/tool_can_use/compare_numbers_tool.py +++ b/src/do_tool/tool_can_use/compare_numbers_tool.py @@ -1,4 +1,4 @@ -from src.do_tool.tool_can_use.base_tool import BaseTool +from src.do_tool.tool_can_use.base_tool import BaseTool,run_lua_code from src.common.logger import get_module_logger from typing import Dict, Any @@ -32,14 +32,14 @@ class CompareNumbersTool(BaseTool): try: num1 = function_args.get("num1") num2 = function_args.get("num2") - - if num1 > num2: - result = f"{num1} 大于 {num2}" - elif num1 < num2: - result = f"{num1} 小于 {num2}" - else: - result = f"{num1} 等于 {num2}" - + lua_code = """ + function CompareNumbers(a, b) + return a .. (a > b and " 大于 " or a < b and " 小于 " or " 等于 ") .. b + end + """ + CompareNumbers = run_lua_code(lua_code).CompareNumbers + result = CompareNumbers(num1, num2) + return {"name": self.name, "content": result} except Exception as e: logger.error(f"比较数字失败: {str(e)}") diff --git a/src/do_tool/tool_can_use/get_time_date.py b/src/do_tool/tool_can_use/get_time_date.py index c3c9c837..b3153e08 100644 --- a/src/do_tool/tool_can_use/get_time_date.py +++ b/src/do_tool/tool_can_use/get_time_date.py @@ -1,7 +1,6 @@ -from src.do_tool.tool_can_use.base_tool import BaseTool +from src.do_tool.tool_can_use.base_tool import BaseTool,run_lua_code from src.common.logger import get_module_logger from typing import Dict, Any -from datetime import datetime logger = get_module_logger("get_time_date") @@ -27,12 +26,13 @@ class GetCurrentDateTimeTool(BaseTool): Returns: Dict: 工具执行结果 """ - current_time = datetime.now().strftime("%H:%M:%S") - current_date = datetime.now().strftime("%Y-%m-%d") - current_year = datetime.now().strftime("%Y") - current_weekday = datetime.now().strftime("%A") - + lua_code = """ + GetCurrentDateTime = function() + return ("当前时间: %s, 日期: %s, 年份: %s, 星期: %s"):format(os.date("%H:%M:%S"), os.date("%Y-%m-%d"), os.date("%Y"), os.date("%A")) + end + """ + GetCurrentDateTime = run_lua_code(lua_code).GetCurrentDateTime return { "name": "get_current_date_time", - "content": f"当前时间: {current_time}, 日期: {current_date}, 年份: {current_year}, 星期: {current_weekday}", + "content": GetCurrentDateTime(), } diff --git a/src/do_tool/tool_can_use/letter_count_tool.py b/src/do_tool/tool_can_use/letter_count_tool.py new file mode 100644 index 00000000..999ac53c --- /dev/null +++ b/src/do_tool/tool_can_use/letter_count_tool.py @@ -0,0 +1,56 @@ +import re +from src.do_tool.tool_can_use.base_tool import BaseTool, run_lua_code +from src.config.config import global_config +from src.plugins.models.utils_model import LLMRequest +from src.common.logger import get_module_logger +from typing import Dict, Any + +logger = get_module_logger("letter_count_tool") + + +class LetterCountTool(BaseTool): + """数单词内某字母个数的工具""" + + name = "word_letter_count" + description = "当有人询问你或者提到某个英文单词内有多少个某字母时,可以使用这个工具来数字母(如果传入的是中文,传入之前要将中文转为英文)" + parameters = { + "type": "object", + "properties": { + "word": {"type": "string", "description": "英文单词"}, + "letter": {"type": "string", "description": "英文字母"}, + }, + "required": ["word", "letter"], + } + + async def execute(self, function_args: Dict[str, Any], message_txt: str = "") -> Dict[str, Any]: + """ + 执行数数该单词的某字母个数的函数 + + Args: + function_args: 工具参数 + message_txt: 原始消息文本 + + Returns: + Dict: 工具执行结果 + """ + try: + word = function_args.get("word") + letter = function_args.get("letter") + if re.match(r"^[a-zA-Z]+$", letter) is None: + raise ValueError("请输入英文字母") + lua_code = """ + function LetterCount(inputStr, targetLetter) + local lower = (inputStr:gsub("[^"..targetLetter:lower().."]", "")):len() + local upper = (inputStr:gsub("[^"..targetLetter:upper().."]", "")):len() + return string.format("字母 %s 在字符串 %s 中出现的次数:%d个(小写), %d个(大写)", targetLetter, inputStr, lower, upper) + end + """ + LetterCount = run_lua_code(lua_code).LetterCount + return {"name": self.name, "content": LetterCount(word, letter)} + except Exception as e: + logger.error(f"数字母失败: {str(e)}") + return {"name": self.name, "content": f"数字母失败: {str(e)}"} + + +# 注册工具 +# register_tool(LetterCountTool) diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py index f054ecdd..9ecd2b7f 100644 --- a/src/do_tool/tool_use.py +++ b/src/do_tool/tool_use.py @@ -61,7 +61,7 @@ class ToolUser: prompt += message_txt # prompt += f"你注意到{sender_name}刚刚说:{message_txt}\n" prompt += f"注意你就是{bot_name},{bot_name}是你的名字。根据之前的聊天记录补充问题信息,搜索时避开你的名字。\n" - prompt += "你现在需要对群里的聊天内容进行回复,现在选择工具来对消息和你的回复进行处理,你是否需要额外的信息,比如回忆或者搜寻已有的知识,改变关系和情感,或者了解你现在正在做什么。" + prompt += "你现在需要对群里的聊天内容进行回复,现在选择工具来对消息和你的回复进行处理,你是否需要额外的信息,比如回忆或者搜寻已有的知识,改变关系和情感,或者了解你现在正在做什么,如果是数字母的话,含中文词的部分要先转换成英文单词再数字母" return prompt @staticmethod From 9b96054c3cd10fa8d2b62c2fffe5579ca61d1ae5 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sat, 19 Apr 2025 18:47:38 +0800 Subject: [PATCH 02/17] =?UTF-8?q?ai=E5=BB=BA=E8=AE=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_can_use/compare_numbers_tool.py | 2 ++ src/do_tool/tool_can_use/letter_count_tool.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/do_tool/tool_can_use/compare_numbers_tool.py b/src/do_tool/tool_can_use/compare_numbers_tool.py index ab96eb18..4e4ce96f 100644 --- a/src/do_tool/tool_can_use/compare_numbers_tool.py +++ b/src/do_tool/tool_can_use/compare_numbers_tool.py @@ -32,6 +32,8 @@ class CompareNumbersTool(BaseTool): try: num1 = function_args.get("num1") num2 = function_args.get("num2") + if not (isinstance(num1, (int, float)) and isinstance(num2, (int, float))): + raise ValueError("参数'num1'和'num2'必须为数字") lua_code = """ function CompareNumbers(a, b) return a .. (a > b and " 大于 " or a < b and " 小于 " or " 等于 ") .. b diff --git a/src/do_tool/tool_can_use/letter_count_tool.py b/src/do_tool/tool_can_use/letter_count_tool.py index 999ac53c..9731606a 100644 --- a/src/do_tool/tool_can_use/letter_count_tool.py +++ b/src/do_tool/tool_can_use/letter_count_tool.py @@ -36,8 +36,8 @@ class LetterCountTool(BaseTool): try: word = function_args.get("word") letter = function_args.get("letter") - if re.match(r"^[a-zA-Z]+$", letter) is None: - raise ValueError("请输入英文字母") + if re.match(r"^[a-zA-Z]$", letter) is None: + raise ValueError("请输入单个英文字母") lua_code = """ function LetterCount(inputStr, targetLetter) local lower = (inputStr:gsub("[^"..targetLetter:lower().."]", "")):len() From c0b511273e4096e8614d2937b8eda932d2165519 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sat, 19 Apr 2025 18:51:04 +0800 Subject: [PATCH 03/17] ruff --- src/do_tool/tool_can_use/base_tool.py | 4 +++- src/do_tool/tool_can_use/compare_numbers_tool.py | 4 ++-- src/do_tool/tool_can_use/get_time_date.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/do_tool/tool_can_use/base_tool.py b/src/do_tool/tool_can_use/base_tool.py index d1cb6a8c..10581c1a 100644 --- a/src/do_tool/tool_can_use/base_tool.py +++ b/src/do_tool/tool_can_use/base_tool.py @@ -114,6 +114,7 @@ def get_tool_instance(tool_name: str) -> Optional[BaseTool]: return None return tool_class() + def run_lua_code(lua_code: str): """兼容Lua代码运行(小工具) @@ -124,6 +125,7 @@ def run_lua_code(lua_code: str): _LuaTable: Lua运行时的全局变量 """ from lupa import LuaRuntime + lua = LuaRuntime(unpack_returned_tuples=True) lua.execute(lua_code) - return lua.globals() \ No newline at end of file + return lua.globals() diff --git a/src/do_tool/tool_can_use/compare_numbers_tool.py b/src/do_tool/tool_can_use/compare_numbers_tool.py index 4e4ce96f..b2f5dd18 100644 --- a/src/do_tool/tool_can_use/compare_numbers_tool.py +++ b/src/do_tool/tool_can_use/compare_numbers_tool.py @@ -1,4 +1,4 @@ -from src.do_tool.tool_can_use.base_tool import BaseTool,run_lua_code +from src.do_tool.tool_can_use.base_tool import BaseTool, run_lua_code from src.common.logger import get_module_logger from typing import Dict, Any @@ -41,7 +41,7 @@ class CompareNumbersTool(BaseTool): """ CompareNumbers = run_lua_code(lua_code).CompareNumbers result = CompareNumbers(num1, num2) - + return {"name": self.name, "content": result} except Exception as e: logger.error(f"比较数字失败: {str(e)}") diff --git a/src/do_tool/tool_can_use/get_time_date.py b/src/do_tool/tool_can_use/get_time_date.py index b3153e08..b54210fc 100644 --- a/src/do_tool/tool_can_use/get_time_date.py +++ b/src/do_tool/tool_can_use/get_time_date.py @@ -1,4 +1,4 @@ -from src.do_tool.tool_can_use.base_tool import BaseTool,run_lua_code +from src.do_tool.tool_can_use.base_tool import BaseTool, run_lua_code from src.common.logger import get_module_logger from typing import Dict, Any From 6c83e3e39aedfc5a051d9504a17e9deebdcea100 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sat, 19 Apr 2025 19:17:08 +0800 Subject: [PATCH 04/17] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E4=B8=8Brun?= =?UTF-8?q?=5Flua=5Fcode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_can_use/base_tool.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/do_tool/tool_can_use/base_tool.py b/src/do_tool/tool_can_use/base_tool.py index 10581c1a..df9d47f0 100644 --- a/src/do_tool/tool_can_use/base_tool.py +++ b/src/do_tool/tool_can_use/base_tool.py @@ -124,8 +124,20 @@ def run_lua_code(lua_code: str): Returns: _LuaTable: Lua运行时的全局变量 """ - from lupa import LuaRuntime + try: + from lupa import LuaRuntime + except ImportError as e: + raise ImportError("无法导入lupa模块,请确保已安装lupa库(可通过pip安装)") from e lua = LuaRuntime(unpack_returned_tuples=True) - lua.execute(lua_code) - return lua.globals() + try: + lua.execute(lua_code) + return lua.globals() + except Exception as e: + # 返回包含错误信息的字典而不是直接抛出异常,保持函数接口的稳定性 + return { + "__error__": True, + "type": type(e).__name__, + "message": "Lua 代码执行出错,请检查代码是否正确。", + "lua_code": lua_code # 可选:返回出错的代码片段便于调试 + } From 575e4d6ab8e3640d7a9bceca0bf96f12ce028720 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sat, 19 Apr 2025 19:25:06 +0800 Subject: [PATCH 05/17] =?UTF-8?q?=E5=8A=A0=E4=BA=86=E4=B8=AA=E9=80=97?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_can_use/base_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/do_tool/tool_can_use/base_tool.py b/src/do_tool/tool_can_use/base_tool.py index df9d47f0..d18549c8 100644 --- a/src/do_tool/tool_can_use/base_tool.py +++ b/src/do_tool/tool_can_use/base_tool.py @@ -139,5 +139,5 @@ def run_lua_code(lua_code: str): "__error__": True, "type": type(e).__name__, "message": "Lua 代码执行出错,请检查代码是否正确。", - "lua_code": lua_code # 可选:返回出错的代码片段便于调试 + "lua_code": lua_code, # 可选:返回出错的代码片段便于调试 } From 5c5021950f29226537128bf8a7f5b7710ece6e34 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sat, 19 Apr 2025 19:28:49 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E6=9C=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_can_use/letter_count_tool.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/do_tool/tool_can_use/letter_count_tool.py b/src/do_tool/tool_can_use/letter_count_tool.py index 9731606a..5c1d3751 100644 --- a/src/do_tool/tool_can_use/letter_count_tool.py +++ b/src/do_tool/tool_can_use/letter_count_tool.py @@ -1,7 +1,5 @@ import re from src.do_tool.tool_can_use.base_tool import BaseTool, run_lua_code -from src.config.config import global_config -from src.plugins.models.utils_model import LLMRequest from src.common.logger import get_module_logger from typing import Dict, Any From faa59270313f3ea8dfde72bfa2a66ccd5e3785cf Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sun, 20 Apr 2025 13:48:16 +0800 Subject: [PATCH 07/17] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=B2=E7=AA=81x1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_use.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py index 9ecd2b7f..23a3f652 100644 --- a/src/do_tool/tool_use.py +++ b/src/do_tool/tool_use.py @@ -61,7 +61,9 @@ class ToolUser: prompt += message_txt # prompt += f"你注意到{sender_name}刚刚说:{message_txt}\n" prompt += f"注意你就是{bot_name},{bot_name}是你的名字。根据之前的聊天记录补充问题信息,搜索时避开你的名字。\n" - prompt += "你现在需要对群里的聊天内容进行回复,现在选择工具来对消息和你的回复进行处理,你是否需要额外的信息,比如回忆或者搜寻已有的知识,改变关系和情感,或者了解你现在正在做什么,如果是数字母的话,含中文词的部分要先转换成英文单词再数字母" + prompt += "你现在需要对群里的聊天内容进行回复,现在选择工具来对消息和你的回复进行处理,你是否需要额外的信息,比如回忆或者搜寻已有的知识,改变关系和情感,或者了解你现在正在做什么。" + + return prompt @staticmethod From 65d620e41d634ef47a050328751c915243f7be80 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sun, 20 Apr 2025 13:50:08 +0800 Subject: [PATCH 08/17] =?UTF-8?q?=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_use.py | 205 ---------------------------------------- 1 file changed, 205 deletions(-) delete mode 100644 src/do_tool/tool_use.py diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py deleted file mode 100644 index 23a3f652..00000000 --- a/src/do_tool/tool_use.py +++ /dev/null @@ -1,205 +0,0 @@ -from src.plugins.models.utils_model import LLMRequest -from src.config.config import global_config -from src.plugins.chat.chat_stream import ChatStream -import json -from src.common.logger import get_module_logger, TOOL_USE_STYLE_CONFIG, LogConfig -from src.do_tool.tool_can_use import get_all_tool_definitions, get_tool_instance -from src.heart_flow.sub_heartflow import SubHeartflow -import traceback - -tool_use_config = LogConfig( - # 使用消息发送专用样式 - console_format=TOOL_USE_STYLE_CONFIG["console_format"], - file_format=TOOL_USE_STYLE_CONFIG["file_format"], -) -logger = get_module_logger("tool_use", config=tool_use_config) - - -class ToolUser: - def __init__(self): - self.llm_model_tool = LLMRequest( - model=global_config.llm_tool_use, temperature=0.2, max_tokens=1000, request_type="tool_use" - ) - - @staticmethod - async def _build_tool_prompt(message_txt: str, chat_stream: ChatStream, subheartflow: SubHeartflow = None): - """构建工具使用的提示词 - - Args: - message_txt: 用户消息文本 - chat_stream: 聊天流对象 - - Returns: - str: 构建好的提示词 - """ - if subheartflow: - mid_memory_info = subheartflow.observations[0].mid_memory_info - # print(f"intol111111111111111111111111111111111222222222222mid_memory_info:{mid_memory_info}") - else: - mid_memory_info = "" - - # stream_id = chat_stream.stream_id - # chat_talking_prompt = "" - # if stream_id: - # chat_talking_prompt = get_recent_group_detailed_plain_text( - # stream_id, limit=global_config.MAX_CONTEXT_SIZE, combine=True - # ) - # new_messages = list( - # db.messages.find({"chat_id": chat_stream.stream_id, "time": {"$gt": time.time()}}).sort("time", 1).limit(15) - # ) - # new_messages_str = "" - # for msg in new_messages: - # if "detailed_plain_text" in msg: - # new_messages_str += f"{msg['detailed_plain_text']}" - - # 这些信息应该从调用者传入,而不是从self获取 - bot_name = global_config.BOT_NICKNAME - prompt = "" - prompt += mid_memory_info - prompt += "你正在思考如何回复群里的消息。\n" - prompt += "之前群里进行了如下讨论:\n" - prompt += message_txt - # prompt += f"你注意到{sender_name}刚刚说:{message_txt}\n" - prompt += f"注意你就是{bot_name},{bot_name}是你的名字。根据之前的聊天记录补充问题信息,搜索时避开你的名字。\n" - prompt += "你现在需要对群里的聊天内容进行回复,现在选择工具来对消息和你的回复进行处理,你是否需要额外的信息,比如回忆或者搜寻已有的知识,改变关系和情感,或者了解你现在正在做什么。" - - - return prompt - - @staticmethod - def _define_tools(): - """获取所有已注册工具的定义 - - Returns: - list: 工具定义列表 - """ - return get_all_tool_definitions() - - @staticmethod - async def _execute_tool_call(tool_call, message_txt: str): - """执行特定的工具调用 - - Args: - tool_call: 工具调用对象 - message_txt: 原始消息文本 - - Returns: - dict: 工具调用结果 - """ - try: - function_name = tool_call["function"]["name"] - function_args = json.loads(tool_call["function"]["arguments"]) - - # 获取对应工具实例 - tool_instance = get_tool_instance(function_name) - if not tool_instance: - logger.warning(f"未知工具名称: {function_name}") - return None - - # 执行工具 - result = await tool_instance.execute(function_args, message_txt) - if result: - # 直接使用 function_name 作为 tool_type - tool_type = function_name - - return { - "tool_call_id": tool_call["id"], - "role": "tool", - "name": function_name, - "type": tool_type, - "content": result["content"], - } - return None - except Exception as e: - logger.error(f"执行工具调用时发生错误: {str(e)}") - return None - - async def use_tool(self, message_txt: str, chat_stream: ChatStream, sub_heartflow: SubHeartflow = None): - """使用工具辅助思考,判断是否需要额外信息 - - Args: - message_txt: 用户消息文本 - sender_name: 发送者名称 - chat_stream: 聊天流对象 - sub_heartflow: 子心流对象(可选) - - Returns: - dict: 工具使用结果,包含结构化的信息 - """ - try: - # 构建提示词 - prompt = await self._build_tool_prompt( - message_txt=message_txt, - chat_stream=chat_stream, - subheartflow=sub_heartflow, - ) - - # 定义可用工具 - tools = self._define_tools() - logger.trace(f"工具定义: {tools}") - - # 使用llm_model_tool发送带工具定义的请求 - payload = { - "model": self.llm_model_tool.model_name, - "messages": [{"role": "user", "content": prompt}], - "max_tokens": global_config.max_response_length, - "tools": tools, - "temperature": 0.2, - } - - logger.trace(f"发送工具调用请求,模型: {self.llm_model_tool.model_name}") - # 发送请求获取模型是否需要调用工具 - response = await self.llm_model_tool._execute_request( - endpoint="/chat/completions", payload=payload, prompt=prompt - ) - - # 根据返回值数量判断是否有工具调用 - if len(response) == 3: - content, reasoning_content, tool_calls = response - # logger.info(f"工具思考: {tool_calls}") - # logger.debug(f"工具思考: {content}") - - # 检查响应中工具调用是否有效 - if not tool_calls: - logger.debug("模型返回了空的tool_calls列表") - return {"used_tools": False} - - tool_calls_str = "" - for tool_call in tool_calls: - tool_calls_str += f"{tool_call['function']['name']}\n" - logger.info(f"根据:\n{prompt[0:100]}...\n模型请求调用{len(tool_calls)}个工具: {tool_calls_str}") - tool_results = [] - structured_info = {} # 动态生成键 - - # 执行所有工具调用 - for tool_call in tool_calls: - result = await self._execute_tool_call(tool_call, message_txt) - if result: - tool_results.append(result) - # 使用工具名称作为键 - tool_name = result["name"] - if tool_name not in structured_info: - structured_info[tool_name] = [] - structured_info[tool_name].append({"name": result["name"], "content": result["content"]}) - - # 如果有工具结果,返回结构化的信息 - if structured_info: - logger.info(f"工具调用收集到结构化信息: {json.dumps(structured_info, ensure_ascii=False)}") - return {"used_tools": True, "structured_info": structured_info} - else: - # 没有工具调用 - content, reasoning_content = response - logger.debug("模型没有请求调用任何工具") - - # 如果没有工具调用或处理失败,直接返回原始思考 - return { - "used_tools": False, - } - - except Exception as e: - logger.error(f"工具调用过程中出错: {str(e)}") - logger.error(f"工具调用过程中出错: {traceback.format_exc()}") - return { - "used_tools": False, - "error": str(e), - } From bb45466f498a88564d91809b4e8e01301a74f488 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sun, 20 Apr 2025 13:52:47 +0800 Subject: [PATCH 09/17] =?UTF-8?q?=E7=BD=AE=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_use.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/do_tool/tool_use.py diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py new file mode 100644 index 00000000..e69de29b From 85d01d3e1b2acd153fafee9d08bfbb0c4879749d Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sun, 20 Apr 2025 13:53:57 +0800 Subject: [PATCH 10/17] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_use.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/do_tool/tool_use.py diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py deleted file mode 100644 index e69de29b..00000000 From 0e9352e3d362c580b0e0a0212c3f38b54e35f978 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Sun, 20 Apr 2025 13:55:18 +0800 Subject: [PATCH 11/17] =?UTF-8?q?=E7=BD=AE=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/do_tool/tool_use.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/do_tool/tool_use.py diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py new file mode 100644 index 00000000..e69de29b From 280feec228724e4f0d06d9c4094b8b6b1788916d Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Thu, 29 May 2025 06:19:34 +0800 Subject: [PATCH 12/17] base_tool --- src/tools/tool_can_use/base_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/tool_can_use/base_tool.py b/src/tools/tool_can_use/base_tool.py index c0d5940d..921af185 100644 --- a/src/tools/tool_can_use/base_tool.py +++ b/src/tools/tool_can_use/base_tool.py @@ -14,7 +14,7 @@ logger = get_logger("base_tool") TOOL_REGISTRY = {} -class BaseTool(ABC): +class BaseTool: """所有工具的基类""" # 工具名称,子类必须重写 From fc306d5990bac1d133b5128d042b05223944040c Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Thu, 29 May 2025 06:43:09 +0800 Subject: [PATCH 13/17] =?UTF-8?q?=E4=BC=98=E9=9B=85=E5=85=B3=E9=97=AD?= =?UTF-8?q?=EF=BC=8C=E5=A4=8D=E5=BD=92=EF=BC=81=EF=BC=88=E6=84=9F=E8=B0=A2?= =?UTF-8?q?=E8=B1=86=E5=8C=85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/bot.py b/bot.py index 3737279d..20299644 100644 --- a/bot.py +++ b/bot.py @@ -2,10 +2,13 @@ import asyncio import hashlib import os import sys + +# import shutil from pathlib import Path import time import platform import traceback +import signal from dotenv import load_dotenv from src.common.logger_manager import get_logger @@ -14,7 +17,8 @@ from src.common.crash_logger import install_crash_handler from src.main import MainSystem from rich.traceback import install -from src.manager.async_task_manager import async_task_manager +from asyncio import CancelledError +# from src.manager.async_task_manager import async_task_manager install(extra_lines=3) @@ -34,15 +38,19 @@ driver = None app = None loop = None -# shutdown_requested = False # 新增全局变量 +shutdown_requested = False # 新增全局变量 async def request_shutdown() -> bool: """请求关闭程序""" + global shutdown_requested + if shutdown_requested: + return True + shutdown_requested = True try: if loop and not loop.is_closed(): try: - loop.run_until_complete(graceful_shutdown()) + await graceful_shutdown() except Exception as ge: # 捕捉优雅关闭时可能发生的错误 logger.error(f"优雅关闭时发生错误: {ge}") return False @@ -65,6 +73,38 @@ def easter_egg(): print(rainbow_text) +# def init_config(): +# # 初次启动检测 +# if not os.path.exists("config/bot_config.toml"): +# logger.warning("检测到bot_config.toml不存在,正在从模板复制") + +# # 检查config目录是否存在 +# if not os.path.exists("config"): +# os.makedirs("config") +# logger.info("创建config目录") + +# shutil.copy("template/bot_config_template.toml", "config/bot_config.toml") +# logger.info("复制完成,请修改config/bot_config.toml和.env中的配置后重新启动") +# if not os.path.exists("config/lpmm_config.toml"): +# logger.warning("检测到lpmm_config.toml不存在,正在从模板复制") + +# # 检查config目录是否存在 +# if not os.path.exists("config"): +# os.makedirs("config") +# logger.info("创建config目录") + +# shutil.copy("template/lpmm_config_template.toml", "config/lpmm_config.toml") +# logger.info("复制完成,请修改config/lpmm_config.toml和.env中的配置后重新启动") + + +# def init_env(): +# # 检测.env文件是否存在 +# if not os.path.exists(".env"): +# logger.error("检测到.env文件不存在") +# shutil.copy("template/template.env", "./.env") +# logger.info("已从template/template.env复制创建.env,请修改配置后重新启动") + + def load_env(): # 直接加载生产环境变量配置 if os.path.exists(".env"): @@ -109,10 +149,7 @@ def scan_provider(env_config: dict): async def graceful_shutdown(): try: logger.info("正在优雅关闭麦麦...") - - # 停止所有异步任务 - await async_task_manager.stop_and_wait_all_tasks() - + # await async_task_manager.stop_and_wait_all_tasks() tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] for task in tasks: task.cancel() @@ -208,9 +245,9 @@ def raw_main(): check_eula() print("检查EULA和隐私条款完成") - easter_egg() - + # init_config() + # init_env() load_env() env_config = {key: os.getenv(key) for key in os.environ} @@ -220,6 +257,11 @@ def raw_main(): return MainSystem() +def signal_handler(sig, frame): + """信号处理函数,捕获SIGINT和SIGTERM信号""" + loop.create_task(request_shutdown()) + + if __name__ == "__main__": exit_code = 0 # 用于记录程序最终的退出状态 try: @@ -230,18 +272,22 @@ if __name__ == "__main__": loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) + # 新增:设置信号处理 + if platform.system().lower() != "windows": + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + try: # 执行初始化和任务调度 loop.run_until_complete(main_system.initialize()) loop.run_until_complete(main_system.schedule_tasks()) + loop.run_forever() except KeyboardInterrupt: - # loop.run_until_complete(global_api.stop()) logger.warning("收到中断信号,正在优雅关闭...") - if loop and not loop.is_closed(): - try: - loop.run_until_complete(graceful_shutdown()) - except Exception as ge: # 捕捉优雅关闭时可能发生的错误 - logger.error(f"优雅关闭时发生错误: {ge}") + try: + loop.run_until_complete(request_shutdown()) + except CancelledError as e: + logger.error(f"优雅关闭时发生错误: {e}") # 新增:检测外部请求关闭 # except Exception as e: # 将主异常捕获移到外层 try...except From 46a413abe45b7d513b58a10c85572ed769d60938 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Thu, 29 May 2025 06:57:58 +0800 Subject: [PATCH 14/17] ruff --- src/chat/utils/chat_message_builder.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index f62c0ad8..4228e3c7 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -451,13 +451,13 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: def get_anon_name(platform, user_id): # print(f"get_anon_name: platform:{platform}, user_id:{user_id}") # print(f"global_config.bot.qq_account:{global_config.bot.qq_account}") - + if user_id == global_config.bot.qq_account: # print("SELF11111111111111") return "SELF" try: person_id = person_info_manager.get_person_id(platform, user_id) - except Exception as e: + except Exception: person_id = None if not person_id: return "?" @@ -472,7 +472,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: # user_info = msg.get("user_info", {}) platform = msg.get("chat_info_platform") user_id = msg.get("user_id") - timestamp = msg.get("time") + # timestamp = msg.get("time") # print(f"msg:{msg}") # print(f"platform:{platform}") # print(f"user_id:{user_id}") @@ -488,11 +488,11 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: content = content.replace("ⁿ", "") # if not all([platform, user_id, timestamp is not None]): - # continue + # continue anon_name = get_anon_name(platform, user_id) # print(f"anon_name:{anon_name}") - + # 处理 回复 reply_pattern = r"回复<([^:<>]+):([^:<>]+)>" match = re.search(reply_pattern, content) @@ -514,7 +514,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: new_content = "" last_end = 0 for m in at_matches: - new_content += content[last_end:m.start()] + new_content += content[last_end : m.start()] bbb = m.group(2) try: anon_at = get_anon_name(platform, bbb) From 1f5bc87b4d7a38a27b6cad16b0237f3da88cfa6e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 28 May 2025 22:59:13 +0000 Subject: [PATCH 15/17] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/mongodb_to_sqlite.py | 2 +- src/chat/focus_chat/expressors/exprssion_learner.py | 2 +- src/chat/focus_chat/heartFC_chat.py | 8 ++++++-- src/main.py | 2 +- src/tools/tool_can_use/compare_numbers_tool.py | 2 +- src/tools/tool_can_use/get_time_date.py | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/mongodb_to_sqlite.py b/scripts/mongodb_to_sqlite.py index e3522c6a..c6d2950f 100644 --- a/scripts/mongodb_to_sqlite.py +++ b/scripts/mongodb_to_sqlite.py @@ -199,7 +199,7 @@ class MongoToSQLiteMigrator: "timestamp": "timestamp", }, enable_validation=True, # 禁用数据验证" - unique_fields=["user_id", "prompt_tokens","completion_tokens","total_tokens","cost"], # 组合唯一性 + unique_fields=["user_id", "prompt_tokens", "completion_tokens", "total_tokens", "cost"], # 组合唯一性 ), # 消息迁移配置 MigrationConfig( diff --git a/src/chat/focus_chat/expressors/exprssion_learner.py b/src/chat/focus_chat/expressors/exprssion_learner.py index 7b70ce6f..96d4e231 100644 --- a/src/chat/focus_chat/expressors/exprssion_learner.py +++ b/src/chat/focus_chat/expressors/exprssion_learner.py @@ -212,7 +212,7 @@ class ExpressionLearner: # random_msg_str: str = await build_readable_messages(random_msg, timestamp_mode="normal") random_msg_str: str = await build_anonymous_messages(random_msg) # print(f"random_msg_str:{random_msg_str}") - + prompt: str = await global_prompt_manager.format_prompt( prompt, chat_str=random_msg_str, diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index d5e9622f..abf89479 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -335,7 +335,9 @@ class HeartFChatting: for pname, ptime in processor_time_costs.items(): formatted_ptime = f"{ptime * 1000:.2f}毫秒" if ptime < 1 else f"{ptime:.2f}秒" processor_time_strings.append(f"{pname}: {formatted_ptime}") - processor_time_log = ("\n各处理器耗时: " + "; ".join(processor_time_strings)) if processor_time_strings else "" + processor_time_log = ( + ("\n各处理器耗时: " + "; ".join(processor_time_strings)) if processor_time_strings else "" + ) logger.info( f"{self.log_prefix} 第{self._current_cycle.cycle_id}次思考," @@ -471,7 +473,9 @@ class HeartFChatting: running_memorys = await self.memory_activator.activate_memory(observations) with Timer("执行 信息处理器", cycle_timers): - all_plan_info, processor_time_costs = await self._process_processors(observations, running_memorys, cycle_timers) + all_plan_info, processor_time_costs = await self._process_processors( + observations, running_memorys, cycle_timers + ) loop_processor_info = { "all_plan_info": all_plan_info, diff --git a/src/main.py b/src/main.py index 4846b913..5680e552 100644 --- a/src/main.py +++ b/src/main.py @@ -162,7 +162,7 @@ class MainSystem: """学习并存储表达方式任务""" while True: await asyncio.sleep(global_config.expression.learning_interval) - if global_config.expression.enable_expression_learning: + if global_config.expression.enable_expression_learning: logger.info("[表达方式学习] 开始学习表达方式...") await expression_learner.learn_and_store_expression() logger.info("[表达方式学习] 表达方式学习完成") diff --git a/src/tools/tool_can_use/compare_numbers_tool.py b/src/tools/tool_can_use/compare_numbers_tool.py index eead09dd..08a50752 100644 --- a/src/tools/tool_can_use/compare_numbers_tool.py +++ b/src/tools/tool_can_use/compare_numbers_tool.py @@ -1,4 +1,4 @@ -from src.tools.tool_can_use.base_tool import BaseTool,run_lua_code +from src.tools.tool_can_use.base_tool import BaseTool, run_lua_code from src.common.logger import get_module_logger from typing import Any diff --git a/src/tools/tool_can_use/get_time_date.py b/src/tools/tool_can_use/get_time_date.py index 8c4c1654..f42c15d5 100644 --- a/src/tools/tool_can_use/get_time_date.py +++ b/src/tools/tool_can_use/get_time_date.py @@ -1,4 +1,4 @@ -from src.tools.tool_can_use.base_tool import BaseTool,run_lua_code +from src.tools.tool_can_use.base_tool import BaseTool, run_lua_code from src.common.logger_manager import get_logger from typing import Dict, Any From 227fd8c972b629dc2dbb7df2dd3aff974a32b956 Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Thu, 29 May 2025 20:27:52 +0800 Subject: [PATCH 16/17] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/utils/chat_message_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 4228e3c7..9c5aec53 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -457,7 +457,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: return "SELF" try: person_id = person_info_manager.get_person_id(platform, user_id) - except Exception: + except Exception as _e: person_id = None if not person_id: return "?" From ec023cdb136f15673fbb9b07cd8f97ef25bd7abb Mon Sep 17 00:00:00 2001 From: SnowindMe <1945743455@qq.com> Date: Thu, 29 May 2025 20:30:59 +0800 Subject: [PATCH 17/17] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=86=B2=E7=AA=81x2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/utils/chat_message_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 9c5aec53..46d603b5 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -472,7 +472,7 @@ async def build_anonymous_messages(messages: List[Dict[str, Any]]) -> str: # user_info = msg.get("user_info", {}) platform = msg.get("chat_info_platform") user_id = msg.get("user_id") - # timestamp = msg.get("time") + _timestamp = msg.get("time") # print(f"msg:{msg}") # print(f"platform:{platform}") # print(f"user_id:{user_id}")