From 09b36585b3f1c0ecc7d091b9009d47f8f1ca3b42 Mon Sep 17 00:00:00 2001 From: minecraft1024a Date: Thu, 21 Aug 2025 19:29:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(proactive=5Fthinking):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=B8=BB=E5=8A=A8=E6=80=9D=E8=80=83=E4=B8=BA=E7=94=B1?= =?UTF-8?q?Planner=E7=9B=B4=E6=8E=A5=E5=86=B3=E7=AD=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构了主动思考的触发和决策流程。原有的通过生成特定prompt来启动思考循环的方式被移除,改为直接调用Planner的`PROACTIVE`模式。 - **Planner增强**: - 新增`PROACTIVE`聊天模式,用于处理主动思考场景。 - 为`PROACTIVE`模式设计了专用的prompt模板,整合了长期记忆、当前状态等信息。 - 引入`do_nothing`动作,允许Planner在分析后决定保持沉默。 - 增加从海马体(长期记忆)获取上下文的功能,为决策提供更丰富的背景。 - **ProactiveThinker简化**: - 移除了原有的prompt生成和调用`observe`的逻辑。 - 现在直接调用`action_planner.plan(mode=ChatMode.PROACTIVE)`来获取决策。 - 根据Planner返回的动作(如`do_nothing`或具体行动),决定是保持沉默还是执行计划。 - **CycleProcessor & Tracker调整**: - `CycleProcessor`新增`execute_plan`方法,用于执行一个已经由Planner预先制定好的计划。 - `CycleTracker`能够区分并标记由主动思考发起的循环(例如,cycle_id为 "1.p"),以便于追踪和分析。 --- src/chat/chat_loop/cycle_processor.py | 28 +++ src/chat/chat_loop/cycle_tracker.py | 12 +- src/chat/chat_loop/hfc_utils.py | 4 +- src/chat/chat_loop/proactive_thinker.py | 55 ++---- src/chat/planner_actions/planner.py | 207 +++++++++++++++------- src/plugin_system/base/component_types.py | 1 + 6 files changed, 191 insertions(+), 116 deletions(-) diff --git a/src/chat/chat_loop/cycle_processor.py b/src/chat/chat_loop/cycle_processor.py index e925df8d0..59eefd181 100644 --- a/src/chat/chat_loop/cycle_processor.py +++ b/src/chat/chat_loop/cycle_processor.py @@ -130,6 +130,34 @@ class CycleProcessor: return True + async def execute_plan(self, action_result: Dict[str, Any], target_message: Optional[Dict[str, Any]]): + """ + 执行一个已经制定好的计划 + """ + action_type = action_result.get("action_type", "error") + + # 这里我们需要为执行计划创建一个新的循环追踪 + cycle_timers, thinking_id = self.cycle_tracker.start_cycle(is_proactive=True) + loop_start_time = time.time() + + if action_type == "reply": + # 主动思考不应该直接触发简单回复,但为了逻辑完整性,我们假设它会调用response_handler + # 注意:这里的 available_actions 和 plan_result 是缺失的,需要根据实际情况处理 + await self._handle_reply_action(target_message, {}, None, loop_start_time, cycle_timers, thinking_id, {"action_result": action_result}) + else: + await self._handle_other_actions( + action_type, + action_result.get("reasoning", ""), + action_result.get("action_data", {}), + action_result.get("is_parallel", False), + None, + target_message, + cycle_timers, + thinking_id, + {"action_result": action_result}, + loop_start_time + ) + async def _handle_reply_action(self, message_data, available_actions, gen_task, loop_start_time, cycle_timers, thinking_id, plan_result): """ 处理回复类型的动作 diff --git a/src/chat/chat_loop/cycle_tracker.py b/src/chat/chat_loop/cycle_tracker.py index 77fd99d83..6d44d264f 100644 --- a/src/chat/chat_loop/cycle_tracker.py +++ b/src/chat/chat_loop/cycle_tracker.py @@ -21,10 +21,13 @@ class CycleTracker: """ self.context = context - def start_cycle(self) -> Tuple[Dict[str, float], str]: + def start_cycle(self, is_proactive: bool = False) -> Tuple[Dict[str, float], str]: """ 开始新的思考循环 + Args: + is_proactive: 标记这个循环是否由主动思考发起 + Returns: tuple: (循环计时器字典, 思考ID字符串) @@ -34,8 +37,11 @@ class CycleTracker: - 生成唯一的思考ID - 初始化循环计时器 """ - self.context.cycle_counter += 1 - self.context.current_cycle_detail = CycleDetail(self.context.cycle_counter) + if not is_proactive: + self.context.cycle_counter += 1 + + cycle_id = self.context.cycle_counter if not is_proactive else f"{self.context.cycle_counter}.p" + self.context.current_cycle_detail = CycleDetail(cycle_id) self.context.current_cycle_detail.thinking_id = f"tid{str(round(time.time(), 2))}" cycle_timers = {} return cycle_timers, self.context.current_cycle_detail.thinking_id diff --git a/src/chat/chat_loop/hfc_utils.py b/src/chat/chat_loop/hfc_utils.py index bad6da384..6ce0136a4 100644 --- a/src/chat/chat_loop/hfc_utils.py +++ b/src/chat/chat_loop/hfc_utils.py @@ -1,5 +1,5 @@ import time -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, Union from src.config.config import global_config from src.common.logger import get_logger @@ -23,7 +23,7 @@ class CycleDetail: - 提供序列化和转换功能 """ - def __init__(self, cycle_id: int): + def __init__(self, cycle_id: Union[int, str]): """ 初始化循环详情记录 diff --git a/src/chat/chat_loop/proactive_thinker.py b/src/chat/chat_loop/proactive_thinker.py index f8eb0ebe8..1422528c1 100644 --- a/src/chat/chat_loop/proactive_thinker.py +++ b/src/chat/chat_loop/proactive_thinker.py @@ -245,58 +245,23 @@ class ProactiveThinker: Args: silence_duration: 沉默持续时间(秒) - - 功能说明: - - 格式化沉默时间并记录触发日志 - - 获取适当的思考提示模板 - - 创建主动思考类型的消息数据 - - 调用循环处理器执行思考和可能的回复 - - 处理执行过程中的异常 """ formatted_time = self._format_duration(silence_duration) logger.info(f"{self.context.log_prefix} 触发主动思考,已沉默{formatted_time}") try: - proactive_prompt = self._get_proactive_prompt(formatted_time) + # 直接调用 planner 的 PROACTIVE 模式 + action_result_tuple, target_message = await self.cycle_processor.action_planner.plan(mode=ChatMode.PROACTIVE) + action_result = action_result_tuple.get("action_result") - thinking_message = { - "processed_plain_text": proactive_prompt, - "user_id": "system_proactive_thinking", - "user_platform": "system", - "timestamp": time.time(), - "message_type": "proactive_thinking", - "user_nickname": "系统主动思考", - "chat_info_platform": "system", - "message_id": f"proactive_{int(time.time())}", - } - - logger.info(f"{self.context.log_prefix} 开始主动思考...") - await self.cycle_processor.observe(message_data=thinking_message) - logger.info(f"{self.context.log_prefix} 主动思考完成") + # 如果决策不是 do_nothing,则执行 + if action_result and action_result.get("action_type") != "do_nothing": + logger.info(f"{self.context.log_prefix} 主动思考决策: {action_result.get('action_type')}, 原因: {action_result.get('reasoning')}") + # 将决策结果交给 cycle_processor 的后续流程处理 + await self.cycle_processor.execute_plan(action_result, target_message) + else: + logger.info(f"{self.context.log_prefix} 主动思考决策: 保持沉默") except Exception as e: logger.error(f"{self.context.log_prefix} 主动思考执行异常: {e}") logger.error(traceback.format_exc()) - - def _get_proactive_prompt(self, formatted_time: str) -> str: - """ - 获取主动思考的提示模板 - - Args: - formatted_time: 格式化后的沉默时间字符串 - - Returns: - str: 填充了时间信息的提示模板 - - 功能说明: - - 优先使用自定义的提示模板(如果配置了) - - 根据聊天类型(群聊/私聊)选择默认模板 - - 将格式化的时间信息填入模板 - - 返回完整的主动思考提示文本 - """ - if hasattr(global_config.chat, 'proactive_thinking_prompt_template') and global_config.chat.proactive_thinking_prompt_template.strip(): - return global_config.chat.proactive_thinking_prompt_template.format(time=formatted_time) - - chat_type = "group" if self.context.chat_stream and self.context.chat_stream.group_info else "private" - prompt_template = self.proactive_thinking_prompts.get(chat_type, self.proactive_thinking_prompts["group"]) - return prompt_template.format(time=formatted_time) \ No newline at end of file diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 573538a6f..be238fb67 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -23,15 +23,16 @@ from src.plugin_system.base.component_types import ActionInfo, ChatMode, Compone from src.plugin_system.core.component_registry import component_registry from src.manager.schedule_manager import schedule_manager from src.mood.mood_manager import mood_manager +from src.chat.memory_system.Hippocampus import hippocampus_manager logger = get_logger("planner") install(extra_lines=3) def init_prompt(): - Prompt( -""" -{schedule_block} + Prompt( +""" +{schedule_block} {mood_block} {time_block} {identity_block} @@ -55,6 +56,32 @@ def init_prompt(): "planner_prompt", ) + Prompt( +""" +# 主动思考决策 + +## 你的内部状态 +{time_block} +{identity_block} +{schedule_block} +{mood_block} + +## 长期记忆摘要 +{long_term_memory_block} + +## 任务 +基于以上所有信息,分析当前情况,决定是否需要主动做些什么。 +如果你认为不需要,就选择 'do_nothing'。 + +## 可用动作 +{action_options_text} + +你必须从上面列出的可用action中选择一个。 +请以严格的 JSON 格式输出,且仅包含 JSON 内容: +""", + "proactive_planner_prompt", + ) + Prompt( """ 动作:{action_name} @@ -84,6 +111,78 @@ class ActionPlanner: self.plan_retry_count = 0 self.max_plan_retries = 3 + async def _get_long_term_memory_context(self) -> str: + """ + 获取长期记忆上下文 + """ + try: + # 1. 生成时间相关的关键词 + now = datetime.now() + keywords = ["今天", "日程", "计划"] + if 5 <= now.hour < 12: + keywords.append("早上") + elif 12 <= now.hour < 18: + keywords.append("中午") + else: + keywords.append("晚上") + + # TODO: 添加与聊天对象相关的关键词 + + # 2. 调用 hippocampus_manager 检索记忆 + retrieved_memories = await hippocampus_manager.get_memory_from_topic( + valid_keywords=keywords, + max_memory_num=5, + max_memory_length=1 + ) + + if not retrieved_memories: + return "最近没有什么特别的记忆。" + + # 3. 格式化记忆 + memory_statements = [] + for topic, memory_item in retrieved_memories: + memory_statements.append(f"关于'{topic}', 你记得'{memory_item}'。") + + return " ".join(memory_statements) + except Exception as e: + logger.error(f"获取长期记忆时出错: {e}") + return "回忆时出现了一些问题。" + + async def _build_action_options(self, current_available_actions: Dict[str, ActionInfo], mode: ChatMode, target_prompt: str = "") -> str: + """ + 构建动作选项 + """ + action_options_block = "" + + if mode == ChatMode.PROACTIVE: + action_options_block += """动作:do_nothing +动作描述:保持沉默,不主动发起任何动作或对话。 +- 当你分析了所有信息后,觉得当前不是一个发起互动的好时机时 +{{ + "action": "do_nothing", + "reason":"决定保持沉默的具体原因" +}} + +""" + for action_name, action_info in current_available_actions.items(): + # TODO: 增加一个字段来判断action是否支持在PROACTIVE模式下使用 + + 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()) + + 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") + action_options_block += using_action_prompt.format( + action_name=action_name, + action_description=action_info.description, + action_parameters=param_text, + action_require=require_text, + target_prompt=target_prompt, + ) + return action_options_block + def find_message_by_id(self, message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]: # sourcery skip: use-next """ @@ -118,7 +217,7 @@ class ActionPlanner: async def plan( self, mode: ChatMode = ChatMode.FOCUS - ) -> Tuple[Dict[str, Dict[str, Any] | str], Optional[Dict[str, Any]]]: + ) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ @@ -267,6 +366,40 @@ class ActionPlanner: ) -> tuple[str, list]: # sourcery skip: use-join """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: + # --- 通用信息获取 --- + 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 "" + bot_core_personality = global_config.personality.personality_core + 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}。" + + mood_block = "" + if global_config.mood.enable_mood: + chat_mood = mood_manager.get_mood_by_chat_id(self.chat_id) + mood_block = f"你现在的心情是:{chat_mood.mood_state}" + + # --- 根据模式构建不同的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) + + prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt") + prompt = prompt_template.format( + time_block=time_block, + identity_block=identity_block, + schedule_block=schedule_block, + mood_block=mood_block, + long_term_memory_block=long_term_memory_block, + action_options_text=action_options_text, + ) + return prompt, [] + + # --- FOCUS 和 NORMAL 模式的逻辑 --- message_list_before_now = get_raw_msg_before_timestamp_with_chat( chat_id=self.chat_id, timestamp=time.time(), @@ -288,16 +421,11 @@ class ActionPlanner: limit=5, ) - actions_before_now_block = build_readable_actions( - actions=actions_before_now, - ) - + actions_before_now_block = build_readable_actions(actions=actions_before_now) actions_before_now_block = f"你刚刚选择并执行过的action是:\n{actions_before_now_block}" - # 注意:不在这里更新last_obs_time_mark,应该在plan成功后再更新,避免异常情况下错误更新时间戳 self.last_obs_time_mark = time.time() - if mode == ChatMode.FOCUS: mentioned_bonus = "" if global_config.chat.mentioned_bot_inevitable_reply: @@ -323,7 +451,7 @@ class ActionPlanner: }} """ - else: + else: # NORMAL Mode by_what = "聊天内容和用户的最新消息" target_prompt = "" no_action_block = """重要说明: @@ -331,67 +459,14 @@ class ActionPlanner: - 其他action表示在普通回复的基础上,执行相应的额外动作""" chat_context_description = "你现在正在一个群聊中" - chat_target_name = None # Only relevant for private 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_name = chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方" chat_context_description = f"你正在和 {chat_target_name} 私聊" - action_options_block = "" - - # 先定义 schedule_block 和 mood_block,这些在主模板中需要使用 - schedule_block = "" - if global_config.schedule.enable: - current_activity = schedule_manager.get_current_activity() - if current_activity: - schedule_block = f"你当前正在:{current_activity}。" - - mood_block = "" - if global_config.mood.enable_mood: - chat_mood = mood_manager.get_mood_by_chat_id(self.chat_id) - mood_block = f"你现在的心情是:{chat_mood.mood_state}" - - for using_actions_name, using_actions_info in current_available_actions.items(): - if using_actions_info.action_parameters: - param_text = "\n" - for param_name, param_description in using_actions_info.action_parameters.items(): - param_text += f' "{param_name}":"{param_description}"\n' - param_text = param_text.rstrip("\n") - else: - param_text = "" - - require_text = "" - for require_item in using_actions_info.action_require: - require_text += f"- {require_item}\n" - require_text = require_text.rstrip("\n") - - using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt") - using_action_prompt = using_action_prompt.format( - schedule_block=schedule_block, - mood_block=mood_block, - action_name=using_actions_name, - action_description=using_actions_info.description, - action_parameters=param_text, - action_require=require_text, - target_prompt=target_prompt, - ) - - action_options_block += using_action_prompt + action_options_block = await self._build_action_options(current_available_actions, mode, target_prompt) moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" - - time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" - - bot_name = global_config.bot.nickname - if global_config.bot.alias_names: - bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" - else: - bot_nickname = "" - bot_core_personality = global_config.personality.personality_core - identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}:" - # 处理自定义提示词 custom_prompt_block = "" if global_config.custom_prompt.planner_custom_prompt_enable and global_config.custom_prompt.planner_custom_prompt_content: custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content diff --git a/src/plugin_system/base/component_types.py b/src/plugin_system/base/component_types.py index 5134b6a36..e4576287a 100644 --- a/src/plugin_system/base/component_types.py +++ b/src/plugin_system/base/component_types.py @@ -40,6 +40,7 @@ class ChatMode(Enum): FOCUS = "focus" # Focus聊天模式 NORMAL = "normal" # Normal聊天模式 + PROACTIVE = "proactive" # 主动思考模式 PRIORITY = "priority" # 优先级聊天模式 ALL = "all" # 所有聊天模式