refactor(proactive_thinker): 优化唤醒逻辑并增加防打扰机制

重构了日常唤醒任务(ProactiveThinkingTask)的逻辑,将其拆分为私聊和群聊的独立处理流程。
- 私聊现在直接遍历白名单,确保能覆盖到所有配置的用户,即使他们当前不在内存中。
- 群聊则继续遍历内存中的活跃流。
这个改动修复了之前版本中,只有当用户发送消息后,bot才有可能对其进行主动唤醒的问题。

同时,在决策模块中引入了防打扰机制:
- 在决策提示词中加入了最近的决策历史记录作为上下文。
- 增加了新的决策原则,明确指示模型在近期已主动发起过对话的情况下,应倾向于保持沉默,以避免过度打扰用户。

此外,对冷启动任务(ColdStartTask)进行了微调,将初始的等待时间移至循环的开始,以确保插件加载后能先等待一段时间再开始工作。
This commit is contained in:
minecraft1024a
2025-10-03 19:39:37 +08:00
parent 4089e714b7
commit 135909449c
2 changed files with 50 additions and 38 deletions

View File

@@ -38,6 +38,8 @@ class ColdStartTask(AsyncTask):
while True:
try:
#开始就先暂停一小时,等bot聊一会再说()
await asyncio.sleep(3600)
logger.info("【冷启动】开始扫描白名单,寻找从未聊过的用户...")
# 从全局配置中获取私聊白名单
@@ -83,9 +85,6 @@ class ColdStartTask(AsyncTask):
except Exception as e:
logger.error(f"【冷启动】处理用户 {chat_id} 时发生未知错误: {e}", exc_info=True)
# 完成一轮检查后,进入长时休眠
await asyncio.sleep(3600)
except asyncio.CancelledError:
logger.info("冷启动任务被正常取消。")
break
@@ -157,42 +156,48 @@ class ProactiveThinkingTask(AsyncTask):
enabled_private = set(global_config.proactive_thinking.enabled_private_chats)
enabled_groups = set(global_config.proactive_thinking.enabled_group_chats)
# 获取当前所有聊天流的快照
all_streams = list(self.chat_manager.streams.values())
# 分别处理私聊和群聊
# 1. 处理私聊:直接遍历白名单,确保能覆盖到所有(包括本次运行尚未活跃的)用户
for chat_id in enabled_private:
try:
platform, user_id_str = chat_id.split(":")
# 【核心逻辑】检查聊天流是否存在。不存在则跳过交由ColdStartTask处理。
stream = chat_api.get_stream_by_user_id(user_id_str, platform)
if not stream:
continue
for stream in all_streams:
# 1. 检查该聊天是否在白名单内(或白名单为空时默认允许)
is_whitelisted = False
if stream.group_info: # 群聊
if not enabled_groups or f"qq:{stream.group_info.group_id}" in enabled_groups:
is_whitelisted = True
else: # 私聊
if not enabled_private or f"qq:{stream.user_info.user_id}" in enabled_private:
is_whitelisted = True
if not is_whitelisted:
continue # 不在白名单内,跳过
# 2. 【核心逻辑】检查聊天冷却时间是否足够长
# 检查冷却时间
time_since_last_active = time.time() - stream.last_active_time
if time_since_last_active > next_interval:
logger.info(
f"【日常唤醒】聊天流 {stream.stream_id} 已冷却 {time_since_last_active:.2f} 秒,触发主动对话。"
f"【日常唤醒-私聊】聊天流 {stream.stream_id} 已冷却 {time_since_last_active:.2f} 秒,触发主动对话。"
)
# 构建符合 executor 期望的 stream_id 格式
if stream.group_info and stream.group_info.group_id:
formatted_stream_id = f"{stream.user_info.platform}:{stream.group_info.group_id}:group"
elif stream.user_info and stream.user_info.user_id:
formatted_stream_id = f"{stream.user_info.platform}:{stream.user_info.user_id}:private"
else:
logger.warning(f"【日常唤醒】跳过 stream {stream.stream_id},因为它缺少有效的用户信息或群组信息。")
continue
await self.executor.execute(stream_id=formatted_stream_id, start_mode="wake_up")
stream.update_active_time()
await self.chat_manager._save_stream(stream)
# 【关键步骤】在触发后,立刻更新活跃时间并保存。
# 这可以防止在同一个检查周期内,对同一个目标因为意外的延迟而发送多条消息。
except ValueError:
logger.warning(f"【日常唤醒】私聊白名单条目格式错误,已跳过: {chat_id}")
except Exception as e:
logger.error(f"【日常唤醒】处理私聊用户 {chat_id} 时发生未知错误: {e}", exc_info=True)
# 2. 处理群聊:遍历内存中的活跃流(群聊不存在冷启动问题)
all_streams = list(self.chat_manager.streams.values())
for stream in all_streams:
if not stream.group_info:
continue # 只处理群聊
# 检查群聊是否在白名单内
if not enabled_groups or f"qq:{stream.group_info.group_id}" in enabled_groups:
# 检查冷却时间
time_since_last_active = time.time() - stream.last_active_time
if time_since_last_active > next_interval:
logger.info(
f"【日常唤醒-群聊】聊天流 {stream.stream_id} 已冷却 {time_since_last_active:.2f} 秒,触发主动对话。"
)
formatted_stream_id = f"{stream.user_info.platform}:{stream.group_info.group_id}:group"
await self.executor.execute(stream_id=formatted_stream_id, start_mode="wake_up")
stream.update_active_time()
await self.chat_manager._save_stream(stream)

View File

@@ -240,6 +240,9 @@ class ProactiveThinkerExecutor:
- 身份: {persona["identity"]}
你的当前情绪状态是: {context["mood_state"]}
# 你最近的相关决策历史 (供参考)
{context["action_history_context"]}
"""
# 根据聊天类型构建任务和情境
if chat_type == "private":
@@ -283,6 +286,10 @@ class ProactiveThinkerExecutor:
- `topic`: str, 如果 `should_reply` 为 true你打算聊什么话题(例如:问候一下今天的日程、关心一下昨天的某件事、分享一个你自己的趣事等)
- `reason`: str, 做出此决策的简要理由。
# 决策原则
- **避免打扰**: 如果你最近(尤其是在最近的几次决策中)已经主动发起过对话,请倾向于选择“不回复”,除非有非常重要和紧急的事情。
---
示例1 (应该回复):
{{