feat(chat): 实现可配置的主动思考范围并优化逻辑

- 将主动思考的prompt移至代码内部,并区分私聊和群聊场景。
- 增加`The_scope_that_proactive_thinking_can_trigger`配置项,允许用户将主动思考限制在“全部”、“私聊”或“群聊”范围。
- 删除了旧的`proactive_thinking_prompt_template`配置。
- 优化了主动思考的触发条件,现在会根据新的范围配置进行检查。
- 清理了代码中多余的空行和未使用的导入。
This commit is contained in:
minecraft1024a
2025-08-20 11:27:37 +08:00
parent 7d627b2671
commit 756f8e5dc3
5 changed files with 83 additions and 77 deletions

View File

@@ -139,6 +139,22 @@ class HeartFChatting:
self.last_message_time = time.time() # 最后一条消息的时间 self.last_message_time = time.time() # 最后一条消息的时间
self._proactive_thinking_task: Optional[asyncio.Task] = None # 主动思考任务 self._proactive_thinking_task: Optional[asyncio.Task] = None # 主动思考任务
self.proactive_thinking_prompts = {
"private": """现在你和你朋友的私聊里面已经隔了{time}没有发送消息了,请你结合上下文以及你和你朋友之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复"沉默"(注意:这个词不会被发送到群聊中)。""",
"group": """现在群里面已经隔了{time}没有人发送消息了,请你结合上下文以及群聊里面之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复"沉默"(注意:这个词不会被发送到群聊中)。""",
}
self.proactive_thinking_chat_scope = global_config.chat.The_scope_that_proactive_thinking_can_trigger
async def start(self): async def start(self):
"""检查是否需要启动主循环,如果未激活则启动。""" """检查是否需要启动主循环,如果未激活则启动。"""
@@ -154,9 +170,8 @@ class HeartFChatting:
self._energy_task = asyncio.create_task(self._energy_loop()) self._energy_task = asyncio.create_task(self._energy_loop())
self._energy_task.add_done_callback(self._handle_energy_completion) self._energy_task.add_done_callback(self._handle_energy_completion)
# 启动主动思考任务(仅在群聊且启用的情况下) # 启动主动思考任务
if (global_config.chat.enable_proactive_thinking and if global_config.chat.enable_proactive_thinking:
self.chat_stream.group_info is not None):
self._proactive_thinking_task = asyncio.create_task(self._proactive_thinking_loop()) self._proactive_thinking_task = asyncio.create_task(self._proactive_thinking_loop())
self._proactive_thinking_task.add_done_callback(self._handle_proactive_thinking_completion) self._proactive_thinking_task.add_done_callback(self._handle_proactive_thinking_completion)
@@ -274,6 +289,11 @@ class HeartFChatting:
# 只在focus模式下进行主动思考 # 只在focus模式下进行主动思考
if self.loop_mode != ChatMode.FOCUS: if self.loop_mode != ChatMode.FOCUS:
continue continue
if self.proactive_thinking_chat_scope == "group" and self.chat_stream.group_info is None:
continue
if self.proactive_thinking_chat_scope == "private" and self.chat_stream.group_info is not None:
continue
current_time = time.time() current_time = time.time()
silence_duration = current_time - self.last_message_time silence_duration = current_time - self.last_message_time
@@ -310,17 +330,12 @@ class HeartFChatting:
logger.info(f"{self.log_prefix} 触发主动思考,已沉默{formatted_time}") logger.info(f"{self.log_prefix} 触发主动思考,已沉默{formatted_time}")
try: try:
# 构建主动思考的prompt # 根据聊天类型选择prompt
proactive_prompt = global_config.chat.proactive_thinking_prompt_template.format( chat_type = "group" if self.chat_stream.group_info else "private"
time=formatted_time prompt_template = self.proactive_thinking_prompts.get(chat_type, self.proactive_thinking_prompts["group"])
) proactive_prompt = prompt_template.format(time=formatted_time)
# 创建一个虚拟的消息数据用于主动思考 # 创建一个虚拟的消息数据用于主动思考
"""
因为主动思考是在没有用户消息的情况下触发的
但规划器仍然需要一个"消息"作为输入来工作
所以需要"伪造"一个消息来触发思考流程本质上是系统与自己的对话让AI能够主动思考和决策。
"""
thinking_message = { thinking_message = {
"processed_plain_text": proactive_prompt, "processed_plain_text": proactive_prompt,
"user_id": "system_proactive_thinking", "user_id": "system_proactive_thinking",
@@ -329,11 +344,10 @@ class HeartFChatting:
"message_type": "proactive_thinking", "message_type": "proactive_thinking",
"user_nickname": "系统主动思考", "user_nickname": "系统主动思考",
"chat_info_platform": "system", "chat_info_platform": "system",
"message_id": f"proactive_{int(time.time())}" "message_id": f"proactive_{int(time.time())}",
} }
# 使用现有的_observe方法来处理主动思考 # 使用现有的_observe方法来处理主动思考
# 这样可以复用现有的完整思考流程
logger.info(f"{self.log_prefix} 开始主动思考...") logger.info(f"{self.log_prefix} 开始主动思考...")
await self._observe(message_data=thinking_message) await self._observe(message_data=thinking_message)
logger.info(f"{self.log_prefix} 主动思考完成") logger.info(f"{self.log_prefix} 主动思考完成")
@@ -356,7 +370,6 @@ class HeartFChatting:
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
) )
async def _loopbody(self): async def _loopbody(self):
recent_messages_dict = message_api.get_messages_by_time_in_chat( recent_messages_dict = message_api.get_messages_by_time_in_chat(
chat_id=self.stream_id, chat_id=self.stream_id,
@@ -374,7 +387,6 @@ class HeartFChatting:
current_time = time.time() current_time = time.time()
self.last_message_time = current_time self.last_message_time = current_time
if self.loop_mode == ChatMode.FOCUS: if self.loop_mode == ChatMode.FOCUS:
# focus模式下在有新消息时进行观察思考 # focus模式下在有新消息时进行观察思考
# 主动思考由独立的 _proactive_thinking_loop 处理 # 主动思考由独立的 _proactive_thinking_loop 处理
@@ -426,9 +438,7 @@ class HeartFChatting:
elif global_config.chat.focus_value != 0: elif global_config.chat.focus_value != 0:
if new_message_count > 3 / pow(global_config.chat.focus_value, 0.5): if new_message_count > 3 / pow(global_config.chat.focus_value, 0.5):
self.loop_mode = ChatMode.FOCUS self.loop_mode = ChatMode.FOCUS
self.energy_value = ( self.energy_value = 10 + (new_message_count / (3 / pow(global_config.chat.focus_value, 0.5))) * 10
10 + (new_message_count / (3 / pow(global_config.chat.focus_value, 0.5))) * 10
)
return True return True
if self.energy_value >= 30: if self.energy_value >= 30:
@@ -466,7 +476,9 @@ class HeartFChatting:
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
# 获取平台信息优先使用chat_info_platform如果为None则使用user_platform # 获取平台信息优先使用chat_info_platform如果为None则使用user_platform
platform = message_data.get("chat_info_platform") or message_data.get("user_platform") or self.chat_stream.platform platform = (
message_data.get("chat_info_platform") or message_data.get("user_platform") or self.chat_stream.platform
)
user_id = message_data.get("user_id") user_id = message_data.get("user_id")
person_id = person_info_manager.get_person_id(platform, user_id) person_id = person_info_manager.get_person_id(platform, user_id)
@@ -490,7 +502,9 @@ class HeartFChatting:
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
# 获取平台信息优先使用chat_info_platform如果为空则使用user_platform # 获取平台信息优先使用chat_info_platform如果为空则使用user_platform
platform = action_message.get("chat_info_platform") or action_message.get("user_platform") or self.chat_stream.platform platform = (
action_message.get("chat_info_platform") or action_message.get("user_platform") or self.chat_stream.platform
)
user_id = action_message.get("user_id", "") user_id = action_message.get("user_id", "")
person_id = person_info_manager.get_person_id(platform, user_id) person_id = person_info_manager.get_person_id(platform, user_id)
@@ -555,12 +569,15 @@ class HeartFChatting:
# 在focus模式下如果你的bot被@/提到了那么就移除no_reply动作 # 在focus模式下如果你的bot被@/提到了那么就移除no_reply动作
is_mentioned_bot = message_data.get("is_mentioned", False) is_mentioned_bot = message_data.get("is_mentioned", False)
at_bot_mentioned = (global_config.chat.mentioned_bot_inevitable_reply and is_mentioned_bot) or \ at_bot_mentioned = (global_config.chat.mentioned_bot_inevitable_reply and is_mentioned_bot) or (
(global_config.chat.at_bot_inevitable_reply and is_mentioned_bot) global_config.chat.at_bot_inevitable_reply and is_mentioned_bot
)
if self.loop_mode == ChatMode.FOCUS and at_bot_mentioned and "no_reply" in available_actions: if self.loop_mode == ChatMode.FOCUS and at_bot_mentioned and "no_reply" in available_actions:
logger.info(f"{self.log_prefix} Focus模式下检测到@或提及bot移除no_reply动作以确保回复") logger.info(f"{self.log_prefix} Focus模式下检测到@或提及bot移除no_reply动作以确保回复")
available_actions = {k: v for k, v in available_actions.items() if k != "no_reply"} # 用一个循环来移除no_reply available_actions = {
k: v for k, v in available_actions.items() if k != "no_reply"
} # 用一个循环来移除no_reply
# 检查是否在normal模式下没有可用动作除了reply相关动作 # 检查是否在normal模式下没有可用动作除了reply相关动作
skip_planner = False skip_planner = False

View File

@@ -25,7 +25,6 @@ from rich.progress import (
SpinnerColumn, SpinnerColumn,
TextColumn, TextColumn,
) )
from src.chat.utils.utils import get_embedding
from src.config.config import global_config from src.config.config import global_config

