This commit is contained in:
雅诺狐
2025-08-27 22:21:44 +08:00
14 changed files with 404 additions and 159 deletions

View File

@@ -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

View File

@@ -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}")

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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

View File

@@ -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

View File

@@ -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}")

View File

@@ -5,12 +5,12 @@
from sqlalchemy import Column, String, Float, Integer, Boolean, Text, Index, create_engine, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.orm import sessionmaker, Session, Mapped, mapped_column
from sqlalchemy.pool import QueuePool
import os
import datetime
import time
from typing import Iterator, Optional
from typing import Iterator, Optional, Any, Dict
from src.common.logger import get_logger
from contextlib import contextmanager
@@ -306,14 +306,14 @@ class Expression(Base):
"""表达风格模型"""
__tablename__ = 'expression'
id = Column(Integer, primary_key=True, autoincrement=True)
situation = Column(Text, nullable=False)
style = Column(Text, nullable=False)
count = Column(Float, nullable=False)
last_active_time = Column(Float, nullable=False)
chat_id = Column(get_string_field(64), nullable=False, index=True)
type = Column(Text, nullable=False)
create_date = Column(Float, nullable=True)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
situation: Mapped[str] = mapped_column(Text, nullable=False)
style: Mapped[str] = mapped_column(Text, nullable=False)
count: Mapped[float] = mapped_column(Float, nullable=False)
last_active_time: Mapped[float] = mapped_column(Float, nullable=False)
chat_id: Mapped[str] = mapped_column(get_string_field(64), nullable=False, index=True)
type: Mapped[str] = mapped_column(Text, nullable=False)
create_date: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
__table_args__ = (
Index('idx_expression_chat_id', 'chat_id'),
@@ -589,7 +589,7 @@ def initialize_database():
config = global_config.database
# 配置引擎参数
engine_kwargs = {
engine_kwargs: Dict[str, Any] = {
'echo': False, # 生产环境关闭SQL日志
'future': True,
}
@@ -642,7 +642,9 @@ def get_db_session() -> Iterator[Session]:
"""数据库会话上下文管理器 - 推荐使用这个而不是get_session()"""
session: Optional[Session] = None
try:
_, SessionLocal = initialize_database()
engine, SessionLocal = initialize_database()
if not SessionLocal:
raise RuntimeError("Database session not initialized")
session = SessionLocal()
yield session
#session.commit()

View File

@@ -93,6 +93,34 @@ class VectorDBBase(ABC):
"""
pass
@abstractmethod
def get(
self,
collection_name: str,
ids: Optional[List[str]] = None,
where: Optional[Dict[str, Any]] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
where_document: Optional[Dict[str, Any]] = None,
include: Optional[List[str]] = None,
) -> Dict[str, Any]:
"""
根据条件从集合中获取数据。
Args:
collection_name (str): 目标集合的名称。
ids (Optional[List[str]], optional): 要获取的条目的 ID 列表。Defaults to None.
where (Optional[Dict[str, Any]], optional): 基于元数据的过滤条件。Defaults to None.
limit (Optional[int], optional): 返回结果的数量限制。Defaults to None.
offset (Optional[int], optional): 返回结果的偏移量。Defaults to None.
where_document (Optional[Dict[str, Any]], optional): 基于文档内容的过滤条件。Defaults to None.
include (Optional[List[str]], optional): 指定返回的数据字段 (e.g., ["metadatas", "documents"])。Defaults to None.
Returns:
Dict[str, Any]: 获取到的数据。
"""
pass
@abstractmethod
def count(self, collection_name: str) -> int:
"""

View File

@@ -102,6 +102,32 @@ class ChromaDBImpl(VectorDBBase):
logger.error(f"查询集合 '{collection_name}' 失败: {e}")
return {}
def get(
self,
collection_name: str,
ids: Optional[List[str]] = None,
where: Optional[Dict[str, Any]] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
where_document: Optional[Dict[str, Any]] = None,
include: Optional[List[str]] = None,
) -> Dict[str, Any]:
"""根据条件从集合中获取数据"""
collection = self.get_or_create_collection(collection_name)
if collection:
try:
return collection.get(
ids=ids,
where=where,
limit=limit,
offset=offset,
where_document=where_document,
include=include,
)
except Exception as e:
logger.error(f"从集合 '{collection_name}' 获取数据失败: {e}")
return {}
def delete(
self,
collection_name: str,

View File

@@ -263,11 +263,20 @@ class NormalChatConfig(ValidatedConfigBase):
class ExpressionRule(ValidatedConfigBase):
"""表达学习规则"""
chat_stream_id: str = Field(..., description="聊天流ID空字符串表示全局")
use_expression: bool = Field(default=True, description="是否使用学到的表达")
learn_expression: bool = Field(default=True, description="是否学习表达")
learning_strength: float = Field(default=1.0, description="学习强度")
group: Optional[str] = Field(default=None, description="表达共享组")
class ExpressionConfig(ValidatedConfigBase):
"""表达配置类"""
expression_learning: list[list] = Field(default_factory=lambda: [], description="表达学习")
expression_groups: list[list[str]] = Field(default_factory=list, description="表达组")
rules: List[ExpressionRule] = Field(default_factory=list, description="表达学习规则")
def _parse_stream_config_to_chat_id(self, stream_config_str: str) -> Optional[str]:
"""
@@ -314,86 +323,23 @@ class ExpressionConfig(ValidatedConfigBase):
Returns:
tuple: (是否使用表达, 是否学习表达, 学习间隔)
"""
if not self.expression_learning:
# 如果没有配置,使用默认值:启用表达,启用学习,300秒间隔
return True, True, 300
if not self.rules:
# 如果没有配置,使用默认值:启用表达,启用学习,强度1.0
return True, True, 1.0
# 优先检查聊天流特定的配置
if chat_stream_id:
specific_config = self._get_stream_specific_config(chat_stream_id)
if specific_config is not None:
return specific_config
for rule in self.rules:
if rule.chat_stream_id and self._parse_stream_config_to_chat_id(rule.chat_stream_id) == chat_stream_id:
return rule.use_expression, rule.learn_expression, rule.learning_strength
# 检查全局配置(第一个元素为空字符串的配置)
global_config = self._get_global_config()
if global_config is not None:
return global_config
# 检查全局配置(chat_stream_id为空字符串的配置)
for rule in self.rules:
if rule.chat_stream_id == "":
return rule.use_expression, rule.learn_expression, rule.learning_strength
# 如果都没有匹配,返回默认值
return True, True, 300
def _get_stream_specific_config(self, chat_stream_id: str) -> Optional[tuple[bool, bool, float]]:
"""
获取特定聊天流的表达配置
Args:
chat_stream_id: 聊天流ID哈希值
Returns:
tuple: (是否使用表达, 是否学习表达, 学习间隔),如果没有配置则返回 None
"""
for config_item in self.expression_learning:
if not config_item or len(config_item) < 4:
continue
stream_config_str = config_item[0] # 例如 "qq:1026294844:group"
# 如果是空字符串,跳过(这是全局配置)
if stream_config_str == "":
continue
# 解析配置字符串并生成对应的 chat_id
config_chat_id = self._parse_stream_config_to_chat_id(stream_config_str)
if config_chat_id is None:
continue
# 比较生成的 chat_id
if config_chat_id != chat_stream_id:
continue
# 解析配置
try:
use_expression = config_item[1].lower() == "enable"
enable_learning = config_item[2].lower() == "enable"
learning_intensity = float(config_item[3])
return use_expression, enable_learning, learning_intensity
except (ValueError, IndexError):
continue
return None
def _get_global_config(self) -> Optional[tuple[bool, bool, float]]:
"""
获取全局表达配置
Returns:
tuple: (是否使用表达, 是否学习表达, 学习间隔),如果没有配置则返回 None
"""
for config_item in self.expression_learning:
if not config_item or len(config_item) < 4:
continue
# 检查是否为全局配置(第一个元素为空字符串)
if config_item[0] == "":
try:
use_expression = config_item[1].lower() == "enable"
enable_learning = config_item[2].lower() == "enable"
learning_intensity = float(config_item[3])
return use_expression, enable_learning, learning_intensity
except (ValueError, IndexError):
continue
return None
return True, True, 1.0
class ToolHistoryConfig(ValidatedConfigBase):
@@ -657,7 +603,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 +614,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):
"""月度计划系统配置类"""

View File

@@ -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)

View File

@@ -1,5 +1,5 @@
[inner]
version = "6.5.3"
version = "6.5.4"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请递增version的值
@@ -44,9 +44,7 @@ connection_timeout = 10 # 连接超时时间(秒)
# Master用户配置拥有最高权限无视所有权限节点
# 格式:[[platform, user_id], ...]
# 示例:[["qq", "123456"], ["telegram", "user789"]]
master_users = [
# ["qq", "123456789"], # 示例QQ平台的Master用户
]
master_users = []# ["qq", "123456789"], # 示例QQ平台的Master用户
[bot]
platform = "qq"
@@ -74,23 +72,37 @@ compress_identity = true # 是否压缩身份,压缩后会精简身份信息
[expression]
# 表达学习配置
expression_learning = [ # 表达学习配置列表,支持按聊天流配置
["", "enable", "enable", 1.0], # 全局配置使用表达启用学习学习强度1.0
["qq:1919810:group", "enable", "enable", 1.5], # 特定群聊配置使用表达启用学习学习强度1.5
["qq:114514:private", "enable", "disable", 0.5], # 特定私聊配置使用表达禁用学习学习强度0.5
# 格式说明:
# 第一位: chat_stream_id空字符串表示全局配置
# 第二位: 是否使用学到的表达 ("enable"/"disable")
# 第三位: 是否学习表达 ("enable"/"disable")
# 第四位: 学习强度(浮点数),影响学习频率,最短学习时间间隔 = 300/学习强度(秒)
# 学习强度越高,学习越频繁;学习强度越低,学习越少
]
# rules是一个列表每个元素都是一个学习规则
# chat_stream_id: 聊天流ID格式为 "platform:id:type",例如 "qq:123456:private"。空字符串""表示全局配置
# use_expression: 是否使用学到的表达 (true/false)
# learn_expression: 是否学习新的表达 (true/false)
# learning_strength: 学习强度(浮点数),影响学习频率
# group: 表达共享组的名称(字符串),相同组的聊天会共享学习到的表达方式
[[expression.rules]]
chat_stream_id = ""
use_expression = true
learn_expression = true
learning_strength = 1.0
expression_groups = [
["qq:1919810:private","qq:114514:private","qq:1111111:group"], # 在这里设置互通组相同组的chat_id会共享学习到的表达方式
# 格式:["qq:123456:private","qq:654321:group"]
# 注意如果为群聊则需要设置为group如果设置为私聊则需要设置为private
]
[[expression.rules]]
chat_stream_id = "qq:1919810:group"
use_expression = true
learn_expression = true
learning_strength = 1.5
[[expression.rules]]
chat_stream_id = "qq:114514:private"
group = "group_A"
use_expression = true
learn_expression = false
learning_strength = 0.5
[[expression.rules]]
chat_stream_id = "qq:1919810:private"
group = "group_A"
use_expression = true
learn_expression = true
learning_strength = 1.0
@@ -427,7 +439,7 @@ guidelines = """
"""
[wakeup_system]
enable = true #"是否启用唤醒度系统"
enable = false #"是否启用唤醒度系统"
wakeup_threshold = 15.0 #唤醒阈值,达到此值时会被唤醒"
private_message_increment = 3.0 #"私聊消息增加的唤醒度"
group_mention_increment = 2.0 #"群聊艾特增加的唤醒度"
@@ -436,6 +448,22 @@ decay_interval = 30.0 #"唤醒度衰减间隔(秒)"
angry_duration = 300.0 #"愤怒状态持续时间(秒)"
angry_prompt = "你被人吵醒了非常生气,说话带着怒气" # "被吵醒后的愤怒提示词"
# --- 失眠机制相关参数 ---
enable_insomnia_system = false # 是否启用失眠系统
# 触发“压力不足型失眠”的睡眠压力阈值
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
insomnia_duration_minutes = 30 # 单次失眠状态的持续时间(分钟)
[cross_context] # 跨群聊上下文共享配置
# 这是总开关,用于一键启用或禁用此功能
enable = false