diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index e96ff2a4d..7b7da9486 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -130,6 +130,10 @@ class HeartFChatting: self.last_energy_log_time = 0 # 上次记录能量值日志的时间 self.energy_log_interval = 90 # 能量值日志间隔(秒) + # 主动思考功能相关属性 + self.last_message_time = time.time() # 最后一条消息的时间 + self._proactive_thinking_task: Optional[asyncio.Task] = None # 主动思考任务 + async def start(self): """检查是否需要启动主循环,如果未激活则启动。""" @@ -142,6 +146,15 @@ class HeartFChatting: # 标记为活动状态,防止重复启动 self.running = True + self._energy_task = asyncio.create_task(self._energy_loop()) + self._energy_task.add_done_callback(self._handle_energy_completion) + + # 启动主动思考任务(仅在群聊且启用的情况下) + if (global_config.chat.enable_proactive_thinking and + self.chat_stream.group_info is not None): + self._proactive_thinking_task = asyncio.create_task(self._proactive_thinking_loop()) + self._proactive_thinking_task.add_done_callback(self._handle_proactive_thinking_completion) + self._loop_task = asyncio.create_task(self._main_chat_loop()) self._loop_task.add_done_callback(self._handle_loop_completion) logger.info(f"{self.log_prefix} HeartFChatting 启动完成") @@ -178,6 +191,24 @@ class HeartFChatting: self._current_cycle_detail.end_time = time.time() def _handle_energy_completion(self, task: asyncio.Task): + """当 energy_loop 任务完成时执行的回调。""" + try: + if exception := task.exception(): + logger.error(f"{self.log_prefix} 能量循环异常: {exception}") + else: + logger.info(f"{self.log_prefix} 能量循环正常结束") + except asyncio.CancelledError: + logger.info(f"{self.log_prefix} 能量循环被取消") + + def _handle_proactive_thinking_completion(self, task: asyncio.Task): + """当 proactive_thinking_loop 任务完成时执行的回调。""" + try: + if exception := task.exception(): + logger.error(f"{self.log_prefix} 主动思考循环异常: {exception}") + else: + logger.info(f"{self.log_prefix} 主动思考循环正常结束") + except asyncio.CancelledError: + logger.info(f"{self.log_prefix} 主动思考循环被取消") """处理能量循环任务的完成""" if task.cancelled(): logger.info(f"{self.log_prefix} 能量循环任务被取消") @@ -230,6 +261,132 @@ class HeartFChatting: self.energy_value -= 0.6 self.energy_value = max(self.energy_value, 0.3) + async def _proactive_thinking_loop(self): + """主动思考循环,仅在focus模式下生效""" + while self.running: + await asyncio.sleep(30) # 每30秒检查一次 + + # 只在focus模式下进行主动思考 + if self.loop_mode != ChatMode.FOCUS: + continue + + current_time = time.time() + silence_duration = current_time - self.last_message_time + + # 检查是否达到主动思考的时间间隔 + if silence_duration >= global_config.chat.proactive_thinking_interval: + try: + await self._execute_proactive_thinking(silence_duration) + # 重置计时器,避免频繁触发 + self.last_message_time = current_time + except Exception as e: + logger.error(f"{self.log_prefix} 主动思考执行出错: {e}") + logger.error(traceback.format_exc()) + + def _format_duration(self, seconds: float) -> str: + """格式化时间间隔为易读格式""" + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + secs = int(seconds % 60) + + parts = [] + if hours > 0: + parts.append(f"{hours}小时") + if minutes > 0: + parts.append(f"{minutes}分") + if secs > 0 or not parts: # 如果没有小时和分钟,显示秒 + parts.append(f"{secs}秒") + + return "".join(parts) + + async def _execute_proactive_thinking(self, silence_duration: float): + """执行主动思考""" + formatted_time = self._format_duration(silence_duration) + logger.info(f"{self.log_prefix} 触发主动思考,已沉默{formatted_time}") + + try: + # 构建主动思考的prompt + proactive_prompt = global_config.chat.proactive_thinking_prompt_template.format( + time=formatted_time + ) + + # 获取当前上下文 + context_messages = message_api.get_recent_messages( + chat_id=self.stream_id, + limit=global_config.chat.max_context_size + ) + + # 构建完整的prompt(结合人设和上下文) + full_context = global_prompt_manager.build_context_prompt( + context_messages, + self.stream_id + ) + + # 创建一个虚拟的消息数据用于主动思考 + """ + 因为主动思考是在没有用户消息的情况下触发的 + 但规划器仍然需要一个"消息"作为输入来工作 + 所以需要"伪造"一个消息来触发思考流程,本质上是系统与自己的对话,让AI能够主动思考和决策。 + """ + thinking_message = { + "processed_plain_text": proactive_prompt, + "user_id": "system_proactive_thinking", + "user_platform": "system", + "timestamp": time.time(), + "message_type": "proactive_thinking" + } + + # 执行思考规划 + cycle_timers, thinking_id = self.start_cycle() + + timer = Timer() + plan_result = await self.action_planner.plan( + new_message_data=[thinking_message], + context_prompt=full_context, + thinking_id=thinking_id, + timeout=global_config.chat.thinking_timeout + ) + cycle_timers["规划"] = timer.elapsed() + + if not plan_result: + logger.info(f"{self.log_prefix} 主动思考规划失败") + self.end_cycle(ERROR_LOOP_INFO, cycle_timers) + return + + # 执行动作 + timer.restart() + action_info = await self.action_modifier.execute_action( + plan_result["action_result"], + context=full_context, + thinking_id=thinking_id + ) + cycle_timers["执行"] = timer.elapsed() + + # 构建循环信息 + loop_info = { + "loop_plan_info": plan_result, + "loop_action_info": action_info, + } + + self.end_cycle(loop_info, cycle_timers) + self.print_cycle_info(cycle_timers) + + # 如果有回复内容且不是"沉默",则发送 + reply_text = action_info.get("reply_text", "").strip() + if reply_text and reply_text != "沉默": + logger.info(f"{self.log_prefix} 主动思考决定发言: {reply_text}") + await send_api.text_to_stream( + text=reply_text, + stream_id=self.stream_id, + typing=True # 主动发言时显示输入状态 + ) + else: + logger.info(f"{self.log_prefix} 主动思考决定保持沉默") + + except Exception as e: + logger.error(f"{self.log_prefix} 主动思考执行异常: {e}") + logger.error(traceback.format_exc()) + def print_cycle_info(self, cycle_timers): # 记录循环信息和计时器结果 timer_strings = [] @@ -370,8 +527,11 @@ class HeartFChatting: filter_command=True, ) - # 统一的消息处理逻辑 - should_process,interest_value = await self._should_process_messages(recent_messages_dict) + # 如果有新消息,更新最后消息时间(用于主动思考计时) + if new_message_count > 0: + current_time = time.time() + self.last_message_time = current_time + if self.loop_mode == ChatMode.FOCUS: diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 210e24dbd..592f98e63 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -223,6 +223,21 @@ class ChatConfig(ConfigBase): - relative: 相对时间格式 (几分钟前/几小时前等) """ + # 主动思考功能配置 + enable_proactive_thinking: bool = False + """是否启用主动思考功能(仅在focus模式下生效)""" + + proactive_thinking_interval: int = 1500 + """主动思考触发间隔时间(秒),默认1500秒(25分钟)""" + + proactive_thinking_prompt_template: str = """现在群里面已经隔了{time}没有人发送消息了,请你结合上下文以及群聊里面之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择: + +1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时) +2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时) + +请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请回复"沉默"。""" + """主动思考时使用的prompt模板,{time}会被替换为实际的沉默时间""" + def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: """ 根据当前时间和聊天流获取对应的 talk_frequency diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index aaa0e1439..67dbcb04c 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "6.3.3" +version = "6.3.4" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -124,6 +124,17 @@ talk_frequency_adjust = [ # 全局配置示例: # [["", "8:00,1", "12:00,2", "18:00,1.5", "00:00,0.5"]] +# 主动思考功能配置(仅在focus模式下生效) +enable_proactive_thinking = false # 是否启用主动思考功能 +proactive_thinking_interval = 1500 # 主动思考触发间隔时间(秒),默认1500秒(25分钟) +# 主动思考prompt模板,{time}会被替换为实际的沉默时间(如"2小时30分15秒") +proactive_thinking_prompt_template = """现在群里面已经隔了{time}没有人发送消息了,请你结合上下文以及群聊里面之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择: + +1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时) +2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时) + +请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请回复"沉默"。""" + # 特定聊天流配置示例: # [ # ["", "8:00,1", "12:00,1.2", "18:00,1.5", "01:00,0.6"], # 全局默认配置