refactor(chat): 重构主动思考模块以提升回复质量和逻辑清晰度(哪个大聪明把我联网搜索烦了)

将主动思考流程拆分为两个主要阶段:规划和内容生成。

在规划阶段(`ActionPlanner`),模型现在会结合最新的聊天上下文来决定是否发起主动对话,并确定一个合适的主题。这使得决策更加贴近当前对话氛围。

在内容生成阶段(`ProactiveThinker`),系统会围绕规划好的主题,主动搜集相关实时信息(如日程、网络资讯),并结合角色设定、心情和聊天历史,构建一个更丰富、更具上下文情境的提示词,从而生成更自然、更有趣的主动回复。

主要变更:
- `ActionPlanner` 在主动模式下增加对近期聊天记录的分析,决策更精准。
- `ProactiveThinker` 新增 `_generate_proactive_content_and_send` 方法,负责整合多源信息(日程、搜索、上下文)生成最终回复。
- 简化了 `ProactiveThinker` 的主逻辑,使其专注于执行 `proactive_reply` 动作,而非处理多种动作类型。
- 优化了相关提示词,使其更专注于生成高质量的主动对话内容。
This commit is contained in:
minecraft1024a
2025-09-06 19:42:48 +08:00
committed by Windpicker-owo
parent 5f1fd4305e
commit 667be49a95
17 changed files with 1432 additions and 28 deletions

View File

