From 1d947374bfaf11d7ac984cc36968b19febbb19e2 Mon Sep 17 00:00:00 2001 From: ChensenCHX <2087826155@qq.com> Date: Thu, 27 Mar 2025 00:13:07 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=85=88=E5=BC=80=E4=B8=AA=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action_executer/action_executer.py | 23 +++++++++++++++++++ src/plugins/chat/config.py | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/plugins/action_executer/action_executer.py diff --git a/src/plugins/action_executer/action_executer.py b/src/plugins/action_executer/action_executer.py new file mode 100644 index 00000000..8edb7340 --- /dev/null +++ b/src/plugins/action_executer/action_executer.py @@ -0,0 +1,23 @@ +class ResponseAction: + def __init__(self): + self.tags = [] + + def parse_action(self, msg: str, action: str) -> str: + if action in msg: + self.tags.append(action) + return msg.replace(action, '') + return msg + + def empty(self): + return len(self.tags) == 0 + + def __contains__(self, other: str): + if isinstance(other, str): + return other in self.tags + else: + # 非str输入直接抛异常 + raise TypeError + + def __bool__(self): + # 这是为了直接嵌入到llm_generator.py的处理流里 便于跳过消息处理流程 + return False \ No newline at end of file diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 1ac2a7ea..37daf580 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -115,7 +115,7 @@ class BotConfig: # experimental enable_friend_chat: bool = False # 是否启用好友聊天 enable_think_flow: bool = False # 是否启用思考流程 - + enable_think_flow: bool = False # 是否允许执行动作 # 模型配置 @@ -421,6 +421,7 @@ class BotConfig: experimental_config = parent["experimental"] config.enable_friend_chat = experimental_config.get("enable_friend_chat", config.enable_friend_chat) config.enable_think_flow = experimental_config.get("enable_think_flow", config.enable_think_flow) + config.enable_action_execute = experimental_config.get("enable_action_execute", config.enable_action_execute) # 版本表达式:>=1.0.0,<2.0.0 # 允许字段:func: method, support: str, notice: str, necessary: bool From eec35b6d8b8d2d7114836f1e9ade8b9cde5134bb Mon Sep 17 00:00:00 2001 From: ChensenCHX <2087826155@qq.com> Date: Thu, 27 Mar 2025 13:21:39 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E7=88=86=E6=94=B9=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ config/auto_update.py | 7 +++++++ .../action_executer/action_executer.py | 16 +++++++++++--- src/plugins/chat/bot.py | 15 ++++++++++++- src/plugins/chat/config.py | 2 +- src/plugins/chat/llm_generator.py | 12 +++++++++++ src/plugins/chat/prompt_builder.py | 4 ++++ template/actions_template.py | 21 +++++++++++++++++++ 8 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 template/actions_template.py diff --git a/.gitignore b/.gitignore index 22e2612d..0cd14149 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ memory_graph.gml config/bot_config_dev.toml config/bot_config.toml config/bot_config.toml.bak +config/actions.py +config/__init__.py src/plugins/remote/client_uuid.json # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/config/auto_update.py b/config/auto_update.py index a0d87852..241e97b2 100644 --- a/config/auto_update.py +++ b/config/auto_update.py @@ -14,6 +14,13 @@ def update_config(): template_path = template_dir / "bot_config_template.toml" old_config_path = config_dir / "bot_config.toml" new_config_path = config_dir / "bot_config.toml" + action_template_path = template_dir / "actions_template.py" + action_config_path = config_dir / "actions.py" + + # 如果config里没有actions.py就复制一份过来 + if not action_config_path.exists(): + shutil.copy(action_template_path, action_config_path) + Path('config/__init__.py').touch() # 读取旧配置文件 old_config = {} diff --git a/src/plugins/action_executer/action_executer.py b/src/plugins/action_executer/action_executer.py index 8edb7340..30b5a55d 100644 --- a/src/plugins/action_executer/action_executer.py +++ b/src/plugins/action_executer/action_executer.py @@ -1,6 +1,7 @@ class ResponseAction: def __init__(self): self.tags = [] + self.msgs = [] def parse_action(self, msg: str, action: str) -> str: if action in msg: @@ -18,6 +19,15 @@ class ResponseAction: # 非str输入直接抛异常 raise TypeError - def __bool__(self): - # 这是为了直接嵌入到llm_generator.py的处理流里 便于跳过消息处理流程 - return False \ No newline at end of file + + + +from ....config.actions import usable_action_description + +extern_prompt = f""" +`` +{'\n'.join(usable_action_description)} +`` +你可以使用**``**中给出的标签来执行特定动作,请参考对应部分的描述。 +注意,标签一定是以“[内容]”形式输出的,你可以在一次响应中执行多个动作。 +""" \ No newline at end of file diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index e8937521..b5bf4e29 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -189,9 +189,17 @@ class ChatBot: willing_manager.change_reply_willing_not_sent(chat) # print(f"response: {response}") + response_action = None + if global_config.enable_action_execute: + from ..action_executer.action_executer import ResponseAction + from ....config.actions import usable_action + if isinstance(response, ResponseAction): + response_actions = response.tags + response = response.msgs + if response: stream_id = message.chat_stream.stream_id - + if global_config.enable_think_flow: chat_talking_prompt = "" if stream_id: @@ -218,6 +226,11 @@ class ChatBot: logger.warning("未找到对应的思考消息,可能已超时被移除") return + # 清理掉思考消息后开始做发送前处理 + if global_config.enable_action_execute: + for action in response_action: + await response = usable_action[action](response) + # 记录开始思考的时间,避免从思考到回复的时间太久 thinking_start_time = thinking_message.thinking_start_time message_set = MessageSet(chat, think_id) diff --git a/src/plugins/chat/config.py b/src/plugins/chat/config.py index 37daf580..bc5a162e 100644 --- a/src/plugins/chat/config.py +++ b/src/plugins/chat/config.py @@ -115,7 +115,7 @@ class BotConfig: # experimental enable_friend_chat: bool = False # 是否启用好友聊天 enable_think_flow: bool = False # 是否启用思考流程 - enable_think_flow: bool = False # 是否允许执行动作 + enable_action_execute: bool = False # 是否允许执行动作 # 模型配置 diff --git a/src/plugins/chat/llm_generator.py b/src/plugins/chat/llm_generator.py index 7b032104..ad3a2c78 100644 --- a/src/plugins/chat/llm_generator.py +++ b/src/plugins/chat/llm_generator.py @@ -68,6 +68,18 @@ class ResponseGenerator: # print(f"raw_content: {raw_content}") # print(f"model_response: {model_response}") + if global_config.enable_action_execute: + from ..action_executer.action_executer import ResponseAction + from ....config.actions import usable_action + actions = ResponseAction() + for action in usable_action: + model_response = actions.parse_action(model_response, action) + if not actions.empty(): + actions.msgs = await self._process_response(model_response) + if actions.msgs: + return actions, raw_content + return None, raw_content + if model_response: logger.info(f"{global_config.BOT_NICKNAME}的回复是:{model_response}") model_response = await self._process_response(model_response) diff --git a/src/plugins/chat/prompt_builder.py b/src/plugins/chat/prompt_builder.py index dc2e5930..bfae2547 100644 --- a/src/plugins/chat/prompt_builder.py +++ b/src/plugins/chat/prompt_builder.py @@ -167,6 +167,10 @@ class PromptBuilder: 请回复的平淡一些,简短一些,说中文,不要刻意突出自身学科背景, 请注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 {moderation_prompt}不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。""" + + if global_config.enable_action_execute: + from ..action_executer.action_executer import extern_prompt + prompt = prompt + extern_prompt prompt_check_if_response = "" diff --git a/template/actions_template.py b/template/actions_template.py new file mode 100644 index 00000000..b9f373ba --- /dev/null +++ b/template/actions_template.py @@ -0,0 +1,21 @@ +from typing import Callable +import time + +# 示例函数 +async def refuse_response(response: list[str]) -> list[str]: + return [] +async def ping_response(response: list[str]) -> list[str]: + return [f"Pong! at {time.asctime()}."] + +# 可用函数表 注意每个函数都应该接收一个list[str](输入的响应), 输出一个list[str](输出的响应) +# 显然 你可以在函数里做各种操作来修改响应,做出其他动作,etc +# 注意到MaiMBot基于Python 3.9, 所以这里的注册顺序实际上决定了tag的执行顺序,越上方的越靠前 +usable_action: dict[str, Callable[[list[str]], list[str]]] = { + "[ping]" : ping_response, + "[refuse]" : refuse_response, +} + +usable_action_description = [ + "[ping]: 此标签**仅**用于用户确认系统是否在线,输出此标签会**导致消息被替换为**`Pong! at %当前时间%`" + "[refuse]: 此标签用于标识认为无需回复的情况,输出此标签会**使得消息不被发送**。", +] \ No newline at end of file From 8f4da2989915c0de056c085767ee9ba5f501e055 Mon Sep 17 00:00:00 2001 From: ChensenCHX <2087826155@qq.com> Date: Thu, 27 Mar 2025 16:00:57 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E7=BB=A7=E7=BB=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +-- config/auto_update.py | 7 +++---- src/plugins/action_executer/__init__.py | 0 .../action_executer/action_executer.py | 14 +++++++++----- src/plugins/chat/bot.py | 11 ++++++----- src/plugins/chat/llm_generator.py | 4 ++-- template/actions_template.py | 19 +++++++++++++------ template/bot_config_template.toml | 1 + 8 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 src/plugins/action_executer/__init__.py diff --git a/.gitignore b/.gitignore index 0cd14149..2215acb4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,8 @@ memory_graph.gml config/bot_config_dev.toml config/bot_config.toml config/bot_config.toml.bak -config/actions.py -config/__init__.py src/plugins/remote/client_uuid.json +src/plugins/action_executer/actions.py # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/config/auto_update.py b/config/auto_update.py index 241e97b2..90e9bd16 100644 --- a/config/auto_update.py +++ b/config/auto_update.py @@ -15,13 +15,12 @@ def update_config(): old_config_path = config_dir / "bot_config.toml" new_config_path = config_dir / "bot_config.toml" action_template_path = template_dir / "actions_template.py" - action_config_path = config_dir / "actions.py" + action_config_path = root_dir / "src" / "plugins" / "action_executer" / "actions.py" - # 如果config里没有actions.py就复制一份过来 + # 如果action_executer里没有actions.py就复制一份过来 if not action_config_path.exists(): shutil.copy(action_template_path, action_config_path) - Path('config/__init__.py').touch() - + # 读取旧配置文件 old_config = {} if old_config_path.exists(): diff --git a/src/plugins/action_executer/__init__.py b/src/plugins/action_executer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/plugins/action_executer/action_executer.py b/src/plugins/action_executer/action_executer.py index 30b5a55d..09c73ecc 100644 --- a/src/plugins/action_executer/action_executer.py +++ b/src/plugins/action_executer/action_executer.py @@ -1,3 +1,6 @@ +from src.common.logger import get_module_logger +logger = get_module_logger("action_executer") + class ResponseAction: def __init__(self): self.tags = [] @@ -5,6 +8,7 @@ class ResponseAction: def parse_action(self, msg: str, action: str) -> str: if action in msg: + logger.info(f"从消息中解析到{action}标签") self.tags.append(action) return msg.replace(action, '') return msg @@ -20,14 +24,14 @@ class ResponseAction: raise TypeError - - -from ....config.actions import usable_action_description +from .actions import usable_action_description extern_prompt = f""" `` -{'\n'.join(usable_action_description)} +{usable_action_description} `` 你可以使用**``**中给出的标签来执行特定动作,请参考对应部分的描述。 注意,标签一定是以“[内容]”形式输出的,你可以在一次响应中执行多个动作。 -""" \ No newline at end of file +""" + +logger.info("成功加载可用动作") \ No newline at end of file diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index b5bf4e29..7027a3df 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -189,10 +189,10 @@ class ChatBot: willing_manager.change_reply_willing_not_sent(chat) # print(f"response: {response}") - response_action = None + response_actions = None if global_config.enable_action_execute: from ..action_executer.action_executer import ResponseAction - from ....config.actions import usable_action + from ..action_executer.actions import usable_action if isinstance(response, ResponseAction): response_actions = response.tags response = response.msgs @@ -227,9 +227,10 @@ class ChatBot: return # 清理掉思考消息后开始做发送前处理 - if global_config.enable_action_execute: - for action in response_action: - await response = usable_action[action](response) + if global_config.enable_action_execute and response_actions: + for action in response_actions: + response = usable_action[action](response) + logger.info(f"正在处理{action}") # 记录开始思考的时间,避免从思考到回复的时间太久 thinking_start_time = thinking_message.thinking_start_time diff --git a/src/plugins/chat/llm_generator.py b/src/plugins/chat/llm_generator.py index ad3a2c78..d3711a06 100644 --- a/src/plugins/chat/llm_generator.py +++ b/src/plugins/chat/llm_generator.py @@ -70,15 +70,15 @@ class ResponseGenerator: if global_config.enable_action_execute: from ..action_executer.action_executer import ResponseAction - from ....config.actions import usable_action + from ..action_executer.actions import usable_action actions = ResponseAction() + logger.info(f"{global_config.BOT_NICKNAME}的原始回复是:{model_response}") for action in usable_action: model_response = actions.parse_action(model_response, action) if not actions.empty(): actions.msgs = await self._process_response(model_response) if actions.msgs: return actions, raw_content - return None, raw_content if model_response: logger.info(f"{global_config.BOT_NICKNAME}的回复是:{model_response}") diff --git a/template/actions_template.py b/template/actions_template.py index b9f373ba..bf0f74d6 100644 --- a/template/actions_template.py +++ b/template/actions_template.py @@ -1,10 +1,17 @@ from typing import Callable import time +from ..chat.config import global_config +from src.common.logger import get_module_logger +logger = get_module_logger("Actions") + # 示例函数 -async def refuse_response(response: list[str]) -> list[str]: +def refuse_response(response: list[str]) -> list[str]: + logger.info(f"{global_config.BOT_NICKNAME}认为不需要进行回复。") return [] -async def ping_response(response: list[str]) -> list[str]: + +def ping_response(response: list[str]) -> list[str]: + logger.info(f"{global_config.BOT_NICKNAME}认为这是测试是否在线。") return [f"Pong! at {time.asctime()}."] # 可用函数表 注意每个函数都应该接收一个list[str](输入的响应), 输出一个list[str](输出的响应) @@ -15,7 +22,7 @@ usable_action: dict[str, Callable[[list[str]], list[str]]] = { "[refuse]" : refuse_response, } -usable_action_description = [ - "[ping]: 此标签**仅**用于用户确认系统是否在线,输出此标签会**导致消息被替换为**`Pong! at %当前时间%`" - "[refuse]: 此标签用于标识认为无需回复的情况,输出此标签会**使得消息不被发送**。", -] \ No newline at end of file +usable_action_description = """ +[ping]: 此标签**仅在用户输入--Ping时**使用,输出此标签会**导致消息被替换为**`Pong! at %当前时间%`; +[refuse]: 此标签用于标识认为无需回复的情况,输出此标签会**使得消息不被发送**; +""" \ No newline at end of file diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 172a0387..a700fe47 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -135,6 +135,7 @@ enable = true enable_friend_chat = false # 是否启用好友聊天 enable_think_flow = false # 是否启用思维流 注意:可能会消耗大量token,请谨慎开启 #思维流适合搭配低能耗普通模型使用,例如qwen2.5 32b +enable_action_execute = false # 是否启用动作执行器 #下面的模型若使用硅基流动则不需要更改,使用ds官方则改成.env.prod自定义的宏,使用自定义模型则选择定位相似的模型自己填写 #推理模型 From 48d99be49791082ab4de05001ae517fc4864a1c6 Mon Sep 17 00:00:00 2001 From: ChensenCHX <2087826155@qq.com> Date: Thu, 27 Mar 2025 17:27:06 +0800 Subject: [PATCH 4/5] =?UTF-8?q?info=E4=BD=8D=E7=BD=AE=E5=8F=8D=E4=BA=86?= =?UTF-8?q?=E5=8F=AF=E8=BF=98=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 7027a3df..52eaba1e 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -229,8 +229,8 @@ class ChatBot: # 清理掉思考消息后开始做发送前处理 if global_config.enable_action_execute and response_actions: for action in response_actions: - response = usable_action[action](response) logger.info(f"正在处理{action}") + response = usable_action[action](response) # 记录开始思考的时间,避免从思考到回复的时间太久 thinking_start_time = thinking_message.thinking_start_time From 608a63f698896acbae362d58ab68abcaa2a35116 Mon Sep 17 00:00:00 2001 From: ChensenCHX <2087826155@qq.com> Date: Thu, 27 Mar 2025 19:35:51 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E5=B8=A6=E4=B8=8Alocals()=EF=BC=8C?= =?UTF-8?q?=E8=AE=A9=E6=93=8D=E4=BD=9C=E5=92=8C=E6=95=B0=E6=8D=AE=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=9B=B4=E7=AE=80=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/bot.py | 2 +- template/actions_template.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 52eaba1e..6291a2a8 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -230,7 +230,7 @@ class ChatBot: if global_config.enable_action_execute and response_actions: for action in response_actions: logger.info(f"正在处理{action}") - response = usable_action[action](response) + response = usable_action[action](response, locals()) # 记录开始思考的时间,避免从思考到回复的时间太久 thinking_start_time = thinking_message.thinking_start_time diff --git a/template/actions_template.py b/template/actions_template.py index bf0f74d6..8663d894 100644 --- a/template/actions_template.py +++ b/template/actions_template.py @@ -1,4 +1,4 @@ -from typing import Callable +from typing import Callable, Any import time from ..chat.config import global_config @@ -6,18 +6,21 @@ from src.common.logger import get_module_logger logger = get_module_logger("Actions") # 示例函数 -def refuse_response(response: list[str]) -> list[str]: +def refuse_response(response: list[str], env: dict[str, Any]) -> list[str]: logger.info(f"{global_config.BOT_NICKNAME}认为不需要进行回复。") return [] -def ping_response(response: list[str]) -> list[str]: +def ping_response(response: list[str], env: dict[str, Any]) -> list[str]: logger.info(f"{global_config.BOT_NICKNAME}认为这是测试是否在线。") return [f"Pong! at {time.asctime()}."] -# 可用函数表 注意每个函数都应该接收一个list[str](输入的响应), 输出一个list[str](输出的响应) +# 可用函数表 注意每个函数都应该接收一个list[str]与dict[str, Any] +# 它们是输入的响应与上下文中的环境变量 +# 特别地, env['userinfo'].user_id 是本次响应中消息发送者的qq号 +# 每个函数都应当输出一个list[str](输出的响应) # 显然 你可以在函数里做各种操作来修改响应,做出其他动作,etc # 注意到MaiMBot基于Python 3.9, 所以这里的注册顺序实际上决定了tag的执行顺序,越上方的越靠前 -usable_action: dict[str, Callable[[list[str]], list[str]]] = { +usable_action: dict[str, Callable[[list[str], dict], list[str]]] = { "[ping]" : ping_response, "[refuse]" : refuse_response, }