diff --git a/src/chat/focus_chat/planners/modify_actions.py b/src/chat/focus_chat/planners/modify_actions.py index f9d347fc6..cf0c00b12 100644 --- a/src/chat/focus_chat/planners/modify_actions.py +++ b/src/chat/focus_chat/planners/modify_actions.py @@ -58,6 +58,8 @@ class ActionModifier: logger.debug(f"{self.log_prefix}开始完整动作修改流程") # === 第一阶段:传统观察处理 === + chat_content = None + if observations: hfc_obs = None chat_obs = None @@ -78,7 +80,7 @@ class ActionModifier: if hfc_obs: obs = hfc_obs # 获取适用于FOCUS模式的动作 - all_actions = self.action_manager.get_using_actions_for_mode("focus") + all_actions = self.all_actions action_changes = await self.analyze_loop_actions(obs) if action_changes["add"] or action_changes["remove"]: # 合并动作变更 @@ -94,9 +96,8 @@ class ActionModifier: # 处理ChattingObservation - 传统的类型匹配检查 if chat_obs: - obs = chat_obs # 检查动作的关联类型 - chat_context = get_chat_manager().get_stream(obs.chat_id).context + chat_context = get_chat_manager().get_stream(chat_obs.chat_id).context type_mismatched_actions = [] for action_name in all_actions.keys(): @@ -128,26 +129,13 @@ class ActionModifier: f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}" ) - # === chat_mode检查:强制移除非auto模式下的exit_focus_chat === - if global_config.chat.chat_mode != "auto": - if "exit_focus_chat" in self.action_manager.get_using_actions(): - self.action_manager.remove_action_from_using("exit_focus_chat") - logger.info( - f"{self.log_prefix}移除动作: exit_focus_chat,原因: chat_mode不为auto(当前模式: {global_config.chat.chat_mode})" - ) + # 注释:已移除exit_focus_chat动作,现在由no_reply动作处理频率检测退出专注模式 # === 第二阶段:激活类型判定 === # 如果提供了聊天上下文,则进行激活类型判定 if chat_content is not None: logger.debug(f"{self.log_prefix}开始激活类型判定阶段") - # 保存exit_focus_chat动作(如果存在) - exit_focus_action = None - if "exit_focus_chat" in self.action_manager.get_using_actions(): - exit_focus_action = self.action_manager.get_using_actions()["exit_focus_chat"] - self.action_manager.remove_action_from_using("exit_focus_chat") - logger.debug(f"{self.log_prefix}临时移除exit_focus_chat动作以进行激活类型判定") - # 获取当前使用的动作集(经过第一阶段处理,且适用于FOCUS模式) current_using_actions = self.action_manager.get_using_actions() all_registered_actions = self.action_manager.get_registered_actions() @@ -197,16 +185,7 @@ class ActionModifier: reason = removal_reasons.get(action_name, "未知原因") logger.info(f"{self.log_prefix}移除动作: {action_name},原因: {reason}") - # 恢复exit_focus_chat动作(如果之前存在) - if exit_focus_action: - # 只有在auto模式下才恢复exit_focus_chat动作 - if global_config.chat.chat_mode == "auto": - self.action_manager.add_action_to_using("exit_focus_chat") - logger.debug(f"{self.log_prefix}恢复exit_focus_chat动作") - else: - logger.debug( - f"{self.log_prefix}跳过恢复exit_focus_chat动作,原因: chat_mode不为auto(当前模式: {global_config.chat.chat_mode})" - ) + # 注释:已完全移除exit_focus_chat动作 logger.info(f"{self.log_prefix}激活类型判定完成,最终可用动作: {list(final_activated_actions.keys())}") @@ -576,30 +555,13 @@ class ActionModifier: if not recent_cycles: return result - # 统计no_reply的数量 - no_reply_count = 0 reply_sequence = [] # 记录最近的动作序列 for cycle in recent_cycles: action_result = cycle.loop_plan_info.get("action_result", {}) action_type = action_result.get("action_type", "unknown") - if action_type == "no_reply": - no_reply_count += 1 reply_sequence.append(action_type == "reply") - # 检查no_reply比例 - if len(recent_cycles) >= (4 * global_config.chat.exit_focus_threshold) and ( - no_reply_count / len(recent_cycles) - ) >= (0.7 * global_config.chat.exit_focus_threshold): - if global_config.chat.chat_mode == "auto": - result["add"].append("exit_focus_chat") - result["remove"].append("no_reply") - result["remove"].append("reply") - no_reply_ratio = no_reply_count / len(recent_cycles) - logger.info( - f"{self.log_prefix}检测到高no_reply比例: {no_reply_ratio:.2f},达到退出聊天阈值,将添加exit_focus_chat并移除no_reply/reply动作" - ) - # 计算连续回复的相关阈值 max_reply_num = int(global_config.focus_chat.consecutive_replies * 3.2) @@ -613,7 +575,7 @@ class ActionModifier: last_max_reply_num = reply_sequence[:] # 详细打印阈值和序列信息,便于调试 - logger.debug( + logger.info( f"连续回复阈值: max={max_reply_num}, sec={sec_thres_reply_num}, one={one_thres_reply_num}," f"最近reply序列: {last_max_reply_num}" ) diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 37e55f3cb..b43cb1b02 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -563,21 +563,21 @@ class NormalChat: self.interest_dict.pop(msg_id, None) # 创建并行任务列表 - tasks = [] + coroutines = [] for msg_id, (message, interest_value, is_mentioned) in items_to_process: - task = process_single_message(msg_id, message, interest_value, is_mentioned) - tasks.append(task) + coroutine = process_single_message(msg_id, message, interest_value, is_mentioned) + coroutines.append(coroutine) # 并行执行所有任务,限制并发数量避免资源过度消耗 - if tasks: + if coroutines: # 使用信号量控制并发数,最多同时处理5个消息 semaphore = asyncio.Semaphore(5) - async def limited_process(task, sem): + async def limited_process(coroutine, sem): async with sem: - await task + await coroutine - limited_tasks = [limited_process(task, semaphore) for task in tasks] + limited_tasks = [limited_process(coroutine, semaphore) for coroutine in coroutines] await asyncio.gather(*limited_tasks, return_exceptions=True) except asyncio.CancelledError: diff --git a/src/plugins/built_in/core_actions/_manifest.json b/src/plugins/built_in/core_actions/_manifest.json index b690838a6..1d1266f67 100644 --- a/src/plugins/built_in/core_actions/_manifest.json +++ b/src/plugins/built_in/core_actions/_manifest.json @@ -39,16 +39,6 @@ "type": "action", "name": "emoji", "description": "发送表情包辅助表达情绪" - }, - { - "type": "action", - "name": "change_to_focus_chat", - "description": "切换到专注聊天,从普通模式切换到专注模式" - }, - { - "type": "action", - "name": "exit_focus_chat", - "description": "退出专注聊天,从专注模式切换到普通模式" } ] } diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index e263c59b1..a4d2ef57b 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -147,9 +147,10 @@ class NoReplyAction(BaseAction): # 跳过LLM判断的配置 _skip_judge_when_tired = True - _skip_probability_light = 0.2 # 轻度疲惫跳过概率 - _skip_probability_medium = 0.4 # 中度疲惫跳过概率 - _skip_probability_heavy = 0.6 # 重度疲惫跳过概率 + _skip_probability = 0.5 + + # 新增:回复频率退出专注模式的配置 + _frequency_check_window = 600 # 频率检查窗口时间(秒) # 动作参数定义 action_parameters = {"reason": "不回复的原因"} @@ -214,6 +215,20 @@ class NoReplyAction(BaseAction): ) return True, exit_reason + # **新增**:检查回复频率,决定是否退出专注模式 + should_exit_focus = await self._check_frequency_and_exit_focus(current_time) + if should_exit_focus: + logger.info(f"{self.log_prefix} 检测到回复频率过高,退出专注模式") + # 标记退出专注模式 + self.action_data["_system_command"] = "stop_focus_chat" + exit_reason = f"{global_config.bot.nickname}(你)发现自己回复太频繁了,决定退出专注模式,稍作休息" + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=exit_reason, + action_done=True, + ) + return True, exit_reason + # 检查是否有新消息 new_message_count = message_api.count_new_messages( chat_id=self.chat_id, start_time=start_time, end_time=current_time @@ -314,13 +329,14 @@ class NoReplyAction(BaseAction): over_count = bot_message_count - talk_frequency_threshold # 根据超过的数量设置不同的提示词和跳过概率 + skip_probability = 0 if over_count <= 3: frequency_block = "你感觉稍微有些累,回复的有点多了。\n" elif over_count <= 5: frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n" else: frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n" - skip_probability = self._skip_probability_heavy + skip_probability = self._skip_probability # 根据配置和概率决定是否跳过LLM判断 if self._skip_judge_when_tired and random.random() < skip_probability: @@ -464,6 +480,64 @@ class NoReplyAction(BaseAction): ) return False, f"不回复动作执行失败: {e}" + async def _check_frequency_and_exit_focus(self, current_time: float) -> bool: + """检查回复频率,决定是否退出专注模式 + + Args: + current_time: 当前时间戳 + + Returns: + bool: 是否应该退出专注模式 + """ + try: + # 只在auto模式下进行频率检查 + if global_config.chat.chat_mode != "auto": + return False + + # 获取检查窗口内的所有消息 + window_start_time = current_time - self._frequency_check_window + all_messages = message_api.get_messages_by_time_in_chat( + chat_id=self.chat_id, + start_time=window_start_time, + end_time=current_time, + ) + + if not all_messages: + return False + + # 统计bot自己的回复数量 + bot_message_count = 0 + user_id = global_config.bot.qq_account + + for message in all_messages: + sender_id = message.get("user_id", "") + if sender_id == user_id: + bot_message_count += 1 + + # 计算当前回复频率(每分钟回复数) + window_minutes = self._frequency_check_window / 60 + current_frequency = bot_message_count / window_minutes + + # 计算阈值频率:使用 exit_focus_threshold * 1.5 + threshold_multiplier = global_config.chat.exit_focus_threshold * 1.5 + threshold_frequency = global_config.chat.talk_frequency * threshold_multiplier + + # 判断是否超过阈值 + if current_frequency > threshold_frequency: + logger.info( + f"{self.log_prefix} 回复频率检查:当前频率 {current_frequency:.2f}/分钟,超过阈值 {threshold_frequency:.2f}/分钟 (exit_threshold={global_config.chat.exit_focus_threshold} * 1.5),准备退出专注模式" + ) + return True + else: + logger.debug( + f"{self.log_prefix} 回复频率检查:当前频率 {current_frequency:.2f}/分钟,未超过阈值 {threshold_frequency:.2f}/分钟 (exit_threshold={global_config.chat.exit_focus_threshold} * 1.5)" + ) + return False + + except Exception as e: + logger.error(f"{self.log_prefix} 检查回复频率时出错: {e}") + return False + def _parse_llm_judge_response(self, response: str) -> tuple[str, str]: """解析LLM判断响应,使用JSON格式提取判断结果和理由 @@ -596,66 +670,6 @@ class EmojiAction(BaseAction): return False, f"表情发送失败: {str(e)}" -class ExitFocusChatAction(BaseAction): - """退出专注聊天动作 - 从专注模式切换到普通模式""" - - # 激活设置 - focus_activation_type = ActionActivationType.NEVER - normal_activation_type = ActionActivationType.NEVER - mode_enable = ChatMode.FOCUS - parallel_action = False - - # 动作基本信息 - action_name = "exit_focus_chat" - action_description = "退出专注聊天,从专注模式切换到普通模式" - - # LLM判断提示词 - llm_judge_prompt = """ - 判定是否需要退出专注聊天的条件: - 1. 很长时间没有回复,应该退出专注聊天 - 2. 当前内容不需要持续专注关注 - 3. 聊天内容已经完成,话题结束 - - 请回答"是"或"否"。 - """ - - # 动作参数定义 - action_parameters = {} - - # 动作使用场景 - action_require = [ - "很长时间没有回复,你决定退出专注聊天", - "当前内容不需要持续专注关注,你决定退出专注聊天", - "聊天内容已经完成,你决定退出专注聊天", - ] - - # 关联类型 - associated_types = [] - - async def execute(self) -> Tuple[bool, str]: - """执行退出专注聊天动作""" - logger.info(f"{self.log_prefix} 决定退出专注聊天: {self.reasoning}") - - try: - # 标记状态切换请求 - self._mark_state_change() - - # 重置NoReplyAction的连续计数器 - NoReplyAction.reset_consecutive_count() - - status_message = "决定退出专注聊天模式" - return True, status_message - - except Exception as e: - logger.error(f"{self.log_prefix} 退出专注聊天动作执行失败: {e}") - return False, f"退出专注聊天失败: {str(e)}" - - def _mark_state_change(self): - """标记状态切换请求""" - # 通过action_data传递状态切换命令 - self.action_data["_system_command"] = "stop_focus_chat" - logger.info(f"{self.log_prefix} 已标记状态切换命令: stop_focus_chat") - @register_plugin class CoreActionsPlugin(BasePlugin): @@ -757,6 +771,10 @@ class CoreActionsPlugin(BasePlugin): skip_probability_heavy = self.get_config("no_reply.skip_probability_heavy", 0.6) NoReplyAction._skip_probability_heavy = skip_probability_heavy + # 新增:频率检测相关配置 + frequency_check_window = self.get_config("no_reply.frequency_check_window", 600) + NoReplyAction._frequency_check_window = frequency_check_window + # --- 根据配置注册组件 --- components = [] if self.get_config("components.enable_reply", True): @@ -765,14 +783,10 @@ class CoreActionsPlugin(BasePlugin): components.append((NoReplyAction.get_action_info(), NoReplyAction)) if self.get_config("components.enable_emoji", True): components.append((EmojiAction.get_action_info(), EmojiAction)) - if self.get_config("components.enable_exit_focus", True): - components.append((ExitFocusChatAction.get_action_info(), ExitFocusChatAction)) # components.append((DeepReplyAction.get_action_info(), DeepReplyAction)) return components - - # class DeepReplyAction(BaseAction): # """回复动作 - 参与聊天回复""" @@ -895,3 +909,5 @@ class CoreActionsPlugin(BasePlugin): # data = reply[1] # reply_text += data # return reply_text + +