From f6ce69c45b6f0b3f31bce7fb2fa463e3db5c6c46 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:25:24 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(chat):=20=E5=AE=9E=E7=8E=B0=E5=A4=9A?= =?UTF-8?q?=E9=87=8D=E5=9B=9E=E5=A4=8D=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E5=8D=95=E8=BD=AE=E5=A4=84=E7=90=86=E5=A4=9A=E6=9D=A1?= =?UTF-8?q?=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次更新引入了核心的多重回复功能,使得 Bot 能够在单次规划中响应多条不同的未读消息。这显著提升了其在活跃群聊中的交互效率和上下文处理能力。 主要变更包括: - **Planner Prompts 更新**: - 增加了明确的指令和示例,引导 LLM 在检测到多个需要回应的消息时,生成一个包含多个 `reply` 动作的 `actions` 列表。 - **PlanExecutor 逻辑增强**: - `_execute_reply_actions` 方法被重构,以串行方式执行计划中的所有回复动作。 - 引入了新的控制逻辑,确保只有在执行完最后一个 `reply` 动作后,才会清除未读消息队列。 - **ActionManager 功能扩展**: - `execute_action` 方法新增 `clear_unread_messages` 参数(默认为 `True`),允许调用方控制是否在动作执行后清空未读消息。 - `PlanExecutor` 在调用非 `reply` 动作或非最后一个 `reply` 动作时,会显式将此参数设为 `False`,防止过早清除消息。 --- src/chat/planner_actions/action_manager.py | 8 ++-- .../affinity_flow_chatter/plan_executor.py | 39 ++++++++++++---- .../affinity_flow_chatter/planner_prompts.py | 44 +++++++++++++++++-- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index 28d64f94c..f92956bc2 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -145,6 +145,7 @@ class ChatterActionManager: action_data: Optional[dict] = None, thinking_id: Optional[str] = None, log_prefix: str = "", + clear_unread_messages: bool = True, ) -> Any: """ 执行单个动作的通用函数 @@ -217,7 +218,8 @@ class ChatterActionManager: if success: await self._record_action_to_message(chat_stream, action_name, target_message, action_data) # 自动清空所有未读消息 - await self._clear_all_unread_messages(chat_stream.stream_id, action_name) + if clear_unread_messages: + await self._clear_all_unread_messages(chat_stream.stream_id, action_name) # 重置打断计数 await self._reset_interruption_count_after_action(chat_stream.stream_id) @@ -262,8 +264,8 @@ class ChatterActionManager: # 记录回复动作到目标消息 await self._record_action_to_message(chat_stream, "reply", target_message, action_data) - # 自动清空所有未读消息 - await self._clear_all_unread_messages(chat_stream.stream_id, "reply") + if clear_unread_messages: + await self._clear_all_unread_messages(chat_stream.stream_id, "reply") # 回复成功,重置打断计数 await self._reset_interruption_count_after_action(chat_stream.stream_id) diff --git a/src/plugins/built_in/affinity_flow_chatter/plan_executor.py b/src/plugins/built_in/affinity_flow_chatter/plan_executor.py index 3aa1a28c0..821d52bd6 100644 --- a/src/plugins/built_in/affinity_flow_chatter/plan_executor.py +++ b/src/plugins/built_in/affinity_flow_chatter/plan_executor.py @@ -111,16 +111,26 @@ class ChatterPlanExecutor: } async def _execute_reply_actions(self, reply_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]: - """执行回复动作""" + """串行执行所有回复动作""" results = [] + total_actions = len(reply_actions) + if total_actions > 1: + logger.info(f"[多重回复] 开始执行 {total_actions} 个回复任务。") - for action_info in reply_actions: - result = await self._execute_single_reply_action(action_info, plan) + for i, action_info in enumerate(reply_actions): + is_last_action = i == total_actions - 1 + if total_actions > 1: + logger.info(f"[多重回复] 正在执行第 {i+1}/{total_actions} 个回复...") + + # 传递 clear_unread 参数 + result = await self._execute_single_reply_action(action_info, plan, clear_unread=is_last_action) results.append(result) + if total_actions > 1: + logger.info(f"[多重回复] 所有回复任务执行完毕。") return {"results": results} - async def _execute_single_reply_action(self, action_info: ActionPlannerInfo, plan: Plan) -> Dict[str, any]: + async def _execute_single_reply_action(self, action_info: ActionPlannerInfo, plan: Plan, clear_unread: bool = True) -> Dict[str, any]: """执行单个回复动作""" start_time = time.time() success = False @@ -152,17 +162,29 @@ class ChatterPlanExecutor: "target_message": action_info.action_message, "reasoning": action_info.reasoning, "action_data": action_info.action_data or {}, + "clear_unread_messages": clear_unread, } logger.debug(f"📬 [PlanExecutor] 准备调用 ActionManager,target_message: {action_info.action_message}") # 通过动作管理器执行回复 - reply_content = await self.action_manager.execute_action( + execution_result = await self.action_manager.execute_action( action_name=action_info.action_type, **action_params ) + + # 从返回结果中提取真正的回复文本 + if isinstance(execution_result, dict): + reply_content = execution_result.get("reply_text", "") + success = execution_result.get("success", False) + else: + # 兼容旧的返回值(虽然可能性不大) + reply_content = str(execution_result) if execution_result else "" + success = bool(reply_content) - success = True - logger.info(f"回复动作 '{action_info.action_type}' 执行成功。") + if success: + logger.info(f"回复动作 '{action_info.action_type}' 执行成功。") + else: + raise Exception(execution_result.get("error", "未知错误")) except Exception as e: error_message = str(e) @@ -181,7 +203,7 @@ class ChatterPlanExecutor: "error_message": error_message, "execution_time": execution_time, "reasoning": action_info.reasoning, - "reply_content": reply_content[:200] + "..." if len(reply_content) > 200 else reply_content, + "reply_content": reply_content[:200] + "..." if reply_content and len(reply_content) > 200 else reply_content, } async def _execute_other_actions(self, other_actions: List[ActionPlannerInfo], plan: Plan) -> Dict[str, any]: @@ -251,6 +273,7 @@ class ChatterPlanExecutor: "target_message": action_info.action_message, "reasoning": action_info.reasoning, "action_data": action_data, + "clear_unread_messages": False, # 其他动作不应清除未读消息 } # 通过动作管理器执行动作 diff --git a/src/plugins/built_in/affinity_flow_chatter/planner_prompts.py b/src/plugins/built_in/affinity_flow_chatter/planner_prompts.py index c8f448067..fdf07b296 100644 --- a/src/plugins/built_in/affinity_flow_chatter/planner_prompts.py +++ b/src/plugins/built_in/affinity_flow_chatter/planner_prompts.py @@ -44,10 +44,11 @@ def init_prompts(): 1. **重要:已读历史消息仅作为当前聊天情景的参考,帮助你理解对话上下文。** 2. **重要:所有动作的执行对象只能是未读历史消息中的消息,不能对已读消息执行动作。** 3. 在未读历史消息中,优先对兴趣值高的消息做出动作(兴趣值标注在消息末尾)。 -4. 首先,决定是否要对未读消息进行 `reply`(如果有)。 -5. 然后,评估当前的对话气氛和用户情绪,判断是否需要一个**辅助动作**来让你的回应更生动、更符合你的性格。 -6. 如果需要,选择一个最合适的辅助动作与 `reply`(如果有) 组合。 -7. 如果用户明确要求了某个动作,请务必优先满足。 +4. **核心:如果有多条未读消息都需要回应(例如多人@你),你应该并行处理,在`actions`列表中生成多个`reply`动作。** +5. 首先,决定是否要对未读消息进行 `reply`(如果有)。 +6. 然后,评估当前的对话气氛和用户情绪,判断是否需要一个**辅助动作**来让你的回应更生动、更符合你的性格。 +7. 如果需要,选择一个最合适的辅助动作与 `reply`(如果有) 组合。 +8. 如果用户明确要求了某个动作,请务必优先满足。 **重要提醒:** - **回复消息时必须遵循对话的流程,不要重复已经说过的话。** @@ -70,6 +71,7 @@ def init_prompts(): ## 可用动作列表 {action_options_text} +### 单动作示例: ```json {{ "thinking": "在这里写下你的思绪流...", @@ -86,6 +88,40 @@ def init_prompts(): }} ``` +### **多重回复示例 (核心功能)** +当有多人与你互动时,你需要同时回应他们,甚至可以同时处理三个! +```json +{{ + "thinking": "哇,群里好热闹呀!张三、李四、王五都在@我!让我看看...张三在问我昨天推荐的电影好不好看,这个得好好分享一下观后感。李四在说他家的猫咪学会了新技能,好可爱,得夸夸他!王五好像遇到点麻烦,在问我一个技术问题,这个得优先、详细地解答一下!得一个个来!", + "actions": [ + {{ + "action_type": "reply", + "reasoning": "回应张三关于电影的提问,并分享我的看法。", + "action_data": {{ + "target_message_id": "m124", + "content": "张三!你问的那部电影我昨天也看啦,真的超赞!特别是最后那个反转,简直让人意想不到!" + }} + }}, + {{ + "action_type": "reply", + "reasoning": "回应李四分享的趣事,表达赞美和羡慕。", + "action_data": {{ + "target_message_id": "m125", + "content": "哇,李四你家猫咪也太聪明了吧!居然会握手了!好羡慕呀!" + }} + }}, + {{ + "action_type": "reply", + "reasoning": "优先回应王五的技术求助,并提供详细的解答。", + "action_data": {{ + "target_message_id": "m126", + "content": "王五别急,你说的那个问题我之前也遇到过。你试试看是不是配置文件里的`enable_magic`选项没有设置成`true`?如果还不行你再把错误截图发我看看。" + }} + }} + ] +}} +``` + **强制规则**: - 对于每一个需要目标消息的动作(如`reply`, `poke_user`, `set_emoji_like`),你 **必须** 在`action_data`中提供准确的`target_message_id`,这个ID来源于`## 未读历史消息`中消息前的``标签。 - 当你选择的动作需要参数时(例如 `set_emoji_like` 需要 `emoji` 参数),你 **必须** 在 `action_data` 中提供所有必需的参数及其对应的值。 From 136496c883dad3af65d4f82a14c917d318708a3d Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:04:04 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(config):=20=E6=96=B0=E5=A2=9E=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E5=B9=B6=E5=8F=91=E5=88=86=E5=8F=91=E6=95=B0=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 在 `ChatConfig` 中引入了 `max_concurrent_distributions` 配置,允许用户自定义最大并发处理的消息流数量。 此举旨在提供更灵活的性能调优选项,帮助用户根据服务器负载和API速率限制来控制资源消耗,防止系统过载。该配置的默认值为10。 --- src/chat/message_manager/distribution_manager.py | 4 ++-- src/config/official_configs.py | 3 +++ template/bot_config_template.toml | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/chat/message_manager/distribution_manager.py b/src/chat/message_manager/distribution_manager.py index 44c48376a..ff06ec5d3 100644 --- a/src/chat/message_manager/distribution_manager.py +++ b/src/chat/message_manager/distribution_manager.py @@ -34,8 +34,8 @@ class StreamLoopManager: } # 配置参数 - self.max_concurrent_streams = max_concurrent_streams or getattr( - global_config.chat, "max_concurrent_distributions", 10 + self.max_concurrent_streams = ( + max_concurrent_streams or global_config.chat.max_concurrent_distributions ) # Chatter管理器 diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 1dd517834..3615f49a7 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -158,6 +158,9 @@ class ChatConfig(ValidatedConfigBase): dynamic_distribution_jitter_factor: float = Field( default=0.2, ge=0.0, le=0.5, description="分发间隔随机扰动因子" ) + max_concurrent_distributions: int = Field( + default=10, ge=1, le=100, description="最大并发处理的消息流数量" + ) def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: """ diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 4b08c8cd0..d9d94a06d 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "7.0.2" +version = "7.0.3" #----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -131,6 +131,7 @@ dynamic_distribution_base_interval = 5.0 # 基础分发间隔(秒) dynamic_distribution_min_interval = 1.0 # 最小分发间隔(秒) dynamic_distribution_max_interval = 30.0 # 最大分发间隔(秒) dynamic_distribution_jitter_factor = 0.2 # 分发间隔随机扰动因子 +max_concurrent_distributions = 10 # 最大并发处理的消息流数量,可以根据API性能和服务器负载调整 talk_frequency_adjust = [ ["", "8:00,1", "12:00,1.2", "18:00,1.5", "01:00,0.6"],