refactor(chat): 重构主动思考逻辑并将其集成到主聊天循环
将原有的 `ProactiveThinker` 类中的逻辑直接整合进 `HeartFChatting` 类中。此举简化了整体架构,减少了类之间的耦合,并使得主动思考的触发机制与主聊天循环的状态管理更加统一。 主要变更: - 删除了独立的 `proactive_thinker.py` 文件。 - 将主动思考的监控循环、条件检查、动态间隔计算等功能实现为 `HeartFChatting` 的私有方法。 - 引入了 `ProactiveTriggerEvent` 事件,使触发源更加明确。 - 调整了相关模块的导入路径和配置项的调用方式,以适应新的结构。
This commit is contained in:
@@ -15,11 +15,12 @@ from src.plugin_system.apis import message_api
|
||||
|
||||
from .hfc_context import HfcContext
|
||||
from .energy_manager import EnergyManager
|
||||
from .proactive_thinker import ProactiveThinker
|
||||
from .proactive.proactive_thinker import ProactiveThinker
|
||||
from .cycle_processor import CycleProcessor
|
||||
from .response_handler import ResponseHandler
|
||||
from .cycle_tracker import CycleTracker
|
||||
from .wakeup_manager import WakeUpManager
|
||||
from .proactive.events import ProactiveTriggerEvent
|
||||
|
||||
logger = get_logger("hfc")
|
||||
|
||||
@@ -54,6 +55,7 @@ class HeartFChatting:
|
||||
self.context.chat_instance = self
|
||||
|
||||
self._loop_task: Optional[asyncio.Task] = None
|
||||
self._proactive_monitor_task: Optional[asyncio.Task] = None
|
||||
|
||||
# 记录最近3次的兴趣度
|
||||
self.recent_interest_records: deque = deque(maxlen=3)
|
||||
@@ -93,8 +95,12 @@ class HeartFChatting:
|
||||
self.context.relationship_builder = relationship_builder_manager.get_or_create_builder(self.context.stream_id)
|
||||
self.context.expression_learner = expression_learner_manager.get_expression_learner(self.context.stream_id)
|
||||
|
||||
#await self.energy_manager.start()
|
||||
await self.proactive_thinker.start()
|
||||
# 启动主动思考监视器
|
||||
if global_config.chat.enable_proactive_thinking:
|
||||
self._proactive_monitor_task = asyncio.create_task(self._proactive_monitor_loop())
|
||||
self._proactive_monitor_task.add_done_callback(self._handle_proactive_monitor_completion)
|
||||
logger.info(f"{self.context.log_prefix} 主动思考监视器已启动")
|
||||
|
||||
await self.wakeup_manager.start()
|
||||
|
||||
self._loop_task = asyncio.create_task(self._main_chat_loop())
|
||||
@@ -116,8 +122,12 @@ class HeartFChatting:
|
||||
return
|
||||
self.context.running = False
|
||||
|
||||
#await self.energy_manager.stop()
|
||||
await self.proactive_thinker.stop()
|
||||
# 停止主动思考监视器
|
||||
if self._proactive_monitor_task and not self._proactive_monitor_task.done():
|
||||
self._proactive_monitor_task.cancel()
|
||||
await asyncio.sleep(0)
|
||||
logger.info(f"{self.context.log_prefix} 主动思考监视器已停止")
|
||||
|
||||
await self.wakeup_manager.stop()
|
||||
|
||||
if self._loop_task and not self._loop_task.done():
|
||||
@@ -147,6 +157,92 @@ class HeartFChatting:
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{self.context.log_prefix} HeartFChatting: 结束了聊天")
|
||||
|
||||
def _handle_proactive_monitor_completion(self, task: asyncio.Task):
|
||||
try:
|
||||
if exception := task.exception():
|
||||
logger.error(f"{self.context.log_prefix} 主动思考监视器异常: {exception}")
|
||||
else:
|
||||
logger.info(f"{self.context.log_prefix} 主动思考监视器正常结束")
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{self.context.log_prefix} 主动思考监视器被取消")
|
||||
|
||||
async def _proactive_monitor_loop(self):
|
||||
while self.context.running:
|
||||
await asyncio.sleep(15)
|
||||
|
||||
if not self._should_enable_proactive_thinking():
|
||||
continue
|
||||
|
||||
current_time = time.time()
|
||||
silence_duration = current_time - self.context.last_message_time
|
||||
target_interval = self._get_dynamic_thinking_interval()
|
||||
|
||||
if silence_duration >= target_interval:
|
||||
try:
|
||||
formatted_time = self._format_duration(silence_duration)
|
||||
event = ProactiveTriggerEvent(
|
||||
source="silence_monitor",
|
||||
reason=f"聊天已沉默 {formatted_time}",
|
||||
metadata={"silence_duration": silence_duration}
|
||||
)
|
||||
await self.proactive_thinker.think(event)
|
||||
self.context.last_message_time = current_time
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 主动思考触发执行出错: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def _should_enable_proactive_thinking(self) -> bool:
|
||||
if not self.context.chat_stream:
|
||||
return False
|
||||
|
||||
is_group_chat = self.context.chat_stream.group_info is not None
|
||||
|
||||
if is_group_chat and not global_config.chat.proactive_thinking_in_group:
|
||||
return False
|
||||
if not is_group_chat and not global_config.chat.proactive_thinking_in_private:
|
||||
return False
|
||||
|
||||
stream_parts = self.context.stream_id.split(":")
|
||||
current_chat_identifier = f"{stream_parts}:{stream_parts}" if len(stream_parts) >= 2 else self.context.stream_id
|
||||
|
||||
enable_list = getattr(global_config.chat, "proactive_thinking_enable_in_groups" if is_group_chat else "proactive_thinking_enable_in_private", [])
|
||||
return not enable_list or current_chat_identifier in enable_list
|
||||
|
||||
def _get_dynamic_thinking_interval(self) -> float:
|
||||
try:
|
||||
from src.utils.timing_utils import get_normal_distributed_interval
|
||||
base_interval = global_config.chat.proactive_thinking_interval
|
||||
delta_sigma = getattr(global_config.chat, "delta_sigma", 120)
|
||||
|
||||
if base_interval <= 0: base_interval = abs(base_interval)
|
||||
if delta_sigma < 0: delta_sigma = abs(delta_sigma)
|
||||
|
||||
if base_interval == 0 and delta_sigma == 0: return 300
|
||||
if delta_sigma == 0: return base_interval
|
||||
|
||||
sigma_percentage = delta_sigma / base_interval if base_interval > 0 else delta_sigma / 1000
|
||||
return get_normal_distributed_interval(base_interval, sigma_percentage, 1, 86400, use_3sigma_rule=True)
|
||||
|
||||
except ImportError:
|
||||
logger.warning(f"{self.context.log_prefix} timing_utils不可用,使用固定间隔")
|
||||
return max(300, abs(global_config.chat.proactive_thinking_interval))
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 动态间隔计算出错: {e},使用固定间隔")
|
||||
return max(300, abs(global_config.chat.proactive_thinking_interval))
|
||||
|
||||
def _format_duration(self, seconds: float) -> str:
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
secs = int(seconds % 60)
|
||||
parts = []
|
||||
if hours > 0:
|
||||
parts.append(f"{hours}小时")
|
||||
if minutes > 0:
|
||||
parts.append(f"{minutes}分")
|
||||
if secs > 0 or not parts:
|
||||
parts.append(f"{secs}秒")
|
||||
return "".join(parts)
|
||||
|
||||
async def _main_chat_loop(self):
|
||||
"""
|
||||
主聊天循环
|
||||
@@ -239,7 +335,7 @@ class HeartFChatting:
|
||||
|
||||
# 根据聊天模式处理新消息
|
||||
# 统一使用 _should_process_messages 判断是否应该处理
|
||||
should_process,interest_value = await self._should_process_messages(recent_messages if has_new_messages else None)
|
||||
should_process,interest_value = await self._should_process_messages(recent_messages)
|
||||
if should_process:
|
||||
self.context.last_read_time = time.time()
|
||||
await self.cycle_processor.observe(interest_value = interest_value)
|
||||
@@ -248,7 +344,7 @@ class HeartFChatting:
|
||||
await asyncio.sleep(0.5)
|
||||
return True
|
||||
|
||||
if not await self._should_process_messages(recent_messages if has_new_messages else None):
|
||||
if not await self._should_process_messages(recent_messages):
|
||||
return has_new_messages
|
||||
|
||||
# 处理新消息
|
||||
@@ -321,14 +417,15 @@ class HeartFChatting:
|
||||
def _determine_form_type(self) -> str:
|
||||
"""判断使用哪种形式的no_reply"""
|
||||
# 检查是否启用breaking模式
|
||||
if not global_config.chat.enable_breaking_mode:
|
||||
if not getattr(global_config.chat, "enable_breaking_mode", False):
|
||||
logger.info(f"{self.context.log_prefix} breaking模式已禁用,使用waiting形式")
|
||||
self.context.focus_energy = 1
|
||||
return
|
||||
return "waiting"
|
||||
|
||||
# 如果连续no_reply次数少于3次,使用waiting形式
|
||||
if self.context.no_reply_consecutive <= 3:
|
||||
self.context.focus_energy = 1
|
||||
return "waiting"
|
||||
else:
|
||||
# 使用累积兴趣值而不是最近3次的记录
|
||||
total_interest = self.context.breaking_accumulated_interest
|
||||
@@ -342,18 +439,23 @@ class HeartFChatting:
|
||||
if total_interest < adjusted_threshold:
|
||||
logger.info(f"{self.context.log_prefix} 累积兴趣度不足,进入breaking形式")
|
||||
self.context.focus_energy = random.randint(3, 6)
|
||||
return "breaking"
|
||||
else:
|
||||
logger.info(f"{self.context.log_prefix} 累积兴趣度充足,使用waiting形式")
|
||||
self.context.focus_energy = 1
|
||||
return "waiting"
|
||||
|
||||
async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool,float]:
|
||||
"""
|
||||
统一判断是否应该处理消息的函数
|
||||
根据当前循环模式和消息内容决定是否继续处理
|
||||
"""
|
||||
if not new_message:
|
||||
return False, 0.0
|
||||
|
||||
new_message_count = len(new_message)
|
||||
|
||||
talk_frequency = global_config.chat.get_current_talk_frequency(self.context.chat_stream.stream_id)
|
||||
talk_frequency = global_config.chat.get_current_talk_frequency(self.context.stream_id)
|
||||
|
||||
modified_exit_count_threshold = self.context.focus_energy * 0.5 / talk_frequency
|
||||
modified_exit_interest_threshold = 1.5 / talk_frequency
|
||||
@@ -402,7 +504,7 @@ class HeartFChatting:
|
||||
# 每10秒输出一次等待状态
|
||||
if int(time.time() - self.context.last_read_time) > 0 and int(time.time() - self.context.last_read_time) % 10 == 0:
|
||||
logger.info(
|
||||
f"{self.context.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累积兴趣{total_interest:.1f},继续等待..."
|
||||
f"{self.context.log_prefix} 已等待{time.time() - self.context.last_read_time:.0f}秒,累计{new_message_count}条消息,累积兴趣{total_interest:.1f},继续等待..."
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from src.chat.chat_loop.hfc_utils import CycleDetail
|
||||
if TYPE_CHECKING:
|
||||
from .wakeup_manager import WakeUpManager
|
||||
from .energy_manager import EnergyManager
|
||||
from .heartFC_chat import HeartFChatting
|
||||
|
||||
|
||||
class HfcContext:
|
||||
@@ -69,7 +70,7 @@ class HfcContext:
|
||||
# breaking形式下的累积兴趣值
|
||||
self.breaking_accumulated_interest = 0.0
|
||||
# 引用HeartFChatting实例,以便其他组件可以调用其方法
|
||||
self.chat_instance = None
|
||||
self.chat_instance: Optional["HeartFChatting"] = None
|
||||
|
||||
def save_context_state(self):
|
||||
"""将当前状态保存到聊天流"""
|
||||
|
||||
11
src/chat/chat_loop/proactive/events.py
Normal file
11
src/chat/chat_loop/proactive/events.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
@dataclass
|
||||
class ProactiveTriggerEvent:
|
||||
"""
|
||||
主动思考触发事件的数据类
|
||||
"""
|
||||
source: str # 触发源的标识,例如 "silence_monitor", "insomnia_manager"
|
||||
reason: str # 触发的具体原因,例如 "聊天已沉默10分钟", "深夜emo"
|
||||
metadata: Optional[Dict[str, Any]] = field(default_factory=dict) # 可选的元数据,用于传递额外信息
|
||||
125
src/chat/chat_loop/proactive/proactive_thinker.py
Normal file
125
src/chat/chat_loop/proactive/proactive_thinker.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import time
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..cycle_processor import CycleProcessor
|
||||
|
||||
logger = get_logger("hfc")
|
||||
|
||||
|
||||
class ProactiveThinker:
|
||||
def __init__(self, context: HfcContext, cycle_processor: "CycleProcessor"):
|
||||
"""
|
||||
初始化主动思考器
|
||||
|
||||
Args:
|
||||
context: HFC聊天上下文对象
|
||||
cycle_processor: 循环处理器,用于执行主动思考的结果
|
||||
|
||||
功能说明:
|
||||
- 接收主动思考事件并执行思考流程
|
||||
- 根据事件类型执行不同的前置操作(如修改情绪)
|
||||
- 调用planner进行决策并由cycle_processor执行
|
||||
"""
|
||||
self.context = context
|
||||
self.cycle_processor = cycle_processor
|
||||
|
||||
async def think(self, trigger_event: ProactiveTriggerEvent):
|
||||
"""
|
||||
统一的API入口,用于触发主动思考
|
||||
|
||||
Args:
|
||||
trigger_event: 描述触发上下文的事件对象
|
||||
"""
|
||||
logger.info(f"{self.context.log_prefix} 接收到主动思考事件: "
|
||||
f"来源='{trigger_event.source}', 原因='{trigger_event.reason}'")
|
||||
|
||||
try:
|
||||
# 1. 根据事件类型执行前置操作
|
||||
await self._prepare_for_thinking(trigger_event)
|
||||
|
||||
# 2. 执行核心思考逻辑
|
||||
await self._execute_proactive_thinking(trigger_event)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 主动思考 think 方法执行异常: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
async def _prepare_for_thinking(self, trigger_event: ProactiveTriggerEvent):
|
||||
"""
|
||||
根据事件类型,执行思考前的准备工作,例如修改情绪
|
||||
|
||||
Args:
|
||||
trigger_event: 触发事件
|
||||
"""
|
||||
if trigger_event.source != "insomnia_manager":
|
||||
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
|
||||
|
||||
if trigger_event.reason == "low_pressure":
|
||||
new_mood = "精力过剩,毫无睡意"
|
||||
elif trigger_event.reason == "random":
|
||||
new_mood = "深夜emo,胡思乱想"
|
||||
elif trigger_event.reason == "goodnight":
|
||||
new_mood = "有点困了,准备睡觉了"
|
||||
|
||||
if new_mood:
|
||||
mood_obj.mood_state = new_mood
|
||||
mood_obj.last_change_time = time.time()
|
||||
logger.info(f"{self.context.log_prefix} 因 '{trigger_event.reason}',"
|
||||
f"情绪状态被强制更新为: {mood_obj.mood_state}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 设置失眠情绪时出错: {e}")
|
||||
|
||||
async def _execute_proactive_thinking(self, trigger_event: ProactiveTriggerEvent):
|
||||
"""
|
||||
执行主动思考的核心逻辑
|
||||
|
||||
Args:
|
||||
trigger_event: 触发事件
|
||||
"""
|
||||
try:
|
||||
# 直接调用 planner 的 PROACTIVE 模式
|
||||
actions, target_message = await self.cycle_processor.action_planner.plan(
|
||||
mode=ChatMode.PROACTIVE
|
||||
)
|
||||
|
||||
# 获取第一个规划出的动作作为主要决策
|
||||
action_result = actions[0] if actions else {}
|
||||
|
||||
# 如果决策不是 do_nothing,则执行
|
||||
if action_result and action_result.get("action_type") != "do_nothing":
|
||||
|
||||
# 在主动思考时,如果 target_message 为 None,则默认选取最新 message 作为 target_message
|
||||
if target_message is None and self.context.chat_stream and self.context.chat_stream.context:
|
||||
from src.chat.message_receive.message import MessageRecv
|
||||
latest_message = self.context.chat_stream.context.get_last_message()
|
||||
if isinstance(latest_message, MessageRecv):
|
||||
user_info = latest_message.message_info.user_info
|
||||
target_message = {
|
||||
"chat_info_platform": latest_message.message_info.platform,
|
||||
"user_platform": user_info.platform if user_info else None,
|
||||
"user_id": user_info.user_id if user_info else None,
|
||||
"processed_plain_text": latest_message.processed_plain_text,
|
||||
"is_mentioned": latest_message.is_mentioned,
|
||||
}
|
||||
|
||||
# 将决策结果交给 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())
|
||||
@@ -1,353 +0,0 @@
|
||||
import asyncio
|
||||
import time
|
||||
import traceback
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.plugin_system.base.component_types import ChatMode
|
||||
from .hfc_context import HfcContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .cycle_processor import CycleProcessor
|
||||
|
||||
logger = get_logger("hfc")
|
||||
|
||||
|
||||
class ProactiveThinker:
|
||||
def __init__(self, context: HfcContext, cycle_processor: "CycleProcessor"):
|
||||
"""
|
||||
初始化主动思考器
|
||||
|
||||
Args:
|
||||
context: HFC聊天上下文对象
|
||||
cycle_processor: 循环处理器,用于执行主动思考的结果
|
||||
|
||||
功能说明:
|
||||
- 管理机器人的主动发言功能
|
||||
- 根据沉默时间和配置触发主动思考
|
||||
- 提供私聊和群聊不同的思考提示模板
|
||||
- 使用3-sigma规则计算动态思考间隔
|
||||
"""
|
||||
self.context = context
|
||||
self.cycle_processor = cycle_processor
|
||||
self._proactive_thinking_task: Optional[asyncio.Task] = None
|
||||
|
||||
self.proactive_thinking_prompts = {
|
||||
"private": """现在你和你朋友的私聊里面已经隔了{time}没有发送消息了,请你结合上下文以及你和你朋友之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
|
||||
|
||||
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
|
||||
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
|
||||
|
||||
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复"沉默"(注意:这个词不会被发送到群聊中)。""",
|
||||
"group": """现在群里面已经隔了{time}没有人发送消息了,请你结合上下文以及群聊里面之前聊过的话题和你的人设来决定要不要主动发送消息,你可以选择:
|
||||
|
||||
1. 继续保持沉默(当{time}以前已经结束了一个话题并且你不想挑起新话题时)
|
||||
2. 选择回复(当{time}以前你发送了一条消息且没有人回复你时、你想主动挑起一个话题时)
|
||||
|
||||
请根据当前情况做出选择。如果选择回复,请直接发送你想说的内容;如果选择保持沉默,请只回复"沉默"(注意:这个词不会被发送到群聊中)。""",
|
||||
}
|
||||
|
||||
async def start(self):
|
||||
"""
|
||||
启动主动思考器
|
||||
|
||||
功能说明:
|
||||
- 检查运行状态和配置,避免重复启动
|
||||
- 只有在启用主动思考功能时才启动
|
||||
- 创建主动思考循环异步任务
|
||||
- 设置任务完成回调处理
|
||||
- 记录启动日志
|
||||
"""
|
||||
if self.context.running and not self._proactive_thinking_task and global_config.chat.enable_proactive_thinking:
|
||||
self._proactive_thinking_task = asyncio.create_task(self._proactive_thinking_loop())
|
||||
self._proactive_thinking_task.add_done_callback(self._handle_proactive_thinking_completion)
|
||||
logger.info(f"{self.context.log_prefix} 主动思考器已启动")
|
||||
|
||||
async def stop(self):
|
||||
"""
|
||||
停止主动思考器
|
||||
|
||||
功能说明:
|
||||
- 取消正在运行的主动思考任务
|
||||
- 等待任务完全停止
|
||||
- 记录停止日志
|
||||
"""
|
||||
if self._proactive_thinking_task and not self._proactive_thinking_task.done():
|
||||
self._proactive_thinking_task.cancel()
|
||||
await asyncio.sleep(0)
|
||||
logger.info(f"{self.context.log_prefix} 主动思考器已停止")
|
||||
|
||||
def _handle_proactive_thinking_completion(self, task: asyncio.Task):
|
||||
"""
|
||||
处理主动思考任务完成
|
||||
|
||||
Args:
|
||||
task: 完成的异步任务对象
|
||||
|
||||
功能说明:
|
||||
- 处理任务正常完成或异常情况
|
||||
- 记录相应的日志信息
|
||||
- 区分取消和异常终止的情况
|
||||
"""
|
||||
try:
|
||||
if exception := task.exception():
|
||||
logger.error(f"{self.context.log_prefix} 主动思考循环异常: {exception}")
|
||||
else:
|
||||
logger.info(f"{self.context.log_prefix} 主动思考循环正常结束")
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{self.context.log_prefix} 主动思考循环被取消")
|
||||
|
||||
async def _proactive_thinking_loop(self):
|
||||
"""
|
||||
主动思考的主循环
|
||||
|
||||
功能说明:
|
||||
- 每15秒检查一次是否需要主动思考
|
||||
- 只在FOCUS模式下进行主动思考
|
||||
- 检查是否启用主动思考功能
|
||||
- 计算沉默时间并与动态间隔比较
|
||||
- 达到条件时执行主动思考并更新最后消息时间
|
||||
- 处理执行过程中的异常
|
||||
"""
|
||||
while self.context.running:
|
||||
await asyncio.sleep(15)
|
||||
|
||||
if self.context.loop_mode != ChatMode.FOCUS:
|
||||
continue
|
||||
|
||||
if not self._should_enable_proactive_thinking():
|
||||
continue
|
||||
|
||||
current_time = time.time()
|
||||
silence_duration = current_time - self.context.last_message_time
|
||||
|
||||
target_interval = self._get_dynamic_thinking_interval()
|
||||
|
||||
if silence_duration >= target_interval:
|
||||
try:
|
||||
await self._execute_proactive_thinking(silence_duration)
|
||||
self.context.last_message_time = current_time
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 主动思考执行出错: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def _should_enable_proactive_thinking(self) -> bool:
|
||||
"""
|
||||
检查是否应该启用主动思考
|
||||
|
||||
Returns:
|
||||
bool: 如果应该启用主动思考则返回True
|
||||
|
||||
功能说明:
|
||||
- 检查聊天流是否存在
|
||||
- 检查当前聊天是否在启用列表中(按平台和类型分别检查)
|
||||
- 根据聊天类型(群聊/私聊)和配置决定是否启用
|
||||
- 群聊需要proactive_thinking_in_group为True
|
||||
- 私聊需要proactive_thinking_in_private为True
|
||||
"""
|
||||
if not self.context.chat_stream:
|
||||
return False
|
||||
|
||||
is_group_chat = self.context.chat_stream.group_info is not None
|
||||
|
||||
# 检查基础开关
|
||||
if is_group_chat and not global_config.chat.proactive_thinking_in_group:
|
||||
return False
|
||||
if not is_group_chat and not global_config.chat.proactive_thinking_in_private:
|
||||
return False
|
||||
|
||||
# 获取当前聊天的完整标识 (platform:chat_id)
|
||||
stream_parts = self.context.stream_id.split(":")
|
||||
if len(stream_parts) >= 2:
|
||||
platform = stream_parts[0]
|
||||
chat_id = stream_parts[1]
|
||||
current_chat_identifier = f"{platform}:{chat_id}"
|
||||
else:
|
||||
# 如果无法解析,则使用原始stream_id
|
||||
current_chat_identifier = self.context.stream_id
|
||||
|
||||
# 检查是否在启用列表中
|
||||
if is_group_chat:
|
||||
# 群聊检查
|
||||
enable_list = getattr(global_config.chat, "proactive_thinking_enable_in_groups", [])
|
||||
if enable_list and current_chat_identifier not in enable_list:
|
||||
return False
|
||||
else:
|
||||
# 私聊检查
|
||||
enable_list = getattr(global_config.chat, "proactive_thinking_enable_in_private", [])
|
||||
if enable_list and current_chat_identifier not in enable_list:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _get_dynamic_thinking_interval(self) -> float:
|
||||
"""
|
||||
获取动态思考间隔
|
||||
|
||||
Returns:
|
||||
float: 计算得出的思考间隔时间(秒)
|
||||
|
||||
功能说明:
|
||||
- 使用3-sigma规则计算正态分布的思考间隔
|
||||
- 基于base_interval和delta_sigma配置计算
|
||||
- 处理特殊情况(为0或负数的配置)
|
||||
- 如果timing_utils不可用则使用固定间隔
|
||||
- 间隔范围被限制在1秒到86400秒(1天)之间
|
||||
"""
|
||||
try:
|
||||
from src.utils.timing_utils import get_normal_distributed_interval
|
||||
|
||||
base_interval = global_config.chat.proactive_thinking_interval
|
||||
delta_sigma = getattr(global_config.chat, "delta_sigma", 120)
|
||||
|
||||
if base_interval < 0:
|
||||
base_interval = abs(base_interval)
|
||||
if delta_sigma < 0:
|
||||
delta_sigma = abs(delta_sigma)
|
||||
|
||||
if base_interval == 0 and delta_sigma == 0:
|
||||
return 300
|
||||
elif base_interval == 0:
|
||||
sigma_percentage = delta_sigma / 1000
|
||||
return get_normal_distributed_interval(0, sigma_percentage, 1, 86400, use_3sigma_rule=True)
|
||||
elif delta_sigma == 0:
|
||||
return base_interval
|
||||
|
||||
sigma_percentage = delta_sigma / base_interval
|
||||
return get_normal_distributed_interval(base_interval, sigma_percentage, 1, 86400, use_3sigma_rule=True)
|
||||
|
||||
except ImportError:
|
||||
logger.warning(f"{self.context.log_prefix} timing_utils不可用,使用固定间隔")
|
||||
return max(300, abs(global_config.chat.proactive_thinking_interval))
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 动态间隔计算出错: {e},使用固定间隔")
|
||||
return max(300, abs(global_config.chat.proactive_thinking_interval))
|
||||
|
||||
def _format_duration(self, seconds: float) -> str:
|
||||
"""
|
||||
格式化持续时间为中文描述
|
||||
|
||||
Args:
|
||||
seconds: 持续时间(秒)
|
||||
|
||||
Returns:
|
||||
str: 格式化后的时间字符串,如"1小时30分45秒"
|
||||
|
||||
功能说明:
|
||||
- 将秒数转换为小时、分钟、秒的组合
|
||||
- 只显示非零的时间单位
|
||||
- 如果所有单位都为0则显示"0秒"
|
||||
- 用于主动思考日志的时间显示
|
||||
"""
|
||||
hours = int(seconds // 3600)
|
||||
minutes = int((seconds % 3600) // 60)
|
||||
secs = int(seconds % 60)
|
||||
|
||||
parts = []
|
||||
if hours > 0:
|
||||
parts.append(f"{hours}小时")
|
||||
if minutes > 0:
|
||||
parts.append(f"{minutes}分")
|
||||
if secs > 0 or not parts:
|
||||
parts.append(f"{secs}秒")
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
async def _execute_proactive_thinking(self, silence_duration: float):
|
||||
"""
|
||||
执行主动思考
|
||||
|
||||
Args:
|
||||
silence_duration: 沉默持续时间(秒)
|
||||
"""
|
||||
formatted_time = self._format_duration(silence_duration)
|
||||
logger.info(f"{self.context.log_prefix} 触发主动思考,已沉默{formatted_time}")
|
||||
|
||||
try:
|
||||
# 直接调用 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")
|
||||
|
||||
# 如果决策不是 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')}")
|
||||
# 在主动思考时,如果 target_message 为 None,则默认选取最新 message 作为 target_message
|
||||
if target_message is None and self.context.chat_stream and self.context.chat_stream.context:
|
||||
from src.chat.message_receive.message import MessageRecv
|
||||
latest_message = self.context.chat_stream.context.get_last_message()
|
||||
if isinstance(latest_message, MessageRecv):
|
||||
user_info = latest_message.message_info.user_info
|
||||
target_message = {
|
||||
"chat_info_platform": latest_message.message_info.platform,
|
||||
"user_platform": user_info.platform if user_info else None,
|
||||
"user_id": user_info.user_id if user_info else None,
|
||||
"processed_plain_text": latest_message.processed_plain_text,
|
||||
"is_mentioned": latest_message.is_mentioned,
|
||||
}
|
||||
|
||||
# 将决策结果交给 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())
|
||||
|
||||
async def trigger_insomnia_thinking(self, reason: str):
|
||||
"""
|
||||
由外部事件(如失眠)触发的一次性主动思考
|
||||
|
||||
Args:
|
||||
reason: 触发的原因 (e.g., "low_pressure", "random")
|
||||
"""
|
||||
logger.info(f"{self.context.log_prefix} 因“{reason}”触发失眠,开始深夜思考...")
|
||||
|
||||
# 1. 根据原因修改情绪
|
||||
try:
|
||||
from src.mood.mood_manager import mood_manager
|
||||
|
||||
mood_obj = mood_manager.get_mood_by_chat_id(self.context.stream_id)
|
||||
if reason == "low_pressure":
|
||||
mood_obj.mood_state = "精力过剩,毫无睡意"
|
||||
elif reason == "random":
|
||||
mood_obj.mood_state = "深夜emo,胡思乱想"
|
||||
mood_obj.last_change_time = time.time() # 更新时间戳以允许后续的情绪回归
|
||||
logger.info(f"{self.context.log_prefix} 因失眠,情绪状态被强制更新为: {mood_obj.mood_state}")
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 设置失眠情绪时出错: {e}")
|
||||
|
||||
# 2. 直接执行主动思考逻辑
|
||||
try:
|
||||
# 传入一个象征性的silence_duration,因为它在这里不重要
|
||||
await self._execute_proactive_thinking(silence_duration=1)
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 失眠思考执行出错: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
async def trigger_goodnight_thinking(self):
|
||||
"""
|
||||
在失眠状态结束后,触发一次准备睡觉的主动思考
|
||||
"""
|
||||
logger.info(f"{self.context.log_prefix} 失眠状态结束,准备睡觉,触发告别思考...")
|
||||
|
||||
# 1. 设置一个准备睡觉的特定情绪
|
||||
try:
|
||||
from src.mood.mood_manager import mood_manager
|
||||
|
||||
mood_obj = mood_manager.get_mood_by_chat_id(self.context.stream_id)
|
||||
mood_obj.mood_state = "有点困了,准备睡觉了"
|
||||
mood_obj.last_change_time = time.time()
|
||||
logger.info(f"{self.context.log_prefix} 情绪状态更新为: {mood_obj.mood_state}")
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 设置睡前情绪时出错: {e}")
|
||||
|
||||
# 2. 直接执行主动思考逻辑
|
||||
try:
|
||||
await self._execute_proactive_thinking(silence_duration=1)
|
||||
except Exception as e:
|
||||
logger.error(f"{self.context.log_prefix} 睡前告别思考执行出错: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -85,6 +85,7 @@ class ChatStream:
|
||||
self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息
|
||||
self.focus_energy = 1
|
||||
self.no_reply_consecutive = 0
|
||||
self.breaking_accumulated_interest = 0.0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""转换为字典格式"""
|
||||
@@ -97,6 +98,7 @@ class ChatStream:
|
||||
"last_active_time": self.last_active_time,
|
||||
"energy_value": self.energy_value,
|
||||
"sleep_pressure": self.sleep_pressure,
|
||||
"breaking_accumulated_interest": self.breaking_accumulated_interest,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -257,7 +259,7 @@ class ChatManager:
|
||||
"user_cardname": model_instance.user_cardname or "",
|
||||
}
|
||||
group_info_data = None
|
||||
if model_instance.group_id:
|
||||
if model_instance and getattr(model_instance, 'group_id', None):
|
||||
group_info_data = {
|
||||
"platform": model_instance.group_platform,
|
||||
"group_id": model_instance.group_id,
|
||||
@@ -403,7 +405,7 @@ class ChatManager:
|
||||
"user_cardname": model_instance.user_cardname or "",
|
||||
}
|
||||
group_info_data = None
|
||||
if model_instance.group_id:
|
||||
if model_instance and getattr(model_instance, 'group_id', None):
|
||||
group_info_data = {
|
||||
"platform": model_instance.group_platform,
|
||||
"group_id": model_instance.group_id,
|
||||
|
||||
Reference in New Issue
Block a user