diff --git a/README.md b/README.md
index 9b057508..403b42e0 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,11 @@
> - QQ 机器人存在被限制风险,请自行了解,谨慎使用。
> - 由于程序处于开发中,可能消耗较多 token。
+## 麦麦MC项目(早期开发)
+[让麦麦玩MC](https://github.com/MaiM-with-u/Maicraft)
+
+交流群:1058573197
+
## 💬 讨论
**技术交流群:**
diff --git a/bot.py b/bot.py
index 631abd61..ea5244f2 100644
--- a/bot.py
+++ b/bot.py
@@ -66,7 +66,7 @@ async def graceful_shutdown(): # sourcery skip: use-named-expression
from src.plugin_system.core.events_manager import events_manager
from src.plugin_system.base.component_types import EventType
# 触发 ON_STOP 事件
- _ = await events_manager.handle_mai_events(event_type=EventType.ON_STOP)
+ await events_manager.handle_mai_events(event_type=EventType.ON_STOP)
# 停止所有异步任务
await async_task_manager.stop_and_wait_all_tasks()
diff --git a/changelogs/changelog.md b/changelogs/changelog.md
index 99fcd682..5970904a 100644
--- a/changelogs/changelog.md
+++ b/changelogs/changelog.md
@@ -1,10 +1,24 @@
# Changelog
+
+## [0.10.2] - 2025-8-31
+### 🌟 主要功能更改
+- 大幅优化了聊天逻辑,更易配置,动态控制
+- 记忆系统重新启用,更好更优秀
+- 更好的event系统
+- 现在支持提及100%回复
+
+### 细节功能更改
+- 为空回复添加重试机制
+- 修复tts插件可能的复读问题
+
+
## [0.10.1] - 2025-8-24
### 🌟 主要功能更改
- planner现在改为大小核结构,移除激活阶段,提高回复速度和动作调用精准度
- 优化关系的表现的效率
+### 细节功能更改
- 优化识图的表现
- 为planner添加单独控制的提示词
- 修复激活值计算异常的BUG
diff --git a/plugins/hello_world_plugin/plugin.py b/plugins/hello_world_plugin/plugin.py
index f9855481..c4e6d72c 100644
--- a/plugins/hello_world_plugin/plugin.py
+++ b/plugins/hello_world_plugin/plugin.py
@@ -11,7 +11,7 @@ from src.plugin_system import (
BaseEventHandler,
EventType,
MaiMessages,
- ToolParamType
+ ToolParamType,
)
@@ -136,12 +136,12 @@ class PrintMessage(BaseEventHandler):
handler_name = "print_message_handler"
handler_description = "打印接收到的消息"
- async def execute(self, message: MaiMessages) -> Tuple[bool, bool, str | None]:
+ async def execute(self, message: MaiMessages | None) -> Tuple[bool, bool, str | None, None]:
"""执行打印消息事件处理"""
# 打印接收到的消息
if self.get_config("print_message.enabled", False):
- print(f"接收到消息: {message.raw_message}")
- return True, True, "消息已打印"
+ print(f"接收到消息: {message.raw_message if message else '无效消息'}")
+ return True, True, "消息已打印", None
# ===== 插件注册 =====
diff --git a/src/chat/frequency_control/focus_value_control.py b/src/chat/frequency_control/focus_value_control.py
index 290dcc9e..be820760 100644
--- a/src/chat/frequency_control/focus_value_control.py
+++ b/src/chat/frequency_control/focus_value_control.py
@@ -3,26 +3,7 @@ from src.config.config import global_config
from src.chat.frequency_control.utils import parse_stream_config_to_chat_id
-class FocusValueControl:
- def __init__(self, chat_id: str):
- self.chat_id = chat_id
- self.focus_value_adjust: float = 1
-
- def get_current_focus_value(self) -> float:
- return get_current_focus_value(self.chat_id) * self.focus_value_adjust
-
-
-class FocusValueControlManager:
- def __init__(self):
- self.focus_value_controls: dict[str, FocusValueControl] = {}
-
- def get_focus_value_control(self, chat_id: str) -> FocusValueControl:
- if chat_id not in self.focus_value_controls:
- self.focus_value_controls[chat_id] = FocusValueControl(chat_id)
- return self.focus_value_controls[chat_id]
-
-
-def get_current_focus_value(chat_id: Optional[str] = None) -> float:
+def get_config_base_focus_value(chat_id: Optional[str] = None) -> float:
"""
根据当前时间和聊天流获取对应的 focus_value
"""
@@ -139,5 +120,3 @@ def get_global_focus_value() -> Optional[float]:
return None
-
-focus_value_control = FocusValueControlManager()
diff --git a/src/chat/frequency_control/frequency_control.py b/src/chat/frequency_control/frequency_control.py
new file mode 100644
index 00000000..a71a171f
--- /dev/null
+++ b/src/chat/frequency_control/frequency_control.py
@@ -0,0 +1,477 @@
+import time
+from typing import Optional, Dict, List
+from src.plugin_system.apis import message_api
+from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
+from src.common.logger import get_logger
+from src.config.config import global_config
+from src.chat.frequency_control.talk_frequency_control import get_config_base_talk_frequency
+from src.chat.frequency_control.focus_value_control import get_config_base_focus_value
+
+logger = get_logger("frequency_control")
+
+
+class FrequencyControl:
+ """
+ 频率控制类,可以根据最近时间段的发言数量和发言人数动态调整频率
+
+ 特点:
+ - 发言频率调整:基于最近10分钟的数据,评估单位为"消息数/10分钟"
+ - 专注度调整:基于最近10分钟的数据,评估单位为"消息数/10分钟"
+ - 历史基准值:基于最近一周的数据,按小时统计,每小时都有独立的基准值(需要至少50条历史消息)
+ - 统一标准:两个调整都使用10分钟窗口,确保逻辑一致性和响应速度
+ - 双向调整:根据活跃度高低,既能提高也能降低频率和专注度
+ - 数据充足性检查:当历史数据不足50条时,不更新基准值;当基准值为默认值时,不进行动态调整
+ - 基准值更新:直接使用新计算的周均值,无平滑更新
+ """
+
+ def __init__(self, chat_id: str):
+ self.chat_id = chat_id
+ self.chat_stream: ChatStream = get_chat_manager().get_stream(self.chat_id)
+ if not self.chat_stream:
+ raise ValueError(f"无法找到聊天流: {chat_id}")
+ self.log_prefix = f"[{get_chat_manager().get_stream_name(self.chat_id) or self.chat_id}]"
+ # 发言频率调整值
+ self.talk_frequency_adjust: float = 1.0
+ self.talk_frequency_external_adjust: float = 1.0
+ # 专注度调整值
+ self.focus_value_adjust: float = 1.0
+ self.focus_value_external_adjust: float = 1.0
+
+ # 动态调整相关参数
+ self.last_update_time = time.time()
+ self.update_interval = 60 # 每60秒更新一次
+
+ # 历史数据缓存
+ self._message_count_cache = 0
+ self._user_count_cache = 0
+ self._last_cache_time = 0
+ self._cache_duration = 30 # 缓存30秒
+
+ # 调整参数
+ self.min_adjust = 0.3 # 最小调整值
+ self.max_adjust = 2.0 # 最大调整值
+
+ # 动态基准值(将根据历史数据计算)
+ self.base_message_count = 5 # 默认基准消息数量,将被动态更新
+ self.base_user_count = 3 # 默认基准用户数量,将被动态更新
+
+ # 平滑因子
+ self.smoothing_factor = 0.3
+
+ # 历史数据相关参数
+ self._last_historical_update = 0
+ self._historical_update_interval = 600 # 每十分钟更新一次历史基准值
+ self._historical_days = 7 # 使用最近7天的数据计算基准值
+
+ # 按小时统计的历史基准值
+ self._hourly_baseline = {
+ 'messages': {}, # {0-23: 平均消息数}
+ 'users': {} # {0-23: 平均用户数}
+ }
+
+ # 初始化24小时的默认基准值
+ for hour in range(24):
+ self._hourly_baseline['messages'][hour] = 0.0
+ self._hourly_baseline['users'][hour] = 0.0
+
+ def _update_historical_baseline(self):
+ """
+ 更新基于历史数据的基准值
+ 使用最近一周的数据,按小时统计平均消息数量和用户数量
+ """
+ current_time = time.time()
+
+ # 检查是否需要更新历史基准值
+ if current_time - self._last_historical_update < self._historical_update_interval:
+ return
+
+ try:
+ # 计算一周前的时间戳
+ week_ago = current_time - (self._historical_days * 24 * 3600)
+
+ # 获取最近一周的消息数据
+ historical_messages = message_api.get_messages_by_time_in_chat(
+ chat_id=self.chat_stream.stream_id,
+ start_time=week_ago,
+ end_time=current_time,
+ filter_mai=True,
+ filter_command=True
+ )
+
+ if historical_messages and len(historical_messages) >= 50:
+ # 按小时统计消息数和用户数
+ hourly_stats = {hour: {'messages': [], 'users': set()} for hour in range(24)}
+
+ for msg in historical_messages:
+ # 获取消息的小时(UTC时间)
+ msg_time = time.localtime(msg.time)
+ msg_hour = msg_time.tm_hour
+
+ # 统计消息数
+ hourly_stats[msg_hour]['messages'].append(msg)
+
+ # 统计用户数
+ if msg.user_info and msg.user_info.user_id:
+ hourly_stats[msg_hour]['users'].add(msg.user_info.user_id)
+
+ # 计算每个小时的平均值(基于一周的数据)
+ for hour in range(24):
+ # 计算该小时的平均消息数(一周内该小时的总消息数 / 7天)
+ total_messages = len(hourly_stats[hour]['messages'])
+ total_users = len(hourly_stats[hour]['users'])
+
+ # 只计算有消息的时段,没有消息的时段设为0
+ if total_messages > 0:
+ avg_messages = total_messages / self._historical_days
+ avg_users = total_users / self._historical_days
+ self._hourly_baseline['messages'][hour] = avg_messages
+ self._hourly_baseline['users'][hour] = avg_users
+ else:
+ # 没有消息的时段设为0,表示该时段不活跃
+ self._hourly_baseline['messages'][hour] = 0.0
+ self._hourly_baseline['users'][hour] = 0.0
+
+ # 更新整体基准值(用于兼容性)- 基于原始数据计算,不受max(1.0)限制影响
+ overall_avg_messages = sum(len(hourly_stats[hour]['messages']) for hour in range(24)) / (24 * self._historical_days)
+ overall_avg_users = sum(len(hourly_stats[hour]['users']) for hour in range(24)) / (24 * self._historical_days)
+
+ self.base_message_count = overall_avg_messages
+ self.base_user_count = overall_avg_users
+
+ logger.info(
+ f"{self.log_prefix} 历史基准值更新完成: "
+ f"整体平均消息数={overall_avg_messages:.2f}, 整体平均用户数={overall_avg_users:.2f}"
+ )
+
+ # 记录几个关键时段的基准值
+ key_hours = [8, 12, 18, 22] # 早、中、晚、夜
+ for hour in key_hours:
+ # 计算该小时平均每10分钟的消息数和用户数
+ hourly_10min_messages = self._hourly_baseline['messages'][hour] / 6 # 1小时 = 6个10分钟
+ hourly_10min_users = self._hourly_baseline['users'][hour] / 6
+ logger.info(
+ f"{self.log_prefix} {hour}时基准值: "
+ f"消息数={self._hourly_baseline['messages'][hour]:.2f}/小时 "
+ f"({hourly_10min_messages:.2f}/10分钟), "
+ f"用户数={self._hourly_baseline['users'][hour]:.2f}/小时 "
+ f"({hourly_10min_users:.2f}/10分钟)"
+ )
+
+ elif historical_messages and len(historical_messages) < 50:
+ # 历史数据不足50条,不更新基准值
+ logger.info(f"{self.log_prefix} 历史数据不足50条({len(historical_messages)}条),不更新基准值")
+ else:
+ # 如果没有历史数据,不更新基准值
+ logger.info(f"{self.log_prefix} 无历史数据,不更新基准值")
+
+ except Exception as e:
+ logger.error(f"{self.log_prefix} 更新历史基准值时出错: {e}")
+ # 出错时保持原有基准值不变
+
+ self._last_historical_update = current_time
+
+ def _get_current_hour_baseline(self) -> tuple[float, float]:
+ """
+ 获取当前小时的基准值
+
+ Returns:
+ tuple: (基准消息数, 基准用户数)
+ """
+ current_hour = time.localtime().tm_hour
+ return (
+ self._hourly_baseline['messages'][current_hour],
+ self._hourly_baseline['users'][current_hour]
+ )
+
+ def get_dynamic_talk_frequency_adjust(self) -> float:
+ """
+ 获取纯动态调整值(不包含配置文件基础值)
+
+ Returns:
+ float: 动态调整值
+ """
+ self._update_talk_frequency_adjust()
+ return self.talk_frequency_adjust
+
+ def get_dynamic_focus_value_adjust(self) -> float:
+ """
+ 获取纯动态调整值(不包含配置文件基础值)
+
+ Returns:
+ float: 动态调整值
+ """
+ self._update_focus_value_adjust()
+ return self.focus_value_adjust
+
+ def _update_talk_frequency_adjust(self):
+ """
+ 更新发言频率调整值
+ 适合人少话多的时候:人少但消息多,提高回复频率
+ """
+ current_time = time.time()
+
+ # 检查是否需要更新
+ if current_time - self.last_update_time < self.update_interval:
+ return
+
+ # 先更新历史基准值
+ self._update_historical_baseline()
+
+ try:
+ # 获取最近10分钟的数据(发言频率更敏感)
+ recent_messages = message_api.get_messages_by_time_in_chat(
+ chat_id=self.chat_stream.stream_id,
+ start_time=current_time - 600, # 10分钟前
+ end_time=current_time,
+ filter_mai=True,
+ filter_command=True
+ )
+
+ # 计算消息数量和用户数量
+ message_count = len(recent_messages)
+ user_ids = set()
+ for msg in recent_messages:
+ if msg.user_info and msg.user_info.user_id:
+ user_ids.add(msg.user_info.user_id)
+ user_count = len(user_ids)
+
+ # 获取当前小时的基准值
+ current_hour_base_messages, current_hour_base_users = self._get_current_hour_baseline()
+
+ # 计算当前小时平均每10分钟的基准值
+ current_hour_10min_messages = current_hour_base_messages / 6 # 1小时 = 6个10分钟
+ current_hour_10min_users = current_hour_base_users / 6
+
+ # 发言频率调整逻辑:根据活跃度双向调整
+ # 检查是否有足够的数据进行分析
+ if user_count > 0 and message_count >= 2: # 至少需要2条消息才能进行有意义的分析
+ # 检查历史基准值是否有效(该时段有活跃度)
+ if current_hour_base_messages > 0.0 and current_hour_base_users > 0.0:
+ # 计算人均消息数(10分钟窗口)
+ messages_per_user = message_count / user_count
+ # 使用当前小时每10分钟的基准人均消息数
+ base_messages_per_user = current_hour_10min_messages / current_hour_10min_users if current_hour_10min_users > 0 else 1.0
+
+ # 双向调整逻辑
+ if messages_per_user > base_messages_per_user * 1.2:
+ # 活跃度很高:提高回复频率
+ target_talk_adjust = min(self.max_adjust, messages_per_user / base_messages_per_user)
+ elif messages_per_user < base_messages_per_user * 0.8:
+ # 活跃度很低:降低回复频率
+ target_talk_adjust = max(self.min_adjust, messages_per_user / base_messages_per_user)
+ else:
+ # 活跃度正常:保持正常
+ target_talk_adjust = 1.0
+ else:
+ # 历史基准值不足,不调整
+ target_talk_adjust = 1.0
+ else:
+ # 数据不足:不调整
+ target_talk_adjust = 1.0
+
+ # 限制调整范围
+ target_talk_adjust = max(self.min_adjust, min(self.max_adjust, target_talk_adjust))
+
+ # 平滑调整
+ self.talk_frequency_adjust = (
+ self.talk_frequency_adjust * (1 - self.smoothing_factor) +
+ target_talk_adjust * self.smoothing_factor
+ )
+
+ # 判断调整方向
+ if target_talk_adjust > 1.0:
+ adjust_direction = "提高"
+ elif target_talk_adjust < 1.0:
+ adjust_direction = "降低"
+ else:
+ if current_hour_base_messages <= 0.0 or current_hour_base_users <= 0.0:
+ adjust_direction = "不调整(该时段无活跃度)"
+ else:
+ adjust_direction = "保持"
+
+ logger.info(
+ f"{self.log_prefix} 发言频率调整: "
+ f"当前: {message_count}消息/{user_count}用户, 人均: {message_count/user_count if user_count > 0 else 0:.2f}消息/用户, "
+ f"基准: {current_hour_10min_messages:.2f}消息/{current_hour_10min_users:.2f}用户,人均:{current_hour_10min_messages/current_hour_10min_users if current_hour_10min_users > 0 else 0:.2f}消息/用户, "
+ f"调整: {adjust_direction} → {target_talk_adjust:.2f} → {self.talk_frequency_adjust:.2f}"
+ )
+
+ except Exception as e:
+ logger.error(f"{self.log_prefix} 更新发言频率调整值时出错: {e}")
+
+ def _update_focus_value_adjust(self):
+ """
+ 更新专注度调整值
+ 适合人多话多的时候:人多且消息多,提高专注度(LLM消耗更多,但回复更精准)
+ """
+ current_time = time.time()
+
+ # 检查是否需要更新
+ if current_time - self.last_update_time < self.update_interval:
+ return
+
+ try:
+ # 获取最近10分钟的数据(与发言频率保持一致)
+ recent_messages = message_api.get_messages_by_time_in_chat(
+ chat_id=self.chat_stream.stream_id,
+ start_time=current_time - 600, # 10分钟前
+ end_time=current_time,
+ filter_mai=True,
+ filter_command=True
+ )
+
+ # 计算消息数量和用户数量
+ message_count = len(recent_messages)
+ user_ids = set()
+ for msg in recent_messages:
+ if msg.user_info and msg.user_info.user_id:
+ user_ids.add(msg.user_info.user_id)
+ user_count = len(user_ids)
+
+ # 获取当前小时的基准值
+ current_hour_base_messages, current_hour_base_users = self._get_current_hour_baseline()
+
+ # 计算当前小时平均每10分钟的基准值
+ current_hour_10min_messages = current_hour_base_messages / 6 # 1小时 = 6个10分钟
+ current_hour_10min_users = current_hour_base_users / 6
+
+ # 专注度调整逻辑:根据活跃度双向调整
+ # 检查是否有足够的数据进行分析
+ if user_count > 0 and current_hour_10min_users > 0 and message_count >= 2:
+ # 检查历史基准值是否有效(该时段有活跃度)
+ if current_hour_base_messages > 0.0 and current_hour_base_users > 0.0:
+ # 计算用户活跃度比率(基于10分钟数据)
+ user_ratio = user_count / current_hour_10min_users
+ # 计算消息活跃度比率(基于10分钟数据)
+ message_ratio = message_count / current_hour_10min_messages if current_hour_10min_messages > 0 else 1.0
+
+ # 双向调整逻辑
+ if user_ratio > 1.3 and message_ratio > 1.3:
+ # 活跃度很高:提高专注度,消耗更多LLM资源但回复更精准
+ target_focus_adjust = min(self.max_adjust, (user_ratio + message_ratio) / 2)
+ elif user_ratio > 1.1 and message_ratio > 1.1:
+ # 活跃度较高:适度提高专注度
+ target_focus_adjust = min(self.max_adjust, 1.0 + (user_ratio + message_ratio - 2.0) * 0.2)
+ elif user_ratio < 0.7 or message_ratio < 0.7:
+ # 活跃度很低:降低专注度,节省LLM资源
+ target_focus_adjust = max(self.min_adjust, min(user_ratio, message_ratio))
+ else:
+ # 正常情况:保持默认专注度
+ target_focus_adjust = 1.0
+ else:
+ # 历史基准值不足,不调整
+ target_focus_adjust = 1.0
+ else:
+ # 数据不足:不调整
+ target_focus_adjust = 1.0
+
+ # 限制调整范围
+ target_focus_adjust = max(self.min_adjust, min(self.max_adjust, target_focus_adjust))
+
+ # 平滑调整
+ self.focus_value_adjust = (
+ self.focus_value_adjust * (1 - self.smoothing_factor) +
+ target_focus_adjust * self.smoothing_factor
+ )
+
+ # 计算当前小时平均每10分钟的基准值
+ current_hour_10min_messages = current_hour_base_messages / 6 # 1小时 = 6个10分钟
+ current_hour_10min_users = current_hour_base_users / 6
+
+ # 判断调整方向
+ if target_focus_adjust > 1.0:
+ adjust_direction = "提高"
+ elif target_focus_adjust < 1.0:
+ adjust_direction = "降低"
+ else:
+ if current_hour_base_messages <= 0.0 or current_hour_base_users <= 0.0:
+ adjust_direction = "不调整(该时段无活跃度)"
+ else:
+ adjust_direction = "保持"
+
+ logger.info(
+ f"{self.log_prefix} 专注度调整(10分钟): "
+ f"当前: {message_count}消息/{user_count}用户,人均:{message_count/user_count if user_count > 0 else 0:.2f}消息/用户, "
+ f"基准: {current_hour_10min_messages:.2f}消息/{current_hour_10min_users:.2f}用户,人均:{current_hour_10min_messages/current_hour_10min_users if current_hour_10min_users > 0 else 0:.2f}消息/用户, "
+ f"比率: 用户{user_count/current_hour_10min_users if current_hour_10min_users > 0 else 0:.2f}x, 消息{message_count/current_hour_10min_messages if current_hour_10min_messages > 0 else 0:.2f}x, "
+ f"调整: {adjust_direction} → {target_focus_adjust:.2f} → {self.focus_value_adjust:.2f}"
+ )
+
+ except Exception as e:
+ logger.error(f"{self.log_prefix} 更新专注度调整值时出错: {e}")
+
+ def get_final_talk_frequency(self) -> float:
+ return get_config_base_talk_frequency(self.chat_stream.stream_id) * self.get_dynamic_talk_frequency_adjust() * self.talk_frequency_external_adjust
+
+ def get_final_focus_value(self) -> float:
+ return get_config_base_focus_value(self.chat_stream.stream_id) * self.get_dynamic_focus_value_adjust() * self.focus_value_external_adjust
+
+
+ def set_adjustment_parameters(
+ self,
+ min_adjust: Optional[float] = None,
+ max_adjust: Optional[float] = None,
+ base_message_count: Optional[int] = None,
+ base_user_count: Optional[int] = None,
+ smoothing_factor: Optional[float] = None,
+ update_interval: Optional[int] = None,
+ historical_update_interval: Optional[int] = None,
+ historical_days: Optional[int] = None
+ ):
+ """
+ 设置调整参数
+
+ Args:
+ min_adjust: 最小调整值
+ max_adjust: 最大调整值
+ base_message_count: 基准消息数量
+ base_user_count: 基准用户数量
+ smoothing_factor: 平滑因子
+ update_interval: 更新间隔(秒)
+ """
+ if min_adjust is not None:
+ self.min_adjust = max(0.1, min_adjust)
+ if max_adjust is not None:
+ self.max_adjust = max(1.0, max_adjust)
+ if base_message_count is not None:
+ self.base_message_count = max(1, base_message_count)
+ if base_user_count is not None:
+ self.base_user_count = max(1, base_user_count)
+ if smoothing_factor is not None:
+ self.smoothing_factor = max(0.0, min(1.0, smoothing_factor))
+ if update_interval is not None:
+ self.update_interval = max(10, update_interval)
+ if historical_update_interval is not None:
+ self._historical_update_interval = max(300, historical_update_interval) # 最少5分钟
+ if historical_days is not None:
+ self._historical_days = max(1, min(30, historical_days)) # 1-30天之间
+
+
+class FrequencyControlManager:
+ """
+ 频率控制管理器,管理多个聊天流的频率控制实例
+ """
+
+ def __init__(self):
+ self.frequency_control_dict: Dict[str, FrequencyControl] = {}
+
+ def get_or_create_frequency_control(self, chat_id: str) -> FrequencyControl:
+ """
+ 获取或创建指定聊天流的频率控制实例
+
+ Args:
+ chat_id: 聊天流ID
+
+ Returns:
+ FrequencyControl: 频率控制实例
+ """
+ if chat_id not in self.frequency_control_dict:
+ self.frequency_control_dict[chat_id] = FrequencyControl(chat_id)
+ return self.frequency_control_dict[chat_id]
+
+# 创建全局实例
+frequency_control_manager = FrequencyControlManager()
+
+
+
+
diff --git a/src/chat/frequency_control/talk_frequency_control.py b/src/chat/frequency_control/talk_frequency_control.py
index ad81fbd8..11728e26 100644
--- a/src/chat/frequency_control/talk_frequency_control.py
+++ b/src/chat/frequency_control/talk_frequency_control.py
@@ -3,26 +3,7 @@ from src.config.config import global_config
from src.chat.frequency_control.utils import parse_stream_config_to_chat_id
-class TalkFrequencyControl:
- def __init__(self, chat_id: str):
- self.chat_id = chat_id
- self.talk_frequency_adjust: float = 1
-
- def get_current_talk_frequency(self) -> float:
- return get_current_talk_frequency(self.chat_id) * self.talk_frequency_adjust
-
-
-class TalkFrequencyControlManager:
- def __init__(self):
- self.talk_frequency_controls = {}
-
- def get_talk_frequency_control(self, chat_id: str) -> TalkFrequencyControl:
- if chat_id not in self.talk_frequency_controls:
- self.talk_frequency_controls[chat_id] = TalkFrequencyControl(chat_id)
- return self.talk_frequency_controls[chat_id]
-
-
-def get_current_talk_frequency(chat_id: Optional[str] = None) -> float:
+def get_config_base_talk_frequency(chat_id: Optional[str] = None) -> float:
"""
根据当前时间和聊天流获取对应的 talk_frequency
@@ -145,4 +126,3 @@ def get_global_frequency() -> Optional[float]:
return None
-talk_frequency_control = TalkFrequencyControlManager()
diff --git a/src/chat/heart_flow/heartFC_chat.py b/src/chat/heart_flow/heartFC_chat.py
index 8680392a..289752d0 100644
--- a/src/chat/heart_flow/heartFC_chat.py
+++ b/src/chat/heart_flow/heartFC_chat.py
@@ -18,8 +18,7 @@ from src.chat.planner_actions.action_modifier import ActionModifier
from src.chat.planner_actions.action_manager import ActionManager
from src.chat.heart_flow.hfc_utils import CycleDetail
from src.chat.heart_flow.hfc_utils import send_typing, stop_typing
-from src.chat.frequency_control.talk_frequency_control import talk_frequency_control
-from src.chat.frequency_control.focus_value_control import focus_value_control
+from src.chat.frequency_control.frequency_control import frequency_control_manager
from src.chat.express.expression_learner import expression_learner_manager
from src.person_info.person_info import Person
from src.plugin_system.base.component_types import ChatMode, EventType, ActionInfo
@@ -85,8 +84,7 @@ class HeartFChatting:
self.expression_learner = expression_learner_manager.get_expression_learner(self.stream_id)
- self.talk_frequency_control = talk_frequency_control.get_talk_frequency_control(self.stream_id)
- self.focus_value_control = focus_value_control.get_focus_value_control(self.stream_id)
+ self.frequency_control = frequency_control_manager.get_or_create_frequency_control(self.stream_id)
self.action_manager = ActionManager()
self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager)
@@ -101,15 +99,8 @@ class HeartFChatting:
self._cycle_counter = 0
self._current_cycle_detail: CycleDetail = None # type: ignore
- self.reply_timeout_count = 0
- self.plan_timeout_count = 0
-
self.last_read_time = time.time() - 10
- self.focus_energy = 1
- self.no_action_consecutive = 0
- # 最近三次no_action的新消息兴趣度记录
- self.recent_interest_records: deque = deque(maxlen=3)
async def start(self):
"""检查是否需要启动主循环,如果未激活则启动。"""
@@ -187,87 +178,14 @@ class HeartFChatting:
f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " # type: ignore
f"选择动作: {action_type}" + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
)
-
- def _determine_form_type(self) -> None:
- """判断使用哪种形式的no_action"""
- # 如果连续no_action次数少于3次,使用waiting形式
- if self.no_action_consecutive <= 3:
- self.focus_energy = 1
- else:
- # 计算最近三次记录的兴趣度总和
- total_recent_interest = sum(self.recent_interest_records)
-
- # 计算调整后的阈值
- adjusted_threshold = 1 / self.talk_frequency_control.get_current_talk_frequency()
-
- logger.info(
- f"{self.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}"
- )
-
- # 如果兴趣度总和小于阈值,进入breaking形式
- if total_recent_interest < adjusted_threshold:
- logger.info(f"{self.log_prefix} 兴趣度不足,进入休息")
- self.focus_energy = random.randint(3, 6)
- else:
- logger.info(f"{self.log_prefix} 兴趣度充足,等待新消息")
- self.focus_energy = 1
-
- async def _should_process_messages(self, new_message: List["DatabaseMessages"]) -> tuple[bool, float]:
- """
- 判断是否应该处理消息
-
- Args:
- new_message: 新消息列表
- mode: 当前聊天模式
-
- Returns:
- bool: 是否应该处理消息
- """
- new_message_count = len(new_message)
- talk_frequency = self.talk_frequency_control.get_current_talk_frequency()
-
- modified_exit_count_threshold = self.focus_energy * 0.5 / talk_frequency
- modified_exit_interest_threshold = 1.5 / talk_frequency
+
+ async def caculate_interest_value(self, recent_messages_list: List["DatabaseMessages"]) -> float:
total_interest = 0.0
- for msg in new_message:
+ for msg in recent_messages_list:
interest_value = msg.interest_value
if interest_value is not None and msg.processed_plain_text:
total_interest += float(interest_value)
-
- if new_message_count >= modified_exit_count_threshold:
- self.recent_interest_records.append(total_interest)
- logger.info(
- f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待"
- )
- # logger.info(self.last_read_time)
- # logger.info(new_message)
- return True, total_interest / new_message_count if new_message_count > 0 else 0.0
-
- # 检查累计兴趣值
- if new_message_count > 0:
- # 只在兴趣值变化时输出log
- if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest:
- logger.info(
- f"{self.log_prefix} 休息中,新消息:{new_message_count}条,累计兴趣值: {total_interest:.2f}, 活跃度: {talk_frequency:.1f}"
- )
- self._last_accumulated_interest = total_interest
-
- if total_interest >= modified_exit_interest_threshold:
- # 记录兴趣度到列表
- self.recent_interest_records.append(total_interest)
- logger.info(
- f"{self.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{modified_exit_interest_threshold:.1f}),结束等待"
- )
- return True, total_interest / new_message_count if new_message_count > 0 else 0.0
-
- # 每10秒输出一次等待状态
- if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 15 == 0:
- logger.debug(
- f"{self.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累计兴趣{total_interest:.1f},继续等待..."
- )
- await asyncio.sleep(0.5)
-
- return False, 0.0
+ return total_interest / len(recent_messages_list)
async def _loopbody(self):
recent_messages_list = message_api.get_messages_by_time_in_chat(
@@ -279,16 +197,13 @@ class HeartFChatting:
filter_mai=True,
filter_command=True,
)
- # 统一的消息处理逻辑
- should_process, interest_value = await self._should_process_messages(recent_messages_list)
-
- if should_process:
+
+ if recent_messages_list:
self.last_read_time = time.time()
- await self._observe(interest_value=interest_value)
-
+ await self._observe(interest_value=await self.caculate_interest_value(recent_messages_list),recent_messages_list=recent_messages_list)
else:
# Normal模式:消息数量不足,等待
- await asyncio.sleep(0.5)
+ await asyncio.sleep(0.2)
return True
return True
@@ -342,8 +257,7 @@ class HeartFChatting:
return loop_info, reply_text, cycle_timers
- async def _observe(self, interest_value: float = 0.0) -> bool:
- action_type = "no_action"
+ async def _observe(self, interest_value: float = 0.0,recent_messages_list: List["DatabaseMessages"] = []) -> bool:
reply_text = "" # 初始化reply_text变量,避免UnboundLocalError
# 使用sigmoid函数将interest_value转换为概率
@@ -362,22 +276,28 @@ class HeartFChatting:
normal_mode_probability = (
calculate_normal_mode_probability(interest_value)
* 2
- * self.talk_frequency_control.get_current_talk_frequency()
+ * self.frequency_control.get_final_talk_frequency()
)
+
+ #对呼唤名字进行增幅
+ for msg in recent_messages_list:
+ if msg.reply_probability_boost is not None and msg.reply_probability_boost > 0.0:
+ normal_mode_probability += msg.reply_probability_boost
+ if global_config.chat.mentioned_bot_reply and msg.is_mentioned:
+ normal_mode_probability += global_config.chat.mentioned_bot_reply
+ if global_config.chat.at_bot_inevitable_reply and msg.is_at:
+ normal_mode_probability += global_config.chat.at_bot_inevitable_reply
+
- # 根据概率决定使用哪种模式
+ # 根据概率决定使用直接回复
+ interest_triggerd = False
+ focus_triggerd = False
+
if random.random() < normal_mode_probability:
- mode = ChatMode.NORMAL
+ interest_triggerd = True
logger.info(
- f"{self.log_prefix} 有兴趣({interest_value:.2f}),在{normal_mode_probability * 100:.0f}%概率下选择回复"
+ f"{self.log_prefix} 有新消息,在{normal_mode_probability * 100:.0f}%概率下选择回复"
)
- else:
- mode = ChatMode.FOCUS
-
- # 创建新的循环信息
- cycle_timers, thinking_id = self.start_cycle()
-
- logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考")
if s4u_config.enable_s4u:
await send_typing()
@@ -385,30 +305,28 @@ class HeartFChatting:
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
await self.expression_learner.trigger_learning_for_chat()
- # # 记忆构建:为当前chat_id构建记忆
- # try:
- # await hippocampus_manager.build_memory_for_chat(self.stream_id)
- # except Exception as e:
- # logger.error(f"{self.log_prefix} 记忆构建失败: {e}")
-
available_actions: Dict[str, ActionInfo] = {}
- if random.random() > self.focus_value_control.get_current_focus_value() and mode == ChatMode.FOCUS:
- # 如果激活度没有激活,并且聊天活跃度低,有可能不进行plan,相当于不在电脑前,不进行认真思考
- action_to_use_info = [
- ActionPlannerInfo(
- action_type="no_action",
- reasoning="专注不足",
- action_data={},
- )
- ]
- else:
+
+ #如果兴趣度不足以激活
+ if not interest_triggerd:
+ #看看专注值够不够
+ if random.random() < self.frequency_control.get_final_focus_value():
+ #专注值足够,仍然进入正式思考
+ focus_triggerd = True #都没触发,路边
+
+
+ # 任意一种触发都行
+ if interest_triggerd or focus_triggerd:
+ # 进入正式思考模式
+ cycle_timers, thinking_id = self.start_cycle()
+ logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考")
+
# 第一步:动作检查
- with Timer("动作检查", cycle_timers):
- try:
- await self.action_modifier.modify_actions()
- available_actions = self.action_manager.get_using_actions()
- except Exception as e:
- logger.error(f"{self.log_prefix} 动作修改失败: {e}")
+ try:
+ await self.action_modifier.modify_actions()
+ available_actions = self.action_manager.get_using_actions()
+ except Exception as e:
+ logger.error(f"{self.log_prefix} 动作修改失败: {e}")
# 执行planner
is_group_chat, chat_target_info, _ = self.action_planner.get_necessary_info()
@@ -439,103 +357,93 @@ class HeartFChatting:
):
return False
with Timer("规划器", cycle_timers):
+ # 根据不同触发,进入不同plan
+ if focus_triggerd:
+ mode = ChatMode.FOCUS
+ else:
+ mode = ChatMode.NORMAL
+
action_to_use_info, _ = await self.action_planner.plan(
mode=mode,
loop_start_time=self.last_read_time,
available_actions=available_actions,
)
- for action in action_to_use_info:
- print(action.action_type)
+ # 3. 并行执行所有动作
+ action_tasks = [
+ asyncio.create_task(
+ self._execute_action(action, action_to_use_info, thinking_id, available_actions, cycle_timers)
+ )
+ for action in action_to_use_info
+ ]
- # 3. 并行执行所有动作
- action_tasks = [
- asyncio.create_task(
- self._execute_action(action, action_to_use_info, thinking_id, available_actions, cycle_timers)
- )
- for action in action_to_use_info
- ]
+ # 并行执行所有任务
+ results = await asyncio.gather(*action_tasks, return_exceptions=True)
- # 并行执行所有任务
- results = await asyncio.gather(*action_tasks, return_exceptions=True)
+ # 处理执行结果
+ reply_loop_info = None
+ reply_text_from_reply = ""
+ action_success = False
+ action_reply_text = ""
+ action_command = ""
- # 处理执行结果
- reply_loop_info = None
- reply_text_from_reply = ""
- action_success = False
- action_reply_text = ""
- action_command = ""
+ for i, result in enumerate(results):
+ if isinstance(result, BaseException):
+ logger.error(f"{self.log_prefix} 动作执行异常: {result}")
+ continue
- for i, result in enumerate(results):
- if isinstance(result, BaseException):
- logger.error(f"{self.log_prefix} 动作执行异常: {result}")
- continue
+ _cur_action = action_to_use_info[i]
+ if result["action_type"] != "reply":
+ action_success = result["success"]
+ action_reply_text = result["reply_text"]
+ action_command = result.get("command", "")
+ elif result["action_type"] == "reply":
+ if result["success"]:
+ reply_loop_info = result["loop_info"]
+ reply_text_from_reply = result["reply_text"]
+ else:
+ logger.warning(f"{self.log_prefix} 回复动作执行失败")
- _cur_action = action_to_use_info[i]
- if result["action_type"] != "reply":
- action_success = result["success"]
- action_reply_text = result["reply_text"]
- action_command = result.get("command", "")
- elif result["action_type"] == "reply":
- if result["success"]:
- reply_loop_info = result["loop_info"]
- reply_text_from_reply = result["reply_text"]
- else:
- logger.warning(f"{self.log_prefix} 回复动作执行失败")
-
- # 构建最终的循环信息
- if reply_loop_info:
- # 如果有回复信息,使用回复的loop_info作为基础
- loop_info = reply_loop_info
- # 更新动作执行信息
- loop_info["loop_action_info"].update(
- {
- "action_taken": action_success,
- "command": action_command,
- "taken_time": time.time(),
+ # 构建最终的循环信息
+ if reply_loop_info:
+ # 如果有回复信息,使用回复的loop_info作为基础
+ loop_info = reply_loop_info
+ # 更新动作执行信息
+ loop_info["loop_action_info"].update(
+ {
+ "action_taken": action_success,
+ "command": action_command,
+ "taken_time": time.time(),
+ }
+ )
+ reply_text = reply_text_from_reply
+ else:
+ # 没有回复信息,构建纯动作的loop_info
+ loop_info = {
+ "loop_plan_info": {
+ "action_result": action_to_use_info,
+ },
+ "loop_action_info": {
+ "action_taken": action_success,
+ "reply_text": action_reply_text,
+ "command": action_command,
+ "taken_time": time.time(),
+ },
}
- )
- reply_text = reply_text_from_reply
- else:
- # 没有回复信息,构建纯动作的loop_info
- loop_info = {
- "loop_plan_info": {
- "action_result": action_to_use_info,
- },
- "loop_action_info": {
- "action_taken": action_success,
- "reply_text": action_reply_text,
- "command": action_command,
- "taken_time": time.time(),
- },
- }
- reply_text = action_reply_text
+ reply_text = action_reply_text
+
+
+ self.end_cycle(loop_info, cycle_timers)
+ self.print_cycle_info(cycle_timers)
- if s4u_config.enable_s4u:
- await stop_typing()
- await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
+ """S4U内容,暂时保留"""
+ if s4u_config.enable_s4u:
+ await stop_typing()
+ await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
+ """S4U内容,暂时保留"""
- self.end_cycle(loop_info, cycle_timers)
- self.print_cycle_info(cycle_timers)
-
- # await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", ""))
-
- action_type = action_to_use_info[0].action_type if action_to_use_info else "no_action"
-
- # 管理no_action计数器:当执行了非no_action动作时,重置计数器
- if action_type != "no_action":
- # no_action逻辑已集成到heartFC_chat.py中,直接重置计数器
- self.recent_interest_records.clear()
- self.no_action_consecutive = 0
- logger.debug(f"{self.log_prefix} 执行了{action_type}动作,重置no_action计数器")
return True
- if action_type == "no_action":
- self.no_action_consecutive += 1
- self._determine_form_type()
-
- return True
-
async def _main_chat_loop(self):
"""主循环,持续进行计划并可能回复消息,直到被外部取消。"""
try:
diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py
index b1dccdaf..ac424c66 100644
--- a/src/chat/heart_flow/heartflow_message_processor.py
+++ b/src/chat/heart_flow/heartflow_message_processor.py
@@ -32,10 +32,10 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, list[str]]:
Returns:
Tuple[float, bool, list[str]]: (兴趣度, 是否被提及, 关键词)
"""
- if message.is_picid:
+ if message.is_picid or message.is_emoji:
return 0.0, []
- is_mentioned, _ = is_mentioned_bot_in_message(message)
+ is_mentioned,is_at,reply_probability_boost = is_mentioned_bot_in_message(message)
interested_rate = 0.0
with Timer("记忆激活"):
@@ -79,17 +79,13 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, list[str]]:
# 确保在范围内
base_interest = min(max(base_interest, 0.01), 0.3)
- interested_rate += base_interest
-
- if is_mentioned:
- interest_increase_on_mention = 2
- interested_rate += interest_increase_on_mention
-
- message.interest_value = interested_rate
+ message.interest_value = base_interest
message.is_mentioned = is_mentioned
-
- return interested_rate, keywords
+ message.is_at = is_at
+ message.reply_probability_boost = reply_probability_boost
+
+ return base_interest, keywords
class HeartFCMessageReceiver:
diff --git a/src/chat/memory_system/Hippocampus.py b/src/chat/memory_system/Hippocampus.py
index 1b15d717..82901a91 100644
--- a/src/chat/memory_system/Hippocampus.py
+++ b/src/chat/memory_system/Hippocampus.py
@@ -18,6 +18,7 @@ from src.config.config import global_config, model_config
from src.common.data_models.database_data_model import DatabaseMessages
from src.common.database.database_model import GraphNodes, GraphEdges # Peewee Models导入
from src.common.logger import get_logger
+from src.chat.utils.utils import cut_key_words
from src.chat.utils.chat_message_builder import (
build_readable_messages,
get_raw_msg_by_timestamp_with_chat_inclusive,
@@ -98,19 +99,23 @@ class MemoryGraph:
current_weight = self.G.nodes[concept].get("weight", 0.0)
self.G.nodes[concept]["weight"] = current_weight + 1.0
logger.debug(f"节点 {concept} 记忆整合成功,权重增加到 {current_weight + 1.0}")
+ logger.info(f"节点 {concept} 记忆内容已更新:{integrated_memory}")
except Exception as e:
logger.error(f"LLM整合记忆失败: {e}")
# 降级到简单连接
new_memory_str = f"{existing_memory} | {memory}"
self.G.nodes[concept]["memory_items"] = new_memory_str
+ logger.info(f"节点 {concept} 记忆内容已简单拼接并更新:{new_memory_str}")
else:
new_memory_str = str(memory)
self.G.nodes[concept]["memory_items"] = new_memory_str
+ logger.info(f"节点 {concept} 记忆内容已直接更新:{new_memory_str}")
else:
self.G.nodes[concept]["memory_items"] = str(memory)
# 如果节点存在但没有memory_items,说明是第一次添加memory,设置created_time
if "created_time" not in self.G.nodes[concept]:
self.G.nodes[concept]["created_time"] = current_time
+ logger.info(f"节点 {concept} 创建新记忆:{str(memory)}")
# 更新最后修改时间
self.G.nodes[concept]["last_modified"] = current_time
else:
@@ -122,6 +127,7 @@ class MemoryGraph:
created_time=current_time, # 添加创建时间
last_modified=current_time,
) # 添加最后修改时间
+ logger.info(f"新节点 {concept} 已添加,记忆内容已写入:{str(memory)}")
def get_dot(self, concept):
# 检查节点是否存在于图中
@@ -402,9 +408,7 @@ class Hippocampus:
text_length = len(text)
topic_num: int | list[int] = 0
- words = jieba.cut(text)
- keywords_lite = [word for word in words if len(word) > 1]
- keywords_lite = list(set(keywords_lite))
+ keywords_lite = cut_key_words(text)
if keywords_lite:
logger.debug(f"提取关键词极简版: {keywords_lite}")
@@ -1113,6 +1117,7 @@ class ParahippocampalGyrus:
# 4. 创建所有话题的摘要生成任务
tasks: List[Tuple[str, Coroutine[Any, Any, Tuple[str, Tuple[str, str, List | None]]]]] = []
+ topic_what_prompt: str = ""
for topic in filtered_topics:
# 调用修改后的 topic_what,不再需要 time_info
topic_what_prompt = self.hippocampus.topic_what(input_text, topic)
@@ -1159,6 +1164,131 @@ class ParahippocampalGyrus:
return compressed_memory, similar_topics_dict
+ def get_similar_topics_from_keywords(
+ self,
+ keywords: list[str] | str,
+ top_k: int = 3,
+ threshold: float = 0.7,
+ ) -> dict[str, list[tuple[str, float]]]:
+ """基于输入的关键词,返回每个关键词对应的相似主题列表。
+
+ Args:
+ keywords: 关键词列表或以逗号/空格/顿号分隔的字符串。
+ top_k: 每个关键词返回的相似主题数量上限。
+ threshold: 相似度阈值,低于该值的主题将被过滤。
+
+ Returns:
+ dict[str, list[tuple[str, float]]]: {keyword: [(topic, similarity), ...]}
+ """
+ # 规范化输入为列表[str]
+ if isinstance(keywords, str):
+ # 支持中英文逗号、顿号、空格分隔
+ parts = (
+ keywords.replace(",", ",").replace("、", ",").replace(" ", ",").strip(", ")
+ )
+ keyword_list = [p.strip() for p in parts.split(",") if p.strip()]
+ else:
+ keyword_list = [k.strip() for k in keywords if isinstance(k, str) and k.strip()]
+
+ if not keyword_list:
+ return {}
+
+ existing_topics = list(self.memory_graph.G.nodes())
+ result: dict[str, list[tuple[str, float]]] = {}
+
+ for kw in keyword_list:
+ kw_words = set(jieba.cut(kw))
+ similar_topics: list[tuple[str, float]] = []
+
+ for topic in existing_topics:
+ topic_words = set(jieba.cut(topic))
+ all_words = kw_words | topic_words
+ if not all_words:
+ continue
+ v1 = [1 if w in kw_words else 0 for w in all_words]
+ v2 = [1 if w in topic_words else 0 for w in all_words]
+ sim = cosine_similarity(v1, v2)
+ if sim >= threshold:
+ similar_topics.append((topic, sim))
+
+ similar_topics.sort(key=lambda x: x[1], reverse=True)
+ result[kw] = similar_topics[:top_k]
+
+ return result
+
+ async def add_memory_with_similar(
+ self,
+ memory_item: str,
+ similar_topics_dict: dict[str, list[tuple[str, float]]],
+ ) -> bool:
+ """将单条记忆内容与相似主题写入记忆网络并同步数据库。
+
+ 按 build_memory_for_chat 的方式:为 similar_topics_dict 的每个键作为主题添加节点内容,
+ 并与其相似主题建立连接,连接强度为 int(similarity * 10)。
+
+ Args:
+ memory_item: 记忆内容字符串,将作为每个主题节点的 memory_items。
+ similar_topics_dict: {topic: [(similar_topic, similarity), ...]}
+
+ Returns:
+ bool: 是否成功执行添加与同步。
+ """
+ try:
+ if not memory_item or not isinstance(memory_item, str):
+ return False
+
+ if not similar_topics_dict or not isinstance(similar_topics_dict, dict):
+ return False
+
+ current_time = time.time()
+
+ # 为每个主题写入节点
+ for topic, similar_list in similar_topics_dict.items():
+ if not topic or not isinstance(topic, str):
+ continue
+
+ await self.hippocampus.memory_graph.add_dot(topic, memory_item, self.hippocampus)
+
+ # 连接相似主题
+ if isinstance(similar_list, list):
+ for item in similar_list:
+ try:
+ similar_topic, similarity = item
+ except Exception:
+ continue
+ if not isinstance(similar_topic, str):
+ continue
+ if topic == similar_topic:
+ continue
+ # 强度按 build_memory_for_chat 的规则
+ strength = int(max(0.0, float(similarity)) * 10) if similarity is not None else 0
+ if strength <= 0:
+ continue
+ # 确保相似主题节点存在(如果没有,也可以只建立边,networkx会创建节点,但需初始化属性)
+ if similar_topic not in self.memory_graph.G:
+ # 创建一个空的相似主题节点,避免悬空边,memory_items 为空字符串
+ self.memory_graph.G.add_node(
+ similar_topic,
+ memory_items="",
+ weight=1.0,
+ created_time=current_time,
+ last_modified=current_time,
+ )
+ self.memory_graph.G.add_edge(
+ topic,
+ similar_topic,
+ strength=strength,
+ created_time=current_time,
+ last_modified=current_time,
+ )
+
+ # 同步数据库
+ await self.hippocampus.entorhinal_cortex.sync_memory_to_db()
+ return True
+ except Exception as e:
+ logger.error(f"添加记忆节点失败: {e}")
+ return False
+
async def operation_forget_topic(self, percentage=0.005):
start_time = time.time()
logger.info("[遗忘] 开始检查数据库...")
@@ -1325,7 +1455,6 @@ class HippocampusManager:
logger.info(f"""
--------------------------------
记忆系统参数配置:
- 构建频率: {global_config.memory.memory_build_frequency}秒|压缩率: {global_config.memory.memory_compress_rate}
遗忘间隔: {global_config.memory.forget_memory_interval}秒|遗忘比例: {global_config.memory.memory_forget_percentage}|遗忘: {global_config.memory.memory_forget_time}小时之后
记忆图统计信息: 节点数量: {node_count}, 连接数量: {edge_count}
--------------------------------""") # noqa: E501
@@ -1343,61 +1472,6 @@ class HippocampusManager:
raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法")
return await self._hippocampus.parahippocampal_gyrus.operation_forget_topic(percentage)
- async def build_memory_for_chat(self, chat_id: str):
- """为指定chat_id构建记忆(在heartFC_chat.py中调用)"""
- if not self._initialized:
- raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法")
-
- try:
- # 检查是否需要构建记忆
- logger.info(f"为 {chat_id} 构建记忆")
- if memory_segment_manager.check_and_build_memory_for_chat(chat_id):
- logger.info(f"为 {chat_id} 构建记忆,需要构建记忆")
- messages = memory_segment_manager.get_messages_for_memory_build(chat_id, 50)
-
- build_probability = 0.3 * global_config.memory.memory_build_frequency
-
- if messages and random.random() < build_probability:
- logger.info(f"为 {chat_id} 构建记忆,消息数量: {len(messages)}")
-
- # 调用记忆压缩和构建
- (
- compressed_memory,
- similar_topics_dict,
- ) = await self._hippocampus.parahippocampal_gyrus.memory_compress(
- messages, global_config.memory.memory_compress_rate
- )
-
- # 添加记忆节点
- current_time = time.time()
- for topic, memory in compressed_memory:
- await self._hippocampus.memory_graph.add_dot(topic, memory, self._hippocampus)
-
- # 连接相似主题
- if topic in similar_topics_dict:
- similar_topics = similar_topics_dict[topic]
- for similar_topic, similarity in similar_topics:
- if topic != similar_topic:
- strength = int(similarity * 10)
- self._hippocampus.memory_graph.G.add_edge(
- topic,
- similar_topic,
- strength=strength,
- created_time=current_time,
- last_modified=current_time,
- )
-
- # 同步到数据库
- await self._hippocampus.entorhinal_cortex.sync_memory_to_db()
- logger.info(f"为 {chat_id} 构建记忆完成")
- return True
-
- except Exception as e:
- logger.error(f"为 {chat_id} 构建记忆失败: {e}")
- return False
-
- return False
-
async def get_memory_from_topic(
self, valid_keywords: list[str], max_memory_num: int = 3, max_memory_length: int = 2, max_depth: int = 3
) -> list:
@@ -1441,89 +1515,3 @@ class HippocampusManager:
# 创建全局实例
hippocampus_manager = HippocampusManager()
-
-
-# 在Hippocampus类中添加新的记忆构建管理器
-class MemoryBuilder:
- """记忆构建器
-
- 为每个chat_id维护消息缓存和触发机制,类似ExpressionLearner
- """
-
- def __init__(self, chat_id: str):
- self.chat_id = chat_id
- self.last_update_time: float = time.time()
- self.last_processed_time: float = 0.0
-
- def should_trigger_memory_build(self) -> bool:
- # sourcery skip: assign-if-exp, boolean-if-exp-identity, reintroduce-else
- """检查是否应该触发记忆构建"""
- current_time = time.time()
-
- # 检查时间间隔
- time_diff = current_time - self.last_update_time
- if time_diff < 600 / global_config.memory.memory_build_frequency:
- return False
-
- # 检查消息数量
-
- recent_messages = get_raw_msg_by_timestamp_with_chat_inclusive(
- chat_id=self.chat_id,
- timestamp_start=self.last_update_time,
- timestamp_end=current_time,
- )
-
- logger.info(f"最近消息数量: {len(recent_messages)},间隔时间: {time_diff}")
-
- if not recent_messages or len(recent_messages) < 30 / global_config.memory.memory_build_frequency:
- return False
-
- return True
-
- def get_messages_for_memory_build(self, threshold: int = 25) -> List[DatabaseMessages]:
- """获取用于记忆构建的消息"""
- current_time = time.time()
-
- messages = get_raw_msg_by_timestamp_with_chat_inclusive(
- chat_id=self.chat_id,
- timestamp_start=self.last_update_time,
- timestamp_end=current_time,
- limit=threshold,
- )
- if messages:
- # 更新最后处理时间
- self.last_processed_time = current_time
- self.last_update_time = current_time
-
- return messages or []
-
-
-class MemorySegmentManager:
- """记忆段管理器
-
- 管理所有chat_id的MemoryBuilder实例,自动检查和触发记忆构建
- """
-
- def __init__(self):
- self.builders: Dict[str, MemoryBuilder] = {}
-
- def get_or_create_builder(self, chat_id: str) -> MemoryBuilder:
- """获取或创建指定chat_id的MemoryBuilder"""
- if chat_id not in self.builders:
- self.builders[chat_id] = MemoryBuilder(chat_id)
- return self.builders[chat_id]
-
- def check_and_build_memory_for_chat(self, chat_id: str) -> bool:
- """检查指定chat_id是否需要构建记忆,如果需要则返回True"""
- builder = self.get_or_create_builder(chat_id)
- return builder.should_trigger_memory_build()
-
- def get_messages_for_memory_build(self, chat_id: str, threshold: int = 25) -> List[DatabaseMessages]:
- """获取指定chat_id用于记忆构建的消息"""
- if chat_id not in self.builders:
- return []
- return self.builders[chat_id].get_messages_for_memory_build(threshold)
-
-
-# 创建全局实例
-memory_segment_manager = MemorySegmentManager()
diff --git a/src/chat/memory_system/instant_memory.py b/src/chat/memory_system/instant_memory.py
deleted file mode 100644
index f8e91b5c..00000000
--- a/src/chat/memory_system/instant_memory.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# -*- coding: utf-8 -*-
-import time
-import re
-import json
-import ast
-import traceback
-
-from json_repair import repair_json
-from datetime import datetime, timedelta
-
-from src.llm_models.utils_model import LLMRequest
-from src.common.logger import get_logger
-from src.common.database.database_model import Memory # Peewee Models导入
-from src.config.config import model_config, global_config
-
-
-logger = get_logger(__name__)
-
-
-class MemoryItem:
- def __init__(self, memory_id: str, chat_id: str, memory_text: str, keywords: list[str]):
- self.memory_id = memory_id
- self.chat_id = chat_id
- self.memory_text: str = memory_text
- self.keywords: list[str] = keywords
- self.create_time: float = time.time()
- self.last_view_time: float = time.time()
-
-
-class MemoryManager:
- def __init__(self):
- # self.memory_items:list[MemoryItem] = []
- pass
-
-
-class InstantMemory:
- def __init__(self, chat_id):
- self.chat_id = chat_id
- self.last_view_time = time.time()
- self.summary_model = LLMRequest(
- model_set=model_config.model_task_config.utils,
- request_type="memory.summary",
- )
-
- async def if_need_build(self, text: str):
- prompt = f"""
-请判断以下内容中是否有值得记忆的信息,如果有,请输出1,否则输出0
-{text}
-请只输出1或0就好
- """
-
- try:
- response, _ = await self.summary_model.generate_response_async(prompt, temperature=0.5)
- if global_config.debug.show_prompt:
- print(prompt)
- print(response)
-
- return "1" in response
- except Exception as e:
- logger.error(f"判断是否需要记忆出现错误:{str(e)} {traceback.format_exc()}")
- return False
-
- async def build_memory(self, text):
- prompt = f"""
- 以下内容中存在值得记忆的信息,请你从中总结出一段值得记忆的信息,并输出
- {text}
- 请以json格式输出一段概括的记忆内容和关键词
- {{
- "memory_text": "记忆内容",
- "keywords": "关键词,用/划分"
- }}
- """
- try:
- response, _ = await self.summary_model.generate_response_async(prompt, temperature=0.5)
- # print(prompt)
- # print(response)
- if not response:
- return None
- try:
- repaired = repair_json(response)
- result = json.loads(repaired)
- memory_text = result.get("memory_text", "")
- keywords = result.get("keywords", "")
- if isinstance(keywords, str):
- keywords_list = [k.strip() for k in keywords.split("/") if k.strip()]
- elif isinstance(keywords, list):
- keywords_list = keywords
- else:
- keywords_list = []
- return {"memory_text": memory_text, "keywords": keywords_list}
- except Exception as parse_e:
- logger.error(f"解析记忆json失败:{str(parse_e)} {traceback.format_exc()}")
- return None
- except Exception as e:
- logger.error(f"构建记忆出现错误:{str(e)} {traceback.format_exc()}")
- return None
-
- async def create_and_store_memory(self, text: str):
- if_need = await self.if_need_build(text)
- if if_need:
- logger.info(f"需要记忆:{text}")
- memory = await self.build_memory(text)
- if memory and memory.get("memory_text"):
- memory_id = f"{self.chat_id}_{time.time()}"
- memory_item = MemoryItem(
- memory_id=memory_id,
- chat_id=self.chat_id,
- memory_text=memory["memory_text"],
- keywords=memory.get("keywords", []),
- )
- await self.store_memory(memory_item)
- else:
- logger.info(f"不需要记忆:{text}")
-
- async def store_memory(self, memory_item: MemoryItem):
- memory = Memory(
- memory_id=memory_item.memory_id,
- chat_id=memory_item.chat_id,
- memory_text=memory_item.memory_text,
- keywords=memory_item.keywords,
- create_time=memory_item.create_time,
- last_view_time=memory_item.last_view_time,
- )
- memory.save()
-
- async def get_memory(self, target: str):
- from json_repair import repair_json
-
- prompt = f"""
-请根据以下发言内容,判断是否需要提取记忆
-{target}
-请用json格式输出,包含以下字段:
-其中,time的要求是:
-可以选择具体日期时间,格式为YYYY-MM-DD HH:MM:SS,或者大致时间,格式为YYYY-MM-DD
-可以选择相对时间,例如:今天,昨天,前天,5天前,1个月前
-可以选择留空进行模糊搜索
-{{
- "need_memory": 1,
- "keywords": "希望获取的记忆关键词,用/划分",
- "time": "希望获取的记忆大致时间"
-}}
-请只输出json格式,不要输出其他多余内容
-"""
- try:
- response, _ = await self.summary_model.generate_response_async(prompt, temperature=0.5)
- if global_config.debug.show_prompt:
- print(prompt)
- print(response)
- if not response:
- return None
- try:
- repaired = repair_json(response)
- result = json.loads(repaired)
- # 解析keywords
- keywords = result.get("keywords", "")
- if isinstance(keywords, str):
- keywords_list = [k.strip() for k in keywords.split("/") if k.strip()]
- elif isinstance(keywords, list):
- keywords_list = keywords
- else:
- keywords_list = []
- # 解析time为时间段
- time_str = result.get("time", "").strip()
- start_time, end_time = self._parse_time_range(time_str)
- logger.info(f"start_time: {start_time}, end_time: {end_time}")
- # 检索包含关键词的记忆
- memories_set = set()
- if start_time and end_time:
- start_ts = start_time.timestamp()
- end_ts = end_time.timestamp()
- query = Memory.select().where(
- (Memory.chat_id == self.chat_id)
- & (Memory.create_time >= start_ts) # type: ignore
- & (Memory.create_time < end_ts) # type: ignore
- )
- else:
- query = Memory.select().where(Memory.chat_id == self.chat_id)
-
- for mem in query:
- # 对每条记忆
- mem_keywords = mem.keywords or ""
- parsed = ast.literal_eval(mem_keywords)
- if isinstance(parsed, list):
- mem_keywords = [str(k).strip() for k in parsed if str(k).strip()]
- else:
- mem_keywords = []
- # logger.info(f"mem_keywords: {mem_keywords}")
- # logger.info(f"keywords_list: {keywords_list}")
- for kw in keywords_list:
- # logger.info(f"kw: {kw}")
- # logger.info(f"kw in mem_keywords: {kw in mem_keywords}")
- if kw in mem_keywords:
- # logger.info(f"mem.memory_text: {mem.memory_text}")
- memories_set.add(mem.memory_text)
- break
- return list(memories_set)
- except Exception as parse_e:
- logger.error(f"解析记忆json失败:{str(parse_e)} {traceback.format_exc()}")
- return None
- except Exception as e:
- logger.error(f"获取记忆出现错误:{str(e)} {traceback.format_exc()}")
- return None
-
- def _parse_time_range(self, time_str):
- # sourcery skip: extract-duplicate-method, use-contextlib-suppress
- """
- 支持解析如下格式:
- - 具体日期时间:YYYY-MM-DD HH:MM:SS
- - 具体日期:YYYY-MM-DD
- - 相对时间:今天,昨天,前天,N天前,N个月前
- - 空字符串:返回(None, None)
- """
- now = datetime.now()
- if not time_str:
- return 0, now
- time_str = time_str.strip()
- # 具体日期时间
- try:
- dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
- return dt, dt + timedelta(hours=1)
- except Exception:
- pass
- # 具体日期
- try:
- dt = datetime.strptime(time_str, "%Y-%m-%d")
- return dt, dt + timedelta(days=1)
- except Exception:
- pass
- # 相对时间
- if time_str == "今天":
- start = now.replace(hour=0, minute=0, second=0, microsecond=0)
- end = start + timedelta(days=1)
- return start, end
- if time_str == "昨天":
- start = (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
- end = start + timedelta(days=1)
- return start, end
- if time_str == "前天":
- start = (now - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)
- end = start + timedelta(days=1)
- return start, end
- if m := re.match(r"(\d+)天前", time_str):
- days = int(m.group(1))
- start = (now - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
- end = start + timedelta(days=1)
- return start, end
- if m := re.match(r"(\d+)个月前", time_str):
- months = int(m.group(1))
- # 近似每月30天
- start = (now - timedelta(days=months * 30)).replace(hour=0, minute=0, second=0, microsecond=0)
- end = start + timedelta(days=1)
- return start, end
- # 其他无法解析
- return 0, now
diff --git a/src/chat/message_receive/message.py b/src/chat/message_receive/message.py
index 66a1c029..8af56605 100644
--- a/src/chat/message_receive/message.py
+++ b/src/chat/message_receive/message.py
@@ -84,7 +84,7 @@ class Message(MessageBase):
return await self._process_single_segment(segment) # type: ignore
@abstractmethod
- async def _process_single_segment(self, segment):
+ async def _process_single_segment(self, segment) -> str:
pass
@@ -108,6 +108,8 @@ class MessageRecv(Message):
self.has_picid = False
self.is_voice = False
self.is_mentioned = None
+ self.is_at = False
+ self.reply_probability_boost = 0.0
self.is_notify = False
self.is_command = False
@@ -353,44 +355,44 @@ class MessageProcessBase(Message):
self.thinking_time = round(time.time() - self.thinking_start_time, 2)
return self.thinking_time
- async def _process_single_segment(self, seg: Seg) -> str | None:
+ async def _process_single_segment(self, segment: Seg) -> str:
"""处理单个消息段
Args:
- seg: 要处理的消息段
+ segment: 要处理的消息段
Returns:
str: 处理后的文本
"""
try:
- if seg.type == "text":
- return seg.data # type: ignore
- elif seg.type == "image":
+ if segment.type == "text":
+ return segment.data # type: ignore
+ elif segment.type == "image":
# 如果是base64图片数据
- if isinstance(seg.data, str):
- return await get_image_manager().get_image_description(seg.data)
+ if isinstance(segment.data, str):
+ return await get_image_manager().get_image_description(segment.data)
return "[图片,网卡了加载不出来]"
- elif seg.type == "emoji":
- if isinstance(seg.data, str):
- return await get_image_manager().get_emoji_tag(seg.data)
+ elif segment.type == "emoji":
+ if isinstance(segment.data, str):
+ return await get_image_manager().get_emoji_tag(segment.data)
return "[表情,网卡了加载不出来]"
- elif seg.type == "voice":
- if isinstance(seg.data, str):
- return await get_voice_text(seg.data)
+ elif segment.type == "voice":
+ if isinstance(segment.data, str):
+ return await get_voice_text(segment.data)
return "[发了一段语音,网卡了加载不出来]"
- elif seg.type == "at":
- return f"[@{seg.data}]"
- elif seg.type == "reply":
+ elif segment.type == "at":
+ return f"[@{segment.data}]"
+ elif segment.type == "reply":
if self.reply and hasattr(self.reply, "processed_plain_text"):
# print(f"self.reply.processed_plain_text: {self.reply.processed_plain_text}")
# print(f"reply: {self.reply}")
return f"[回复<{self.reply.message_info.user_info.user_nickname}:{self.reply.message_info.user_info.user_id}> 的消息:{self.reply.processed_plain_text}]" # type: ignore
- return None
+ return ""
else:
- return f"[{seg.type}:{str(seg.data)}]"
+ return f"[{segment.type}:{str(segment.data)}]"
except Exception as e:
- logger.error(f"处理消息段失败: {str(e)}, 类型: {seg.type}, 数据: {seg.data}")
- return f"[处理失败的{seg.type}消息]"
+ logger.error(f"处理消息段失败: {str(e)}, 类型: {segment.type}, 数据: {segment.data}")
+ return f"[处理失败的{segment.type}消息]"
def _generate_detailed_text(self) -> str:
"""生成详细文本,包含时间和用户信息"""
diff --git a/src/chat/message_receive/storage.py b/src/chat/message_receive/storage.py
index c9de76ec..3d84f270 100644
--- a/src/chat/message_receive/storage.py
+++ b/src/chat/message_receive/storage.py
@@ -56,6 +56,8 @@ class MessageStorage:
filtered_display_message = ""
interest_value = 0
is_mentioned = False
+ is_at = False
+ reply_probability_boost = 0.0
reply_to = message.reply_to
priority_mode = ""
priority_info = {}
@@ -70,6 +72,8 @@ class MessageStorage:
filtered_display_message = ""
interest_value = message.interest_value
is_mentioned = message.is_mentioned
+ is_at = message.is_at
+ reply_probability_boost = message.reply_probability_boost
reply_to = ""
priority_mode = message.priority_mode
priority_info = message.priority_info
@@ -100,6 +104,8 @@ class MessageStorage:
# Flattened chat_info
reply_to=reply_to,
is_mentioned=is_mentioned,
+ is_at=is_at,
+ reply_probability_boost=reply_probability_boost,
chat_info_stream_id=chat_info_dict.get("stream_id"),
chat_info_platform=chat_info_dict.get("platform"),
chat_info_user_platform=user_info_from_chat.get("platform"),
diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py
index b4587474..1de033bf 100644
--- a/src/chat/planner_actions/action_manager.py
+++ b/src/chat/planner_actions/action_manager.py
@@ -84,7 +84,7 @@ class ActionManager:
log_prefix=log_prefix,
shutting_down=shutting_down,
plugin_config=plugin_config,
- action_message=action_message.flatten() if action_message else None,
+ action_message=action_message,
)
logger.debug(f"创建Action实例成功: {action_name}")
diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py
index 61bc0675..8bbd1632 100644
--- a/src/chat/planner_actions/planner.py
+++ b/src/chat/planner_actions/planner.py
@@ -65,7 +65,6 @@ def init_prompt():
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附和
- 有人提到了你,但是你还没有回应
-- {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
{{
"action": "reply",
@@ -93,7 +92,6 @@ def init_prompt():
现在,最新的聊天消息引起了你的兴趣,你想要对其中的消息进行回复,回复标准如下:
- 你想要闲聊或者随便附和
- 有人提到了你,但是你还没有回应
-- {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
你之前的动作记录:
@@ -465,7 +463,7 @@ class ActionPlanner:
)
)
- logger.info(f"{self.log_prefix}副规划器返回了{len(action_planner_infos)}个action")
+ logger.debug(f"{self.log_prefix}副规划器返回了{len(action_planner_infos)}个action")
return action_planner_infos
async def plan(
@@ -510,7 +508,7 @@ class ActionPlanner:
)
self.last_obs_time_mark = time.time()
-
+ all_sub_planner_results: List[ActionPlannerInfo] = [] # 防止Unbound
try:
sub_planner_actions: Dict[str, ActionInfo] = {}
@@ -553,7 +551,7 @@ class ActionPlanner:
for i, (action_name, action_info) in enumerate(action_items):
sub_planner_lists[i % sub_planner_num].append((action_name, action_info))
- logger.info(
+ logger.debug(
f"{self.log_prefix}成功将{sub_planner_actions_num}个actions分配到{sub_planner_num}个子列表中"
)
for i, action_list in enumerate(sub_planner_lists):
@@ -581,11 +579,10 @@ class ActionPlanner:
sub_plan_results = await asyncio.gather(*sub_plan_tasks)
# 收集所有结果
- all_sub_planner_results: List[ActionPlannerInfo] = []
for sub_result in sub_plan_results:
all_sub_planner_results.extend(sub_result)
- logger.info(f"{self.log_prefix}所有副规划器共返回了{len(all_sub_planner_results)}个action")
+ logger.info(f"{self.log_prefix}小脑决定执行{len(all_sub_planner_results)}个动作")
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
prompt, message_id_list = await self.build_planner_prompt(
@@ -726,9 +723,7 @@ class ActionPlanner:
action_str = ""
for action_planner_info in actions:
action_str += f"{action_planner_info.action_type} "
- logger.info(
- f"{self.log_prefix}大脑小脑决定执行{len(actions)}个动作: {action_str}"
- )
+ logger.info(f"{self.log_prefix}大脑小脑决定执行{len(actions)}个动作: {action_str}")
else:
# 如果为假,只返回副规划器的结果
actions = self._filter_no_actions(all_sub_planner_results)
@@ -777,12 +772,6 @@ class ActionPlanner:
else:
actions_before_now_block = ""
- mentioned_bonus = ""
- if global_config.chat.mentioned_bot_inevitable_reply:
- mentioned_bonus = "\n- 有人提到你"
- if global_config.chat.at_bot_inevitable_reply:
- mentioned_bonus = "\n- 有人提到你,或者at你"
-
chat_context_description = "你现在正在一个群聊中"
chat_target_name = None
if not is_group_chat and chat_target_info:
@@ -838,7 +827,6 @@ class ActionPlanner:
chat_context_description=chat_context_description,
chat_content_block=chat_content_block,
actions_before_now_block=actions_before_now_block,
- mentioned_bonus=mentioned_bonus,
# action_options_text=action_options_block,
moderation_prompt=moderation_prompt_block,
name_block=name_block,
@@ -850,7 +838,6 @@ class ActionPlanner:
time_block=time_block,
chat_context_description=chat_context_description,
chat_content_block=chat_content_block,
- mentioned_bonus=mentioned_bonus,
moderation_prompt=moderation_prompt_block,
name_block=name_block,
actions_before_now_block=actions_before_now_block,
diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py
index 1db4efa6..410fe17c 100644
--- a/src/chat/replyer/default_generator.py
+++ b/src/chat/replyer/default_generator.py
@@ -26,7 +26,6 @@ from src.chat.utils.chat_message_builder import (
)
from src.chat.express.expression_selector import expression_selector
from src.chat.memory_system.memory_activator import MemoryActivator
-from src.chat.memory_system.instant_memory import InstantMemory
from src.mood.mood_manager import mood_manager
from src.person_info.person_info import Person, is_person_known
from src.plugin_system.base.component_types import ActionInfo, EventType
@@ -147,7 +146,6 @@ class DefaultReplyer:
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_stream.stream_id)
self.heart_fc_sender = HeartFCSender()
self.memory_activator = MemoryActivator()
- self.instant_memory = InstantMemory(chat_id=self.chat_stream.stream_id)
from src.plugin_system.core.tool_use import ToolExecutor # 延迟导入ToolExecutor,不然会循环依赖
@@ -375,20 +373,11 @@ class DefaultReplyer:
instant_memory = None
- # running_memories = await self.memory_activator.activate_memory_with_chat_history(
- # target_message=target, chat_history=chat_history
- # )
+ running_memories = await self.memory_activator.activate_memory_with_chat_history(
+ target_message=target, chat_history=chat_history
+ )
running_memories = None
- if global_config.memory.enable_instant_memory:
- chat_history_str = build_readable_messages(
- messages=chat_history, replace_bot_name=True, timestamp_mode="normal"
- )
- asyncio.create_task(self.instant_memory.create_and_store_memory(chat_history_str))
-
- instant_memory = await self.instant_memory.get_memory(target)
- logger.info(f"即时记忆:{instant_memory}")
-
if not running_memories:
return ""
@@ -662,9 +651,11 @@ class DefaultReplyer:
action_name = action_plan_info.action_type
if action_name == "reply":
continue
+ action_description: str = "无描述"
+ reasoning: str = "无原因"
if action := available_actions.get(action_name):
- action_description = action.description or "无描述"
- reasoning = action_plan_info.reasoning or "无原因"
+ action_description = action.description or action_description
+ reasoning = action_plan_info.reasoning or reasoning
chosen_action_descriptions += f"- {action_name}: {action_description},原因:{reasoning}\n"
@@ -716,22 +707,22 @@ class DefaultReplyer:
is_group_chat = bool(chat_stream.group_info)
platform = chat_stream.platform
+ user_id = "用户ID"
+ person_name = "用户"
+ sender = "用户"
+ target = "消息"
+
if reply_message:
user_id = reply_message.user_info.user_id
person = Person(platform=platform, user_id=user_id)
person_name = person.person_name or user_id
sender = person_name
target = reply_message.processed_plain_text
- else:
- person_name = "用户"
- sender = "用户"
- target = "消息"
+ mood_prompt: str = ""
if global_config.mood.enable_mood:
chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
mood_prompt = chat_mood.mood_state
- else:
- mood_prompt = ""
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
@@ -950,9 +941,7 @@ class DefaultReplyer:
else:
chat_target_name = "对方"
if self.chat_target_info:
- chat_target_name = (
- self.chat_target_info.person_name or self.chat_target_info.user_nickname or "对方"
- )
+ chat_target_name = self.chat_target_info.person_name or self.chat_target_info.user_nickname or "对方"
chat_target_1 = await global_prompt_manager.format_prompt(
"chat_target_private1", sender_name=chat_target_name
)
diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py
index 2dbb19a1..1bd72c85 100644
--- a/src/chat/utils/chat_message_builder.py
+++ b/src/chat/utils/chat_message_builder.py
@@ -203,18 +203,21 @@ def get_actions_by_timestamp_with_chat(
query = query.order_by(ActionRecords.time.asc())
actions = list(query)
- return [DatabaseActionRecords(
- action_id=action.action_id,
- time=action.time,
- action_name=action.action_name,
- action_data=action.action_data,
- action_done=action.action_done,
- action_build_into_prompt=action.action_build_into_prompt,
- action_prompt_display=action.action_prompt_display,
- chat_id=action.chat_id,
- chat_info_stream_id=action.chat_info_stream_id,
- chat_info_platform=action.chat_info_platform,
- ) for action in actions]
+ return [
+ DatabaseActionRecords(
+ action_id=action.action_id,
+ time=action.time,
+ action_name=action.action_name,
+ action_data=action.action_data,
+ action_done=action.action_done,
+ action_build_into_prompt=action.action_build_into_prompt,
+ action_prompt_display=action.action_prompt_display,
+ chat_id=action.chat_id,
+ chat_info_stream_id=action.chat_info_stream_id,
+ chat_info_platform=action.chat_info_platform,
+ )
+ for action in actions
+ ]
def get_actions_by_timestamp_with_chat_inclusive(
@@ -474,7 +477,7 @@ def _build_readable_messages_internal(
truncated_content = content
if 0 < limit < original_len:
- truncated_content = f"{content[:limit]}{replace_content}"
+ truncated_content = f"{content[:limit]}{replace_content}" # pyright: ignore[reportPossiblyUnboundVariable]
detailed_message.append((timestamp, name, truncated_content, is_action))
else:
@@ -544,7 +547,7 @@ def build_pic_mapping_info(pic_id_mapping: Dict[str, str]) -> str:
return "\n".join(mapping_lines)
-def build_readable_actions(actions: List[DatabaseActionRecords],mode:str="relative") -> str:
+def build_readable_actions(actions: List[DatabaseActionRecords], mode: str = "relative") -> str:
"""
将动作列表转换为可读的文本格式。
格式: 在()分钟前,你使用了(action_name),具体内容是:(action_prompt_display)
@@ -585,6 +588,8 @@ def build_readable_actions(actions: List[DatabaseActionRecords],mode:str="relati
action_time_struct = time.localtime(action_time)
time_str = time.strftime("%H:%M:%S", action_time_struct)
time_ago_str = f"在{time_str}"
+ else:
+ raise ValueError(f"Unsupported mode: {mode}")
line = f"{time_ago_str},你使用了“{action_name}”,具体内容是:“{action_prompt_display}”"
output_lines.append(line)
diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py
index b489e1e7..79b18906 100644
--- a/src/chat/utils/utils.py
+++ b/src/chat/utils/utils.py
@@ -43,15 +43,15 @@ def db_message_to_str(message_dict: dict) -> str:
return result
-def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
+def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, bool, float]:
"""检查消息是否提到了机器人"""
- keywords = [global_config.bot.nickname]
- nicknames = global_config.bot.alias_names
+ keywords = [global_config.bot.nickname] + list(global_config.bot.alias_names)
reply_probability = 0.0
is_at = False
is_mentioned = False
- if message.is_mentioned is not None:
- return bool(message.is_mentioned), message.is_mentioned
+
+ # 这部分怎么处理啊啊啊啊
+ #我觉得可以给消息加一个 reply_probability_boost字段
if (
message.message_info.additional_config is not None
and message.message_info.additional_config.get("is_mentioned") is not None
@@ -59,18 +59,15 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
try:
reply_probability = float(message.message_info.additional_config.get("is_mentioned")) # type: ignore
is_mentioned = True
- return is_mentioned, reply_probability
+ return is_mentioned, is_at, reply_probability
except Exception as e:
logger.warning(str(e))
logger.warning(
f"消息中包含不合理的设置 is_mentioned: {message.message_info.additional_config.get('is_mentioned')}"
)
- if global_config.bot.nickname in message.processed_plain_text:
- is_mentioned = True
-
- for alias_name in global_config.bot.alias_names:
- if alias_name in message.processed_plain_text:
+ for keyword in keywords:
+ if keyword in message.processed_plain_text:
is_mentioned = True
# 判断是否被@
@@ -78,10 +75,6 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
is_at = True
is_mentioned = True
- # print(f"message.processed_plain_text: {message.processed_plain_text}")
- # print(f"is_mentioned: {is_mentioned}")
- # print(f"is_at: {is_at}")
-
if is_at and global_config.chat.at_bot_inevitable_reply:
reply_probability = 1.0
logger.debug("被@,回复概率设置为100%")
@@ -104,13 +97,10 @@ def is_mentioned_bot_in_message(message: MessageRecv) -> tuple[bool, float]:
for keyword in keywords:
if keyword in message_content:
is_mentioned = True
- for nickname in nicknames:
- if nickname in message_content:
- is_mentioned = True
- if is_mentioned and global_config.chat.mentioned_bot_inevitable_reply:
+ if is_mentioned and global_config.chat.mentioned_bot_reply:
reply_probability = 1.0
logger.debug("被提及,回复概率设置为100%")
- return is_mentioned, reply_probability
+ return is_mentioned, is_at, reply_probability
async def get_embedding(text, request_type="embedding") -> Optional[List[float]]:
@@ -834,3 +824,79 @@ def parse_keywords_string(keywords_input) -> list[str]:
# 如果没有分隔符,返回单个关键词
return [keywords_str] if keywords_str else []
+
+
+
+
+def cut_key_words(concept_name: str) -> list[str]:
+ """对概念名称进行jieba分词,并过滤掉关键词列表中的关键词"""
+ concept_name_tokens = list(jieba.cut(concept_name))
+
+ # 定义常见连词、停用词与标点
+ conjunctions = {
+ "和", "与", "及", "跟", "以及", "并且", "而且", "或", "或者", "并"
+ }
+ stop_words = {
+ "的", "了", "呢", "吗", "吧", "啊", "哦", "恩", "嗯", "呀", "嘛", "哇",
+ "在", "是", "很", "也", "又", "就", "都", "还", "更", "最", "被", "把",
+ "给", "对", "和", "与", "及", "跟", "并", "而且", "或者", "或", "以及"
+ }
+ chinese_punctuations = set(",。!?、;:()【】《》“”‘’—…·-——,.!?;:()[]<>'\"/\\")
+
+ # 清理空白并初步过滤纯标点
+ cleaned_tokens = []
+ for tok in concept_name_tokens:
+ t = tok.strip()
+ if not t:
+ continue
+ # 去除纯标点
+ if all(ch in chinese_punctuations for ch in t):
+ continue
+ cleaned_tokens.append(t)
+
+ # 合并连词两侧的词(仅当两侧都存在且不是标点/停用词时)
+ merged_tokens = []
+ i = 0
+ n = len(cleaned_tokens)
+ while i < n:
+ tok = cleaned_tokens[i]
+ if tok in conjunctions and merged_tokens and i + 1 < n:
+ left = merged_tokens[-1]
+ right = cleaned_tokens[i + 1]
+ # 左右都需要是有效词
+ if left and right \
+ and left not in conjunctions and right not in conjunctions \
+ and left not in stop_words and right not in stop_words \
+ and not all(ch in chinese_punctuations for ch in left) \
+ and not all(ch in chinese_punctuations for ch in right):
+ # 合并为一个新词,并替换掉左侧与跳过右侧
+ combined = f"{left}{tok}{right}"
+ merged_tokens[-1] = combined
+ i += 2
+ continue
+ # 常规推进
+ merged_tokens.append(tok)
+ i += 1
+
+ # 二次过滤:去除停用词、单字符纯标点与无意义项
+ result_tokens = []
+ seen = set()
+ # ban_words = set(getattr(global_config.memory, "memory_ban_words", []) or [])
+ for tok in merged_tokens:
+ if tok in conjunctions:
+ # 独立连词丢弃
+ continue
+ if tok in stop_words:
+ continue
+ # if tok in ban_words:
+ # continue
+ if all(ch in chinese_punctuations for ch in tok):
+ continue
+ if tok.strip() == "":
+ continue
+ if tok not in seen:
+ seen.add(tok)
+ result_tokens.append(tok)
+
+ filtered_concept_name_tokens = result_tokens
+ return filtered_concept_name_tokens
\ No newline at end of file
diff --git a/src/chat/utils/utils_image.py b/src/chat/utils/utils_image.py
index 2bec09be..3c9c51e9 100644
--- a/src/chat/utils/utils_image.py
+++ b/src/chat/utils/utils_image.py
@@ -4,7 +4,6 @@ import time
import hashlib
import uuid
import io
-import asyncio
import numpy as np
from typing import Optional, Tuple
@@ -177,7 +176,7 @@ class ImageManager:
emotion_prompt, temperature=0.3, max_tokens=50
)
- if emotion_result is None:
+ if not emotion_result:
logger.warning("LLM未能生成情感标签,使用详细描述的前几个词")
# 降级处理:从详细描述中提取关键词
import jieba
diff --git a/src/common/data_models/database_data_model.py b/src/common/data_models/database_data_model.py
index b752cbb7..bf4a5f52 100644
--- a/src/common/data_models/database_data_model.py
+++ b/src/common/data_models/database_data_model.py
@@ -67,6 +67,8 @@ class DatabaseMessages(BaseDataModel):
key_words: Optional[str] = None,
key_words_lite: Optional[str] = None,
is_mentioned: Optional[bool] = None,
+ is_at: Optional[bool] = None,
+ reply_probability_boost: Optional[float] = None,
processed_plain_text: Optional[str] = None,
display_message: Optional[str] = None,
priority_mode: Optional[str] = None,
@@ -104,6 +106,9 @@ class DatabaseMessages(BaseDataModel):
self.key_words_lite = key_words_lite
self.is_mentioned = is_mentioned
+ self.is_at = is_at
+ self.reply_probability_boost = reply_probability_boost
+
self.processed_plain_text = processed_plain_text
self.display_message = display_message
@@ -171,6 +176,8 @@ class DatabaseMessages(BaseDataModel):
"key_words": self.key_words,
"key_words_lite": self.key_words_lite,
"is_mentioned": self.is_mentioned,
+ "is_at": self.is_at,
+ "reply_probability_boost": self.reply_probability_boost,
"processed_plain_text": self.processed_plain_text,
"display_message": self.display_message,
"priority_mode": self.priority_mode,
diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py
index 8a6ea8cb..14ce741d 100644
--- a/src/common/database/database_model.py
+++ b/src/common/database/database_model.py
@@ -137,7 +137,8 @@ class Messages(BaseModel):
key_words_lite = TextField(null=True)
is_mentioned = BooleanField(null=True)
-
+ is_at = BooleanField(null=True)
+ reply_probability_boost = DoubleField(null=True)
# 从 chat_info 扁平化而来的字段
chat_info_stream_id = TextField()
chat_info_platform = TextField()
@@ -298,19 +299,6 @@ class GroupInfo(BaseModel):
# database = db # 继承自 BaseModel
table_name = "group_info"
-
-class Memory(BaseModel):
- memory_id = TextField(index=True)
- chat_id = TextField(null=True)
- memory_text = TextField(null=True)
- keywords = TextField(null=True)
- create_time = FloatField(null=True)
- last_view_time = FloatField(null=True)
-
- class Meta:
- table_name = "memory"
-
-
class Expression(BaseModel):
"""
用于存储表达风格的模型。
@@ -377,7 +365,6 @@ def create_tables():
Expression,
GraphNodes, # 添加图节点表
GraphEdges, # 添加图边表
- Memory,
ActionRecords, # 添加 ActionRecords 到初始化列表
]
)
@@ -403,7 +390,6 @@ def initialize_database(sync_constraints=False):
OnlineTime,
PersonInfo,
Expression,
- Memory,
GraphNodes,
GraphEdges,
ActionRecords, # 添加 ActionRecords 到初始化列表
@@ -501,7 +487,6 @@ def sync_field_constraints():
OnlineTime,
PersonInfo,
Expression,
- Memory,
GraphNodes,
GraphEdges,
ActionRecords,
@@ -680,7 +665,6 @@ def check_field_constraints():
OnlineTime,
PersonInfo,
Expression,
- Memory,
GraphNodes,
GraphEdges,
ActionRecords,
diff --git a/src/common/logger.py b/src/common/logger.py
index d2872b4e..ab0fd849 100644
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -355,6 +355,7 @@ MODULE_COLORS = {
# 核心模块
"main": "\033[1;97m", # 亮白色+粗体 (主程序)
+ "memory": "\033[38;5;34m", # 天蓝色
"config": "\033[93m", # 亮黄色
"common": "\033[95m", # 亮紫色
@@ -366,10 +367,9 @@ MODULE_COLORS = {
"llm_models": "\033[36m", # 青色
"remote": "\033[38;5;242m", # 深灰色,更不显眼
"planner": "\033[36m",
- "memory": "\033[38;5;117m", # 天蓝色
- "hfc": "\033[38;5;81m", # 稍微暗一些的青色,保持可读
- "action_manager": "\033[38;5;208m", # 橙色,不与replyer重复
- # 关系系统
+
+
+
"relation": "\033[38;5;139m", # 柔和的紫色,不刺眼
# 聊天相关模块
"normal_chat": "\033[38;5;81m", # 亮蓝绿色
diff --git a/src/config/config.py b/src/config/config.py
index bb12b1d3..99b8d00b 100644
--- a/src/config/config.py
+++ b/src/config/config.py
@@ -56,7 +56,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template")
# 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码
# 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/
-MMC_VERSION = "0.10.1"
+MMC_VERSION = "0.10.2-snapshot.2"
def get_key_comment(toml_table, key):
diff --git a/src/config/official_configs.py b/src/config/official_configs.py
index c99c5dad..4d3758ce 100644
--- a/src/config/official_configs.py
+++ b/src/config/official_configs.py
@@ -60,9 +60,6 @@ class RelationshipConfig(ConfigBase):
enable_relationship: bool = True
"""是否启用关系系统"""
- relation_frequency: int = 1
- """关系频率,麦麦构建关系的速度"""
-
@dataclass
class ChatConfig(ConfigBase):
@@ -74,14 +71,14 @@ class ChatConfig(ConfigBase):
interest_rate_mode: Literal["fast", "accurate"] = "fast"
"""兴趣值计算模式,fast为快速计算,accurate为精确计算"""
- mentioned_bot_inevitable_reply: bool = False
- """提及 bot 必然回复"""
+ mentioned_bot_reply: float = 1
+ """提及 bot 必然回复,1为100%回复,0为不额外增幅"""
planner_size: float = 1.5
"""副规划器大小,越小,麦麦的动作执行能力越精细,但是消耗更多token,调大可以缓解429类错误"""
- at_bot_inevitable_reply: bool = False
- """@bot 必然回复"""
+ at_bot_inevitable_reply: float = 1
+ """@bot 必然回复,1为100%回复,0为不额外增幅"""
talk_frequency: float = 0.5
"""回复频率阈值"""
@@ -336,14 +333,8 @@ class MemoryConfig(ConfigBase):
enable_memory: bool = True
"""是否启用记忆系统"""
-
- memory_build_frequency: int = 1
- """记忆构建频率(秒)"""
- memory_compress_rate: float = 0.1
- """记忆压缩率"""
-
- forget_memory_interval: int = 1000
+ forget_memory_interval: int = 1500
"""记忆遗忘间隔(秒)"""
memory_forget_time: int = 24
@@ -355,9 +346,6 @@ class MemoryConfig(ConfigBase):
memory_ban_words: list[str] = field(default_factory=lambda: ["表情包", "图片", "回复", "聊天记录"])
"""不允许记忆的词列表"""
- enable_instant_memory: bool = True
- """是否启用即时记忆"""
-
@dataclass
class MoodConfig(ConfigBase):
diff --git a/src/llm_models/exceptions.py b/src/llm_models/exceptions.py
index 5b04f58c..ff847ad8 100644
--- a/src/llm_models/exceptions.py
+++ b/src/llm_models/exceptions.py
@@ -96,3 +96,14 @@ class PermissionDeniedException(Exception):
def __str__(self):
return self.message
+
+
+class EmptyResponseException(Exception):
+ """响应内容为空"""
+
+ def __init__(self, message: str = "响应内容为空,这可能是一个临时性问题"):
+ super().__init__(message)
+ self.message = message
+
+ def __str__(self):
+ return self.message
diff --git a/src/llm_models/model_client/gemini_client.py b/src/llm_models/model_client/gemini_client.py
index d253d29c..e58466d1 100644
--- a/src/llm_models/model_client/gemini_client.py
+++ b/src/llm_models/model_client/gemini_client.py
@@ -37,6 +37,7 @@ from ..exceptions import (
NetworkConnectionError,
RespNotOkException,
ReqAbortException,
+ EmptyResponseException,
)
from ..payload_content.message import Message, RoleType
from ..payload_content.resp_format import RespFormat, RespFormatType
@@ -85,6 +86,8 @@ def _convert_messages(
role = "model"
elif message.role == RoleType.User:
role = "user"
+ else:
+ raise ValueError(f"Unsupported role: {message.role}")
# 添加Content
if isinstance(message.content, str):
@@ -224,6 +227,9 @@ def _build_stream_api_resp(
resp.tool_calls.append(ToolCall(call_id, function_name, arguments))
+ if not resp.content and not resp.tool_calls:
+ raise EmptyResponseException()
+
return resp
@@ -284,26 +290,27 @@ def _default_normal_response_parser(
"""
api_response = APIResponse()
- if not hasattr(resp, "candidates") or not resp.candidates:
- raise RespParseException(resp, "响应解析失败,缺失candidates字段")
+ # 解析思考内容
try:
- if resp.candidates[0].content and resp.candidates[0].content.parts:
- for part in resp.candidates[0].content.parts:
- if not part.text:
- continue
- if part.thought:
- api_response.reasoning_content = (
- api_response.reasoning_content + part.text if api_response.reasoning_content else part.text
- )
+ if candidates := resp.candidates:
+ if candidates[0].content and candidates[0].content.parts:
+ for part in candidates[0].content.parts:
+ if not part.text:
+ continue
+ if part.thought:
+ api_response.reasoning_content = (
+ api_response.reasoning_content + part.text if api_response.reasoning_content else part.text
+ )
except Exception as e:
logger.warning(f"解析思考内容时发生错误: {e},跳过解析")
- if resp.text:
- api_response.content = resp.text
+ # 解析响应内容
+ api_response.content = resp.text
- if resp.function_calls:
+ # 解析工具调用
+ if function_calls := resp.function_calls:
api_response.tool_calls = []
- for call in resp.function_calls:
+ for call in function_calls:
try:
if not isinstance(call.args, dict):
raise RespParseException(resp, "响应解析失败,工具调用参数无法解析为字典类型")
@@ -313,17 +320,22 @@ def _default_normal_response_parser(
except Exception as e:
raise RespParseException(resp, "响应解析失败,无法解析工具调用参数") from e
- if resp.usage_metadata:
+ # 解析使用情况
+ if usage_metadata := resp.usage_metadata:
_usage_record = (
- resp.usage_metadata.prompt_token_count or 0,
- (resp.usage_metadata.candidates_token_count or 0) + (resp.usage_metadata.thoughts_token_count or 0),
- resp.usage_metadata.total_token_count or 0,
+ usage_metadata.prompt_token_count or 0,
+ (usage_metadata.candidates_token_count or 0) + (usage_metadata.thoughts_token_count or 0),
+ usage_metadata.total_token_count or 0,
)
else:
_usage_record = None
api_response.raw_data = resp
+ # 最终的、唯一的空响应检查
+ if not api_response.content and not api_response.tool_calls:
+ raise EmptyResponseException("响应中既无文本内容也无工具调用")
+
return api_response, _usage_record
diff --git a/src/llm_models/model_client/openai_client.py b/src/llm_models/model_client/openai_client.py
index bba00f94..51bb692f 100644
--- a/src/llm_models/model_client/openai_client.py
+++ b/src/llm_models/model_client/openai_client.py
@@ -30,6 +30,7 @@ from ..exceptions import (
NetworkConnectionError,
RespNotOkException,
ReqAbortException,
+ EmptyResponseException,
)
from ..payload_content.message import Message, RoleType
from ..payload_content.resp_format import RespFormat
@@ -235,6 +236,9 @@ def _build_stream_api_resp(
resp.tool_calls.append(ToolCall(call_id, function_name, arguments))
+ if not resp.content and not resp.tool_calls:
+ raise EmptyResponseException()
+
return resp
@@ -332,7 +336,7 @@ def _default_normal_response_parser(
api_response = APIResponse()
if not hasattr(resp, "choices") or len(resp.choices) == 0:
- raise RespParseException(resp, "响应解析失败,缺失choices字段")
+ raise EmptyResponseException("响应解析失败,缺失choices字段或choices列表为空")
message_part = resp.choices[0].message
if hasattr(message_part, "reasoning_content") and message_part.reasoning_content: # type: ignore
@@ -377,6 +381,9 @@ def _default_normal_response_parser(
# 将原始响应存储在原始数据中
api_response.raw_data = resp
+ if not api_response.content and not api_response.tool_calls:
+ raise EmptyResponseException()
+
return api_response, _usage_record
diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py
index 1125e9fd..529c52b0 100644
--- a/src/llm_models/utils_model.py
+++ b/src/llm_models/utils_model.py
@@ -14,7 +14,13 @@ from .payload_content.resp_format import RespFormat
from .payload_content.tool_option import ToolOption, ToolCall, ToolOptionBuilder, ToolParamType
from .model_client.base_client import BaseClient, APIResponse, client_registry
from .utils import compress_messages, llm_usage_recorder
-from .exceptions import NetworkConnectionError, ReqAbortException, RespNotOkException, RespParseException
+from .exceptions import (
+ NetworkConnectionError,
+ ReqAbortException,
+ RespNotOkException,
+ RespParseException,
+ EmptyResponseException,
+)
install(extra_lines=3)
@@ -150,19 +156,19 @@ class LLMRequest:
"""
# 请求体构建
start_time = time.time()
-
+
message_builder = MessageBuilder()
message_builder.add_text_content(prompt)
messages = [message_builder.build()]
-
+
tool_built = self._build_tool_options(tools)
-
+
# 模型选择
model_info, api_provider, client = self._select_model()
-
+
# 请求并处理返回值
logger.debug(f"LLM选择耗时: {model_info.name} {time.time() - start_time}")
-
+
response = await self._execute_request(
api_provider=api_provider,
client=client,
@@ -173,8 +179,7 @@ class LLMRequest:
max_tokens=max_tokens,
tool_options=tool_built,
)
-
-
+
content = response.content
reasoning_content = response.reasoning_content or ""
tool_calls = response.tool_calls
@@ -182,7 +187,7 @@ class LLMRequest:
if not reasoning_content and content:
content, extracted_reasoning = self._extract_reasoning(content)
reasoning_content = extracted_reasoning
-
+
if usage := response.usage:
llm_usage_recorder.record_usage_to_database(
model_info=model_info,
@@ -192,14 +197,8 @@ class LLMRequest:
endpoint="/chat/completions",
time_cost=time.time() - start_time,
)
-
- if not content:
- if raise_when_empty:
- logger.warning(f"生成的响应为空, 请求类型: {self.request_type}")
- raise RuntimeError("生成的响应为空")
- content = "生成的响应为空,请检查模型配置或输入内容是否正确"
- return content, (reasoning_content, model_info.name, tool_calls)
+ return content or "", (reasoning_content, model_info.name, tool_calls)
async def get_embedding(self, embedding_input: str) -> Tuple[List[float], str]:
"""获取嵌入向量
@@ -248,11 +247,11 @@ class LLMRequest:
)
model_info = model_config.get_model_info(least_used_model_name)
api_provider = model_config.get_provider(model_info.api_provider)
-
+
# 对于嵌入任务,强制创建新的客户端实例以避免事件循环问题
- force_new_client = (self.request_type == "embedding")
+ force_new_client = self.request_type == "embedding"
client = client_registry.get_client_class_instance(api_provider, force_new=force_new_client)
-
+
logger.debug(f"选择请求模型: {model_info.name}")
total_tokens, penalty, usage_penalty = self.model_usage[model_info.name]
self.model_usage[model_info.name] = (total_tokens, penalty, usage_penalty + 1) # 增加使用惩罚值防止连续使用
@@ -367,6 +366,13 @@ class LLMRequest:
can_retry_msg=f"任务-'{task_name}' 模型-'{model_name}': 连接异常,将于{retry_interval}秒后重试",
cannot_retry_msg=f"任务-'{task_name}' 模型-'{model_name}': 连接异常,超过最大重试次数,请检查网络连接状态或URL是否正确",
)
+ elif isinstance(e, EmptyResponseException): # 空响应错误
+ return self._check_retry(
+ remain_try,
+ retry_interval,
+ can_retry_msg=f"任务-'{task_name}' 模型-'{model_name}': 收到空响应,将于{retry_interval}秒后重试。原因: {e}",
+ cannot_retry_msg=f"任务-'{task_name}' 模型-'{model_name}': 收到空响应,超过最大重试次数,放弃请求",
+ )
elif isinstance(e, ReqAbortException):
logger.warning(f"任务-'{task_name}' 模型-'{model_name}': 请求被中断,详细信息-{str(e.message)}")
return -1, None # 不再重试请求该模型
diff --git a/src/main.py b/src/main.py
index bc90e056..7e14ff7f 100644
--- a/src/main.py
+++ b/src/main.py
@@ -37,10 +37,9 @@ logger = get_logger("main")
class MainSystem:
def __init__(self):
# 根据配置条件性地初始化记忆系统
+ self.hippocampus_manager = None
if global_config.memory.enable_memory:
self.hippocampus_manager = hippocampus_manager
- else:
- self.hippocampus_manager = None
# 使用消息API替代直接的FastAPI实例
self.app: MessageServer = get_global_api()
@@ -81,12 +80,12 @@ class MainSystem:
# 启动API服务器
# start_api_server()
# logger.info("API服务器启动成功")
-
+
# 启动LPMM
lpmm_start_up()
# 加载所有actions,包括默认的和插件的
- plugin_manager.load_all_plugins()
+ plugin_manager.load_all_plugins()
# 初始化表情管理器
get_emoji_manager().initialize()
@@ -114,16 +113,14 @@ class MainSystem:
# 将bot.py中的chat_bot.message_process消息处理函数注册到api.py的消息处理基类中
self.app.register_message_handler(chat_bot.message_process)
-
+
await check_and_run_migrations()
-
# 触发 ON_START 事件
from src.plugin_system.core.events_manager import events_manager
from src.plugin_system.base.component_types import EventType
- await events_manager.handle_mai_events(
- event_type=EventType.ON_START
- )
+
+ await events_manager.handle_mai_events(event_type=EventType.ON_START)
# logger.info("已触发 ON_START 事件")
try:
init_time = int(1000 * (time.time() - init_start_time))
@@ -162,8 +159,6 @@ class MainSystem:
logger.info("[记忆遗忘] 记忆遗忘完成")
-
-
async def main():
"""主函数"""
system = MainSystem()
@@ -175,5 +170,3 @@ async def main():
if __name__ == "__main__":
asyncio.run(main())
-
-
\ No newline at end of file
diff --git a/src/mais4u/mais4u_chat/body_emotion_action_manager.py b/src/mais4u/mais4u_chat/body_emotion_action_manager.py
index 83e6818f..18780686 100644
--- a/src/mais4u/mais4u_chat/body_emotion_action_manager.py
+++ b/src/mais4u/mais4u_chat/body_emotion_action_manager.py
@@ -43,9 +43,9 @@ DEFAULT_BODY_CODE = {
}
-def get_head_code() -> dict:
+async def get_head_code() -> dict:
"""获取头部动作代码字典"""
- head_code_str = global_prompt_manager.get_prompt("head_code_prompt")
+ head_code_str = await global_prompt_manager.format_prompt("head_code_prompt")
if not head_code_str:
return DEFAULT_HEAD_CODE
try:
@@ -55,9 +55,9 @@ def get_head_code() -> dict:
return DEFAULT_HEAD_CODE
-def get_body_code() -> dict:
+async def get_body_code() -> dict:
"""获取身体动作代码字典"""
- body_code_str = global_prompt_manager.get_prompt("body_code_prompt")
+ body_code_str = await global_prompt_manager.format_prompt("body_code_prompt")
if not body_code_str:
return DEFAULT_BODY_CODE
try:
@@ -143,7 +143,7 @@ class ChatAction:
async def send_action_update(self):
"""发送动作更新到前端"""
- body_code = get_body_code().get(self.body_action, "")
+ body_code = (await get_body_code()).get(self.body_action, "")
await send_api.custom_to_stream(
message_type="body_action",
content=body_code,
@@ -184,7 +184,7 @@ class ChatAction:
try:
# 冷却池处理:过滤掉冷却中的动作
self._update_body_action_cooldown()
- available_actions = [k for k in get_body_code().keys() if k not in self.body_action_cooldown]
+ available_actions = [k for k in (await get_body_code()).keys() if k not in self.body_action_cooldown]
all_actions = "\n".join(available_actions)
prompt = await global_prompt_manager.format_prompt(
@@ -246,7 +246,7 @@ class ChatAction:
try:
# 冷却池处理:过滤掉冷却中的动作
self._update_body_action_cooldown()
- available_actions = [k for k in get_body_code().keys() if k not in self.body_action_cooldown]
+ available_actions = [k for k in (await get_body_code()).keys() if k not in self.body_action_cooldown]
all_actions = "\n".join(available_actions)
prompt = await global_prompt_manager.format_prompt(
diff --git a/src/person_info/person_info.py b/src/person_info/person_info.py
index 3b4c1af6..584af8b8 100644
--- a/src/person_info/person_info.py
+++ b/src/person_info/person_info.py
@@ -241,7 +241,7 @@ class Person:
self.name_reason: Optional[str] = None
self.know_times = 0
self.know_since = None
- self.last_know = None
+ self.last_know: Optional[float] = None
self.memory_points = []
# 初始化性格特征相关字段
diff --git a/src/plugin_system/__init__.py b/src/plugin_system/__init__.py
index a102ecd0..535b25d4 100644
--- a/src/plugin_system/__init__.py
+++ b/src/plugin_system/__init__.py
@@ -25,6 +25,7 @@ from .base import (
EventType,
MaiMessages,
ToolParamType,
+ CustomEventHandlerResult,
)
# 导入工具模块
@@ -37,7 +38,7 @@ from .utils import (
from .apis import (
chat_api,
- tool_api,
+ tool_api,
component_manage_api,
config_api,
database_api,
@@ -52,6 +53,15 @@ from .apis import (
get_logger,
)
+from src.common.data_models.database_data_model import (
+ DatabaseMessages,
+ DatabaseUserInfo,
+ DatabaseGroupInfo,
+ DatabaseChatInfo,
+)
+from src.common.data_models.info_data_model import TargetPersonInfo, ActionPlannerInfo
+from src.common.data_models.llm_data_model import LLMGenerationDataModel
+
__version__ = "2.0.0"
@@ -92,6 +102,7 @@ __all__ = [
"ToolParamType",
# 消息
"MaiMessages",
+ "CustomEventHandlerResult",
# 装饰器
"register_plugin",
"ConfigField",
@@ -101,4 +112,12 @@ __all__ = [
# "ManifestGenerator",
# "validate_plugin_manifest",
# "generate_plugin_manifest",
+ # 数据模型
+ "DatabaseMessages",
+ "DatabaseUserInfo",
+ "DatabaseGroupInfo",
+ "DatabaseChatInfo",
+ "TargetPersonInfo",
+ "ActionPlannerInfo",
+ "LLMGenerationDataModel"
]
diff --git a/src/plugin_system/apis/frequency_api.py b/src/plugin_system/apis/frequency_api.py
index 0b0fe3cf..448050b9 100644
--- a/src/plugin_system/apis/frequency_api.py
+++ b/src/plugin_system/apis/frequency_api.py
@@ -1,27 +1,26 @@
from src.common.logger import get_logger
-from src.chat.frequency_control.focus_value_control import focus_value_control
-from src.chat.frequency_control.talk_frequency_control import talk_frequency_control
+from src.chat.frequency_control.frequency_control import frequency_control_manager
logger = get_logger("frequency_api")
def get_current_focus_value(chat_id: str) -> float:
- return focus_value_control.get_focus_value_control(chat_id).get_current_focus_value()
+ return frequency_control_manager.get_or_create_frequency_control(chat_id).get_final_focus_value()
def get_current_talk_frequency(chat_id: str) -> float:
- return talk_frequency_control.get_talk_frequency_control(chat_id).get_current_talk_frequency()
+ return frequency_control_manager.get_or_create_frequency_control(chat_id).get_final_talk_frequency()
def set_focus_value_adjust(chat_id: str, focus_value_adjust: float) -> None:
- focus_value_control.get_focus_value_control(chat_id).focus_value_adjust = focus_value_adjust
+ frequency_control_manager.get_or_create_frequency_control(chat_id).focus_value_external_adjust = focus_value_adjust
def set_talk_frequency_adjust(chat_id: str, talk_frequency_adjust: float) -> None:
- talk_frequency_control.get_talk_frequency_control(chat_id).talk_frequency_adjust = talk_frequency_adjust
+ frequency_control_manager.get_or_create_frequency_control(chat_id).talk_frequency_external_adjust = talk_frequency_adjust
def get_focus_value_adjust(chat_id: str) -> float:
- return focus_value_control.get_focus_value_control(chat_id).focus_value_adjust
+ return frequency_control_manager.get_or_create_frequency_control(chat_id).focus_value_external_adjust
def get_talk_frequency_adjust(chat_id: str) -> float:
- return talk_frequency_control.get_talk_frequency_control(chat_id).talk_frequency_adjust
+ return frequency_control_manager.get_or_create_frequency_control(chat_id).talk_frequency_external_adjust
diff --git a/src/plugin_system/apis/send_api.py b/src/plugin_system/apis/send_api.py
index 4bdab41e..21f764cd 100644
--- a/src/plugin_system/apis/send_api.py
+++ b/src/plugin_system/apis/send_api.py
@@ -99,6 +99,8 @@ async def _send_to_target(
# 创建消息段
message_segment = Seg(type=message_type, data=content) # type: ignore
+ reply_to_platform_id = ""
+ anchor_message: Union["MessageRecv", None] = None
if reply_message:
anchor_message = message_dict_to_message_recv(reply_message.flatten())
if anchor_message:
@@ -107,9 +109,6 @@ async def _send_to_target(
reply_to_platform_id = (
f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}"
)
- else:
- reply_to_platform_id = ""
- anchor_message = None
# 构建发送消息对象
bot_message = MessageSending(
diff --git a/src/plugin_system/base/__init__.py b/src/plugin_system/base/__init__.py
index bc63d35d..19b608e4 100644
--- a/src/plugin_system/base/__init__.py
+++ b/src/plugin_system/base/__init__.py
@@ -23,6 +23,7 @@ from .component_types import (
EventType,
MaiMessages,
ToolParamType,
+ CustomEventHandlerResult,
)
from .config_types import ConfigField
@@ -46,4 +47,5 @@ __all__ = [
"BaseEventHandler",
"MaiMessages",
"ToolParamType",
+ "CustomEventHandlerResult",
]
diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py
index cd686edb..0e58885b 100644
--- a/src/plugin_system/base/base_action.py
+++ b/src/plugin_system/base/base_action.py
@@ -39,7 +39,7 @@ class BaseAction(ABC):
chat_stream: ChatStream,
log_prefix: str = "",
plugin_config: Optional[dict] = None,
- action_message: Optional[dict] = None,
+ action_message: Optional["DatabaseMessages"] = None,
**kwargs,
):
# sourcery skip: hoist-similar-statement-from-if, merge-else-if-into-elif, move-assign-in-block, swap-if-else-branches, swap-nested-ifs
@@ -76,15 +76,19 @@ class BaseAction(ABC):
self.action_require: list[str] = getattr(self.__class__, "action_require", []).copy()
# 设置激活类型实例属性(从类属性复制,提供默认值)
- self.focus_activation_type = getattr(self.__class__, "focus_activation_type", ActionActivationType.ALWAYS) #已弃用
+ self.focus_activation_type = getattr(
+ self.__class__, "focus_activation_type", ActionActivationType.ALWAYS
+ ) # 已弃用
"""FOCUS模式下的激活类型"""
- self.normal_activation_type = getattr(self.__class__, "normal_activation_type", ActionActivationType.ALWAYS) #已弃用
+ self.normal_activation_type = getattr(
+ self.__class__, "normal_activation_type", ActionActivationType.ALWAYS
+ ) # 已弃用
"""NORMAL模式下的激活类型"""
self.activation_type = getattr(self.__class__, "activation_type", self.focus_activation_type)
"""激活类型"""
self.random_activation_probability: float = getattr(self.__class__, "random_activation_probability", 0.0)
"""当激活类型为RANDOM时的概率"""
- self.llm_judge_prompt: str = getattr(self.__class__, "llm_judge_prompt", "") #已弃用
+ self.llm_judge_prompt: str = getattr(self.__class__, "llm_judge_prompt", "") # 已弃用
"""协助LLM进行判断的Prompt"""
self.activation_keywords: list[str] = getattr(self.__class__, "activation_keywords", []).copy()
"""激活类型为KEYWORD时的KEYWORDS列表"""
@@ -114,16 +118,21 @@ class BaseAction(ABC):
if self.action_message:
self.has_action_message = True
- else:
- self.action_message = {}
- if self.has_action_message:
if self.action_name != "no_action":
- self.group_id = str(self.action_message.get("chat_info_group_id", None))
- self.group_name = self.action_message.get("chat_info_group_name", None)
+ self.group_id = (
+ str(self.action_message.chat_info.group_info.group_id)
+ if self.action_message.chat_info.group_info
+ else None
+ )
+ self.group_name = (
+ self.action_message.chat_info.group_info.group_name
+ if self.action_message.chat_info.group_info
+ else None
+ )
- self.user_id = str(self.action_message.get("user_id", None))
- self.user_nickname = self.action_message.get("user_nickname", None)
+ self.user_id = str(self.action_message.user_info.user_id)
+ self.user_nickname = self.action_message.user_info.user_nickname
if self.group_id:
self.is_group = True
self.target_id = self.group_id
diff --git a/src/plugin_system/base/base_event.py b/src/plugin_system/base/base_event.py
deleted file mode 100644
index 6adb333c..00000000
--- a/src/plugin_system/base/base_event.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from typing import TYPE_CHECKING, List, Type
-
-from src.common.logger import get_logger
-from src.plugin_system.base.component_types import EventType, MaiMessages
-
-if TYPE_CHECKING:
- from .base_events_handler import BaseEventHandler
-
-logger = get_logger("base_event")
-
-class BaseEvent:
- def __init__(self, event_type: EventType | str) -> None:
- self.event_type = event_type
- self.subscribers: List["BaseEventHandler"] = []
-
- def register_handler_to_event(self, handler: "BaseEventHandler") -> bool:
- if handler not in self.subscribers:
- self.subscribers.append(handler)
- return True
- logger.warning(f"Handler {handler.handler_name} 已经注册,不可多次注册")
- return False
-
- def remove_handler_from_event(self, handler_class: Type["BaseEventHandler"]) -> bool:
- for handler in self.subscribers:
- if isinstance(handler, handler_class):
- self.subscribers.remove(handler)
- return True
- logger.warning(f"Handler {handler_class.__name__} 未注册,无法移除")
- return False
-
- def trigger_event(self, message: MaiMessages):
- copied_message = message.deepcopy()
- for handler in self.subscribers:
- result = handler.execute(copied_message)
-
- # TODO: Unfinished Events Handler
-
-
\ No newline at end of file
diff --git a/src/plugin_system/base/base_events_handler.py b/src/plugin_system/base/base_events_handler.py
index 630b1ef2..130858e7 100644
--- a/src/plugin_system/base/base_events_handler.py
+++ b/src/plugin_system/base/base_events_handler.py
@@ -1,8 +1,8 @@
from abc import ABC, abstractmethod
-from typing import Tuple, Optional, Dict
+from typing import Tuple, Optional, Dict, List
from src.common.logger import get_logger
-from .component_types import MaiMessages, EventType, EventHandlerInfo, ComponentType
+from .component_types import MaiMessages, EventType, EventHandlerInfo, ComponentType, CustomEventHandlerResult
logger = get_logger("base_event_handler")
@@ -30,16 +30,19 @@ class BaseEventHandler(ABC):
"""对应插件名"""
self.plugin_config: Optional[Dict] = None
"""插件配置字典"""
+ self._events_subscribed: List[EventType | str] = []
if self.event_type == EventType.UNKNOWN:
raise NotImplementedError("事件处理器必须指定 event_type")
@abstractmethod
- async def execute(self, message: MaiMessages | None) -> Tuple[bool, bool, Optional[str]]:
+ async def execute(
+ self, message: MaiMessages | None
+ ) -> Tuple[bool, bool, Optional[str], Optional[CustomEventHandlerResult]]:
"""执行事件处理的抽象方法,子类必须实现
Args:
message (MaiMessages | None): 事件消息对象,当你注册的事件为ON_START和ON_STOP时message为None
Returns:
- Tuple[bool, bool, Optional[str]]: (是否执行成功, 是否需要继续处理, 可选的返回消息)
+ Tuple[bool, bool, Optional[str], Optional[CustomEventHandlerResult]]: (是否执行成功, 是否需要继续处理, 可选的返回消息, 可选的自定义结果)
"""
raise NotImplementedError("子类必须实现 execute 方法")
diff --git a/src/plugin_system/base/component_types.py b/src/plugin_system/base/component_types.py
index d02ad1ef..5473d7f0 100644
--- a/src/plugin_system/base/component_types.py
+++ b/src/plugin_system/base/component_types.py
@@ -285,3 +285,9 @@ class MaiMessages:
def deepcopy(self):
return copy.deepcopy(self)
+
+@dataclass
+class CustomEventHandlerResult:
+ message: str = ""
+ timestamp: float = 0.0
+ extra_info: Optional[Dict] = None
\ No newline at end of file
diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py
index 59a03b73..19fda27e 100644
--- a/src/plugin_system/core/component_registry.py
+++ b/src/plugin_system/core/component_registry.py
@@ -124,6 +124,7 @@ class ComponentRegistry:
self._components_classes[namespaced_name] = component_class
# 根据组件类型进行特定注册(使用原始名称)
+ ret = False
match component_type:
case ComponentType.ACTION:
assert isinstance(component_info, ActionInfo)
diff --git a/src/plugin_system/core/events_manager.py b/src/plugin_system/core/events_manager.py
index b00dfd6f..baada939 100644
--- a/src/plugin_system/core/events_manager.py
+++ b/src/plugin_system/core/events_manager.py
@@ -5,7 +5,7 @@ from typing import List, Dict, Optional, Type, Tuple, TYPE_CHECKING
from src.chat.message_receive.message import MessageRecv
from src.chat.message_receive.chat_stream import get_chat_manager
from src.common.logger import get_logger
-from src.plugin_system.base.component_types import EventType, EventHandlerInfo, MaiMessages
+from src.plugin_system.base.component_types import EventType, EventHandlerInfo, MaiMessages, CustomEventHandlerResult
from src.plugin_system.base.base_events_handler import BaseEventHandler
from .global_announcement_manager import global_announcement_manager
@@ -18,9 +18,23 @@ logger = get_logger("events_manager")
class EventsManager:
def __init__(self):
# 有权重的 events 订阅者注册表
- self._events_subscribers: Dict[EventType | str, List[BaseEventHandler]] = {event: [] for event in EventType}
+ self._events_subscribers: Dict[EventType | str, List[BaseEventHandler]] = {}
self._handler_mapping: Dict[str, Type[BaseEventHandler]] = {} # 事件处理器映射表
self._handler_tasks: Dict[str, List[asyncio.Task]] = {} # 事件处理器正在处理的任务
+ self._events_result_history: Dict[EventType | str, List[CustomEventHandlerResult]] = {} # 事件的结果历史记录
+ self._history_enable_map: Dict[EventType | str, bool] = {} # 是否启用历史记录的映射表,同时作为events注册表
+
+ # 事件注册(同时作为注册样例)
+ for event in EventType:
+ self.register_event(event, enable_history_result=False)
+
+ def register_event(self, event_type: EventType | str, enable_history_result: bool = False):
+ if event_type in self._events_subscribers:
+ raise ValueError(f"事件类型 {event_type} 已存在")
+ self._events_subscribers[event_type] = []
+ self._history_enable_map[event_type] = enable_history_result
+ if enable_history_result:
+ self._events_result_history[event_type] = []
def register_event_subscriber(self, handler_info: EventHandlerInfo, handler_class: Type[BaseEventHandler]) -> bool:
"""注册事件处理器
@@ -32,69 +46,23 @@ class EventsManager:
Returns:
bool: 是否注册成功
"""
+ if not issubclass(handler_class, BaseEventHandler):
+ logger.error(f"类 {handler_class.__name__} 不是 BaseEventHandler 的子类")
+ return False
+
handler_name = handler_info.name
if handler_name in self._handler_mapping:
logger.warning(f"事件处理器 {handler_name} 已存在,跳过注册")
return False
- if not issubclass(handler_class, BaseEventHandler):
- logger.error(f"类 {handler_class.__name__} 不是 BaseEventHandler 的子类")
+ if handler_info.event_type not in self._history_enable_map:
+ logger.error(f"事件类型 {handler_info.event_type} 未注册,无法为其注册处理器 {handler_name}")
return False
self._handler_mapping[handler_name] = handler_class
return self._insert_event_handler(handler_class, handler_info)
- def _prepare_message(
- self,
- event_type: EventType,
- message: Optional[MessageRecv] = None,
- llm_prompt: Optional[str] = None,
- llm_response: Optional["LLMGenerationDataModel"] = None,
- stream_id: Optional[str] = None,
- action_usage: Optional[List[str]] = None,
- ) -> Optional[MaiMessages]:
- """根据事件类型和输入,准备和转换消息对象。"""
- if message:
- return self._transform_event_message(message, llm_prompt, llm_response)
-
- if event_type not in [EventType.ON_START, EventType.ON_STOP]:
- assert stream_id, "如果没有消息,必须为非启动/关闭事件提供流ID"
- if event_type in [EventType.ON_MESSAGE, EventType.ON_PLAN, EventType.POST_LLM, EventType.AFTER_LLM]:
- return self._build_message_from_stream(stream_id, llm_prompt, llm_response)
- else:
- return self._transform_event_without_message(stream_id, llm_prompt, llm_response, action_usage)
-
- return None # ON_START, ON_STOP事件没有消息体
-
- def _dispatch_handler_task(self, handler: BaseEventHandler, message: Optional[MaiMessages]):
- """分发一个非阻塞(异步)的事件处理任务。"""
- try:
- task = asyncio.create_task(handler.execute(message))
-
- task_name = f"{handler.plugin_name}-{handler.handler_name}"
- task.set_name(task_name)
- task.add_done_callback(self._task_done_callback)
-
- self._handler_tasks.setdefault(handler.handler_name, []).append(task)
- except Exception as e:
- logger.error(f"创建事件处理器任务 {handler.handler_name} 时发生异常: {e}", exc_info=True)
-
- async def _dispatch_intercepting_handler(self, handler: BaseEventHandler, message: Optional[MaiMessages]) -> bool:
- """分发并等待一个阻塞(同步)的事件处理器,返回是否应继续处理。"""
- try:
- success, continue_processing, result = await handler.execute(message)
-
- if not success:
- logger.error(f"EventHandler {handler.handler_name} 执行失败: {result}")
- else:
- logger.debug(f"EventHandler {handler.handler_name} 执行成功: {result}")
-
- return continue_processing
- except Exception as e:
- logger.error(f"EventHandler {handler.handler_name} 发生异常: {e}", exc_info=True)
- return True # 发生异常时默认不中断其他处理
-
async def handle_mai_events(
self,
event_type: EventType,
@@ -115,6 +83,8 @@ class EventsManager:
transformed_message = self._prepare_message(
event_type, message, llm_prompt, llm_response, stream_id, action_usage
)
+ if transformed_message:
+ transformed_message = transformed_message.deepcopy()
# 2. 获取并遍历处理器
handlers = self._events_subscribers.get(event_type, [])
@@ -137,16 +107,68 @@ class EventsManager:
handler.set_plugin_config(plugin_config)
# 4. 根据类型分发任务
- if handler.intercept_message:
+ if handler.intercept_message or event_type == EventType.ON_STOP: # 让ON_STOP的所有事件处理器都发挥作用,防止还没执行即被取消
# 阻塞执行,并更新 continue_flag
- should_continue = await self._dispatch_intercepting_handler(handler, transformed_message)
+ should_continue = await self._dispatch_intercepting_handler(handler, event_type, transformed_message)
continue_flag = continue_flag and should_continue
else:
# 异步执行,不阻塞
- self._dispatch_handler_task(handler, transformed_message)
+ self._dispatch_handler_task(handler, event_type, transformed_message)
return continue_flag
+ async def cancel_handler_tasks(self, handler_name: str) -> None:
+ tasks_to_be_cancelled = self._handler_tasks.get(handler_name, [])
+ if remaining_tasks := [task for task in tasks_to_be_cancelled if not task.done()]:
+ for task in remaining_tasks:
+ task.cancel()
+ try:
+ await asyncio.wait_for(asyncio.gather(*remaining_tasks, return_exceptions=True), timeout=5)
+ logger.info(f"已取消事件处理器 {handler_name} 的所有任务")
+ except asyncio.TimeoutError:
+ logger.warning(f"取消事件处理器 {handler_name} 的任务超时,开始强制取消")
+ except Exception as e:
+ logger.error(f"取消事件处理器 {handler_name} 的任务时发生异常: {e}")
+ if handler_name in self._handler_tasks:
+ del self._handler_tasks[handler_name]
+
+ async def unregister_event_subscriber(self, handler_name: str) -> bool:
+ """取消注册事件处理器"""
+ if handler_name not in self._handler_mapping:
+ logger.warning(f"事件处理器 {handler_name} 不存在,无法取消注册")
+ return False
+
+ await self.cancel_handler_tasks(handler_name)
+
+ handler_class = self._handler_mapping.pop(handler_name)
+ if not self._remove_event_handler_instance(handler_class):
+ return False
+
+ logger.info(f"事件处理器 {handler_name} 已成功取消注册")
+ return True
+
+ async def get_event_result_history(self, event_type: EventType | str) -> List[CustomEventHandlerResult]:
+ """获取事件的结果历史记录"""
+ if event_type == EventType.UNKNOWN:
+ raise ValueError("未知事件类型")
+ if event_type not in self._history_enable_map:
+ raise ValueError(f"事件类型 {event_type} 未注册")
+ if not self._history_enable_map[event_type]:
+ raise ValueError(f"事件类型 {event_type} 的历史记录未启用")
+
+ return self._events_result_history[event_type]
+
+ async def clear_event_result_history(self, event_type: EventType | str) -> None:
+ """清空事件的结果历史记录"""
+ if event_type == EventType.UNKNOWN:
+ raise ValueError("未知事件类型")
+ if event_type not in self._history_enable_map:
+ raise ValueError(f"事件类型 {event_type} 未注册")
+ if not self._history_enable_map[event_type]:
+ raise ValueError(f"事件类型 {event_type} 的历史记录未启用")
+
+ self._events_result_history[event_type] = []
+
def _insert_event_handler(self, handler_class: Type[BaseEventHandler], handler_info: EventHandlerInfo) -> bool:
"""插入事件处理器到对应的事件类型列表中并设置其插件配置"""
if handler_class.event_type == EventType.UNKNOWN:
@@ -179,7 +201,10 @@ class EventsManager:
return False
def _transform_event_message(
- self, message: MessageRecv, llm_prompt: Optional[str] = None, llm_response: Optional["LLMGenerationDataModel"] = None
+ self,
+ message: MessageRecv,
+ llm_prompt: Optional[str] = None,
+ llm_response: Optional["LLMGenerationDataModel"] = None,
) -> MaiMessages:
"""转换事件消息格式"""
# 直接赋值部分内容
@@ -263,52 +288,100 @@ class EventsManager:
additional_data={"response_is_processed": True},
)
- def _task_done_callback(self, task: asyncio.Task[Tuple[bool, bool, str | None]]):
+ def _prepare_message(
+ self,
+ event_type: EventType,
+ message: Optional[MessageRecv] = None,
+ llm_prompt: Optional[str] = None,
+ llm_response: Optional["LLMGenerationDataModel"] = None,
+ stream_id: Optional[str] = None,
+ action_usage: Optional[List[str]] = None,
+ ) -> Optional[MaiMessages]:
+ """根据事件类型和输入,准备和转换消息对象。"""
+ if message:
+ return self._transform_event_message(message, llm_prompt, llm_response)
+
+ if event_type not in [EventType.ON_START, EventType.ON_STOP]:
+ assert stream_id, "如果没有消息,必须为非启动/关闭事件提供流ID"
+ if event_type in [EventType.ON_MESSAGE, EventType.ON_PLAN, EventType.POST_LLM, EventType.AFTER_LLM]:
+ return self._build_message_from_stream(stream_id, llm_prompt, llm_response)
+ else:
+ return self._transform_event_without_message(stream_id, llm_prompt, llm_response, action_usage)
+
+ return None # ON_START, ON_STOP事件没有消息体
+
+ def _dispatch_handler_task(
+ self, handler: BaseEventHandler, event_type: EventType | str, message: Optional[MaiMessages] = None
+ ):
+ """分发一个非阻塞(异步)的事件处理任务。"""
+ if event_type == EventType.UNKNOWN:
+ raise ValueError("未知事件类型")
+ try:
+ task = asyncio.create_task(handler.execute(message))
+
+ task_name = f"{handler.plugin_name}-{handler.handler_name}"
+ task.set_name(task_name)
+ task.add_done_callback(lambda t: self._task_done_callback(t, event_type))
+
+ self._handler_tasks.setdefault(handler.handler_name, []).append(task)
+ except Exception as e:
+ logger.error(f"创建事件处理器任务 {handler.handler_name} 时发生异常: {e}", exc_info=True)
+
+ async def _dispatch_intercepting_handler(
+ self, handler: BaseEventHandler, event_type: EventType | str, message: Optional[MaiMessages] = None
+ ) -> bool:
+ """分发并等待一个阻塞(同步)的事件处理器,返回是否应继续处理。"""
+ if event_type == EventType.UNKNOWN:
+ raise ValueError("未知事件类型")
+ if event_type not in self._history_enable_map:
+ raise ValueError(f"事件类型 {event_type} 未注册")
+ try:
+ success, continue_processing, return_message, custom_result = await handler.execute(message)
+
+ if not success:
+ logger.error(f"EventHandler {handler.handler_name} 执行失败: {return_message}")
+ else:
+ logger.debug(f"EventHandler {handler.handler_name} 执行成功: {return_message}")
+
+ if self._history_enable_map[event_type] and custom_result:
+ self._events_result_history[event_type].append(custom_result)
+ return continue_processing
+ except KeyError:
+ logger.error(f"事件 {event_type} 注册的历史记录启用情况与实际不符合")
+ return True
+ except Exception as e:
+ logger.error(f"EventHandler {handler.handler_name} 发生异常: {e}", exc_info=True)
+ return True # 发生异常时默认不中断其他处理
+
+ def _task_done_callback(
+ self,
+ task: asyncio.Task[Tuple[bool, bool, str | None, CustomEventHandlerResult | None]],
+ event_type: EventType | str,
+ ):
"""任务完成回调"""
task_name = task.get_name() or "Unknown Task"
+ if event_type == EventType.UNKNOWN:
+ raise ValueError("未知事件类型")
+ if event_type not in self._history_enable_map:
+ raise ValueError(f"事件类型 {event_type} 未注册")
try:
- success, _, result = task.result() # 忽略是否继续的标志,因为消息本身未被拦截
+ success, _, result, custom_result = task.result() # 忽略是否继续的标志,因为消息本身未被拦截
if success:
logger.debug(f"事件处理任务 {task_name} 已成功完成: {result}")
else:
logger.error(f"事件处理任务 {task_name} 执行失败: {result}")
+
+ if self._history_enable_map[event_type] and custom_result:
+ self._events_result_history[event_type].append(custom_result)
except asyncio.CancelledError:
pass
+ except KeyError:
+ logger.error(f"事件 {event_type} 注册的历史记录启用情况与实际不符合")
except Exception as e:
logger.error(f"事件处理任务 {task_name} 发生异常: {e}")
finally:
with contextlib.suppress(ValueError, KeyError):
self._handler_tasks[task_name].remove(task)
- async def cancel_handler_tasks(self, handler_name: str) -> None:
- tasks_to_be_cancelled = self._handler_tasks.get(handler_name, [])
- if remaining_tasks := [task for task in tasks_to_be_cancelled if not task.done()]:
- for task in remaining_tasks:
- task.cancel()
- try:
- await asyncio.wait_for(asyncio.gather(*remaining_tasks, return_exceptions=True), timeout=5)
- logger.info(f"已取消事件处理器 {handler_name} 的所有任务")
- except asyncio.TimeoutError:
- logger.warning(f"取消事件处理器 {handler_name} 的任务超时,开始强制取消")
- except Exception as e:
- logger.error(f"取消事件处理器 {handler_name} 的任务时发生异常: {e}")
- if handler_name in self._handler_tasks:
- del self._handler_tasks[handler_name]
-
- async def unregister_event_subscriber(self, handler_name: str) -> bool:
- """取消注册事件处理器"""
- if handler_name not in self._handler_mapping:
- logger.warning(f"事件处理器 {handler_name} 不存在,无法取消注册")
- return False
-
- await self.cancel_handler_tasks(handler_name)
-
- handler_class = self._handler_mapping.pop(handler_name)
- if not self._remove_event_handler_instance(handler_class):
- return False
-
- logger.info(f"事件处理器 {handler_name} 已成功取消注册")
- return True
-
events_manager = EventsManager()
diff --git a/src/plugin_system/core/to_do_event.md b/src/plugin_system/core/to_do_event.md
index 11923ff0..bebce6d9 100644
--- a/src/plugin_system/core/to_do_event.md
+++ b/src/plugin_system/core/to_do_event.md
@@ -1,13 +1,12 @@
- [x] 自定义事件
- [ ] 允许handler随时订阅
-- [ ] 允许handler随时取消订阅
-- [ ] 允许其他组件给handler增加订阅
-- [ ] 允许其他组件给handler取消订阅
+- [x] 允许其他组件给handler增加订阅
+- [x] 允许其他组件给handler取消订阅
- [ ] 允许一个handler订阅多个事件
-- [ ] event激活时给handler传递参数
+- [x] event激活时给handler传递参数
- [ ] handler能拿到所有handlers的结果(按照处理权重)
- [x] 随时注册
-- [ ] 删除event
+- [ ] 删除event
- [ ] 必要性?
- [ ] 能够更改prompt
- [ ] 能够更改llm_response
diff --git a/src/plugins/built_in/memory/_manifest.json b/src/plugins/built_in/memory/_manifest.json
new file mode 100644
index 00000000..08a58540
--- /dev/null
+++ b/src/plugins/built_in/memory/_manifest.json
@@ -0,0 +1,34 @@
+{
+ "manifest_version": 1,
+ "name": "Memory Build组件",
+ "version": "1.0.0",
+ "description": "可以构建和管理记忆",
+ "author": {
+ "name": "Mai",
+ "url": "https://github.com/MaiM-with-u"
+ },
+ "license": "GPL-v3.0-or-later",
+
+ "host_application": {
+ "min_version": "0.10.1"
+ },
+ "homepage_url": "https://github.com/MaiM-with-u/maibot",
+ "repository_url": "https://github.com/MaiM-with-u/maibot",
+ "keywords": ["memory", "build", "built-in"],
+ "categories": ["Memory"],
+
+ "default_locale": "zh-CN",
+ "locales_path": "_locales",
+
+ "plugin_info": {
+ "is_built_in": true,
+ "plugin_type": "action_provider",
+ "components": [
+ {
+ "type": "build_memory",
+ "name": "build_memory",
+ "description": "构建记忆"
+ }
+ ]
+ }
+}
diff --git a/src/plugins/built_in/memory/build_memory.py b/src/plugins/built_in/memory/build_memory.py
new file mode 100644
index 00000000..939f6c23
--- /dev/null
+++ b/src/plugins/built_in/memory/build_memory.py
@@ -0,0 +1,134 @@
+from typing import Tuple
+
+from src.common.logger import get_logger
+from src.config.config import global_config
+from src.chat.utils.prompt_builder import Prompt
+from src.plugin_system import BaseAction, ActionActivationType
+from src.chat.memory_system.Hippocampus import hippocampus_manager
+from src.chat.utils.utils import cut_key_words
+
+logger = get_logger("memory")
+
+
+def init_prompt():
+ Prompt(
+ """
+以下是一些记忆条目的分类:
+----------------------
+{category_list}
+----------------------
+每一个分类条目类型代表了你对用户:"{person_name}"的印象的一个类别
+
+现在,你有一条对 {person_name} 的新记忆内容:
+{memory_point}
+
+请判断该记忆内容是否属于上述分类,请给出分类的名称。
+如果不属于上述分类,请输出一个合适的分类名称,对新记忆内容进行概括。要求分类名具有概括性。
+注意分类数一般不超过5个
+请严格用json格式输出,不要输出任何其他内容:
+{{
+ "category": "分类名称"
+}} """,
+ "relation_category",
+ )
+
+ Prompt(
+ """
+以下是有关{category}的现有记忆:
+----------------------
+{memory_list}
+----------------------
+
+现在,你有一条对 {person_name} 的新记忆内容:
+{memory_point}
+
+请判断该新记忆内容是否已经存在于现有记忆中,你可以对现有进行进行以下修改:
+注意,一般来说记忆内容不超过5个,且记忆文本不应太长
+
+1.新增:当记忆内容不存在于现有记忆,且不存在矛盾,请用json格式输出:
+{{
+ "new_memory": "需要新增的记忆内容"
+}}
+2.加深印象:如果这个新记忆已经存在于现有记忆中,在内容上与现有记忆类似,请用json格式输出:
+{{
+ "memory_id": 1, #请输出你认为需要加深印象的,与新记忆内容类似的,已经存在的记忆的序号
+ "integrate_memory": "加深后的记忆内容,合并内容类似的新记忆和旧记忆"
+}}
+3.整合:如果这个新记忆与现有记忆产生矛盾,请你结合其他记忆进行整合,用json格式输出:
+{{
+ "memory_id": 1, #请输出你认为需要整合的,与新记忆存在矛盾的,已经存在的记忆的序号
+ "integrate_memory": "整合后的记忆内容,合并内容矛盾的新记忆和旧记忆"
+}}
+
+现在,请你根据情况选出合适的修改方式,并输出json,不要输出其他内容:
+""",
+ "relation_category_update",
+ )
+
+
+class BuildMemoryAction(BaseAction):
+ """关系动作 - 构建关系"""
+
+ activation_type = ActionActivationType.LLM_JUDGE
+ parallel_action = True
+
+ # 动作基本信息
+ action_name = "build_memory"
+ action_description = "了解对于某个概念或者某件事的记忆,并存储下来,在之后的聊天中,你可以根据这条记忆来获取相关信息"
+
+ # 动作参数定义
+ action_parameters = {
+ "concept_name": "需要了解或记忆的概念或事件的名称",
+ "concept_description": "需要了解或记忆的概念或事件的描述,需要具体且明确",
+ }
+
+ # 动作使用场景
+ action_require = [
+ "了解对于某个概念或者某件事的记忆,并存储下来,在之后的聊天中,你可以根据这条记忆来获取相关信息",
+ "有你不了解的概念",
+ "有人要求你记住某个概念或者事件",
+ "你对某件事或概念有新的理解,或产生了兴趣",
+ ]
+
+ # 关联类型
+ associated_types = ["text"]
+
+ async def execute(self) -> Tuple[bool, str]:
+ """执行关系动作"""
+
+ try:
+ # 1. 获取构建关系的原因
+ concept_description = self.action_data.get("concept_description", "")
+ logger.info(f"{self.log_prefix} 添加记忆原因: {self.reasoning}")
+ concept_name = self.action_data.get("concept_name", "")
+ # 2. 获取目标用户信息
+
+
+
+ # 对 concept_name 进行jieba分词
+ concept_name_tokens = cut_key_words(concept_name)
+ # logger.info(f"{self.log_prefix} 对 concept_name 进行分词结果: {concept_name_tokens}")
+
+ filtered_concept_name_tokens = [
+ token for token in concept_name_tokens if all(keyword not in token for keyword in global_config.memory.memory_ban_words)
+ ]
+
+ if not filtered_concept_name_tokens:
+ logger.warning(f"{self.log_prefix} 过滤后的概念名称列表为空,跳过添加记忆")
+ return False, "过滤后的概念名称列表为空,跳过添加记忆"
+
+ similar_topics_dict = hippocampus_manager.get_hippocampus().parahippocampal_gyrus.get_similar_topics_from_keywords(filtered_concept_name_tokens)
+ await hippocampus_manager.get_hippocampus().parahippocampal_gyrus.add_memory_with_similar(concept_description, similar_topics_dict)
+
+
+
+ return True, f"成功添加记忆: {concept_name}"
+
+ except Exception as e:
+ logger.error(f"{self.log_prefix} 构建记忆时出错: {e}")
+ return False, f"构建记忆时出错: {e}"
+
+
+
+# 还缺一个关系的太多遗忘和对应的提取
+init_prompt()
diff --git a/src/plugins/built_in/memory/plugin.py b/src/plugins/built_in/memory/plugin.py
new file mode 100644
index 00000000..8eaaf900
--- /dev/null
+++ b/src/plugins/built_in/memory/plugin.py
@@ -0,0 +1,58 @@
+from typing import List, Tuple, Type
+
+# 导入新插件系统
+from src.plugin_system import BasePlugin, register_plugin, ComponentInfo
+from src.plugin_system.base.config_types import ConfigField
+
+# 导入依赖的系统组件
+from src.common.logger import get_logger
+
+from src.plugins.built_in.memory.build_memory import BuildMemoryAction
+
+logger = get_logger("relation_actions")
+
+
+@register_plugin
+class MemoryBuildPlugin(BasePlugin):
+ """关系动作插件
+
+ 系统内置插件,提供基础的聊天交互功能:
+ - Reply: 回复动作
+ - NoReply: 不回复动作
+ - Emoji: 表情动作
+
+ 注意:插件基本信息优先从_manifest.json文件中读取
+ """
+
+ # 插件基本信息
+ plugin_name: str = "memory_build" # 内部标识符
+ enable_plugin: bool = True
+ dependencies: list[str] = [] # 插件依赖列表
+ python_dependencies: list[str] = [] # Python包依赖列表
+ config_file_name: str = "config.toml"
+
+ # 配置节描述
+ config_section_descriptions = {
+ "plugin": "插件启用配置",
+ "components": "核心组件启用配置",
+ }
+
+ # 配置Schema定义
+ config_schema: dict = {
+ "plugin": {
+ "enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
+ "config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"),
+ },
+ "components": {
+ "memory_max_memory_num": ConfigField(type=int, default=10, description="记忆最大数量"),
+ },
+ }
+
+ def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
+ """返回插件包含的组件列表"""
+
+ # --- 根据配置注册组件 ---
+ components = []
+ components.append((BuildMemoryAction.get_action_info(), BuildMemoryAction))
+
+ return components
diff --git a/src/plugins/built_in/plugin_management/_manifest.json b/src/plugins/built_in/plugin_management/_manifest.json
index f394b867..a0175d77 100644
--- a/src/plugins/built_in/plugin_management/_manifest.json
+++ b/src/plugins/built_in/plugin_management/_manifest.json
@@ -9,7 +9,7 @@
},
"license": "GPL-v3.0-or-later",
"host_application": {
- "min_version": "0.9.1"
+ "min_version": "0.10.1"
},
"homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot",
diff --git a/src/plugins/built_in/relation/relation.py b/src/plugins/built_in/relation/relation.py
index bab9090d..1f6f0d0f 100644
--- a/src/plugins/built_in/relation/relation.py
+++ b/src/plugins/built_in/relation/relation.py
@@ -1,6 +1,7 @@
import json
from json_repair import repair_json
from typing import Tuple
+import time
from src.common.logger import get_logger
from src.config.config import global_config
@@ -79,16 +80,6 @@ class BuildRelationAction(BaseAction):
action_name = "build_relation"
action_description = "了解对于某人的记忆,并添加到你对对方的印象中"
- # LLM判断提示词
- llm_judge_prompt = """
- 判定是否需要使用关系动作,添加对于某人的记忆:
- 1. 对方与你的交互让你对其有新记忆
- 2. 对方有提到其个人信息,包括喜好,身份,等等
- 3. 对方希望你记住对方的信息
-
- 请回答"是"或"否"。
- """
-
# 动作参数定义
action_parameters = {"person_name": "需要了解或记忆的人的名称", "impression": "需要了解的对某人的记忆或印象"}
@@ -109,13 +100,17 @@ class BuildRelationAction(BaseAction):
try:
# 1. 获取构建关系的原因
impression = self.action_data.get("impression", "")
- logger.info(f"{self.log_prefix} 添加记忆原因: {self.reasoning}")
+ logger.info(f"{self.log_prefix} 添加关系印象原因: {self.reasoning}")
person_name = self.action_data.get("person_name", "")
# 2. 获取目标用户信息
person = Person(person_name=person_name)
if not person.is_known:
logger.warning(f"{self.log_prefix} 用户 {person_name} 不存在,跳过添加记忆")
return False, f"用户 {person_name} 不存在,跳过添加记忆"
+
+ person.last_know = time.time()
+ person.know_times += 1
+ person.sync_to_database()
category_list = person.get_all_category()
if not category_list:
@@ -195,6 +190,8 @@ class BuildRelationAction(BaseAction):
# 新记忆
person.memory_points.append(f"{category}:{new_memory}:1.0")
person.sync_to_database()
+
+ logger.info(f"{self.log_prefix} 为{person.person_name}新增记忆点: {new_memory}")
return True, f"为{person.person_name}新增记忆点: {new_memory}"
elif memory_id and integrate_memory:
@@ -204,12 +201,14 @@ class BuildRelationAction(BaseAction):
del_count = person.del_memory(category, memory_content)
if del_count > 0:
- logger.info(f"{self.log_prefix} 删除记忆点: {memory_content}")
+ # logger.info(f"{self.log_prefix} 删除记忆点: {memory_content}")
memory_weight = get_weight_from_memory(memory)
person.memory_points.append(f"{category}:{integrate_memory}:{memory_weight + 1.0}")
person.sync_to_database()
+ logger.info(f"{self.log_prefix} 更新{person.person_name}的记忆点: {memory_content} -> {integrate_memory}")
+
return True, f"更新{person.person_name}的记忆点: {memory_content} -> {integrate_memory}"
else:
diff --git a/src/plugins/built_in/tts_plugin/plugin.py b/src/plugins/built_in/tts_plugin/plugin.py
index d83fc762..8d772b3e 100644
--- a/src/plugins/built_in/tts_plugin/plugin.py
+++ b/src/plugins/built_in/tts_plugin/plugin.py
@@ -13,21 +13,16 @@ class TTSAction(BaseAction):
"""TTS语音转换动作处理类"""
# 激活设置
- focus_activation_type = ActionActivationType.LLM_JUDGE
- normal_activation_type = ActionActivationType.KEYWORD
+ activation_type = ActionActivationType.LLM_JUDGE
parallel_action = False
# 动作基本信息
action_name = "tts_action"
action_description = "将文本转换为语音进行播放,适用于需要语音输出的场景"
- # 关键词配置 - Normal模式下使用关键词触发
- activation_keywords = ["语音", "tts", "播报", "读出来", "语音播放", "听", "朗读"]
- keyword_case_sensitive = False
-
# 动作参数定义
action_parameters = {
- "text": "需要转换为语音的文本内容,必填,内容应当适合语音播报,语句流畅、清晰",
+ "voice_text": "你想用语音表达的内容,这段内容将会以语音形式发出",
}
# 动作使用场景
@@ -46,7 +41,7 @@ class TTSAction(BaseAction):
logger.info(f"{self.log_prefix} 执行TTS动作: {self.reasoning}")
# 获取要转换的文本
- text = self.action_data.get("text")
+ text = self.action_data.get("voice_text")
if not text:
logger.error(f"{self.log_prefix} 执行TTS动作时未提供文本内容")
diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml
index 97eb5b4a..d2e9a007 100644
--- a/template/bot_config_template.toml
+++ b/template/bot_config_template.toml
@@ -1,5 +1,5 @@
[inner]
-version = "6.7.1"
+version = "6.8.0"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件,请递增version的值
@@ -59,19 +59,17 @@ expression_groups = [
[chat] #麦麦的聊天设置
talk_frequency = 0.5
-# 麦麦活跃度,越高,麦麦回复越多,范围0-1
+# 麦麦活跃度,越高,麦麦越容易回复,范围0-1
focus_value = 0.5
# 麦麦的专注度,越高越容易持续连续对话,可能消耗更多token, 范围0-1
+mentioned_bot_reply = 1 # 提及时,回复概率增幅,1为100%回复,0为不额外增幅
+at_bot_inevitable_reply = 1 # at时,回复概率增幅,1为100%回复,0为不额外增幅
+
max_context_size = 20 # 上下文长度
-interest_rate_mode = "fast" #激活值计算模式,可选fast或者accurate
-
planner_size = 2.5 # 副规划器大小,越小,麦麦的动作执行能力越精细,但是消耗更多token,调大可以缓解429类错误
-mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
-at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
-
focus_value_adjust = [
["", "8:00,1", "12:00,0.8", "18:00,1", "01:00,0.3"],
["qq:114514:group", "12:20,0.6", "16:10,0.5", "20:10,0.8", "00:10,0.3"],
@@ -102,22 +100,8 @@ talk_frequency_adjust = [
# - 后续元素是"时间,频率"格式,表示从该时间开始使用该活跃度,直到下一个时间点
# - 优先级:特定聊天流配置 > 全局配置 > 默认 talk_frequency
-
[relationship]
enable_relationship = true # 是否启用关系系统
-relation_frequency = 1 # 关系频率,麦麦构建关系的频率
-
-[message_receive]
-# 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息
-ban_words = [
- # "403","张三"
- ]
-
-ban_msgs_regex = [
- # 需要过滤的消息(原始消息)匹配的正则表达式,匹配到的消息将被过滤,若不了解正则表达式请勿修改
- #"https?://[^\\s]+", # 匹配https链接
- #"\\d{4}-\\d{2}-\\d{2}", # 匹配日期
-]
[tool]
enable_tool = false # 是否在普通聊天中启用工具
@@ -138,21 +122,30 @@ filtration_prompt = "符合公序良俗" # 表情包过滤要求,只有符合
[memory]
enable_memory = true # 是否启用记忆系统
-memory_build_frequency = 1 # 记忆构建频率 越高,麦麦学习越多
-memory_compress_rate = 0.1 # 记忆压缩率 控制记忆精简程度 建议保持默认,调高可以获得更多信息,但是冗余信息也会增多
-forget_memory_interval = 3000 # 记忆遗忘间隔 单位秒 间隔越低,麦麦遗忘越频繁,记忆更精简,但更难学习
+forget_memory_interval = 1500 # 记忆遗忘间隔 单位秒 间隔越低,麦麦遗忘越频繁,记忆更精简,但更难学习
memory_forget_time = 48 #多长时间后的记忆会被遗忘 单位小时
memory_forget_percentage = 0.008 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认
-enable_instant_memory = false # 是否启用即时记忆,测试功能,可能存在未知问题
-
#不希望记忆的词,已经记忆的不会受到影响,需要手动清理
memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ]
[voice]
enable_asr = false # 是否启用语音识别,启用后麦麦可以识别语音消息,启用该功能需要配置语音识别模型[model.voice]s
+[message_receive]
+# 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息
+ban_words = [
+ # "403","张三"
+ ]
+
+ban_msgs_regex = [
+ # 需要过滤的消息(原始消息)匹配的正则表达式,匹配到的消息将被过滤,若不了解正则表达式请勿修改
+ #"https?://[^\\s]+", # 匹配https链接
+ #"\\d{4}-\\d{2}-\\d{2}", # 匹配日期
+]
+
+
[lpmm_knowledge] # lpmm知识库配置
enable = false # 是否启用lpmm知识库
rag_synonym_search_top_k = 10 # 同义词搜索TopK