feat:记忆遗忘和动态合并阈值

pull/1336/head
SengokuCola 2025-10-30 11:27:01 +08:00
parent 4d5456ed4b
commit e0aa745ba4
4 changed files with 101 additions and 11 deletions

View File

@ -772,7 +772,7 @@ class DefaultReplyer:
continue
timing_logs.append(f"{chinese_name}: {duration:.1f}s")
if duration > 8:
if duration > 12:
logger.warning(f"回复生成前信息获取耗时过长: {chinese_name} 耗时: {duration:.1f}s请使用更快的模型")
logger.info(f"回复准备: {'; '.join(timing_logs)}; {almost_zero_str} <0.1s")

View File

@ -679,7 +679,7 @@ class PrivateReplyer:
continue
timing_logs.append(f"{chinese_name}: {duration:.1f}s")
if duration > 8:
if duration > 12:
logger.warning(f"回复生成前信息获取耗时过长: {chinese_name} 耗时: {duration:.1f}s请使用更快的模型")
logger.info(f"回复准备: {'; '.join(timing_logs)}; {almost_zero_str} <0.1s")

View File

@ -41,6 +41,78 @@ class MemoryChest:
self.running_content_list = {} # {chat_id: {"content": running_content, "last_update_time": timestamp, "create_time": timestamp}}
self.fetched_memory_list = [] # [(chat_id, (question, answer, timestamp)), ...]
def remove_one_memory_by_age_weight(self) -> bool:
"""
删除一条记忆越老/越新更易被删的权重随机选择=较小id=较大id
返回是否删除成功
"""
try:
memories = list(MemoryChestModel.select())
if not memories:
return False
# 排除锁定项
candidates = [m for m in memories if not getattr(m, "locked", False)]
if not candidates:
return False
# 按 id 排序,使用 id 近似时间顺序(小 -> 老,大 -> 新)
candidates.sort(key=lambda m: m.id)
n = len(candidates)
if n == 1:
MemoryChestModel.delete().where(MemoryChestModel.id == candidates[0].id).execute()
logger.info(f"[记忆管理] 已删除一条记忆(权重抽样){candidates[0].title}")
return True
# 计算U型权重中间最低两端最高
# r ∈ [0,1] 为位置归一化w = 0.1 + 0.9 * (abs(r-0.5)*2)**1.5
weights = []
for idx, _m in enumerate(candidates):
r = idx / (n - 1)
w = 0.1 + 0.9 * (abs(r - 0.5) * 2) ** 1.5
weights.append(w)
import random as _random
selected = _random.choices(candidates, weights=weights, k=1)[0]
MemoryChestModel.delete().where(MemoryChestModel.id == selected.id).execute()
logger.info(f"[记忆管理] 已删除一条记忆(权重抽样){selected.title}")
return True
except Exception as e:
logger.error(f"[记忆管理] 按年龄权重删除记忆时出错: {e}")
return False
def _compute_merge_similarity_threshold(self) -> float:
"""
根据当前记忆数量占比动态计算合并相似度阈值
规则占比越高阈值越低
- < 60%: 0.80更严格避免早期误合并
- < 80%: 0.70
- < 100%: 0.60
- < 120%: 0.50
- >= 120%: 0.45最宽松加速收敛
"""
try:
current_count = MemoryChestModel.select().count()
max_count = max(1, int(global_config.memory.max_memory_number))
percentage = current_count / max_count
if percentage < 0.6:
return 0.70
elif percentage < 0.8:
return 0.60
elif percentage < 1.0:
return 0.50
elif percentage < 1.2:
return 0.40
else:
return 0.35
except Exception:
# 发生异常时使用保守阈值
return 0.70
async def build_running_content(self, chat_id: str = None) -> str:
"""
构建记忆仓库的运行内容
@ -446,19 +518,22 @@ class MemoryChest:
logger.warning("未提供chat_id无法进行记忆匹配")
return [], []
# 使用相似度匹配查找最相似的记忆
# 动态计算相似度阈值(占比越高阈值越低)
dynamic_threshold = self._compute_merge_similarity_threshold()
# 使用相似度匹配查找最相似的记忆(基于动态阈值)
similar_memory = find_most_similar_memory_by_chat_id(
target_title=memory_title,
target_chat_id=chat_id,
similarity_threshold=0.5 # 相似度阈值
similarity_threshold=dynamic_threshold
)
if similar_memory:
selected_title, selected_content, similarity = similar_memory
logger.info(f"'{memory_title}' 找到相似记忆: '{selected_title}' (相似度: {similarity:.3f})")
logger.info(f"'{memory_title}' 找到相似记忆: '{selected_title}' (相似度: {similarity:.3f} 阈值: {dynamic_threshold:.2f})")
return [selected_title], [selected_content]
else:
logger.info(f"'{memory_title}' 未找到相似度 >= 0.7 的记忆")
logger.info(f"'{memory_title}' 未找到相似度 >= {dynamic_threshold:.2f} 的记忆")
return [], []
except Exception as e:

View File

@ -8,7 +8,6 @@ from src.memory_system.Memory_chest import global_memory_chest
from src.common.logger import get_logger
from src.common.database.database_model import MemoryChest as MemoryChestModel
from src.config.config import global_config
from src.memory_system.memory_utils import get_all_titles
logger = get_logger("memory")
@ -56,14 +55,14 @@ class MemoryManagementTask(AsyncTask):
current_count = self._get_memory_count()
percentage = current_count / self.max_memory_number
if percentage < 0.5:
if percentage < 0.6:
# 小于50%每600秒执行一次
return 3600
elif percentage < 0.7:
elif percentage < 0.8:
# 大于等于50%每300秒执行一次
return 1800
elif percentage < 0.9:
# 大于等于70%每120秒执行一次
elif percentage < 1.0:
# 大于等于100%每120秒执行一次
return 300
elif percentage < 1.2:
return 30
@ -93,6 +92,22 @@ class MemoryManagementTask(AsyncTask):
percentage = current_count / self.max_memory_number
logger.info(f"当前记忆数量: {current_count}/{self.max_memory_number} ({percentage:.1%})")
# 当占比 > 1.6 时,持续删除直到占比 <= 1.6(越老/越新更易被删)
if percentage > 1.6:
logger.info("记忆过多,开始遗忘记忆")
while True:
if percentage <= 1.6:
break
removed = global_memory_chest.remove_one_memory_by_age_weight()
if not removed:
logger.warning("没有可删除的记忆,停止连续删除")
break
# 重新计算占比
current_count = self._get_memory_count()
percentage = current_count / self.max_memory_number
logger.info(f"遗忘进度: 当前 {current_count}/{self.max_memory_number} ({percentage:.1%})")
logger.info("遗忘记忆结束")
# 如果记忆数量为0跳过执行
if current_count < 10:
return