feat(chat): 实现动态消息分发系统和消息打断机制

添加基于focus_energy的动态消息分发周期调整功能,根据聊天流兴趣度智能调整检查间隔
实现消息打断系统,允许高优先级消息打断正在处理的任务
重构ChatStream类,引入动态兴趣度计算系统,包括消息兴趣度统计和用户关系评分
扩展数据库模型和配置系统以支持新功能,增加相关配置项
更新版本号至0.11.0-alpha-1以反映重大功能更新
This commit is contained in:
Windpicker-owo
2025-09-25 17:14:01 +08:00
parent 4cbd86aa96
commit a2baec088e
15 changed files with 703 additions and 73 deletions

View File

@@ -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
@@ -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:
@@ -139,8 +149,10 @@ class MessageManager:
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} 是否有唤醒触发器。")
@@ -152,11 +164,11 @@ 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()

View File

@@ -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
@@ -465,6 +453,7 @@ class ChatBot:
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

View File

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

View File

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

View File

@@ -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
@@ -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:
@@ -95,12 +101,55 @@ class StreamContext(BaseDataModel):
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):

View File

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

View File

@@ -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": "配置",

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
# 检查兴趣度是否达到非回复动作阈值

View File

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

View File

@@ -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="配置版本号,请勿修改"),

View File

@@ -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" # 日期格式