View File

@@ -1,18 +1,13 @@
import asyncio import asyncio
import time import time
import json from typing import List, Dict, Any
import hashlib
from typing import List, Optional, Tuple, Dict, Any
from dataclasses import dataclass from dataclasses import dataclass
import threading import threading
from datetime import datetime, timedelta
import numpy as np
import chromadb import chromadb
from chromadb.config import Settings from chromadb.config import Settings
from src.common.logger import get_logger from src.common.logger import get_logger
from src.chat.utils.utils import get_embedding from src.chat.utils.utils import get_embedding
from src.config.config import global_config
logger = get_logger("vector_instant_memory_v2") logger = get_logger("vector_instant_memory_v2")

View File

@@ -81,7 +81,7 @@ class ChatConfig(ValidatedConfigBase):
timestamp_display_mode: Literal["normal", "normal_no_YMD", "relative"] = Field(default="normal_no_YMD", description="时间戳显示模式") timestamp_display_mode: Literal["normal", "normal_no_YMD", "relative"] = Field(default="normal_no_YMD", description="时间戳显示模式")
enable_proactive_thinking: bool = Field(default=False, description="启用主动思考") enable_proactive_thinking: bool = Field(default=False, description="启用主动思考")
proactive_thinking_interval: int = Field(default=1500, description="主动思考间隔") proactive_thinking_interval: int = Field(default=1500, description="主动思考间隔")
proactive_thinking_prompt_template: str = Field(default="", description="主动思考提示模板") The_scope_that_proactive_thinking_can_trigger: str = Field(default="all", description="主动思考可以触发的范围")
def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float:
""" """

