From a2baec088e536554048271c9853fdba2728cde62 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Thu, 25 Sep 2025 17:14:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E5=AE=9E=E7=8E=B0=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=B6=88=E6=81=AF=E5=88=86=E5=8F=91=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=92=8C=E6=B6=88=E6=81=AF=E6=89=93=E6=96=AD=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加基于focus_energy的动态消息分发周期调整功能,根据聊天流兴趣度智能调整检查间隔 实现消息打断系统,允许高优先级消息打断正在处理的任务 重构ChatStream类,引入动态兴趣度计算系统,包括消息兴趣度统计和用户关系评分 扩展数据库模型和配置系统以支持新功能,增加相关配置项 更新版本号至0.11.0-alpha-1以反映重大功能更新 --- src/chat/message_manager/message_manager.py | 375 +++++++++++++++++- src/chat/message_receive/bot.py | 17 +- src/chat/message_receive/chat_stream.py | 188 ++++++++- src/chat/planner_actions/action_manager.py | 25 ++ .../data_models/message_manager_data_model.py | 65 ++- src/common/database/sqlalchemy_models.py | 10 +- src/common/logger.py | 8 + src/config/config.py | 2 +- src/config/official_configs.py | 25 ++ src/main.py | 1 + src/mood/mood_manager.py | 5 + .../built_in/affinity_flow_chatter/planner.py | 8 +- .../napcat_adapter_plugin/_manifest.json | 2 +- .../built_in/napcat_adapter_plugin/plugin.py | 2 +- template/bot_config_template.toml | 43 +- 15 files changed, 703 insertions(+), 73 deletions(-) diff --git a/src/chat/message_manager/message_manager.py b/src/chat/message_manager/message_manager.py index fd9ca7366..551e60782 100644 --- a/src/chat/message_manager/message_manager.py +++ b/src/chat/message_manager/message_manager.py @@ -4,6 +4,7 @@ """ import asyncio +import random import time import traceback from typing import Dict, Optional, Any, TYPE_CHECKING @@ -16,6 +17,7 @@ from src.chat.planner_actions.action_manager import ChatterActionManager from src.plugin_system.base.component_types import ChatMode from .sleep_manager.sleep_manager import SleepManager from .sleep_manager.wakeup_manager import WakeUpManager +from src.config.config import global_config if TYPE_CHECKING: from src.common.data_models.message_manager_data_model import StreamContext @@ -69,7 +71,7 @@ class MessageManager: # 停止管理器任务 if self.manager_task and not self.manager_task.done(): self.manager_task.cancel() - + await self.wakeup_manager.stop() logger.info("消息管理器已停止") @@ -88,14 +90,22 @@ class MessageManager: logger.debug(f"添加消息到聊天流 {stream_id}: {message.message_id}") async def _manager_loop(self): - """管理器主循环""" + """管理器主循环 - 独立聊天流分发周期版本""" while self.is_running: try: # 更新睡眠状态 await self.sleep_manager.update_sleep_state(self.wakeup_manager) - - await self._check_all_streams() - await asyncio.sleep(self.check_interval) + + # 执行独立分发周期的检查 + await self._check_streams_with_individual_intervals() + + # 计算下次检查时间(使用最小间隔或固定间隔) + if global_config.chat.dynamic_distribution_enabled: + next_check_delay = self._calculate_next_manager_delay() + else: + next_check_delay = self.check_interval + + await asyncio.sleep(next_check_delay) except asyncio.CancelledError: break except Exception as e: @@ -138,12 +148,14 @@ class MessageManager: unread_messages = context.get_unread_messages() if not unread_messages: return - + + # 检查是否需要打断现有处理 + await self._check_and_handle_interruption(context, stream_id) + # --- 睡眠状态检查 --- - from .sleep_manager.sleep_state import SleepState if self.sleep_manager.is_sleeping(): logger.info(f"Bot正在睡觉,检查聊天流 {stream_id} 是否有唤醒触发器。") - + was_woken_up = False is_private = context.is_private_chat() @@ -152,12 +164,12 @@ class MessageManager: if is_private or is_mentioned: if self.wakeup_manager.add_wakeup_value(is_private, is_mentioned): was_woken_up = True - break # 一旦被吵醒,就跳出循环并处理消息 - + break # 一旦被吵醒,就跳出循环并处理消息 + if not was_woken_up: logger.debug(f"聊天流 {stream_id} 中没有唤醒触发器,保持消息未读状态。") - return # 退出,不处理消息 - + return # 退出,不处理消息 + logger.info(f"Bot被聊天流 {stream_id} 中的消息吵醒,继续处理。") # --- 睡眠状态检查结束 --- @@ -252,6 +264,345 @@ class MessageManager: del self.stream_contexts[stream_id] logger.info(f"清理不活跃聊天流: {stream_id}") + async def _check_and_handle_interruption(self, context: StreamContext, stream_id: str): + """检查并处理消息打断""" + if not global_config.chat.interruption_enabled: + return + + # 检查是否有正在进行的处理任务 + if context.processing_task and not context.processing_task.done(): + # 计算打断概率 + interruption_probability = context.calculate_interruption_probability( + global_config.chat.interruption_max_limit, global_config.chat.interruption_probability_factor + ) + + # 根据概率决定是否打断 + if random.random() < interruption_probability: + logger.info(f"聊天流 {stream_id} 触发消息打断,打断概率: {interruption_probability:.2f}") + + # 取消现有任务 + context.processing_task.cancel() + try: + await context.processing_task + except asyncio.CancelledError: + pass + + # 增加打断计数并应用afc阈值降低 + context.increment_interruption_count() + context.apply_interruption_afc_reduction(global_config.chat.interruption_afc_reduction) + logger.info( + f"聊天流 {stream_id} 已打断,当前打断次数: {context.interruption_count}/{global_config.chat.interruption_max_limit}, afc阈值调整: {context.get_afc_threshold_adjustment()}" + ) + else: + logger.debug(f"聊天流 {stream_id} 未触发打断,打断概率: {interruption_probability:.2f}") + + def _calculate_dynamic_distribution_interval(self) -> float: + """根据所有活跃聊天流的focus_energy动态计算分发周期""" + if not global_config.chat.dynamic_distribution_enabled: + return self.check_interval # 使用固定间隔 + + if not self.stream_contexts: + return self.check_interval # 默认间隔 + + # 计算活跃流的平均focus_energy + active_streams = [ctx for ctx in self.stream_contexts.values() if ctx.is_active] + if not active_streams: + return self.check_interval + + total_focus_energy = 0.0 + max_focus_energy = 0.0 + stream_count = 0 + + for context in active_streams: + if hasattr(context, 'chat_stream') and context.chat_stream: + focus_energy = context.chat_stream.focus_energy + total_focus_energy += focus_energy + max_focus_energy = max(max_focus_energy, focus_energy) + stream_count += 1 + + if stream_count == 0: + return self.check_interval + + avg_focus_energy = total_focus_energy / stream_count + + # 使用配置参数 + base_interval = global_config.chat.dynamic_distribution_base_interval + min_interval = global_config.chat.dynamic_distribution_min_interval + max_interval = global_config.chat.dynamic_distribution_max_interval + jitter_factor = global_config.chat.dynamic_distribution_jitter_factor + + # 根据平均兴趣度调整间隔 + # 高兴趣度 -> 更频繁检查 (间隔更短) + # 低兴趣度 -> 较少检查 (间隔更长) + if avg_focus_energy >= 0.7: + # 高兴趣度:1-5秒 + interval = base_interval * (1.0 - (avg_focus_energy - 0.7) * 2.0) + elif avg_focus_energy >= 0.4: + # 中等兴趣度:5-15秒 + interval = base_interval * (1.0 + (avg_focus_energy - 0.4) * 3.33) + else: + # 低兴趣度:15-30秒 + interval = base_interval * (3.0 + (0.4 - avg_focus_energy) * 5.0) + + # 添加随机扰动避免同步 + import random + jitter = random.uniform(1.0 - jitter_factor, 1.0 + jitter_factor) + final_interval = interval * jitter + + # 限制在合理范围内 + final_interval = max(min_interval, min(max_interval, final_interval)) + + logger.debug( + f"动态分发周期: {final_interval:.2f}s | " + f"平均兴趣度: {avg_focus_energy:.3f} | " + f"活跃流数: {stream_count}" + ) + + return final_interval + + def _calculate_stream_distribution_interval(self, context: StreamContext) -> float: + """计算单个聊天流的分发周期 - 基于阈值感知的focus_energy""" + if not global_config.chat.dynamic_distribution_enabled: + return self.check_interval # 使用固定间隔 + + # 获取该流的focus_energy(新的阈值感知版本) + focus_energy = 0.5 # 默认值 + avg_message_interest = 0.5 # 默认平均兴趣度 + + if hasattr(context, 'chat_stream') and context.chat_stream: + focus_energy = context.chat_stream.focus_energy + # 获取平均消息兴趣度用于更精确的计算 + if context.chat_stream.message_count > 0: + avg_message_interest = context.chat_stream.message_interest_total / context.chat_stream.message_count + + # 获取AFC阈值用于参考,添加None值检查 + reply_threshold = getattr(global_config.affinity_flow, 'reply_action_interest_threshold', 0.4) + non_reply_threshold = getattr(global_config.affinity_flow, 'non_reply_action_interest_threshold', 0.2) + high_match_threshold = getattr(global_config.affinity_flow, 'high_match_interest_threshold', 0.8) + + # 使用配置参数 + base_interval = global_config.chat.dynamic_distribution_base_interval + min_interval = global_config.chat.dynamic_distribution_min_interval + max_interval = global_config.chat.dynamic_distribution_max_interval + jitter_factor = global_config.chat.dynamic_distribution_jitter_factor + + # 基于阈值感知的智能分发周期计算 + if avg_message_interest >= high_match_threshold: + # 超高兴趣度:极快响应 (1-2秒) + interval_multiplier = 0.3 + (focus_energy - 0.7) * 2.0 + elif avg_message_interest >= reply_threshold: + # 高兴趣度:快速响应 (2-6秒) + gap_from_reply = (avg_message_interest - reply_threshold) / (high_match_threshold - reply_threshold) + interval_multiplier = 0.6 + gap_from_reply * 0.4 + elif avg_message_interest >= non_reply_threshold: + # 中等兴趣度:正常响应 (6-15秒) + gap_from_non_reply = (avg_message_interest - non_reply_threshold) / (reply_threshold - non_reply_threshold) + interval_multiplier = 1.2 + gap_from_non_reply * 1.8 + else: + # 低兴趣度:缓慢响应 (15-30秒) + gap_ratio = max(0, avg_message_interest / non_reply_threshold) + interval_multiplier = 3.0 + (1.0 - gap_ratio) * 3.0 + + # 应用focus_energy微调 + energy_adjustment = 1.0 + (focus_energy - 0.5) * 0.5 + interval = base_interval * interval_multiplier * energy_adjustment + + # 添加随机扰动避免同步 + import random + jitter = random.uniform(1.0 - jitter_factor, 1.0 + jitter_factor) + final_interval = interval * jitter + + # 限制在合理范围内 + final_interval = max(min_interval, min(max_interval, final_interval)) + + # 根据兴趣度级别调整日志级别 + if avg_message_interest >= high_match_threshold: + log_level = "info" + elif avg_message_interest >= reply_threshold: + log_level = "info" + else: + log_level = "debug" + + log_msg = ( + f"流 {context.stream_id} 分发周期: {final_interval:.2f}s | " + f"focus_energy: {focus_energy:.3f} | " + f"avg_interest: {avg_message_interest:.3f} | " + f"阈值参考: {non_reply_threshold:.2f}/{reply_threshold:.2f}/{high_match_threshold:.2f}" + ) + + if log_level == "info": + logger.info(log_msg) + else: + logger.debug(log_msg) + + return final_interval + + def _calculate_next_manager_delay(self) -> float: + """计算管理器下次检查的延迟时间""" + current_time = time.time() + min_delay = float('inf') + + # 找到最近需要检查的流 + for context in self.stream_contexts.values(): + if not context.is_active: + continue + + time_until_check = context.next_check_time - current_time + if time_until_check > 0: + min_delay = min(min_delay, time_until_check) + else: + min_delay = 0.1 # 立即检查 + break + + # 如果没有活跃流,使用默认间隔 + if min_delay == float('inf'): + return self.check_interval + + # 确保最小延迟 + return max(0.1, min(min_delay, self.check_interval)) + + async def _check_streams_with_individual_intervals(self): + """检查所有达到检查时间的聊天流""" + current_time = time.time() + processed_streams = 0 + + for stream_id, context in self.stream_contexts.items(): + if not context.is_active: + continue + + # 检查是否达到检查时间 + if current_time >= context.next_check_time: + # 更新检查时间 + context.last_check_time = current_time + + # 计算下次检查时间和分发周期 + if global_config.chat.dynamic_distribution_enabled: + context.distribution_interval = self._calculate_stream_distribution_interval(context) + else: + context.distribution_interval = self.check_interval + + # 设置下次检查时间 + context.next_check_time = current_time + context.distribution_interval + + # 检查未读消息 + unread_messages = context.get_unread_messages() + if unread_messages: + processed_streams += 1 + self.stats.total_unread_messages = len(unread_messages) + + # 如果没有处理任务,创建一个 + if not context.processing_task or context.processing_task.done(): + focus_energy = context.chat_stream.focus_energy if hasattr(context, 'chat_stream') and context.chat_stream else 0.5 + + # 根据优先级记录日志 + if focus_energy >= 0.7: + logger.info( + f"高优先级流 {stream_id} 开始处理 | " + f"focus_energy: {focus_energy:.3f} | " + f"分发周期: {context.distribution_interval:.2f}s | " + f"未读消息: {len(unread_messages)}" + ) + else: + logger.debug( + f"流 {stream_id} 开始处理 | " + f"focus_energy: {focus_energy:.3f} | " + f"分发周期: {context.distribution_interval:.2f}s" + ) + + context.processing_task = asyncio.create_task(self._process_stream_messages(stream_id)) + + # 更新活跃流计数 + active_count = sum(1 for ctx in self.stream_contexts.values() if ctx.is_active) + self.stats.active_streams = active_count + + if processed_streams > 0: + logger.debug( + f"本次循环处理了 {processed_streams} 个流 | " + f"活跃流总数: {active_count}" + ) + + async def _check_all_streams_with_priority(self): + """按优先级检查所有聊天流,高focus_energy的流优先处理""" + if not self.stream_contexts: + return + + # 获取活跃的聊天流并按focus_energy排序 + active_streams = [] + for stream_id, context in self.stream_contexts.items(): + if not context.is_active: + continue + + # 获取focus_energy,如果不存在则使用默认值 + focus_energy = 0.5 + if hasattr(context, 'chat_stream') and context.chat_stream: + focus_energy = context.chat_stream.focus_energy + + # 计算流优先级分数 + priority_score = self._calculate_stream_priority(context, focus_energy) + active_streams.append((priority_score, stream_id, context)) + + # 按优先级降序排序 + active_streams.sort(reverse=True, key=lambda x: x[0]) + + # 处理排序后的流 + active_stream_count = 0 + total_unread = 0 + + for priority_score, stream_id, context in active_streams: + active_stream_count += 1 + + # 检查是否有未读消息 + unread_messages = context.get_unread_messages() + if unread_messages: + total_unread += len(unread_messages) + + # 如果没有处理任务,创建一个 + if not context.processing_task or context.processing_task.done(): + context.processing_task = asyncio.create_task(self._process_stream_messages(stream_id)) + + # 高优先级流的额外日志 + if priority_score > 0.7: + logger.info( + f"高优先级流 {stream_id} 开始处理 | " + f"优先级: {priority_score:.3f} | " + f"未读消息: {len(unread_messages)}" + ) + + # 更新统计 + self.stats.active_streams = active_stream_count + self.stats.total_unread_messages = total_unread + + def _calculate_stream_priority(self, context: StreamContext, focus_energy: float) -> float: + """计算聊天流的优先级分数""" + # 基础优先级:focus_energy + base_priority = focus_energy + + # 未读消息数量加权 + unread_count = len(context.get_unread_messages()) + message_count_bonus = min(unread_count * 0.1, 0.3) # 最多30%加成 + + # 时间加权:最近活跃的流优先级更高 + current_time = time.time() + time_since_active = current_time - context.last_check_time + time_penalty = max(0, 1.0 - time_since_active / 3600.0) # 1小时内无惩罚 + + # 连续无回复惩罚 + if hasattr(context, 'chat_stream') and context.chat_stream: + consecutive_no_reply = context.chat_stream.consecutive_no_reply + no_reply_penalty = max(0, 1.0 - consecutive_no_reply * 0.05) # 每次无回复降低5% + else: + no_reply_penalty = 1.0 + + # 综合优先级计算 + final_priority = ( + base_priority * 0.6 + # 基础兴趣度权重60% + message_count_bonus * 0.2 + # 消息数量权重20% + time_penalty * 0.1 + # 时间权重10% + no_reply_penalty * 0.1 # 回复状态权重10% + ) + + return max(0.0, min(1.0, final_priority)) + def _clear_all_unread_messages(self, context: StreamContext): """清除指定上下文中的所有未读消息,防止意外情况导致消息一直未读""" unread_messages = context.get_unread_messages() diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index 04e7a8842..92a44b443 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -360,19 +360,7 @@ class ChatBot: return async def message_process(self, message_data: Dict[str, Any]) -> None: - """处理转化后的统一格式消息 - 这个函数本质是预处理一些数据,根据配置信息和消息内容,预处理消息,并分发到合适的消息处理器中 - heart_flow模式:使用思维流系统进行回复 - - 包含思维流状态管理 - - 在回复前进行观察和状态更新 - - 回复后更新思维流状态 - - 消息过滤 - - 记忆激活 - - 意愿计算 - - 消息生成和发送 - - 表情包处理 - - 性能计时 - """ + """处理转化后的统一格式消息""" try: # 首先处理可能的切片消息重组 from src.utils.message_chunker import reassembler @@ -464,7 +452,8 @@ class ChatBot: result = await event_manager.trigger_event(EventType.ON_MESSAGE, permission_group="SYSTEM", message=message) if not result.all_continue_process(): raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于消息到达时取消了消息处理") - + + # TODO:暂不可用 # 确认从接口发来的message是否有自定义的prompt模板信息 if message.message_info.template_info and not message.message_info.template_info.template_default: template_group_name: Optional[str] = message.message_info.template_info.template_name # type: ignore diff --git a/src/chat/message_receive/chat_stream.py b/src/chat/message_receive/chat_stream.py index 63ec0346e..5eff1f493 100644 --- a/src/chat/message_receive/chat_stream.py +++ b/src/chat/message_receive/chat_stream.py @@ -83,10 +83,18 @@ class ChatStream: self.sleep_pressure = data.get("sleep_pressure", 0.0) if data else 0.0 self.saved = False self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息 - # 从配置文件中读取focus_value,如果没有则使用默认值1.0 - self.focus_energy = ( - data.get("focus_energy", global_config.chat.focus_value) if data else global_config.chat.focus_value - ) + + # 动态兴趣度系统 - 重构后的focus_energy + self.base_interest_energy = data.get("base_interest_energy", 0.5) if data else 0.5 + self.message_interest_total = data.get("message_interest_total", 0.0) if data else 0.0 + self.message_count = data.get("message_count", 0) if data else 0 + self.action_count = data.get("action_count", 0) if data else 0 + self.reply_count = data.get("reply_count", 0) if data else 0 + self.last_interaction_time = data.get("last_interaction_time", time.time()) if data else time.time() + self.consecutive_no_reply = data.get("consecutive_no_reply", 0) if data else 0 + + # 计算动态focus_energy + self.focus_energy = self._calculate_dynamic_focus_energy() self.no_reply_consecutive = 0 self.breaking_accumulated_interest = 0.0 @@ -103,6 +111,14 @@ class ChatStream: "sleep_pressure": self.sleep_pressure, "focus_energy": self.focus_energy, "breaking_accumulated_interest": self.breaking_accumulated_interest, + # 新增动态兴趣度系统字段 + "base_interest_energy": self.base_interest_energy, + "message_interest_total": self.message_interest_total, + "message_count": self.message_count, + "action_count": self.action_count, + "reply_count": self.reply_count, + "last_interaction_time": self.last_interaction_time, + "consecutive_no_reply": self.consecutive_no_reply, } @classmethod @@ -128,6 +144,148 @@ class ChatStream: """设置聊天消息上下文""" self.context = ChatMessageContext(message) + def _calculate_dynamic_focus_energy(self) -> float: + """动态计算聊天流的总体兴趣度""" + try: + # 基础分:平均消息兴趣度 + avg_message_interest = self.message_interest_total / max(self.message_count, 1) + + # 动作参与度:动作执行率 + action_rate = self.action_count / max(self.message_count, 1) + + # 回复活跃度:回复率 + reply_rate = self.reply_count / max(self.message_count, 1) + + # 获取用户关系分(对于私聊,群聊无效) + relationship_factor = self._get_user_relationship_score() + + # 时间衰减因子:最近活跃度 + current_time = time.time() + if not self.last_interaction_time: + self.last_interaction_time = current_time + time_since_interaction = current_time - self.last_interaction_time + time_decay = max(0.3, 1.0 - min(time_since_interaction / (7 * 24 * 3600), 0.7)) # 7天衰减 + + # 连续无回复惩罚 + no_reply_penalty = max(0.1, 1.0 - self.consecutive_no_reply * 0.1) + + # 获取AFC系统阈值,添加None值检查 + reply_threshold = getattr(global_config.affinity_flow, 'reply_action_interest_threshold', 0.4) + non_reply_threshold = getattr(global_config.affinity_flow, 'non_reply_action_interest_threshold', 0.2) + high_match_threshold = getattr(global_config.affinity_flow, 'high_match_interest_threshold', 0.8) + + # 计算与不同阈值的差距比例 + reply_gap_ratio = max(0, (avg_message_interest - reply_threshold) / max(0.1, (1.0 - reply_threshold))) + non_reply_gap_ratio = max(0, (avg_message_interest - non_reply_threshold) / max(0.1, (1.0 - non_reply_threshold))) + high_match_gap_ratio = max(0, (avg_message_interest - high_match_threshold) / max(0.1, (1.0 - high_match_threshold))) + + # 基于阈值差距比例的基础分计算 + threshold_based_score = ( + reply_gap_ratio * 0.6 + # 回复阈值差距权重60% + non_reply_gap_ratio * 0.2 + # 非回复阈值差距权重20% + high_match_gap_ratio * 0.2 # 高匹配阈值差距权重20% + ) + + # 动态权重调整:根据平均兴趣度水平调整权重分配 + if avg_message_interest >= high_match_threshold: + # 高兴趣度:更注重阈值差距 + threshold_weight = 0.7 + activity_weight = 0.2 + relationship_weight = 0.1 + elif avg_message_interest >= reply_threshold: + # 中等兴趣度:平衡权重 + threshold_weight = 0.5 + activity_weight = 0.3 + relationship_weight = 0.2 + else: + # 低兴趣度:更注重活跃度提升 + threshold_weight = 0.3 + activity_weight = 0.5 + relationship_weight = 0.2 + + # 计算活跃度得分 + activity_score = (action_rate * 0.6 + reply_rate * 0.4) + + # 综合计算:基于阈值的动态加权 + focus_energy = ( + threshold_based_score * threshold_weight + # 阈值差距基础分 + activity_score * activity_weight + # 活跃度得分 + relationship_factor * relationship_weight + # 关系得分 + self.base_interest_energy * 0.05 # 基础兴趣微调 + ) * time_decay * no_reply_penalty + + # 确保在合理范围内 + focus_energy = max(0.1, min(1.0, focus_energy)) + + # 应用非线性变换增强区分度 + if focus_energy >= 0.7: + # 高兴趣度区域:指数增强,更敏感 + focus_energy = 0.7 + (focus_energy - 0.7) ** 0.8 + elif focus_energy >= 0.4: + # 中等兴趣度区域:线性保持 + pass + else: + # 低兴趣度区域:对数压缩,减少区分度 + focus_energy = 0.4 * (focus_energy / 0.4) ** 1.2 + + return max(0.1, min(1.0, focus_energy)) + + except Exception as e: + logger.error(f"计算动态focus_energy失败: {e}") + return self.base_interest_energy + + def _get_user_relationship_score(self) -> float: + """从外部系统获取用户关系分""" + try: + # 尝试从兴趣评分系统获取用户关系分 + from src.plugins.built_in.affinity_flow_chatter.interest_scoring import ( + chatter_interest_scoring_system, + ) + + if self.user_info and hasattr(self.user_info, 'user_id'): + return chatter_interest_scoring_system.get_user_relationship(str(self.user_info.user_id)) + except Exception: + pass + + # 默认基础分 + return 0.3 + + def add_message_interest(self, interest_score: float): + """添加消息兴趣值并更新focus_energy""" + self.message_interest_total += interest_score + self.message_count += 1 + self.last_interaction_time = time.time() + self.focus_energy = self._calculate_dynamic_focus_energy() + + def record_action(self, is_reply: bool = False): + """记录动作执行""" + self.action_count += 1 + if is_reply: + self.reply_count += 1 + self.consecutive_no_reply = max(0, self.consecutive_no_reply - 1) + self.last_interaction_time = time.time() + self.focus_energy = self._calculate_dynamic_focus_energy() + + def record_no_reply(self): + """记录无回复动作""" + self.consecutive_no_reply += 1 + self.last_interaction_time = time.time() + self.focus_energy = self._calculate_dynamic_focus_energy() + + def get_adjusted_focus_energy(self) -> float: + """获取应用了afc调整的focus_energy""" + try: + from src.chat.message_manager.message_manager import message_manager + if self.stream_id in message_manager.stream_contexts: + context = message_manager.stream_contexts[self.stream_id] + afc_adjustment = context.get_afc_threshold_adjustment() + # 对动态计算的focus_energy应用AFC调整 + adjusted_energy = max(0.0, self.focus_energy - afc_adjustment) + return adjusted_energy + except Exception: + pass + return self.focus_energy + class ChatManager: """聊天管理器,管理所有聊天流""" @@ -364,7 +522,16 @@ class ChatManager: "group_name": group_info_d["group_name"] if group_info_d else "", "energy_value": s_data_dict.get("energy_value", 5.0), "sleep_pressure": s_data_dict.get("sleep_pressure", 0.0), - "focus_energy": s_data_dict.get("focus_energy", global_config.chat.focus_value), + "focus_energy": s_data_dict.get("focus_energy", 0.5), + # 新增动态兴趣度系统字段 + "base_interest_energy": s_data_dict.get("base_interest_energy", 0.5), + "message_interest_total": s_data_dict.get("message_interest_total", 0.0), + "message_count": s_data_dict.get("message_count", 0), + "action_count": s_data_dict.get("action_count", 0), + "reply_count": s_data_dict.get("reply_count", 0), + "last_interaction_time": s_data_dict.get("last_interaction_time", time.time()), + "relationship_score": s_data_dict.get("relationship_score", 0.3), + "consecutive_no_reply": s_data_dict.get("consecutive_no_reply", 0), } # 根据数据库类型选择插入语句 @@ -426,7 +593,16 @@ class ChatManager: "last_active_time": model_instance.last_active_time, "energy_value": model_instance.energy_value, "sleep_pressure": model_instance.sleep_pressure, - "focus_energy": getattr(model_instance, "focus_energy", global_config.chat.focus_value), + "focus_energy": getattr(model_instance, "focus_energy", 0.5), + # 新增动态兴趣度系统字段 - 使用getattr提供默认值 + "base_interest_energy": getattr(model_instance, "base_interest_energy", 0.5), + "message_interest_total": getattr(model_instance, "message_interest_total", 0.0), + "message_count": getattr(model_instance, "message_count", 0), + "action_count": getattr(model_instance, "action_count", 0), + "reply_count": getattr(model_instance, "reply_count", 0), + "last_interaction_time": getattr(model_instance, "last_interaction_time", time.time()), + "relationship_score": getattr(model_instance, "relationship_score", 0.3), + "consecutive_no_reply": getattr(model_instance, "consecutive_no_reply", 0), } loaded_streams_data.append(data_for_from_dict) session.commit() diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index e439f5299..f60146287 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -15,6 +15,7 @@ from src.plugin_system.base.component_types import ComponentType, ActionInfo from src.plugin_system.base.base_action import BaseAction from src.plugin_system.apis import generator_api, database_api, send_api, message_api + logger = get_logger("action_manager") @@ -161,6 +162,7 @@ class ChatterActionManager: Returns: 执行结果 """ + from src.chat.message_manager.message_manager import message_manager try: logger.debug(f"🎯 [ActionManager] execute_action接收到 target_message: {target_message}") # 通过chat_id获取chat_stream @@ -207,6 +209,11 @@ class ChatterActionManager: thinking_id, target_message, ) + + # 如果动作执行成功且不是no_action,重置打断计数 + if success: + await self._reset_interruption_count_after_action(chat_stream.stream_id) + return { "action_type": action_name, "success": success, @@ -244,6 +251,10 @@ class ChatterActionManager: thinking_id, [], # actions ) + + # 回复成功,重置打断计数 + await self._reset_interruption_count_after_action(chat_stream.stream_id) + return {"action_type": "reply", "success": True, "reply_text": reply_text, "loop_info": loop_info} except Exception as e: @@ -257,6 +268,20 @@ class ChatterActionManager: "error": str(e), } + async def _reset_interruption_count_after_action(self, stream_id: str): + """在动作执行成功后重置打断计数""" + from src.chat.message_manager.message_manager import message_manager + try: + if stream_id in message_manager.stream_contexts: + context = message_manager.stream_contexts[stream_id] + if context.interruption_count > 0: + old_count = context.interruption_count + old_afc_adjustment = context.get_afc_threshold_adjustment() + context.reset_interruption_count() + logger.debug(f"动作执行成功,重置聊天流 {stream_id} 的打断计数: {old_count} -> 0, afc调整: {old_afc_adjustment} -> 0") + except Exception as e: + logger.warning(f"重置打断计数时出错: {e}") + async def _handle_action( self, chat_stream, action, reasoning, action_data, cycle_timers, thinking_id, action_message ) -> tuple[bool, str, str]: diff --git a/src/common/data_models/message_manager_data_model.py b/src/common/data_models/message_manager_data_model.py index 5ba25c5ec..70da3591c 100644 --- a/src/common/data_models/message_manager_data_model.py +++ b/src/common/data_models/message_manager_data_model.py @@ -11,10 +11,13 @@ from typing import List, Optional, TYPE_CHECKING from . import BaseDataModel from src.plugin_system.base.component_types import ChatMode, ChatType +from src.common.logger import get_logger if TYPE_CHECKING: from .database_data_model import DatabaseMessages +logger = get_logger("stream_context") + class MessageStatus(Enum): """消息状态枚举""" @@ -36,6 +39,13 @@ class StreamContext(BaseDataModel): last_check_time: float = field(default_factory=time.time) is_active: bool = True processing_task: Optional[asyncio.Task] = None + interruption_count: int = 0 # 打断计数器 + last_interruption_time: float = 0.0 # 上次打断时间 + afc_threshold_adjustment: float = 0.0 # afc阈值调整量 + + # 独立分发周期字段 + next_check_time: float = field(default_factory=time.time) # 下次检查时间 + distribution_interval: float = 5.0 # 当前分发周期(秒) def add_message(self, message: "DatabaseMessages"): """添加消息到上下文""" @@ -50,9 +60,9 @@ class StreamContext(BaseDataModel): # 只有在第一次添加消息时才检测聊天类型,避免后续消息改变类型 if len(self.unread_messages) == 1: # 只有这条消息 # 如果消息包含群组信息,则为群聊 - if hasattr(message, 'chat_info_group_id') and message.chat_info_group_id: + if hasattr(message, "chat_info_group_id") and message.chat_info_group_id: self.chat_type = ChatType.GROUP - elif hasattr(message, 'chat_info_group_name') and message.chat_info_group_name: + elif hasattr(message, "chat_info_group_name") and message.chat_info_group_name: self.chat_type = ChatType.GROUP else: self.chat_type = ChatType.PRIVATE @@ -64,7 +74,7 @@ class StreamContext(BaseDataModel): def set_chat_mode(self, chat_mode: ChatMode): """设置聊天模式""" self.chat_mode = chat_mode - + def is_group_chat(self) -> bool: """检查是否为群聊""" return self.chat_type == ChatType.GROUP @@ -82,10 +92,6 @@ class StreamContext(BaseDataModel): else: return "未知类型" - def get_unread_messages(self) -> List["DatabaseMessages"]: - """获取未读消息""" - return [msg for msg in self.unread_messages if not msg.is_read] - def mark_message_as_read(self, message_id: str): """标记消息为已读""" for msg in self.unread_messages: @@ -94,13 +100,56 @@ class StreamContext(BaseDataModel): self.history_messages.append(msg) self.unread_messages.remove(msg) break - + + def get_unread_messages(self) -> List["DatabaseMessages"]: + """获取未读消息""" + return [msg for msg in self.unread_messages if not msg.is_read] + def get_history_messages(self, limit: int = 20) -> List["DatabaseMessages"]: """获取历史消息""" # 优先返回最近的历史消息和所有未读消息 recent_history = self.history_messages[-limit:] if len(self.history_messages) > limit else self.history_messages return recent_history + def calculate_interruption_probability(self, max_limit: int, probability_factor: float) -> float: + """计算打断概率""" + if max_limit <= 0: + return 0.0 + + # 计算打断比例 + interruption_ratio = self.interruption_count / max_limit + + # 如果超过概率因子,概率下降 + if interruption_ratio > probability_factor: + # 使用指数衰减,超过限制越多,概率越低 + excess_ratio = interruption_ratio - probability_factor + probability = 1.0 * (0.5**excess_ratio) # 基础概率0.5,指数衰减 + else: + # 在限制内,保持较高概率 + probability = 0.8 + + return max(0.0, min(1.0, probability)) + + def increment_interruption_count(self): + """增加打断计数""" + self.interruption_count += 1 + self.last_interruption_time = time.time() + + def reset_interruption_count(self): + """重置打断计数和afc阈值调整""" + self.interruption_count = 0 + self.last_interruption_time = 0.0 + self.afc_threshold_adjustment = 0.0 + + def apply_interruption_afc_reduction(self, reduction_value: float): + """应用打断导致的afc阈值降低""" + self.afc_threshold_adjustment += reduction_value + logger.debug(f"应用afc阈值降低: {reduction_value}, 总调整量: {self.afc_threshold_adjustment}") + + def get_afc_threshold_adjustment(self) -> float: + """获取当前的afc阈值调整量""" + return self.afc_threshold_adjustment + @dataclass class MessageManagerStats(BaseDataModel): diff --git a/src/common/database/sqlalchemy_models.py b/src/common/database/sqlalchemy_models.py index e4724e7e6..46fcdfae8 100644 --- a/src/common/database/sqlalchemy_models.py +++ b/src/common/database/sqlalchemy_models.py @@ -53,7 +53,15 @@ class ChatStreams(Base): user_cardname = Column(Text, nullable=True) energy_value = Column(Float, nullable=True, default=5.0) sleep_pressure = Column(Float, nullable=True, default=0.0) - focus_energy = Column(Float, nullable=True, default=1.0) + focus_energy = Column(Float, nullable=True, default=0.5) + # 动态兴趣度系统字段 + base_interest_energy = Column(Float, nullable=True, default=0.5) + message_interest_total = Column(Float, nullable=True, default=0.0) + message_count = Column(Integer, nullable=True, default=0) + action_count = Column(Integer, nullable=True, default=0) + reply_count = Column(Integer, nullable=True, default=0) + last_interaction_time = Column(Float, nullable=True, default=None) + consecutive_no_reply = Column(Integer, nullable=True, default=0) __table_args__ = ( Index("idx_chatstreams_stream_id", "stream_id"), diff --git a/src/common/logger.py b/src/common/logger.py index b14d63d30..c3d3ca97f 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -350,6 +350,10 @@ MODULE_COLORS = { "memory": "\033[38;5;117m", # 天蓝色 "hfc": "\033[38;5;81m", # 稍微暗一些的青色,保持可读 "action_manager": "\033[38;5;208m", # 橙色,不与replyer重复 + "message_manager": "\033[38;5;27m", # 深蓝色,消息管理器 + "chatter_manager": "\033[38;5;129m", # 紫色,聊天管理器 + "chatter_interest_scoring": "\033[38;5;214m", # 橙黄色,兴趣评分 + "plan_executor": "\033[38;5;172m", # 橙褐色,计划执行器 # 关系系统 "relation": "\033[38;5;139m", # 柔和的紫色,不刺眼 # 聊天相关模块 @@ -551,6 +555,10 @@ MODULE_ALIASES = { "llm_models": "模型", "person_info": "人物", "chat_stream": "聊天流", + "message_manager": "消息管理", + "chatter_manager": "聊天管理", + "chatter_interest_scoring": "兴趣评分", + "plan_executor": "计划执行", "planner": "规划器", "replyer": "言语", "config": "配置", diff --git a/src/config/config.py b/src/config/config.py index fdd450e01..1d9bbfebf 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -67,7 +67,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ -MMC_VERSION = "0.10.0-alpha-2" +MMC_VERSION = "0.11.0-alpha-1" def get_key_comment(toml_table, key): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index d97e92f56..3ba341997 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -104,6 +104,31 @@ class ChatConfig(ValidatedConfigBase): ) delta_sigma: int = Field(default=120, description="采用正态分布随机时间间隔") + # 消息打断系统配置 + interruption_enabled: bool = Field(default=True, description="是否启用消息打断系统") + interruption_max_limit: int = Field(default=3, ge=0, description="每个聊天流的最大打断次数") + interruption_probability_factor: float = Field( + default=0.8, ge=0.0, le=1.0, description="打断概率因子,当前打断次数/最大打断次数超过此值时触发概率下降" + ) + interruption_afc_reduction: float = Field( + default=0.05, ge=0.0, le=1.0, description="每次连续打断降低的afc阈值数值" + ) + + # 动态消息分发系统配置 + dynamic_distribution_enabled: bool = Field(default=True, description="是否启用动态消息分发周期调整") + dynamic_distribution_base_interval: float = Field( + default=5.0, ge=1.0, le=60.0, description="基础分发间隔(秒)" + ) + dynamic_distribution_min_interval: float = Field( + default=1.0, ge=0.5, le=10.0, description="最小分发间隔(秒)" + ) + dynamic_distribution_max_interval: float = Field( + default=30.0, ge=5.0, le=300.0, description="最大分发间隔(秒)" + ) + dynamic_distribution_jitter_factor: float = Field( + default=0.2, ge=0.0, le=0.5, description="分发间隔随机扰动因子" + ) + def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float: """ 根据当前时间和聊天流获取对应的 talk_frequency diff --git a/src/main.py b/src/main.py index be3b609ef..1ff96935c 100644 --- a/src/main.py +++ b/src/main.py @@ -4,6 +4,7 @@ import time import signal import sys from functools import partial +import traceback from typing import Dict, Any from maim_message import MessageServer diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index a9734f61f..6c2ada6c4 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -150,6 +150,11 @@ class ChatMood: self.last_change_time = message_time + # 更新ChatStream的兴趣度数据 + if hasattr(self, 'chat_stream') and self.chat_stream: + self.chat_stream.add_message_interest(interested_rate) + logger.debug(f"{self.log_prefix} 已更新ChatStream兴趣度,当前focus_energy: {self.chat_stream.focus_energy:.3f}") + async def regress_mood(self): message_time = time.time() message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( diff --git a/src/plugins/built_in/affinity_flow_chatter/planner.py b/src/plugins/built_in/affinity_flow_chatter/planner.py index e44fcaa25..56211d80e 100644 --- a/src/plugins/built_in/affinity_flow_chatter/planner.py +++ b/src/plugins/built_in/affinity_flow_chatter/planner.py @@ -125,11 +125,17 @@ class ChatterActionPlanner: logger.info(f"兴趣度不足 ({latest_score.total_score:.2f}),移除回复") reply_not_available = True - # 更新情绪状态 - 使用最新消息的兴趣度 + # 更新情绪状态和ChatStream兴趣度数据 if latest_message and score > 0: chat_mood = mood_manager.get_mood_by_chat_id(self.chat_id) await chat_mood.update_mood_by_message(latest_message, score) logger.debug(f"已更新聊天 {self.chat_id} 的情绪状态,兴趣度: {score:.3f}") + elif latest_message: + # 即使不更新情绪状态,也要更新ChatStream的兴趣度数据 + chat_mood = mood_manager.get_mood_by_chat_id(self.chat_id) + if hasattr(chat_mood, 'chat_stream') and chat_mood.chat_stream: + chat_mood.chat_stream.add_message_interest(score) + logger.debug(f"已更新聊天 {self.chat_id} 的ChatStream兴趣度,分数: {score:.3f}") # base_threshold = self.interest_scoring.reply_threshold # 检查兴趣度是否达到非回复动作阈值 diff --git a/src/plugins/built_in/napcat_adapter_plugin/_manifest.json b/src/plugins/built_in/napcat_adapter_plugin/_manifest.json index 676aa3121..1c8c0686f 100644 --- a/src/plugins/built_in/napcat_adapter_plugin/_manifest.json +++ b/src/plugins/built_in/napcat_adapter_plugin/_manifest.json @@ -11,7 +11,7 @@ "host_application": { "min_version": "0.10.0", - "max_version": "0.10.0" + "max_version": "0.11.0" }, "homepage_url": "https://github.com/Windpicker-owo/InternetSearchPlugin", "repository_url": "https://github.com/Windpicker-owo/InternetSearchPlugin", diff --git a/src/plugins/built_in/napcat_adapter_plugin/plugin.py b/src/plugins/built_in/napcat_adapter_plugin/plugin.py index f2d43a6c3..569c0857a 100644 --- a/src/plugins/built_in/napcat_adapter_plugin/plugin.py +++ b/src/plugins/built_in/napcat_adapter_plugin/plugin.py @@ -299,7 +299,7 @@ class NapcatAdapterPlugin(BasePlugin): "name": ConfigField(type=str, default="napcat_adapter_plugin", description="插件名称"), "version": ConfigField(type=str, default="1.1.0", description="插件版本"), "config_version": ConfigField(type=str, default="1.3.1", description="配置文件版本"), - "enabled": ConfigField(type=bool, default=False, description="是否启用插件"), + "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), }, "inner": { "version": ConfigField(type=str, default="0.2.1", description="配置版本号,请勿修改"), diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index fc8c864e9..c298ecc16 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "6.9.6" +version = "7.0.2" #----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -114,36 +114,23 @@ learn_expression = false learning_strength = 0.5 [chat] #MoFox-Bot的聊天通用设置 -# 群聊聊天模式设置 -group_chat_mode = "auto" # 群聊聊天模式:auto-自动切换,normal-强制普通模式,focus-强制专注模式 -talk_frequency = 1 -# MoFox-Bot活跃度,越高,麦麦回复越多 -# 专注时能更好把握发言时机,能够进行持久的连续对话 - -focus_value = 1 -# MoFox-Bot的专注思考能力,越高越容易持续连续对话 - -# 在专注模式下,只在被艾特或提及时才回复的群组列表 -# 这可以让你在某些群里保持“高冷”,只在被需要时才发言 -# 格式为: ["platform:group_id1", "platform:group_id2"] -# 例如: ["qq:123456789", "qq:987654321"] -focus_mode_quiet_groups = [] - -# breaking模式配置 -enable_breaking_mode = true # 是否启用自动进入breaking模式,关闭后不会自动进入breaking形式 - -# 强制私聊回复 -force_reply_private = false # 是否强制私聊回复,开启后私聊将强制回复 - allow_reply_self = false # 是否允许回复自己说的话 max_context_size = 25 # 上下文长度 thinking_timeout = 40 # MoFox-Bot一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) -replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 -mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 -at_bot_inevitable_reply = true # @bot 或 提及bot 必然回复 -# 兼容normal、focus,在focus模式下为强制移除no_reply动作 +# 消息打断系统配置 +interruption_enabled = true # 是否启用消息打断系统 +interruption_max_limit = 3 # 每个聊天流的最大打断次数 +interruption_probability_factor = 0.8 # 打断概率因子,当前打断次数/最大打断次数超过此值时触发概率下降 +interruption_afc_reduction = 0.05 # 每次连续打断降低的afc阈值数值 + +# 动态消息分发系统配置 +dynamic_distribution_enabled = true # 是否启用动态消息分发周期调整 +dynamic_distribution_base_interval = 5.0 # 基础分发间隔(秒) +dynamic_distribution_min_interval = 1.0 # 最小分发间隔(秒) +dynamic_distribution_max_interval = 30.0 # 最大分发间隔(秒) +dynamic_distribution_jitter_factor = 0.2 # 分发间隔随机扰动因子 talk_frequency_adjust = [ ["", "8:00,1", "12:00,1.2", "18:00,1.5", "01:00,0.6"], @@ -288,7 +275,7 @@ enable_vector_instant_memory = true # 是否启用基于向量的瞬时记忆 memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ] [voice] -enable_asr = false # 是否启用语音识别,启用后MoFox-Bot可以识别语音消息,启用该功能需要配置语音识别模型[model.voice] +enable_asr = true # 是否启用语音识别,启用后MoFox-Bot可以识别语音消息,启用该功能需要配置语音识别模型[model.voice] [lpmm_knowledge] # lpmm知识库配置 enable = false # 是否启用lpmm知识库 @@ -341,7 +328,7 @@ enable = true # 是否启用回复分割器 split_mode = "punctuation" # 分割模式: "llm" - 由语言模型决定, "punctuation" - 基于标点符号 max_length = 512 # 回复允许的最大长度 max_sentence_num = 8 # 回复允许的最大句子数 -enable_kaomoji_protection = false # 是否启用颜文字保护 +enable_kaomoji_protection = true # 是否启用颜文字保护 [log] date_style = "m-d H:i:s" # 日期格式