Merge branch 'master' of https://github.com/MoFox-Studio/MoFox_Bot
This commit is contained in:
@@ -131,6 +131,11 @@ class CycleProcessor:
|
||||
|
||||
if ENABLE_S4U:
|
||||
await stop_typing()
|
||||
|
||||
# 在一轮动作执行完毕后,增加睡眠压力
|
||||
if self.context.energy_manager and global_config.wakeup_system.enable_insomnia_system:
|
||||
if action_type not in ["no_reply", "no_action"]:
|
||||
self.context.energy_manager.increase_sleep_pressure()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
|
||||
logger = get_logger("hfc")
|
||||
|
||||
@@ -77,7 +78,7 @@ class EnergyManager:
|
||||
|
||||
async def _energy_loop(self):
|
||||
"""
|
||||
能量管理的主循环
|
||||
能量与睡眠压力管理的主循环
|
||||
|
||||
功能说明:
|
||||
- 每10秒执行一次能量更新
|
||||
@@ -92,24 +93,35 @@ class EnergyManager:
|
||||
if not self.context.chat_stream:
|
||||
continue
|
||||
|
||||
is_group_chat = self.context.chat_stream.group_info is not None
|
||||
if is_group_chat and global_config.chat.group_chat_mode != "auto":
|
||||
if global_config.chat.group_chat_mode == "focus":
|
||||
self.context.loop_mode = ChatMode.FOCUS
|
||||
self.context.energy_value = 35
|
||||
elif global_config.chat.group_chat_mode == "normal":
|
||||
self.context.loop_mode = ChatMode.NORMAL
|
||||
self.context.energy_value = 15
|
||||
continue
|
||||
# 判断当前是否为睡眠时间
|
||||
is_sleeping = schedule_manager.is_sleeping(self.context.wakeup_manager)
|
||||
|
||||
if self.context.loop_mode == ChatMode.NORMAL:
|
||||
self.context.energy_value -= 0.3
|
||||
self.context.energy_value = max(self.context.energy_value, 0.3)
|
||||
if self.context.loop_mode == ChatMode.FOCUS:
|
||||
self.context.energy_value -= 0.6
|
||||
self.context.energy_value = max(self.context.energy_value, 0.3)
|
||||
|
||||
self._log_energy_change("能量值衰减")
|
||||
if is_sleeping:
|
||||
# 睡眠中:减少睡眠压力
|
||||
decay_per_10s = global_config.wakeup_system.sleep_pressure_decay_rate / 6
|
||||
self.context.sleep_pressure -= decay_per_10s
|
||||
self.context.sleep_pressure = max(self.context.sleep_pressure, 0)
|
||||
self._log_sleep_pressure_change("睡眠压力释放")
|
||||
else:
|
||||
# 清醒时:处理能量衰减
|
||||
is_group_chat = self.context.chat_stream.group_info is not None
|
||||
if is_group_chat and global_config.chat.group_chat_mode != "auto":
|
||||
if global_config.chat.group_chat_mode == "focus":
|
||||
self.context.loop_mode = ChatMode.FOCUS
|
||||
self.context.energy_value = 35
|
||||
elif global_config.chat.group_chat_mode == "normal":
|
||||
self.context.loop_mode = ChatMode.NORMAL
|
||||
self.context.energy_value = 15
|
||||
continue
|
||||
|
||||
if self.context.loop_mode == ChatMode.NORMAL:
|
||||
self.context.energy_value -= 0.3
|
||||
self.context.energy_value = max(self.context.energy_value, 0.3)
|
||||
if self.context.loop_mode == ChatMode.FOCUS:
|
||||
self.context.energy_value -= 0.6
|
||||
self.context.energy_value = max(self.context.energy_value, 0.3)
|
||||
|
||||
self._log_energy_change("能量值衰减")
|
||||
|
||||
def _should_log_energy(self) -> bool:
|
||||
"""
|
||||
@@ -129,6 +141,15 @@ class EnergyManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def increase_sleep_pressure(self):
|
||||
"""
|
||||
在执行动作后增加睡眠压力
|
||||
"""
|
||||
increment = global_config.wakeup_system.sleep_pressure_increment
|
||||
self.context.sleep_pressure += increment
|
||||
self.context.sleep_pressure = min(self.context.sleep_pressure, 100.0) # 设置一个100的上限
|
||||
self._log_sleep_pressure_change("执行动作,睡眠压力累积")
|
||||
|
||||
def _log_energy_change(self, action: str, reason: str = ""):
|
||||
"""
|
||||
记录能量变化日志
|
||||
@@ -151,4 +172,14 @@ class EnergyManager:
|
||||
log_message = f"{self.context.log_prefix} {action},当前能量值:{self.context.energy_value:.1f}"
|
||||
if reason:
|
||||
log_message = f"{self.context.log_prefix} {action},{reason},当前能量值:{self.context.energy_value:.1f}"
|
||||
logger.debug(log_message)
|
||||
logger.debug(log_message)
|
||||
|
||||
def _log_sleep_pressure_change(self, action: str):
|
||||
"""
|
||||
记录睡眠压力变化日志
|
||||
"""
|
||||
# 使用与能量日志相同的频率控制
|
||||
if self._should_log_energy():
|
||||
logger.info(f"{self.context.log_prefix} {action},当前睡眠压力:{self.context.sleep_pressure:.1f}")
|
||||
else:
|
||||
logger.debug(f"{self.context.log_prefix} {action},当前睡眠压力:{self.context.sleep_pressure:.1f}")
|
||||
@@ -10,6 +10,7 @@ from src.chat.express.expression_learner import expression_learner_manager
|
||||
from src.plugin_system.base.component_types import ChatMode
|
||||
from src.schedule.schedule_manager import schedule_manager
|
||||
from src.plugin_system.apis import message_api
|
||||
from src.mood.mood_manager import mood_manager
|
||||
|
||||
from .hfc_context import HfcContext
|
||||
from .energy_manager import EnergyManager
|
||||
@@ -48,6 +49,7 @@ class HeartFChatting:
|
||||
|
||||
# 将唤醒度管理器设置到上下文中
|
||||
self.context.wakeup_manager = self.wakeup_manager
|
||||
self.context.energy_manager = self.energy_manager
|
||||
|
||||
self._loop_task: Optional[asyncio.Task] = None
|
||||
|
||||
@@ -196,8 +198,28 @@ class HeartFChatting:
|
||||
- NORMAL模式:检查进入FOCUS模式的条件,并通过normal_mode_handler处理消息
|
||||
"""
|
||||
is_sleeping = schedule_manager.is_sleeping(self.wakeup_manager)
|
||||
|
||||
# 核心修复:在睡眠模式下获取消息时,不过滤命令消息,以确保@消息能被接收
|
||||
|
||||
# --- 失眠状态管理 ---
|
||||
if self.context.is_in_insomnia and time.time() > self.context.insomnia_end_time:
|
||||
# 失眠状态结束
|
||||
self.context.is_in_insomnia = False
|
||||
await self.proactive_thinker.trigger_goodnight_thinking()
|
||||
|
||||
if is_sleeping and not self.context.was_sleeping:
|
||||
# 刚刚进入睡眠状态,进行一次入睡检查
|
||||
if self.wakeup_manager and self.wakeup_manager.check_for_insomnia():
|
||||
# 触发失眠
|
||||
self.context.is_in_insomnia = True
|
||||
duration = global_config.wakeup_system.insomnia_duration_minutes * 60
|
||||
self.context.insomnia_end_time = time.time() + duration
|
||||
|
||||
# 判断失眠原因并触发思考
|
||||
reason = "random"
|
||||
if self.context.sleep_pressure < global_config.wakeup_system.sleep_pressure_threshold:
|
||||
reason = "low_pressure"
|
||||
await self.proactive_thinker.trigger_insomnia_thinking(reason)
|
||||
|
||||
# 核心修复:在睡眠模式(包括失眠)下获取消息时,不过滤命令消息,以确保@消息能被接收
|
||||
filter_command_flag = not is_sleeping
|
||||
|
||||
recent_messages = message_api.get_messages_by_time_in_chat(
|
||||
@@ -220,8 +242,9 @@ class HeartFChatting:
|
||||
# 处理唤醒度逻辑
|
||||
if is_sleeping:
|
||||
self._handle_wakeup_messages(recent_messages)
|
||||
# 如果仍在睡眠状态,跳过正常处理但仍返回有新消息
|
||||
if schedule_manager.is_sleeping(self.wakeup_manager):
|
||||
# 如果处于失眠状态,则无视睡眠时间,继续处理消息
|
||||
# 否则,如果仍然在睡眠(没被吵醒),则跳过本轮处理
|
||||
if not self.context.is_in_insomnia and schedule_manager.is_sleeping(self.wakeup_manager):
|
||||
return has_new_messages
|
||||
|
||||
# 根据聊天模式处理新消息
|
||||
@@ -239,6 +262,9 @@ class HeartFChatting:
|
||||
self._check_focus_exit()
|
||||
elif self.context.loop_mode == ChatMode.NORMAL:
|
||||
self._check_focus_entry(0) # 传入0表示无新消息
|
||||
|
||||
# 更新上一帧的睡眠状态
|
||||
self.context.was_sleeping = is_sleeping
|
||||
|
||||
return has_new_messages
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from src.chat.chat_loop.hfc_utils import CycleDetail
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .wakeup_manager import WakeUpManager
|
||||
from .energy_manager import EnergyManager
|
||||
|
||||
class HfcContext:
|
||||
def __init__(self, chat_id: str):
|
||||
@@ -40,6 +41,12 @@ class HfcContext:
|
||||
|
||||
self.loop_mode = ChatMode.NORMAL
|
||||
self.energy_value = 5.0
|
||||
self.sleep_pressure = 0.0
|
||||
self.was_sleeping = False # 用于检测睡眠状态的切换
|
||||
|
||||
# 失眠状态
|
||||
self.is_in_insomnia: bool = False
|
||||
self.insomnia_end_time: float = 0.0
|
||||
|
||||
self.last_message_time = time.time()
|
||||
self.last_read_time = time.time() - 10
|
||||
@@ -53,4 +60,5 @@ class HfcContext:
|
||||
self.current_cycle_detail: Optional[CycleDetail] = None
|
||||
|
||||
# 唤醒度管理器 - 延迟初始化以避免循环导入
|
||||
self.wakeup_manager: Optional['WakeUpManager'] = None
|
||||
self.wakeup_manager: Optional['WakeUpManager'] = None
|
||||
self.energy_manager: Optional['EnergyManager'] = None
|
||||
@@ -279,3 +279,56 @@ class ProactiveThinker:
|
||||
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())
|
||||
|
||||
@@ -39,6 +39,13 @@ class WakeUpManager:
|
||||
self.angry_duration = wakeup_config.angry_duration
|
||||
self.enabled = wakeup_config.enable
|
||||
self.angry_prompt = wakeup_config.angry_prompt
|
||||
|
||||
# 失眠系统参数
|
||||
self.insomnia_enabled = wakeup_config.enable_insomnia_system
|
||||
self.sleep_pressure_threshold = wakeup_config.sleep_pressure_threshold
|
||||
self.deep_sleep_threshold = wakeup_config.deep_sleep_threshold
|
||||
self.insomnia_chance_low_pressure = wakeup_config.insomnia_chance_low_pressure
|
||||
self.insomnia_chance_normal_pressure = wakeup_config.insomnia_chance_normal_pressure
|
||||
|
||||
async def start(self):
|
||||
"""启动唤醒度管理器"""
|
||||
@@ -177,4 +184,36 @@ class WakeUpManager:
|
||||
"wakeup_threshold": self.wakeup_threshold,
|
||||
"is_angry": self.is_angry,
|
||||
"angry_remaining_time": max(0, self.angry_duration - (time.time() - self.angry_start_time)) if self.is_angry else 0
|
||||
}
|
||||
}
|
||||
|
||||
def check_for_insomnia(self) -> bool:
|
||||
"""
|
||||
在尝试入睡时检查是否会失眠
|
||||
|
||||
Returns:
|
||||
bool: 如果失眠则返回 True,否则返回 False
|
||||
"""
|
||||
if not self.insomnia_enabled:
|
||||
return False
|
||||
|
||||
import random
|
||||
|
||||
pressure = self.context.sleep_pressure
|
||||
|
||||
# 压力过高,深度睡眠,极难失眠
|
||||
if pressure > self.deep_sleep_threshold:
|
||||
return False
|
||||
|
||||
# 根据睡眠压力决定失眠概率
|
||||
if pressure < self.sleep_pressure_threshold:
|
||||
# 压力不足型失眠
|
||||
if random.random() < self.insomnia_chance_low_pressure:
|
||||
logger.info(f"{self.context.log_prefix} 睡眠压力不足 ({pressure:.1f}),触发失眠!")
|
||||
return True
|
||||
else:
|
||||
# 压力正常,随机失眠
|
||||
if random.random() < self.insomnia_chance_normal_pressure:
|
||||
logger.info(f"{self.context.log_prefix} 睡眠压力正常 ({pressure:.1f}),触发随机失眠!")
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -114,16 +114,27 @@ class ExpressionSelector:
|
||||
return None
|
||||
|
||||
def get_related_chat_ids(self, chat_id: str) -> List[str]:
|
||||
"""根据expression_groups配置,获取与当前chat_id相关的所有chat_id(包括自身)"""
|
||||
groups = global_config.expression.expression_groups
|
||||
for group in groups:
|
||||
group_chat_ids = []
|
||||
for stream_config_str in group:
|
||||
if chat_id_candidate := self._parse_stream_config_to_chat_id(stream_config_str):
|
||||
group_chat_ids.append(chat_id_candidate)
|
||||
if chat_id in group_chat_ids:
|
||||
return group_chat_ids
|
||||
return [chat_id]
|
||||
"""根据expression.rules配置,获取与当前chat_id相关的所有chat_id(包括自身)"""
|
||||
rules = global_config.expression.rules
|
||||
current_group = None
|
||||
|
||||
# 找到当前chat_id所在的组
|
||||
for rule in rules:
|
||||
if rule.chat_stream_id and self._parse_stream_config_to_chat_id(rule.chat_stream_id) == chat_id:
|
||||
current_group = rule.group
|
||||
break
|
||||
|
||||
if not current_group:
|
||||
return [chat_id]
|
||||
|
||||
# 找出同一组的所有chat_id
|
||||
related_chat_ids = []
|
||||
for rule in rules:
|
||||
if rule.group == current_group and rule.chat_stream_id:
|
||||
if chat_id_candidate := self._parse_stream_config_to_chat_id(rule.chat_stream_id):
|
||||
related_chat_ids.append(chat_id_candidate)
|
||||
|
||||
return related_chat_ids if related_chat_ids else [chat_id]
|
||||
|
||||
def get_random_expressions(
|
||||
self, chat_id: str, total_num: int, style_percentage: float, grammar_percentage: float
|
||||
|
||||
@@ -87,22 +87,38 @@ class VectorInstantMemoryV2:
|
||||
"""清理过期的聊天记录"""
|
||||
try:
|
||||
expire_time = time.time() - (self.retention_hours * 3600)
|
||||
|
||||
# 使用 where 条件来删除过期记录
|
||||
# 注意: ChromaDB 的 where 过滤器目前对 timestamp 的 $lt 操作支持可能有限
|
||||
# 一个更可靠的方法是 get() -> filter -> delete()
|
||||
# 但为了简化,我们先尝试直接 delete
|
||||
|
||||
# TODO: 确认 ChromaDB 对 $lt 在 metadata 上的支持。如果不支持,需要实现 get-filter-delete 模式。
|
||||
vector_db_service.delete(
|
||||
|
||||
# 采用 get -> filter -> delete 模式,避免复杂的 where 查询
|
||||
# 1. 获取当前 chat_id 的所有文档
|
||||
results = vector_db_service.get(
|
||||
collection_name=self.collection_name,
|
||||
where={
|
||||
"chat_id": self.chat_id,
|
||||
"timestamp": {"$lt": expire_time}
|
||||
}
|
||||
where={"chat_id": self.chat_id},
|
||||
include=["metadatas"]
|
||||
)
|
||||
logger.info(f"已为 chat_id '{self.chat_id}' 触发过期记录清理")
|
||||
|
||||
|
||||
if not results or not results.get('ids'):
|
||||
logger.info(f"chat_id '{self.chat_id}' 没有找到任何记录,无需清理")
|
||||
return
|
||||
|
||||
# 2. 在内存中过滤出过期的文档
|
||||
expired_ids = []
|
||||
metadatas = results.get('metadatas', [])
|
||||
ids = results.get('ids', [])
|
||||
|
||||
for i, metadata in enumerate(metadatas):
|
||||
if metadata and metadata.get('timestamp', float('inf')) < expire_time:
|
||||
expired_ids.append(ids[i])
|
||||
|
||||
# 3. 如果有过期文档,根据 ID 进行删除
|
||||
if expired_ids:
|
||||
vector_db_service.delete(
|
||||
collection_name=self.collection_name,
|
||||
ids=expired_ids
|
||||
)
|
||||
logger.info(f"为 chat_id '{self.chat_id}' 清理了 {len(expired_ids)} 条过期记录")
|
||||
else:
|
||||
logger.info(f"chat_id '{self.chat_id}' 没有需要清理的过期记录")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"清理过期记录失败: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user