View File

@@ -122,13 +122,8 @@ talk_frequency_adjust = [
# 主动思考功能配置仅在focus模式下生效 # 主动思考功能配置仅在focus模式下生效
enable_proactive_thinking = false # 是否启用主动思考功能 enable_proactive_thinking = false # 是否启用主动思考功能
proactive_thinking_interval = 1500 # 主动思考触发间隔时间默认1500秒25分钟 proactive_thinking_interval = 1500 # 主动思考触发间隔时间默认1500秒25分钟
# 主动思考prompt模板{time}会被替换为实际的沉默时间(如"2小时30分15秒" The_scope_that_proactive_thinking_can_trigger = "all" #主动思考可以触发的范围(all - 所有private - 私聊group - 群聊)
proactive_thinking_prompt_template = """现在群里面已经隔了{time}没有人发送消息了,请你结合上下文以及群聊里面之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复""(注意:这个词不会被发送到群聊中)。"""
# 特定聊天流配置示例: # 特定聊天流配置示例:
# [ # [
@@ -306,7 +301,7 @@ library_log_levels = { "aiohttp" = "WARNING"} # 设置特定库的日志级别
[dependency_management] # 插件Python依赖管理配置 [dependency_management] # 插件Python依赖管理配置
# 是否启用自动安装Python依赖包主开关 # 是否启用自动安装Python依赖包主开关
auto_install = false #暂时关闭一下因为还用不了 auto_install = true #暂时关闭一下因为还用不了
# 安装超时时间(秒) # 安装超时时间(秒)
auto_install_timeout = 300 auto_install_timeout = 300
# 是否使用PyPI镜像源推荐可加速下载 # 是否使用PyPI镜像源推荐可加速下载