diff --git a/src/chat/chat_loop/cycle_processor.py b/src/chat/chat_loop/cycle_processor.py index 1af0adfc5..f913ff079 100644 --- a/src/chat/chat_loop/cycle_processor.py +++ b/src/chat/chat_loop/cycle_processor.py @@ -49,15 +49,21 @@ class CycleProcessor: action_message, cycle_timers: Dict[str, float], thinking_id, - plan_result, + actions, ) -> Tuple[Dict[str, Any], str, Dict[str, float]]: with Timer("回复发送", cycle_timers): reply_text = await self.response_handler.send_response(response_set, reply_to_str, loop_start_time, action_message) # 存储reply action信息 person_info_manager = get_person_info_manager() + + # 获取 platform,如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值 + platform = action_message.get("chat_info_platform") + if platform is None: + platform = getattr(self.chat_stream, "platform", "unknown") + person_id = person_info_manager.get_person_id( - action_message.get("chat_info_platform", ""), + platform, action_message.get("user_id", ""), ) person_name = await person_info_manager.get_value(person_id, "person_name") @@ -76,7 +82,7 @@ class CycleProcessor: # 构建循环信息 loop_info: Dict[str, Any] = { "loop_plan_info": { - "action_result": plan_result.get("action_result", {}), + "action_result": actions, }, "loop_action_info": { "action_taken": True, @@ -88,12 +94,12 @@ class CycleProcessor: return loop_info, reply_text, cycle_timers - async def observe(self, message_data: Optional[Dict[str, Any]] = None) -> bool: + async def observe(self,interest_value:float = 0.0) -> bool: """ 观察和处理单次思考循环的核心方法 Args: - message_data: 可选的消息数据字典,包含用户消息、平台信息等 + interest_value: 兴趣值 Returns: bool: 处理是否成功 @@ -105,13 +111,40 @@ class CycleProcessor: - 执行动作规划或直接回复 - 根据动作类型分发到相应的处理方法 """ - if not message_data: - message_data = {} + action_type = "no_action" + reply_text = "" # 初始化reply_text变量,避免UnboundLocalError + reply_to_str = "" # 初始化reply_to_str变量 + + # 根据interest_value计算概率,决定使用哪种planner模式 + # interest_value越高,越倾向于使用Normal模式 + import random + import math + + # 使用sigmoid函数将interest_value转换为概率 + # 当interest_value为0时,概率接近0(使用Focus模式) + # 当interest_value很高时,概率接近1(使用Normal模式) + def calculate_normal_mode_probability(interest_val: float) -> float: + # 使用sigmoid函数,调整参数使概率分布更合理 + # 当interest_value = 0时,概率约为0.1 + # 当interest_value = 1时,概率约为0.5 + # 当interest_value = 2时,概率约为0.8 + # 当interest_value = 3时,概率约为0.95 + k = 2.0 # 控制曲线陡峭程度 + x0 = 1.0 # 控制曲线中心点 + return 1.0 / (1.0 + math.exp(-k * (interest_val - x0))) + + normal_mode_probability = calculate_normal_mode_probability(interest_value) + + # 根据概率决定使用哪种模式 + if random.random() < normal_mode_probability: + mode = ChatMode.NORMAL + logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f},选择Normal planner模式") + else: + mode = ChatMode.FOCUS + logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f},选择Focus planner模式") cycle_timers, thinking_id = self.cycle_tracker.start_cycle() - logger.info( - f"{self.context.log_prefix} 开始第{self.context.cycle_counter}次思考[模式:{self.context.loop_mode}]" - ) + logger.info(f"{self.log_prefix} 开始第{self.context.cycle_counter}次思考") if ENABLE_S4U: await send_typing() @@ -127,75 +160,26 @@ class CycleProcessor: logger.error(f"{self.context.log_prefix} 动作修改失败: {e}") available_actions = {} - is_mentioned_bot = message_data.get("is_mentioned", False) - at_bot_mentioned = (global_config.chat.mentioned_bot_inevitable_reply and is_mentioned_bot) or ( - global_config.chat.at_bot_inevitable_reply and is_mentioned_bot - ) - - # 专注模式下提及bot必定回复 - if self.context.loop_mode == ChatMode.FOCUS and at_bot_mentioned and "no_reply" in available_actions: - available_actions = {k: v for k, v in available_actions.items() if k != "no_reply"} - - # 检查是否在normal模式下没有可用动作(除了reply相关动作) - skip_planner = False - - if self.context.loop_mode == ChatMode.NORMAL: - non_reply_actions = { - k: v for k, v in available_actions.items() if k not in ["reply", "no_reply", "no_action"] - } - if not non_reply_actions: - skip_planner = True - logger.info(f"Normal模式下没有可用动作,直接回复") - plan_result = self._get_direct_reply_plan(loop_start_time) - target_message = message_data - - - # Focus模式 - if not skip_planner: - from src.plugin_system.core.event_manager import event_manager - from src.plugin_system.base.component_types import EventType - - # 触发 ON_PLAN 事件 - result = await event_manager.trigger_event( - EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.stream_id + # 执行planner + planner_info = self.action_planner.get_necessary_info() + prompt_info = await self.action_planner.build_planner_prompt( + is_group_chat=planner_info[0], + chat_target_info=planner_info[1], + current_available_actions=planner_info[2], ) - if result and not result.all_continue_process(): - return + from src.plugin_system.core.event_manager import event_manager + from src.plugin_system import EventType + + result = await event_manager.trigger_event(EventType.ON_PLAN,plugin_name="SYSTEM", stream_id=self.context.chat_stream) + if not result.all_continue_process(): + raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成") with Timer("规划器", cycle_timers): - plan_result, target_message = await self.action_planner.plan(mode=self.context.loop_mode) - - action_result = plan_result.get("action_result", {}) - - action_type = action_result.get("action_type", "error") - action_data = action_result.get("action_data", {}) - reasoning = action_result.get("reasoning", "未提供理由") - is_parallel = action_result.get("is_parallel", True) - - action_data["loop_start_time"] = loop_start_time - action_message = message_data or target_message - - # is_private_chat = self.context.chat_stream.group_info is None if self.context.chat_stream else False - - # 重构后的动作处理逻辑:先汇总所有动作,然后并行执行 - actions = [] - - # 1. 添加Planner取得的动作 - actions.append({ - "action_type": action_type, - "reasoning": reasoning, - "action_data": action_data, - "action_message": action_message, - "available_actions": available_actions # 添加这个字段 - }) - - # 2. 如果不是reply动作且需要并行执行,额外添加reply动作 - if action_type != "reply" and is_parallel: - actions.append({ - "action_type": "reply", - "action_message": action_message, - "available_actions": available_actions - }) + actions, _= await self.action_planner.plan( + mode=mode, + loop_start_time=loop_start_time, + available_actions=available_actions, + ) async def execute_action(action_info): """执行单个动作的通用函数""" @@ -242,7 +226,6 @@ class CycleProcessor: else: # 执行回复动作 reply_to_str = await self._build_reply_to_str(action_info["action_message"]) - request_type = "chat.replyer" # 生成回复 gather_timeout = global_config.chat.thinking_timeout @@ -252,7 +235,7 @@ class CycleProcessor: message_data=action_info["action_message"], available_actions=action_info["available_actions"], reply_to=reply_to_str, - request_type=request_type, + request_type="chat.replyer", ), timeout=gather_timeout ) @@ -291,7 +274,7 @@ class CycleProcessor: action_info["action_message"], cycle_timers, thinking_id, - plan_result, + actions, ) return { "action_type": "reply", @@ -301,6 +284,7 @@ class CycleProcessor: } except Exception as e: logger.error(f"{self.log_prefix} 执行动作时出错: {e}") + logger.error(f"{self.log_prefix} 错误信息: {traceback.format_exc()}") return { "action_type": action_info["action_type"], "success": False, @@ -310,6 +294,7 @@ class CycleProcessor: } # 创建所有动作的后台任务 + action_tasks = [asyncio.create_task(execute_action(action)) for action in actions] # 并行执行所有任务 @@ -356,7 +341,7 @@ class CycleProcessor: # 没有回复信息,构建纯动作的loop_info loop_info = { "loop_plan_info": { - "action_result": plan_result.get("action_result", {}), + "action_result": actions, }, "loop_action_info": { "action_taken": action_success, @@ -366,8 +351,6 @@ class CycleProcessor: }, } reply_text = action_reply_text - - self.last_action = action_type if ENABLE_S4U: await stop_typing() @@ -375,21 +358,15 @@ class CycleProcessor: self.context.chat_instance.cycle_tracker.end_cycle(loop_info, cycle_timers) self.context.chat_instance.cycle_tracker.print_cycle_info(cycle_timers) - if self.context.loop_mode == ChatMode.NORMAL: - await self.context.chat_instance.willing_manager.after_generate_reply_handle(message_data.get("message_id", "")) - + # await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", "")) + action_type = actions[0]["action_type"] if actions else "no_action" # 管理no_reply计数器:当执行了非no_reply动作时,重置计数器 - if action_type != "no_reply" and action_type != "no_action": + if action_type != "no_reply": # no_reply逻辑已集成到heartFC_chat.py中,直接重置计数器 self.context.chat_instance.recent_interest_records.clear() self.context.no_reply_consecutive = 0 - logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器") + logger.debug(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器") return True - elif action_type == "no_action": - # 当执行回复动作时,也重置no_reply计数 - self.context.chat_instance.recent_interest_records.clear() - self.context.no_reply_consecutive = 0 - logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器") if action_type == "no_reply": self.context.no_reply_consecutive += 1 diff --git a/src/chat/chat_loop/cycle_tracker.py b/src/chat/chat_loop/cycle_tracker.py index ea56ab784..2647bb7c6 100644 --- a/src/chat/chat_loop/cycle_tracker.py +++ b/src/chat/chat_loop/cycle_tracker.py @@ -89,11 +89,27 @@ class CycleTracker: formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" timer_strings.append(f"{name}: {formatted_time}") + # 获取动作类型,兼容新旧格式 + action_type = "未知动作" + if hasattr(self, '_current_cycle_detail') and self._current_cycle_detail: + loop_plan_info = self._current_cycle_detail.loop_plan_info + if isinstance(loop_plan_info, dict): + action_result = loop_plan_info.get('action_result', {}) + if isinstance(action_result, dict): + # 旧格式:action_result是字典 + action_type = action_result.get('action_type', '未知动作') + elif isinstance(action_result, list) and action_result: + # 新格式:action_result是actions列表 + action_type = action_result[0].get('action_type', '未知动作') + elif isinstance(loop_plan_info, list) and loop_plan_info: + # 直接是actions列表的情况 + action_type = loop_plan_info[0].get('action_type', '未知动作') + if self.context.current_cycle_detail.end_time and self.context.current_cycle_detail.start_time: duration = self.context.current_cycle_detail.end_time - self.context.current_cycle_detail.start_time logger.info( f"{self.context.log_prefix} 第{self.context.current_cycle_detail.cycle_id}次思考," f"耗时: {duration:.1f}秒, " - f"选择动作: {self.context.current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" + f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") ) diff --git a/src/chat/chat_loop/energy_manager.py b/src/chat/chat_loop/energy_manager.py index e398716f0..d4db96ef5 100644 --- a/src/chat/chat_loop/energy_manager.py +++ b/src/chat/chat_loop/energy_manager.py @@ -107,21 +107,12 @@ class EnergyManager: else: # 清醒时:处理能量衰减 is_group_chat = self.context.chat_stream.group_info is not None - if is_group_chat and global_config.chat.group_chat_mode != "auto": - if global_config.chat.group_chat_mode == "focus": - self.context.loop_mode = ChatMode.FOCUS - self.context.energy_value = 35 - elif global_config.chat.group_chat_mode == "normal": - self.context.loop_mode = ChatMode.NORMAL - self.context.energy_value = 15 - continue + if is_group_chat: + self.context.energy_value = 25 - if self.context.loop_mode == ChatMode.NORMAL: - self.context.energy_value -= 0.3 - self.context.energy_value = max(self.context.energy_value, 0.3) - if self.context.loop_mode == ChatMode.FOCUS: - self.context.energy_value -= 0.6 - self.context.energy_value = max(self.context.energy_value, 0.3) + await asyncio.sleep(12) + self.context.energy_value -= 0.5 + self.context.energy_value = max(self.context.energy_value, 0.3) self._log_energy_change("能量值衰减") self.context.save_context_state() diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index f46ec0a7c..3e21537e4 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -77,12 +77,7 @@ class HeartFChatting: """ is_group_chat = self.context.chat_stream.group_info is not None if self.context.chat_stream else False if is_group_chat and global_config.chat.group_chat_mode != "auto": - if global_config.chat.group_chat_mode == "focus": - self.context.loop_mode = ChatMode.FOCUS - self.context.energy_value = 35 - elif global_config.chat.group_chat_mode == "normal": - self.context.loop_mode = ChatMode.NORMAL - self.context.energy_value = 15 + self.context.energy_value = 25 async def start(self): """ @@ -241,41 +236,41 @@ class HeartFChatting: if current_sleep_state == SleepState.SLEEPING: # 只有在纯粹的 SLEEPING 状态下才跳过消息处理 - return has_new_messages + return True if current_sleep_state == SleepState.WOKEN_UP: logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。") # 根据聊天模式处理新消息 # 统一使用 _should_process_messages 判断是否应该处理 - if not self._should_process_messages(recent_messages if has_new_messages else None): + should_process,interest_value = await self._should_process_messages(recent_messages if has_new_messages else None) + if should_process: + earliest_message_data = recent_messages[0] + self.last_read_time = earliest_message_data.get("time") + await self.cycle_processor.observe(interest_value = interest_value) + else: + # Normal模式:消息数量不足,等待 + await asyncio.sleep(0.5) + return True + + if not await self._should_process_messages(recent_messages if has_new_messages else None): return has_new_messages - if self.context.loop_mode == ChatMode.FOCUS: - # 处理新消息 - for message in recent_messages: - await self.cycle_processor.observe(message) - - # 如果成功观察,增加能量值 - if has_new_messages: - self.context.energy_value += 1 / global_config.chat.focus_value - logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f}") - - self._check_focus_exit() - elif self.context.loop_mode == ChatMode.NORMAL: - self._check_focus_entry(len(recent_messages)) - # 检查是否有足够的新消息触发处理 - if new_message_count >= self.context.focus_energy: - earliest_messages_data = recent_messages[0] - self.context.last_read_time = earliest_messages_data.get("time") - for message in recent_messages: - await self.normal_mode_handler.handle_message(message) + # 处理新消息 + for message in recent_messages: + await self.cycle_processor.observe(interest_value = interest_value) + + # 如果成功观察,增加能量值 + if has_new_messages: + self.context.energy_value += 1 / global_config.chat.focus_value + logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f}") + + self._check_focus_exit() + else: # 无新消息时,只进行模式检查,不进行思考循环 - if self.context.loop_mode == ChatMode.FOCUS: - self._check_focus_exit() - elif self.context.loop_mode == ChatMode.NORMAL: - self._check_focus_entry(0) # 传入0表示无新消息 + self._check_focus_exit() + # 更新上一帧的睡眠状态 self.context.was_sleeping = is_sleeping @@ -319,7 +314,6 @@ class HeartFChatting: if self.context.energy_value <= 1: # 如果能量值小于等于1(非强制情况) self.context.energy_value = 1 # 将能量值设置为1 - self.context.loop_mode = ChatMode.NORMAL # 切换到普通模式 def _check_focus_entry(self, new_message_count: int): """ @@ -339,7 +333,6 @@ class HeartFChatting: is_group_chat = not is_private_chat if global_config.chat.force_focus_private and is_private_chat: - self.context.loop_mode = ChatMode.FOCUS self.context.energy_value = 10 return @@ -350,15 +343,11 @@ class HeartFChatting: if new_message_count > 3 / pow( global_config.chat.focus_value, 0.5 ): # 如果新消息数超过阈值(基于专注值计算) - self.context.loop_mode = ChatMode.FOCUS # 进入专注模式 self.context.energy_value = ( 10 + (new_message_count / (3 / pow(global_config.chat.focus_value, 0.5))) * 10 ) # 根据消息数量计算能量值 return # 返回,不再检查其他条件 - if self.context.energy_value >= 30: # 如果能量值达到或超过30 - self.context.loop_mode = ChatMode.FOCUS # 进入专注模式 - def _handle_wakeup_messages(self, messages): """ 处理休眠状态下的消息,累积唤醒度 @@ -419,20 +408,58 @@ class HeartFChatting: logger.info(f"{self.context.log_prefix} 兴趣度充足") self.context.focus_energy = 1 - def _should_process_messages(self, messages: List[Dict[str, Any]] = None) -> bool: + async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool,float]: """ 统一判断是否应该处理消息的函数 根据当前循环模式和消息内容决定是否继续处理 """ - if self.context.loop_mode == ChatMode.FOCUS: - if self.context.last_action == "no_reply": - if messages: - return self._execute_no_reply(messages) - return False - return True + new_message_count = len(new_message) + talk_frequency = global_config.chat.get_current_talk_frequency(self.context.chat_stream.stream_id) + modified_exit_count_threshold = self.context.focus_energy / talk_frequency - return True + if new_message_count >= modified_exit_count_threshold: + # 记录兴趣度到列表 + total_interest = 0.0 + for msg_dict in new_message: + interest_value = msg_dict.get("interest_value", 0.0) + if msg_dict.get("processed_plain_text", ""): + total_interest += interest_value + self.recent_interest_records.append(total_interest) + + logger.info( + f"{self.context.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold}),结束等待" + ) + return True,total_interest/new_message_count + + # 检查累计兴趣值 + if new_message_count > 0: + accumulated_interest = 0.0 + for msg_dict in new_message: + text = msg_dict.get("processed_plain_text", "") + interest_value = msg_dict.get("interest_value", 0.0) + if text: + accumulated_interest += interest_value + # 只在兴趣值变化时输出log + if not hasattr(self, "_last_accumulated_interest") or accumulated_interest != self._last_accumulated_interest: + logger.info(f"{self.context.log_prefix} breaking形式当前累计兴趣值: {accumulated_interest:.2f}, 当前聊天频率: {talk_frequency:.2f}") + self._last_accumulated_interest = accumulated_interest + if accumulated_interest >= 3 / talk_frequency: + # 记录兴趣度到列表 + self.recent_interest_records.append(accumulated_interest) + logger.info( + f"{self.context.log_prefix} 累计兴趣值达到{accumulated_interest:.2f}(>{5 / talk_frequency}),结束等待" + ) + return True,accumulated_interest/new_message_count + # 每10秒输出一次等待状态 + if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 10 == 0: + logger.info( + f"{self.context.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,继续等待..." + ) + await asyncio.sleep(0.5) + + return False,0.0 + async def _execute_no_reply(self, new_message: List[Dict[str, Any]]) -> bool: """执行breaking形式的no_reply(原有逻辑)""" new_message_count = len(new_message) diff --git a/src/chat/chat_loop/hfc_context.py b/src/chat/chat_loop/hfc_context.py index 04c42f380..ca99dd467 100644 --- a/src/chat/chat_loop/hfc_context.py +++ b/src/chat/chat_loop/hfc_context.py @@ -41,10 +41,6 @@ class HfcContext: self.relationship_builder: Optional[RelationshipBuilder] = None self.expression_learner: Optional[ExpressionLearner] = None - self.loop_mode = ChatMode.NORMAL - - self.last_action = "no_action" - self.energy_value = self.chat_stream.energy_value self.sleep_pressure = self.chat_stream.sleep_pressure self.was_sleeping = False # 用于检测睡眠状态的切换 diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index e3dc9f6c6..f23bebede 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -162,7 +162,10 @@ class ActionPlanner: return "回忆时出现了一些问题。" async def _build_action_options( - self, current_available_actions: Dict[str, ActionInfo], mode: ChatMode, target_prompt: str = "" + self, + current_available_actions: Dict[str, ActionInfo], + mode: ChatMode, + target_prompt: str = "", ) -> str: """ 构建动作选项 @@ -185,12 +188,15 @@ class ActionPlanner: param_text = "" if action_info.action_parameters: param_text = "\n" + "\n".join( - f' "{p_name}":"{p_desc}"' for p_name, p_desc in action_info.action_parameters.items() + f' "{p_name}":"{p_desc}"' + for p_name, p_desc in action_info.action_parameters.items() ) require_text = "\n".join(f"- {req}" for req in action_info.action_require) - using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt") + using_action_prompt = await global_prompt_manager.get_prompt_async( + "action_prompt" + ) action_options_block += using_action_prompt.format( action_name=action_name, action_description=action_info.description, @@ -200,7 +206,10 @@ class ActionPlanner: ) return action_options_block - def find_message_by_id(self, message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]: + def find_message_by_id( + self, message_id: str, message_id_list: list + ) -> Optional[Dict[str, Any]]: + # sourcery skip: use-next """ 根据message_id从message_id_list中查找对应的原始消息 @@ -231,7 +240,12 @@ class ActionPlanner: # 假设消息列表是按时间顺序排列的,最后一个是最新的 return message_id_list[-1].get("message") - async def plan(self, mode: ChatMode = ChatMode.FOCUS) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]: + async def plan( + self, + mode: ChatMode = ChatMode.FOCUS, + loop_start_time:float = 0.0, + available_actions: Optional[Dict[str, ActionInfo]] = None, + ) -> Tuple[List[Dict[str, Any]], Optional[Dict[str, Any]]]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ @@ -297,31 +311,22 @@ class ActionPlanner: # 将所有其他属性添加到action_data for key, value in parsed_json.items(): - if key not in ["action", "reasoning"]: + if key not in ["action", "reason"]: action_data[key] = value - # 非no_reply动作需要target_message_id + # 在FOCUS模式下,非no_reply动作需要target_message_id if action != "no_reply": if target_message_id := parsed_json.get("target_message_id"): - if isinstance(target_message_id, int): - target_message_id = str(target_message_id) - - if isinstance(target_message_id, str) and not target_message_id.startswith("m"): - target_message_id = f"m{target_message_id}" # 根据target_message_id查找原始消息 target_message = self.find_message_by_id(target_message_id, message_id_list) # 如果获取的target_message为None,输出warning并重新plan if target_message is None: self.plan_retry_count += 1 - logger.warning( - f"{self.log_prefix}无法找到target_message_id '{target_message_id}' 对应的消息,重试次数: {self.plan_retry_count}/{self.max_plan_retries}" - ) - + logger.warning(f"{self.log_prefix}无法找到target_message_id '{target_message_id}' 对应的消息,重试次数: {self.plan_retry_count}/{self.max_plan_retries}") + # 如果连续三次plan均为None,输出error并选取最新消息 if self.plan_retry_count >= self.max_plan_retries: - logger.error( - f"{self.log_prefix}连续{self.max_plan_retries}次plan获取target_message失败,选择最新消息作为target_message" - ) + logger.error(f"{self.log_prefix}连续{self.max_plan_retries}次plan获取target_message失败,选择最新消息作为target_message") target_message = self.get_latest_message(message_id_list) self.plan_retry_count = 0 # 重置计数器 else: @@ -334,11 +339,8 @@ class ActionPlanner: logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id") - if action == "no_action": reasoning = "normal决定不使用额外动作" - elif mode == ChatMode.PROACTIVE and action == "do_nothing": - pass # 在PROACTIVE模式下,do_nothing是有效动作 elif action != "no_reply" and action != "reply" and action not in current_available_actions: logger.warning( f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'" @@ -346,69 +348,18 @@ class ActionPlanner: reasoning = f"LLM 返回了当前不可用的动作 '{action}' (可用: {list(current_available_actions.keys())})。原始理由: {reasoning}" action = "no_reply" - # 检查no_reply是否可用,如果不可用则使用reply作为终极回退 - if "no_reply" not in current_available_actions: - if "reply" in current_available_actions: - action = "reply" - reasoning += " (no_reply不可用,使用reply作为回退)" - logger.warning(f"{self.log_prefix}no_reply不可用,使用reply作为回退") - else: - # 如果连reply都不可用,使用第一个可用的动作 - if current_available_actions: - action = list(current_available_actions.keys())[0] - reasoning += f" (no_reply和reply都不可用,使用{action}作为回退)" - logger.warning(f"{self.log_prefix}no_reply和reply都不可用,使用{action}作为回退") - else: - # 如果没有任何可用动作,这是一个严重错误 - logger.error(f"{self.log_prefix}没有任何可用动作,系统状态异常") - action = "no_reply" # 仍然尝试no_reply,让上层处理 - - # 对no_reply动作本身也进行可用性检查 - elif action == "no_reply" and "no_reply" not in current_available_actions: - if "reply" in current_available_actions: - action = "reply" - reasoning = f"no_reply不可用,自动回退到reply。原因: {reasoning}" - logger.warning(f"{self.log_prefix}no_reply不可用,自动回退到reply") - elif current_available_actions: - action = list(current_available_actions.keys())[0] - reasoning = f"no_reply不可用,自动回退到{action}。原因: {reasoning}" - logger.warning(f"{self.log_prefix}no_reply不可用,自动回退到{action}") - else: - logger.error(f"{self.log_prefix}没有任何可用动作,保持no_reply让上层处理") - except Exception as json_e: logger.warning(f"{self.log_prefix}解析LLM响应JSON失败 {json_e}. LLM原始输出: '{llm_content}'") traceback.print_exc() reasoning = f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_reply'." action = "no_reply" - # 检查no_reply是否可用 - if "no_reply" not in current_available_actions: - if "reply" in current_available_actions: - action = "reply" - reasoning += " (no_reply不可用,使用reply)" - elif current_available_actions: - action = list(current_available_actions.keys())[0] - reasoning += f" (no_reply不可用,使用{action})" - except Exception as outer_e: logger.error(f"{self.log_prefix}Planner 处理过程中发生意外错误,规划失败,将执行 no_reply: {outer_e}") traceback.print_exc() action = "no_reply" reasoning = f"Planner 内部处理错误: {outer_e}" - # 检查no_reply是否可用 - current_available_actions = self.action_manager.get_using_actions() - if "no_reply" not in current_available_actions: - if "reply" in current_available_actions: - action = "reply" - reasoning += " (no_reply不可用,使用reply)" - elif current_available_actions: - action = list(current_available_actions.keys())[0] - reasoning += f" (no_reply不可用,使用{action})" - else: - logger.error(f"{self.log_prefix}严重错误:没有任何可用动作") - is_parallel = False if mode == ChatMode.NORMAL and action in current_available_actions: is_parallel = current_available_actions[action].parallel_action @@ -422,17 +373,19 @@ class ActionPlanner: actions.append({ "action_type": action, "reasoning": reasoning, - "timestamp": time.time(), - "is_parallel": is_parallel, - } - - return ( - { - "action_result": action_result, - "action_prompt": prompt, - }, - target_message, - ) + "action_data": action_data, + "action_message": target_message, + "available_actions": available_actions # 添加这个字段 + }) + + if action != "reply" and is_parallel: + actions.append({ + "action_type": "reply", + "action_message": target_message, + "available_actions": available_actions + }) + + return actions,target_message async def build_planner_prompt( self, @@ -448,15 +401,21 @@ class ActionPlanner: time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" bot_name = global_config.bot.nickname bot_nickname = ( - f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else "" + f",也有人叫你{','.join(global_config.bot.alias_names)}" + if global_config.bot.alias_names + else "" ) bot_core_personality = global_config.personality.personality_core - identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:" + identity_block = ( + f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:" + ) schedule_block = "" if global_config.schedule.enable: if current_activity := schedule_manager.get_current_activity(): - schedule_block = f"你当前正在:{current_activity},但注意它与群聊的聊天无关。" + schedule_block = ( + f"你当前正在:{current_activity},但注意它与群聊的聊天无关。" + ) mood_block = "" if global_config.mood.enable_mood: @@ -466,9 +425,13 @@ class ActionPlanner: # --- 根据模式构建不同的Prompt --- if mode == ChatMode.PROACTIVE: long_term_memory_block = await self._get_long_term_memory_context() - action_options_text = await self._build_action_options(current_available_actions, mode) + action_options_text = await self._build_action_options( + current_available_actions, mode + ) - prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt") + prompt_template = await global_prompt_manager.get_prompt_async( + "proactive_planner_prompt" + ) prompt = prompt_template.format( time_block=time_block, identity_block=identity_block, @@ -501,24 +464,35 @@ class ActionPlanner: limit=5, ) - actions_before_now_block = build_readable_actions(actions=actions_before_now) - actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}" + actions_before_now_block = build_readable_actions( + actions=actions_before_now + ) + actions_before_now_block = ( + f"你刚刚选择并执行过的action是:\n{actions_before_now_block}" + ) - self.last_obs_time_mark = time.time() + if refresh_time: + self.last_obs_time_mark = time.time() + + mentioned_bonus = "" + if global_config.chat.mentioned_bot_inevitable_reply: + mentioned_bonus = "\n- 有人提到你" + if global_config.chat.at_bot_inevitable_reply: + mentioned_bonus = "\n- 有人提到你,或者at你" if mode == ChatMode.FOCUS: - mentioned_bonus = "" - if global_config.chat.mentioned_bot_inevitable_reply: - mentioned_bonus = "\n- 有人提到你" - if global_config.chat.at_bot_inevitable_reply: - mentioned_bonus = "\n- 有人提到你,或者at你" - by_what = "聊天内容" target_prompt = '\n "target_message_id":"触发action的消息id"' no_action_block = f"""重要说明: -- 'no_reply' 表示只进行不进行回复,等待合适的回复时机(由系统直接处理) +- 'no_reply' 表示只进行不进行回复,等待合适的回复时机 - 当你刚刚发送了消息,没有人回复时,选择no_reply - 当你一次发送了太多消息,为了避免打扰聊天节奏,选择no_reply + +动作:reply +动作描述:参与聊天回复,发送文本进行表达 +- 你想要闲聊或者随便附和{mentioned_bonus} +- 如果你刚刚进行了回复,不要对同一个话题重复回应 +- 不要回复自己发送的消息 {{ "action": "no_reply", "reason":"不回复的原因" @@ -527,27 +501,46 @@ class ActionPlanner: else: # NORMAL Mode by_what = "聊天内容和用户的最新消息" target_prompt = "" - no_action_block = """重要说明: + no_action_block = f"""重要说明: - 'reply' 表示只进行普通聊天回复,不执行任何额外动作 - 其他action表示在普通回复的基础上,执行相应的额外动作 -""" + +动作:reply +动作描述:参与聊天回复,发送文本进行表达 +- 你想要闲聊或者随便附 +- {mentioned_bonus} +- 如果你刚刚进行了回复,不要对同一个话题重复回应 +- 不要回复自己发送的消息 +{{ + "action": "reply", + "target_message_id":"触发action的消息id", + "reason":"回复的原因" +}}""" chat_context_description = "你现在正在一个群聊中" if not is_group_chat and chat_target_info: chat_target_name = ( - chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方" + chat_target_info.get("person_name") + or chat_target_info.get("user_nickname") + or "对方" ) chat_context_description = f"你正在和 {chat_target_name} 私聊" - action_options_block = await self._build_action_options(current_available_actions, mode, target_prompt) + action_options_block = await self._build_action_options( + current_available_actions, mode, target_prompt + ) moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" custom_prompt_block = "" if global_config.custom_prompt.planner_custom_prompt_content: - custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content + custom_prompt_block = ( + global_config.custom_prompt.planner_custom_prompt_content + ) - planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") + planner_prompt_template = await global_prompt_manager.get_prompt_async( + "planner_prompt" + ) prompt = planner_prompt_template.format( schedule_block=schedule_block, mood_block=mood_block, @@ -575,7 +568,9 @@ class ActionPlanner: """ is_group_chat = True is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) - logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") + logger.debug( + f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}" + ) current_available_actions_dict = self.action_manager.get_using_actions() @@ -586,9 +581,13 @@ class ActionPlanner: current_available_actions = {} for action_name in current_available_actions_dict: if action_name in all_registered_actions: - current_available_actions[action_name] = all_registered_actions[action_name] + current_available_actions[action_name] = all_registered_actions[ + action_name + ] else: - logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到") + logger.warning( + f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到" + ) # 将no_reply作为系统级特殊动作添加到可用动作中 # no_reply虽然是系统级决策,但需要让规划器认为它是可用的 diff --git a/src/plugins/built_in/core_actions/reply.py b/src/plugins/built_in/core_actions/reply.py deleted file mode 100644 index 1c4a994cd..000000000 --- a/src/plugins/built_in/core_actions/reply.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import Tuple - -# 导入新插件系统 -from src.plugin_system import BaseAction, ActionActivationType, ChatMode - -# 导入依赖的系统组件 -from src.common.logger import get_logger -from src.plugin_system.apis import generator_api - - -logger = get_logger("reply_action") - - -class ReplyAction(BaseAction): - """基本回复动作:确保系统始终有一个可用的回退动作!!!""" - - focus_activation_type = ActionActivationType.ALWAYS - normal_activation_type = ActionActivationType.ALWAYS - mode_enable = ChatMode.ALL - parallel_action = False - - # 动作基本信息 - action_name = "reply" - action_description = "进行基本回复" - - # 动作参数定义 - action_parameters = {} - - # 动作使用场景 - action_require = [""] - - # 关联类型 - associated_types = [] - - async def execute(self) -> Tuple[bool, str]: - """执行回复动作""" - try: - reason = self.action_data.get("reason", "") - - logger.info(f"{self.log_prefix} 执行基本回复动作,原因: {reason}") - - # 获取当前消息和上下文 - if not self.chat_stream or not self.chat_stream.get_latest_message(): - logger.warning(f"{self.log_prefix} 没有可回复的消息") - return False, "" - - latest_message = self.chat_stream.get_latest_message() - - # 使用生成器API生成回复 - success, reply_set, _ = await generator_api.generate_reply( - target_message=latest_message.processed_plain_text, - chat_stream=self.chat_stream, - reasoning=reason, - action_message={}, - ) - - if success and reply_set: - # 提取回复文本 - reply_text = "" - for message_type, content in reply_set: - if message_type == "text": - reply_text += content - break - - if reply_text: - logger.info(f"{self.log_prefix} 回复生成成功: {reply_text[:50]}...") - return True, reply_text - else: - logger.warning(f"{self.log_prefix} 生成的回复为空") - return False, "" - else: - logger.warning(f"{self.log_prefix} 回复生成失败") - return False, "" - - except Exception as e: - logger.error(f"{self.log_prefix} 执行回复动作时发生异常: {e}") - import traceback - - traceback.print_exc() - return False, "" diff --git a/src/plugins/built_in/emoji_plugin/plugin.py b/src/plugins/built_in/emoji_plugin/plugin.py index 14c86f246..473005a22 100644 --- a/src/plugins/built_in/emoji_plugin/plugin.py +++ b/src/plugins/built_in/emoji_plugin/plugin.py @@ -16,7 +16,6 @@ from src.plugin_system.base.config_types import ConfigField from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 -from src.plugins.built_in.core_actions.reply import ReplyAction from src.plugins.built_in.core_actions.emoji import EmojiAction from src.plugins.built_in.core_actions.anti_injector_manager import AntiInjectorStatusCommand @@ -68,8 +67,6 @@ class CoreActionsPlugin(BasePlugin): # --- 根据配置注册组件 --- components = [] - if self.get_config("components.enable_reply", True): - components.append((ReplyAction.get_action_info(), ReplyAction)) if self.get_config("components.enable_emoji", True): components.append((EmojiAction.get_action_info(), EmojiAction)) if self.get_config("components.enable_anti_injector_manager", True):