feat(proactive_thinking): 重构主动思考为由Planner直接决策
重构了主动思考的触发和决策流程。原有的通过生成特定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"),以便于追踪和分析。
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
处理回复类型的动作
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]):
|
||||
"""
|
||||
初始化循环详情记录
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,7 @@ class ChatMode(Enum):
|
||||
|
||||
FOCUS = "focus" # Focus聊天模式
|
||||
NORMAL = "normal" # Normal聊天模式
|
||||
PROACTIVE = "proactive" # 主动思考模式
|
||||
PRIORITY = "priority" # 优先级聊天模式
|
||||
ALL = "all" # 所有聊天模式
|
||||
|
||||
|
||||
Reference in New Issue
Block a user