@@ -1,12 +1,19 @@
import time
import traceback
from typing import TYPE_CHECKING
import orjson
from typing import TYPE_CHECKING, Dict, Any
from src.common.logger import get_logger
from src.plugin_system.base.component_types import ChatMode
from ..hfc_context import HfcContext
from .events import ProactiveTriggerEvent
from src.plugin_system.apis import generator_api
from src.schedule.schedule_manager import schedule_manager
from src.plugin_system import tool_api
from src.plugin_system.base.component_types import ComponentType
from src.config.config import global_config
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages_with_id
from src.mood.mood_manager import mood_manager
if TYPE_CHECKING:
from ..cycle_processor import CycleProcessor
@@ -20,6 +27,7 @@ class ProactiveThinker:
当接收到 ProactiveTriggerEvent 时,它会根据事件内容进行一系列决策和操作,
例如调整情绪、调用规划器生成行动,并最终可能产生一个主动的回复。
"""
def __init__(self, context: HfcContext, cycle_processor: "CycleProcessor"):
"""
初始化主动思考器。
@@ -75,9 +83,6 @@ class ProactiveThinker:
return
try:
# 动态导入情绪管理器,避免循环依赖
from src.mood.mood_manager import mood_manager
# 获取当前聊天的情绪对象
mood_obj = mood_manager.get_mood_by_chat_id(self.context.stream_id)
new_mood = None
@@ -112,29 +117,17 @@ class ProactiveThinker:
"""
try:
# 调用规划器的 PROACTIVE 模式,让其决定下一步的行动
actions, target_message = await self.cycle_processor.action_planner.plan(mode=ChatMode.PROACTIVE)
actions, _ = await self.cycle_processor.action_planner.plan(mode=ChatMode.PROACTIVE)
# 通常只关心规划出的第一个动作
action_result = actions[0] if actions else {}
# 检查规划出的动作是否是“什么都不做”
if action_result and action_result.get("action_type") != "do_nothing":
# 如果动作是“回复”
if action_result.get("action_type") == "reply":
# 调用生成器API来创建回复内容
success, response_set, _ = await generator_api.generate_reply(
chat_stream=self.context.chat_stream,
reply_message=action_result["action_message"],
available_actions={}, # 主动回复不考虑工具使用
enable_tool=False,
request_type="chat.replyer.proactive", # 标记请求类型
from_plugin=False,
)
# 如果成功生成回复,则发送出去
if success and response_set:
await self.cycle_processor.response_handler.send_response(
response_set, time.time(), action_result["action_message"]
)
action_type = action_result.get("action_type")
if action_type == "proactive_reply":
await self._generate_proactive_content_and_send(action_result)
elif action_type != "do_nothing":
logger.warning(f"{self.context.log_prefix} 主动思考返回了未知的动作类型: {action_type}")
else:
# 如果规划结果是“什么都不做”,则记录日志
logger.info(f"{self.context.log_prefix} 主动思考决策: 保持沉默")
@@ -142,3 +135,98 @@ class ProactiveThinker:
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考执行异常: {e}")
logger.error(traceback.format_exc())
async def _generate_proactive_content_and_send(self, action_result: Dict[str, Any]):
"""
获取实时信息,构建最终的生成提示词,并生成和发送主动回复。
Args:
action_result (Dict[str, Any]): 规划器返回的动作结果。
"""
try:
topic = action_result.get("action_data", {}).get("topic", "随便聊聊")
logger.info(f"{self.context.log_prefix} 主动思考确定主题: '{topic}'")
# 1. 获取日程信息
schedule_block = "你今天没有日程安排。"
if global_config.planning_system.schedule_enable:
if current_activity := schedule_manager.get_current_activity():
schedule_block = f"你当前正在:{current_activity}"
# 2. 网络搜索
news_block = "暂时没有获取到最新资讯。"
try:
web_search_tool = tool_api.get_tool_instance("web_search")
if web_search_tool:
tool_args = {"query": topic, "max_results": 10}
# 调用工具,并传递参数
search_result_dict = await web_search_tool.execute(**tool_args)
if search_result_dict and not search_result_dict.get("error"):
news_block = search_result_dict.get("content", "未能提取有效资讯。")
else:
logger.warning(f"{self.context.log_prefix} 网络搜索返回错误: {search_result_dict.get('error')}")
else:
logger.warning(f"{self.context.log_prefix} 未找到 web_search 工具实例。")
except Exception as e:
logger.error(f"{self.context.log_prefix} 主动思考时网络搜索失败: {e}")
# 3. 获取最新的聊天上下文
message_list = get_raw_msg_before_timestamp_with_chat(
chat_id=self.context.stream_id,
timestamp=time.time(),
limit=int(global_config.chat.max_context_size * 0.3),
)
chat_context_block, _ = build_readable_messages_with_id(messages=message_list)
# 4. 构建最终的生成提示词
bot_name = global_config.bot.nickname
identity_block = f"你的名字是{bot_name},你{global_config.personality.personality_core}"
mood_block = f"你现在的心情是:{mood_manager.get_mood_by_chat_id(self.context.stream_id).mood_state}"
final_prompt = f"""
# 主动对话生成
## 你的角色
{identity_block}
## 你的心情
{mood_block}
## 你今天的日程安排
{schedule_block}
## 关于你准备讨论的话题“{topic}”的最新信息
{news_block}
## 最近的聊天内容
{chat_context_block}
## 任务
你之前决定要发起一个关于“{topic}”的对话。现在,请结合以上所有信息,自然地开启这个话题。
## 要求
- 你的发言要听起来像是自发的,而不是在念报告。
- 巧妙地将日程安排或最新信息融入到你的开场白中。
- 风格要符合你的角色设定。
- 直接输出你想要说的内容,不要包含其他额外信息。
"""
# 5. 调用生成器API并发送
response_text = await generator_api.generate_response_custom(
chat_stream=self.context.chat_stream,
prompt=final_prompt,
request_type="chat.replyer.proactive",
)
if response_text:
# 将纯文本包装成 ResponseSet 格式
response_set = [{"type": "text", "data": {"text": response_text}}]
await self.cycle_processor.response_handler.send_response(
response_set, time.time(), action_result.get("action_message")
)
else:
logger.error(f"{self.context.log_prefix} 主动思考生成回复失败。")
except Exception as e:
logger.error(f"{self.context.log_prefix} 生成主动回复内容时异常: {e}")
logger.error(traceback.format_exc())

View File

@@ -90,12 +90,31 @@ def init_prompt():
## 长期记忆摘要
{long_term_memory_block}
## 最近的聊天内容
{chat_content_block}
## 任务
基于以上所有信息,分析当前情况,决定是否需要主动做些什么
如果你认为不需要,就选择 'do_nothing'
基于以上所有信息(特别是最近的聊天内容),分析当前情况,决定是否适合主动开启一个**新的、但又与当前氛围相关**的话题
## 可用动作
{action_options_text}
动作proactive_reply
动作描述:在当前对话的基础上,主动发起一个新的对话,分享一个有趣的想法、见闻或者对未来的计划。
- 当你觉得可以说些什么来活跃气氛,并且内容与当前聊天氛围不冲突时
- 当你有一些新的想法或计划想要分享,并且可以自然地衔接当前话题时
{{
"action": "proactive_reply",
"reason": "决定主动发起对话的具体原因",
"topic": "你想要发起对话的主题或内容(需要简洁)"
}}
动作do_nothing
动作描述:保持沉默,不主动发起任何动作或对话。
- 当你分析了所有信息后,觉得当前不是一个发起互动的好时机时
- 当最近的聊天内容很连贯,你的插入会打断别人时
{{
"action": "do_nothing",
"reason":"决定保持沉默的具体原因"
}}
你必须从上面列出的可用action中选择一个。
请以严格的 JSON 格式输出,且仅包含 JSON 内容:
@@ -643,7 +662,19 @@ 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)
# 获取最近的聊天记录用于主动思考决策
message_list_short = get_raw_msg_before_timestamp_with_chat(
chat_id=self.chat_id,
timestamp=time.time(),
limit=int(global_config.chat.max_context_size * 0.2), # 主动思考时只看少量最近消息
)
chat_content_block, _ = build_readable_messages_with_id(
messages=message_list_short,
timestamp_mode="normal",
truncate=False,
show_actions=False,
)
prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt")
prompt = prompt_template.format(
@@ -652,7 +683,7 @@ class ActionPlanner:
schedule_block=schedule_block,
mood_block=mood_block,
long_term_memory_block=long_term_memory_block,
action_options_text=action_options_text,
chat_content_block=chat_content_block or "最近没有聊天内容。",
)
return prompt, []