mirror of https://github.com/Mai-with-u/MaiBot.git
Merge branch 'MaiM-with-u:dev' into dev
commit
35d99e8b79
|
|
@ -64,6 +64,11 @@
|
|||
> - QQ 机器人存在被限制风险,请自行了解,谨慎使用。
|
||||
> - 由于程序处于开发中,可能消耗较多 token。
|
||||
|
||||
## 麦麦MC项目(早期开发)
|
||||
[让麦麦玩MC](https://github.com/MaiM-with-u/Maicraft)
|
||||
|
||||
交流群:1058573197
|
||||
|
||||
## 💬 讨论
|
||||
|
||||
**技术交流群:**
|
||||
|
|
|
|||
2
bot.py
2
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()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,24 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## [0.10.2] - 2025-8-31
|
||||
### 🌟 主要功能更改
|
||||
- 大幅优化了聊天逻辑,更易配置,动态控制
|
||||
- 记忆系统重新启用,更好更优秀
|
||||
- 更好的event系统
|
||||
- 现在支持提及100%回复
|
||||
|
||||
### 细节功能更改
|
||||
- 为空回复添加重试机制
|
||||
- 修复tts插件可能的复读问题
|
||||
|
||||
|
||||
## [0.10.1] - 2025-8-24
|
||||
### 🌟 主要功能更改
|
||||
- planner现在改为大小核结构,移除激活阶段,提高回复速度和动作调用精准度
|
||||
- 优化关系的表现的效率
|
||||
|
||||
### 细节功能更改
|
||||
- 优化识图的表现
|
||||
- 为planner添加单独控制的提示词
|
||||
- 修复激活值计算异常的BUG
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
# ===== 插件注册 =====
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
"""生成详细文本,包含时间和用户信息"""
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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", # 亮蓝绿色
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 # 不再重试请求该模型
|
||||
|
|
|
|||
19
src/main.py
19
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())
|
||||
|
||||
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
||||
# 初始化性格特征相关字段
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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 方法")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
- [x] 自定义事件
|
||||
- [ ] <del>允许handler随时订阅</del>
|
||||
- [ ] 允许handler随时取消订阅
|
||||
- [ ] 允许其他组件给handler增加订阅
|
||||
- [ ] 允许其他组件给handler取消订阅
|
||||
- [x] 允许其他组件给handler增加订阅
|
||||
- [x] 允许其他组件给handler取消订阅
|
||||
- [ ] <del>允许一个handler订阅多个事件</del>
|
||||
- [ ] event激活时给handler传递参数
|
||||
- [x] event激活时给handler传递参数
|
||||
- [ ] handler能拿到所有handlers的结果(按照处理权重)
|
||||
- [x] 随时注册
|
||||
- [ ] 删除event
|
||||
- [ ] <del>删除event</del>
|
||||
- [ ] 必要性?
|
||||
- [ ] 能够更改prompt
|
||||
- [ ] 能够更改llm_response
|
||||
|
|
|
|||
|
|
@ -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": "构建记忆"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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动作时未提供文本内容")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue