feat(chat): implement sleep pressure and insomnia system
This commit introduces a new sleep pressure and insomnia system to simulate more realistic character behavior. Key features include: - **Sleep Pressure**: A new metric that accumulates with each action the bot takes and decreases during scheduled sleep times. - **Insomnia Mechanic**: When a sleep period begins, the system checks the current sleep pressure. Low pressure can lead to a higher chance of "insomnia," causing the bot to stay awake. There is also a small chance for random insomnia. - **Insomnia State**: During insomnia, the bot enters a special state for a configurable duration. It can trigger unique proactive thoughts related to being unable to sleep, and its mood is adjusted accordingly. - **Configuration**: All parameters, such as insomnia probability, duration, and pressure thresholds, are fully configurable.
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
|
||||
@@ -657,7 +657,7 @@ class PluginsConfig(ValidatedConfigBase):
|
||||
|
||||
|
||||
class WakeUpSystemConfig(ValidatedConfigBase):
|
||||
"""唤醒度系统配置类"""
|
||||
"""唤醒度与失眠系统配置类"""
|
||||
|
||||
enable: bool = Field(default=True, description="是否启用唤醒度系统")
|
||||
wakeup_threshold: float = Field(default=15.0, ge=1.0, description="唤醒阈值,达到此值时会被唤醒")
|
||||
@@ -668,6 +668,16 @@ class WakeUpSystemConfig(ValidatedConfigBase):
|
||||
angry_duration: float = Field(default=300.0, ge=10.0, description="愤怒状态持续时间(秒)")
|
||||
angry_prompt: str = Field(default="你被人吵醒了非常生气,说话带着怒气", description="被吵醒后的愤怒提示词")
|
||||
|
||||
# --- 失眠机制相关参数 ---
|
||||
enable_insomnia_system: bool = Field(default=True, description="是否启用失眠系统")
|
||||
insomnia_duration_minutes: int = Field(default=30, ge=1, description="单次失眠状态的持续时间(分钟)")
|
||||
sleep_pressure_threshold: float = Field(default=30.0, description="触发“压力不足型失眠”的睡眠压力阈值")
|
||||
deep_sleep_threshold: float = Field(default=80.0, description="进入“深度睡眠”的睡眠压力阈值")
|
||||
insomnia_chance_low_pressure: float = Field(default=0.6, ge=0.0, le=1.0, description="压力不足时的失眠基础概率")
|
||||
insomnia_chance_normal_pressure: float = Field(default=0.1, ge=0.0, le=1.0, description="压力正常时的失眠基础概率")
|
||||
sleep_pressure_increment: float = Field(default=1.5, ge=0.0, description="每次AI执行动作后,增加的睡眠压力值")
|
||||
sleep_pressure_decay_rate: float = Field(default=1.5, ge=0.0, description="睡眠时,每分钟衰减的睡眠压力值")
|
||||
|
||||
|
||||
class MonthlyPlanSystemConfig(ValidatedConfigBase):
|
||||
"""月度计划系统配置类"""
|
||||
|
||||
@@ -66,6 +66,11 @@ class ChatMood:
|
||||
self.last_change_time: float = 0
|
||||
|
||||
async def update_mood_by_message(self, message: MessageRecv, interested_rate: float):
|
||||
# 如果当前聊天处于失眠状态,则锁定情绪,不允许更新
|
||||
if self.chat_id in mood_manager.insomnia_chats:
|
||||
logger.debug(f"{self.log_prefix} 处于失眠状态,情绪已锁定,跳过更新。")
|
||||
return
|
||||
|
||||
self.regression_count = 0
|
||||
|
||||
during_last_time = message.message_info.time - self.last_change_time # type: ignore
|
||||
@@ -216,6 +221,7 @@ class MoodManager:
|
||||
self.mood_list: list[ChatMood] = []
|
||||
"""当前情绪状态"""
|
||||
self.task_started: bool = False
|
||||
self.insomnia_chats: set[str] = set() # 正在失眠的聊天ID列表
|
||||
|
||||
async def start(self):
|
||||
"""启动情绪回归后台任务"""
|
||||
@@ -262,6 +268,16 @@ class MoodManager:
|
||||
mood.mood_state = "感觉很平静"
|
||||
logger.info(f"{mood.log_prefix} 清除被吵醒的愤怒状态")
|
||||
|
||||
def start_insomnia(self, chat_id: str):
|
||||
"""开始一个聊天的失眠状态,锁定情绪更新"""
|
||||
logger.info(f"Chat [{chat_id}]进入失眠状态,情绪已锁定。")
|
||||
self.insomnia_chats.add(chat_id)
|
||||
|
||||
def stop_insomnia(self, chat_id: str):
|
||||
"""停止一个聊天的失眠状态,解锁情绪更新"""
|
||||
logger.info(f"Chat [{chat_id}]失眠状态结束,情绪已解锁。")
|
||||
self.insomnia_chats.discard(chat_id)
|
||||
|
||||
def get_angry_prompt_addition(self, chat_id: str) -> str:
|
||||
"""获取愤怒状态下的提示词补充"""
|
||||
mood = self.get_mood_by_chat_id(chat_id)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[inner]
|
||||
version = "6.5.3"
|
||||
version = "6.5.4"
|
||||
|
||||
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
|
||||
#如果你想要修改配置文件,请递增version的值
|
||||
@@ -427,7 +427,7 @@ guidelines = """
|
||||
"""
|
||||
|
||||
[wakeup_system]
|
||||
enable = true #"是否启用唤醒度系统"
|
||||
enable = false #"是否启用唤醒度系统"
|
||||
wakeup_threshold = 15.0 #唤醒阈值,达到此值时会被唤醒"
|
||||
private_message_increment = 3.0 #"私聊消息增加的唤醒度"
|
||||
group_mention_increment = 2.0 #"群聊艾特增加的唤醒度"
|
||||
@@ -436,6 +436,21 @@ decay_interval = 30.0 #"唤醒度衰减间隔(秒)"
|
||||
angry_duration = 300.0 #"愤怒状态持续时间(秒)"
|
||||
angry_prompt = "你被人吵醒了非常生气,说话带着怒气" # "被吵醒后的愤怒提示词"
|
||||
|
||||
# --- 失眠机制相关参数 ---
|
||||
enable_insomnia_system = true # 是否启用失眠系统
|
||||
# 触发“压力不足型失眠”的睡眠压力阈值
|
||||
sleep_pressure_threshold = 30.0
|
||||
# 进入“深度睡眠”的睡眠压力阈值
|
||||
deep_sleep_threshold = 80.0
|
||||
# 压力不足时的失眠基础概率 (0.0 to 1.0)
|
||||
insomnia_chance_low_pressure = 0.6
|
||||
# 压力正常时的失眠基础概率 (0.0 to 1.0)
|
||||
insomnia_chance_normal_pressure = 0.1
|
||||
# 每次AI执行动作后,增加的睡眠压力值
|
||||
sleep_pressure_increment = 1.5
|
||||
# 睡眠时,每分钟衰减的睡眠压力值
|
||||
sleep_pressure_decay_rate = 1.5
|
||||
|
||||
[cross_context] # 跨群聊上下文共享配置
|
||||
# 这是总开关,用于一键启用或禁用此功能
|
||||
enable = false
|
||||
|
||||
Reference in New Issue
Block a user