Agent也会"记错":智能体记忆的遗忘机制设计(重要性评分+时间衰减+敏感过滤,Python 3.10+实现)
导读 :你的Agent记住了很多东西,但有没有想过------它记住的东西可能是错的、旧的、或者不该记的 ?本文从Loop Engineering 的"状态管理"视角出发,用可运行的Python代码 实现三层遗忘机制:重要性评分淘汰 + 时间衰减降权 + 敏感信息自动过滤。
📅 本文版本:2026年7月 · Python 3.10+ · 仅依赖标准库(无需外部包)
文章目录
- [Agent也会"记错":智能体记忆的遗忘机制设计(重要性评分+时间衰减+敏感过滤,Python 3.10+实现)](#Agent也会"记错":智能体记忆的遗忘机制设计(重要性评分+时间衰减+敏感过滤,Python 3.10+实现))
-
- 一、前置条件与版本说明
- 二、为什么Agent需要"遗忘"?三个被忽视的危险信号
-
- [2.1 记忆过多 = 检索噪音](#2.1 记忆过多 = 检索噪音)
- [2.2 敏感信息泄露风险](#2.2 敏感信息泄露风险)
- [2.3 过时信息误导决策](#2.3 过时信息误导决策)
- 三、三层遗忘机制:从"记住一切"到"智能筛选"
- [四、Layer 1:敏感过滤------危险信息不进记忆库](#四、Layer 1:敏感过滤——危险信息不进记忆库)
- [五、Layer 2:重要性评分------常用记忆保留,冷门记忆淘汰](#五、Layer 2:重要性评分——常用记忆保留,冷门记忆淘汰)
- [六、Layer 3:时间衰减------旧记忆自动"褪色"](#六、Layer 3:时间衰减——旧记忆自动"褪色")
- 七、整合三层:智能遗忘管理器
- [八、与Loop Engineering和内容矩阵的关联](#八、与Loop Engineering和内容矩阵的关联)
-
- [8.1 与Loop Engineering的关联](#8.1 与Loop Engineering的关联)
- [8.2 与已有内容矩阵的闭环](#8.2 与已有内容矩阵的闭环)
- 九、总结
- 十、方案对比与选型建议
-
- [10.1 三种衰减模型对比](#10.1 三种衰减模型对比)
- [10.2 三层遗忘机制选型指南](#10.2 三层遗忘机制选型指南)
- [10.3 适用边界说明](#10.3 适用边界说明)
一、前置条件与版本说明
⚠️ 本文环境 :Python 3.10+(推荐3.11+),re / math / datetime 均为标准库,无需 pip install
⚠️ 适用范围 :本文遗忘机制代码适用于所有基于向量或关键词检索的Agent Memory系统
⚠️ 生产环境提示:Demo代码为教学目的设计,生产环境需补充:持久化存储(SQLite/Redis)、分布式锁、日志审计
bash
# 验证Python版本(需要3.7+,推荐3.11+)
python --version
# 预期输出:Python 3.11.x 或 3.10.x
python
# 验证标准库可用性
import re, math, datetime
print(f"re: {re.__name__}, math: {math.__name__}, datetime: {datetime.__name__}")
# 预期输出:re: re, math: math, datetime: datetime
二、为什么Agent需要"遗忘"?三个被忽视的危险信号
2.1 记忆过多 = 检索噪音
Agent Memory的理想假设是"记住越多,回答越好"。但现实中:
当Agent的记忆库里塞了1000条记录,用户问"我的偏好是什么",向量检索返回的前5条可能包含3条无关的过期信息------记忆越多,检索精度反而越低。
这被称为**"记忆诅咒"**(Curse of Memory):与"维度诅咒"类似,记忆空间膨胀后,语义检索的区分度下降。
2.2 敏感信息泄露风险
Agent记住了一条记忆:
"用户张三的密码是MyP@ssw0rd123,保存在文件/home/zhangsan/.env里"
几个月后,另一个用户(或攻击者通过Prompt注入)问Agent:"之前用户存过什么重要信息?"------Agent可能通过语义检索把这条密码返回到LLM上下文中。
问题不是"记住",而是"没有忘记"。
2.3 过时信息误导决策
Agent记住了:
"公司Q2销售目标是1000万"
但到了Q3,目标已经调整为1200万。Agent如果没有"忘记"旧目标,仍然基于1000万给出建议,导致决策偏差。
| 危险信号 | 表现 | 后果 |
|---|---|---|
| 记忆诅咒 | 检索返回大量无关记忆 | 回答质量下降、token浪费 |
| 敏感泄露 | 密码/密钥被语义检索命中 | 安全事件、合规风险 |
| 过时误导 | 旧规则/旧数据影响当前决策 | 业务损失、用户不满 |
| 存储膨胀 | 记忆库无限增长 | 成本上升、检索速度下降 |
三、三层遗忘机制:从"记住一切"到"智能筛选"
我设计的三层遗忘机制,对应人脑记忆的不同类型:
#mermaid-svg-NPk1NVl86afrY83c{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NPk1NVl86afrY83c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NPk1NVl86afrY83c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NPk1NVl86afrY83c .error-icon{fill:#552222;}#mermaid-svg-NPk1NVl86afrY83c .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NPk1NVl86afrY83c .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NPk1NVl86afrY83c .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NPk1NVl86afrY83c .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NPk1NVl86afrY83c .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NPk1NVl86afrY83c .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NPk1NVl86afrY83c .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NPk1NVl86afrY83c .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NPk1NVl86afrY83c .marker.cross{stroke:#333333;}#mermaid-svg-NPk1NVl86afrY83c svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NPk1NVl86afrY83c p{margin:0;}#mermaid-svg-NPk1NVl86afrY83c .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NPk1NVl86afrY83c .cluster-label text{fill:#333;}#mermaid-svg-NPk1NVl86afrY83c .cluster-label span{color:#333;}#mermaid-svg-NPk1NVl86afrY83c .cluster-label span p{background-color:transparent;}#mermaid-svg-NPk1NVl86afrY83c .label text,#mermaid-svg-NPk1NVl86afrY83c span{fill:#333;color:#333;}#mermaid-svg-NPk1NVl86afrY83c .node rect,#mermaid-svg-NPk1NVl86afrY83c .node circle,#mermaid-svg-NPk1NVl86afrY83c .node ellipse,#mermaid-svg-NPk1NVl86afrY83c .node polygon,#mermaid-svg-NPk1NVl86afrY83c .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NPk1NVl86afrY83c .rough-node .label text,#mermaid-svg-NPk1NVl86afrY83c .node .label text,#mermaid-svg-NPk1NVl86afrY83c .image-shape .label,#mermaid-svg-NPk1NVl86afrY83c .icon-shape .label{text-anchor:middle;}#mermaid-svg-NPk1NVl86afrY83c .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NPk1NVl86afrY83c .rough-node .label,#mermaid-svg-NPk1NVl86afrY83c .node .label,#mermaid-svg-NPk1NVl86afrY83c .image-shape .label,#mermaid-svg-NPk1NVl86afrY83c .icon-shape .label{text-align:center;}#mermaid-svg-NPk1NVl86afrY83c .node.clickable{cursor:pointer;}#mermaid-svg-NPk1NVl86afrY83c .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NPk1NVl86afrY83c .arrowheadPath{fill:#333333;}#mermaid-svg-NPk1NVl86afrY83c .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NPk1NVl86afrY83c .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NPk1NVl86afrY83c .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NPk1NVl86afrY83c .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NPk1NVl86afrY83c .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NPk1NVl86afrY83c .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NPk1NVl86afrY83c .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NPk1NVl86afrY83c .cluster text{fill:#333;}#mermaid-svg-NPk1NVl86afrY83c .cluster span{color:#333;}#mermaid-svg-NPk1NVl86afrY83c div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NPk1NVl86afrY83c .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NPk1NVl86afrY83c rect.text{fill:none;stroke-width:0;}#mermaid-svg-NPk1NVl86afrY83c .icon-shape,#mermaid-svg-NPk1NVl86afrY83c .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NPk1NVl86afrY83c .icon-shape p,#mermaid-svg-NPk1NVl86afrY83c .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NPk1NVl86afrY83c .icon-shape .label rect,#mermaid-svg-NPk1NVl86afrY83c .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NPk1NVl86afrY83c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NPk1NVl86afrY83c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NPk1NVl86afrY83c :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
新记忆写入
Layer 1: 敏感过滤
立即检查敏感信息
是否敏感?
拒绝存储/加密存储
或设置短TTL
Layer 2: 重要性评分
记录访问频率和显式标记
Layer 3: 时间衰减
旧记忆自动降权
定期清理
低分+过期记忆淘汰
| 层级 | 机制 | 类比人脑 | 作用时机 | 解决什么问题 |
|---|---|---|---|---|
| Layer 1 | 敏感过滤 | 选择性注意 | 写入时 | 密码/隐私不进入长期记忆 |
| Layer 2 | 重要性评分 | 重复强化 | 每次访问时 | 常用记忆保留,冷门记忆淘汰 |
| Layer 3 | 时间衰减 | 记忆淡化 | 定期批量 | 旧记忆自动降权,新记忆优先 |
四、Layer 1:敏感过滤------危险信息不进记忆库
目标:在记忆写入的第一时间,检测并拦截敏感信息。
python
from typing import Dict, List, Tuple
import re
from enum import Enum
class SensitivityLevel(Enum):
"""数据敏感度分级(GB/Z 185 6.2)。"""
PUBLIC = "public" # 公开
INTERNAL = "internal" # 内部
CONFIDENTIAL = "confidential" # 机密
RESTRICTED = "restricted" # 受限(应立即拦截)
class SensitiveMemoryFilter:
"""
敏感信息过滤器:在记忆写入前检测敏感内容。
检测模式:
- 密码/密钥:password, secret, api_key, token
- 身份信息:身份证号、手机号、银行卡号
- 企业机密:营收、利润、未公开战略
"""
# 敏感关键词模式
SENSITIVE_PATTERNS = {
"password": [
r"password\s*[:=]\s*\S+",
r"pwd\s*[:=]\s*\S+",
r"密码\s*[:=]\s*\S+",
],
"secret_key": [
r"secret\s*[:=]\s*\S+",
r"api_key\s*[:=]\s*\S+",
r"access_token\s*[:=]\s*\S+",
r"sk-[a-zA-Z0-9]{48}", # OpenAI API Key格式
],
"identity": [
r"\d{18}", # 身份证号
r"1[3-9]\d{9}", # 手机号
r"\d{16,19}", # 银行卡号
],
"financial": [
r"营收\d+\.?\d*\s*[万亿]?",
r"利润\d+\.?\d*\s*[万亿]?",
r"毛利率\s*\d+\.?\d*%",
]
}
def check(self, content: str, metadata: Dict = None) -> Tuple[bool, SensitivityLevel, str]:
"""
检查内容是否包含敏感信息。
Returns:
(is_sensitive, level, reason)
"""
for category, patterns in self.SENSITIVE_PATTERNS.items():
for pattern in patterns:
if re.search(pattern, content, re.IGNORECASE):
# 根据类别判断敏感度
if category in ["password", "secret_key"]:
return True, SensitivityLevel.RESTRICTED, f"检测到{category}: {pattern}"
elif category == "identity":
return True, SensitivityLevel.CONFIDENTIAL, f"检测到身份信息: {pattern}"
else:
return True, SensitivityLevel.INTERNAL, f"检测到{category}: {pattern}"
return False, SensitivityLevel.PUBLIC, "无敏感信息"
def sanitize(self, content: str, level: SensitivityLevel) -> str:
"""
对敏感内容进行脱敏处理。
RESTRICTED: 完全删除,返回空
CONFIDENTIAL: 替换为占位符
INTERNAL: 标记为敏感但保留
"""
if level == SensitivityLevel.RESTRICTED:
# 完全拒绝存储,返回空
return ""
if level == SensitivityLevel.CONFIDENTIAL:
# 替换敏感信息为占位符
sanitized = content
sanitized = re.sub(r"\d{18}", "[身份证号已隐藏]", sanitized)
sanitized = re.sub(r"1[3-9]\d{9}", "[手机号已隐藏]", sanitized)
sanitized = re.sub(r"\d{16,19}", "[银行卡号已隐藏]", sanitized)
return sanitized
# INTERNAL: 添加标记但保留内容
return f"[敏感信息-内部级别] {content}"
# ========== 使用示例 ==========
if __name__ == "__main__":
filter = SensitiveMemoryFilter()
# 场景1:密码信息
is_sensitive, level, reason = filter.check("用户密码: MyP@ssw0rd123")
print(f"密码检测: 敏感={is_sensitive}, 级别={level.value}, 原因={reason}")
print(f" 脱敏后: '{filter.sanitize('用户密码: MyP@ssw0rd123', level)}'")
# 场景2:身份证号
is_sensitive, level, reason = filter.check("用户身份证号: 110101199001011234")
print(f"身份证检测: 敏感={is_sensitive}, 级别={level.value}")
print(f" 脱敏后: '{filter.sanitize('用户身份证号: 110101199001011234', level)}'")
# 场景3:正常信息
is_sensitive, level, reason = filter.check("用户喜欢深色模式")
print(f"正常检测: 敏感={is_sensitive}, 级别={level.value}")
⚠️ 生产环境风险:基于正则的敏感检测无法覆盖所有场景(如Base64编码、分块传输)。建议生产环境补充:① NLP敏感分类模型 ② 加密字段标记(用户手动标记) ③ 定期审计日志。
验证步骤:
bash
python -c "
from sensitive_memory_filter import SensitiveMemoryFilter
f = SensitiveMemoryFilter()
assert f.check('用户密码: 123')[0] == True, '密码检测失败'
assert f.check('用户喜欢深色模式')[0] == False, '正常内容误杀'
print('Layer 1 验证通过')
"
五、Layer 2:重要性评分------常用记忆保留,冷门记忆淘汰
目标:给每条记忆打一个"重要性分数",分数低的定期淘汰。
评分维度:
- 访问频率:被检索/引用的次数越多,越重要
- 显式标记:用户主动标记"重要"的记忆,基础分高
- 关联度:与其他重要记忆有关联的,分数会传染
python
from typing import Optional
from dataclasses import dataclass, field
from datetime import datetime
import math
@dataclass
class MemoryItem:
"""带遗忘机制的记忆项。"""
id: str
content: str
created_at: datetime = field(default_factory=datetime.now)
access_count: int = 0
last_accessed: Optional[datetime] = None
importance_score: float = 0.5 # 0-1,初始0.5
explicit_mark: bool = False # 用户是否显式标记重要
sensitivity: SensitivityLevel = SensitivityLevel.PUBLIC
class ImportanceScorer:
"""
重要性评分器:基于访问频率、显式标记、关联度计算记忆重要性。
"""
def __init__(self):
self.access_weight = 0.4
self.mark_weight = 0.3
self.recency_weight = 0.3
def score(self, memory: MemoryItem) -> float:
"""
计算记忆的重要性分数(0-1)。
公式:
score = access_score * 0.4 + mark_score * 0.3 + recency_score * 0.3
"""
# 1. 访问频率分数(对数压缩,防止极端值)
access_score = min(math.log10(memory.access_count + 1) / 3, 1.0)
# 2. 显式标记分数
mark_score = 1.0 if memory.explicit_mark else 0.0
# 3. 时间衰减分数(越近访问越重要)
if memory.last_accessed:
days_since = (datetime.now() - memory.last_accessed).days
recency_score = math.exp(-days_since / 30) # 30天衰减到1/e
else:
recency_score = 0.1 # 从未被访问过
# 综合评分
total_score = (
access_score * self.access_weight +
mark_score * self.mark_weight +
recency_score * self.recency_weight
)
return min(total_score, 1.0)
def mark_important(self, memory: MemoryItem):
"""用户显式标记记忆为重要。"""
memory.explicit_mark = True
memory.importance_score = self.score(memory)
def record_access(self, memory: MemoryItem):
"""记录一次访问,更新分数。"""
memory.access_count += 1
memory.last_accessed = datetime.now()
memory.importance_score = self.score(memory)
def get_eviction_candidates(self, memories: List[MemoryItem],
threshold: float = 0.3,
max_candidates: int = 10) -> List[MemoryItem]:
"""
获取应该被淘汰的低分记忆。
Args:
threshold: 重要性分数低于此值的记忆被淘汰
max_candidates: 每次最多淘汰多少条
"""
# 重新计算所有分数
for memory in memories:
memory.importance_score = self.score(memory)
# 筛选低分记忆
candidates = [
m for m in memories
if m.importance_score < threshold and not m.explicit_mark
]
# 按分数排序,淘汰最低分的
candidates.sort(key=lambda m: m.importance_score)
return candidates[:max_candidates]
# ========== 使用示例 ==========
if __name__ == "__main__":
scorer = ImportanceScorer()
# 创建模拟记忆
memories = [
MemoryItem(id="1", content="用户喜欢深色模式", access_count=50, last_accessed=datetime.now()),
MemoryItem(id="2", content="用户去年用过的一次性配置", access_count=1, last_accessed=datetime(2025, 1, 1)),
MemoryItem(id="3", content="用户显式标记的重要项目规范", access_count=5, explicit_mark=True),
MemoryItem(id="4", content="用户上周问过的一个边缘问题", access_count=2, last_accessed=datetime(2026, 6, 20)),
]
# 计算分数
print("=== 记忆重要性评分 ===")
for memory in memories:
score = scorer.score(memory)
print(f"[{score:.2f}] {memory.content[:30]}... (访问{memory.access_count}次)")
# 获取淘汰候选
candidates = scorer.get_eviction_candidates(memories, threshold=0.3)
print(f"\n=== 建议淘汰的记忆 ===")
for c in candidates:
print(f"[{c.importance_score:.2f}] {c.content}")
⚠️ 使用风险:分数阈值(threshold=0.3)需根据业务场景调整。设置过高→误删重要记忆;设置过低→淘汰不力。建议:首次设为0.3,运行一周后根据"被误删投诉率"调整。
验证步骤:
bash
python -c "
from importance_scorer import ImportanceScorer, MemoryItem
from datetime import datetime
s = ImportanceScorer()
m = MemoryItem(id='t', content='test', access_count=10, last_accessed=datetime.now())
assert s.score(m) > 0.3, '高频访问记忆分数应高于0.3'
m2 = MemoryItem(id='t2', content='test2', access_count=0, last_accessed=datetime(2020,1,1))
assert s.score(m2) < 0.3, '低频过期记忆分数应低于0.3'
print('Layer 2 验证通过')
"
六、Layer 3:时间衰减------旧记忆自动"褪色"
目标:让记忆像人脑一样"新学的东西记得牢,旧知识逐渐模糊"。
衰减函数选择:
| 衰减模型 | 公式 | 特点 | 适用场景 |
|---|---|---|---|
| 指数衰减 | score = initial * e^(-t/λ) | 平滑连续,自然遗忘 | 一般记忆 |
| 阶梯衰减 | score = initial * (1 - 0.1 * t/7) | 每周降一档,可控 | 业务规则 |
| 幂律衰减 | score = initial * t^(-α) | 前期快、后期慢 | 热点信息 |
| 阈值淘汰 | t > TTL → 直接删除 | 简单直接 | 临时数据 |
python
import math
from datetime import datetime, timedelta
class TimeDecayManager:
"""
时间衰减管理器:基于遗忘曲线对记忆进行降权。
"""
def __init__(self, half_life_days: float = 30.0):
"""
half_life_days: 半衰期(分数降到初始值一半所需天数)
"""
self.half_life = half_life_days
self.decay_constant = math.log(2) / half_life_days
def calculate_decay(self, memory: MemoryItem, current_time: datetime = None) -> float:
"""
计算记忆的时间衰减系数。
返回0-1之间的值:1表示未衰减,0表示完全遗忘。
"""
if current_time is None:
current_time = datetime.now()
age_days = (current_time - memory.created_at).days
# 指数衰减
decay_factor = math.exp(-self.decay_constant * age_days)
# 如果记忆被频繁访问,衰减变慢(每次访问重置半衰期)
if memory.last_accessed:
days_since_access = (current_time - memory.last_accessed).days
recency_boost = math.exp(-days_since_access / 7) # 7天内访问过的记忆不衰减
decay_factor = max(decay_factor, recency_boost * 0.5)
return decay_factor
def apply_decay(self, memory: MemoryItem, current_time: datetime = None) -> float:
"""
应用时间衰减到记忆的重要性分数。
返回衰减后的分数。
"""
decay_factor = self.calculate_decay(memory, current_time)
decayed_score = memory.importance_score * decay_factor
return min(decayed_score, 1.0)
def should_forget(self, memory: MemoryItem,
threshold: float = 0.1,
current_time: datetime = None) -> bool:
"""
判断记忆是否应该被"遗忘"(淘汰)。
标准:衰减后的分数低于阈值,且未被显式标记为重要。
"""
if memory.explicit_mark:
return False # 显式标记的记忆不遗忘
decayed_score = self.apply_decay(memory, current_time)
return decayed_score < threshold
# ========== 使用示例:艾宾浩斯遗忘曲线 ==========
if __name__ == "__main__":
decay_manager = TimeDecayManager(half_life_days=30)
# 模拟一条记忆随时间的衰减
memory = MemoryItem(
id="test",
content="用户的重要项目规范",
created_at=datetime(2026, 1, 1),
importance_score=0.8,
access_count=0
)
print("=== 记忆时间衰减模拟 ===")
for month in range(1, 13):
current = datetime(2026, month, 1)
decay_factor = decay_manager.calculate_decay(memory, current)
decayed_score = decay_manager.apply_decay(memory, current)
should_forget = decay_manager.should_forget(memory, threshold=0.1, current_time=current)
status = "🗑️ 遗忘" if should_forget else "📌 保留"
print(f"{current.strftime('%Y-%m')}: 衰减系数={decay_factor:.3f}, 分数={decayed_score:.3f} {status}")
# 对比:如果中途访问一次,衰减变慢
print("\n=== 中途访问后的衰减 ===")
memory.access_count = 1
memory.last_accessed = datetime(2026, 3, 15) # 3月访问过一次
for month in [6, 9, 12]:
current = datetime(2026, month, 1)
decayed_score = decay_manager.apply_decay(memory, current)
print(f"{current.strftime('%Y-%m')}: 分数={decayed_score:.3f} (因3月访问过,衰减变慢)")
⚠️ 半衰期调优:half_life_days=30 适用于一般业务场景。高频业务(如客服对话)建议7天;低频业务(如年度规范)建议90天。半衰期过短会导致"学到就忘",建议结合A/B测试确定最优值。
验证步骤:
bash
python -c "
from time_decay_manager import TimeDecayManager, MemoryItem
from datetime import datetime
dm = TimeDecayManager(half_life_days=30)
fresh = MemoryItem(id='f', content='fresh', created_at=datetime.now(), importance_score=0.8)
old = MemoryItem(id='o', content='old', created_at=datetime(2024,1,1), importance_score=0.8)
assert dm.apply_decay(fresh) > 0.7, '新记忆不应大幅衰减'
assert dm.apply_decay(old) < 0.1, '旧记忆应大幅衰减'
print('Layer 3 验证通过')
"
七、整合三层:智能遗忘管理器
python
class SmartMemoryManager:
"""
智能记忆管理器:整合敏感过滤、重要性评分、时间衰减三层遗忘机制。
"""
def __init__(self, half_life_days: float = 30.0,
importance_threshold: float = 0.3,
max_memory_size: int = 1000):
self.memories: Dict[str, MemoryItem] = {}
self.sensitive_filter = SensitiveMemoryFilter()
self.importance_scorer = ImportanceScorer()
self.decay_manager = TimeDecayManager(half_life_days)
self.importance_threshold = importance_threshold
self.max_memory_size = max_memory_size
def store(self, content: str, memory_id: str = None,
explicit_mark: bool = False,
metadata: Dict = None) -> Optional[MemoryItem]:
"""
存储记忆(带三层遗忘检查)。
Returns:
MemoryItem: 成功存储
None: 被敏感过滤拦截
"""
# Layer 1: 敏感过滤
is_sensitive, level, reason = self.sensitive_filter.check(content)
if level == SensitivityLevel.RESTRICTED:
print(f"[遗忘-敏感拦截] 记忆被拒绝: {reason}")
return None
# 脱敏处理
content = self.sensitive_filter.sanitize(content, level)
# 创建记忆项
memory = MemoryItem(
id=memory_id or f"mem_{len(self.memories)}",
content=content,
explicit_mark=explicit_mark,
sensitivity=level
)
# Layer 2: 初始重要性评分
memory.importance_score = self.importance_scorer.score(memory)
self.memories[memory.id] = memory
print(f"[存储] {memory.id}: 分数={memory.importance_score:.2f}, 敏感级别={level.value}")
# 检查是否需要触发清理
if len(self.memories) > self.max_memory_size:
self.cleanup()
return memory
def recall(self, query: str) -> List[MemoryItem]:
"""
检索记忆(同时更新访问记录和分数)。
简化版:基于关键词匹配(实际应用向量检索)。
"""
# 1. 先清理过期记忆
self._apply_decay_to_all()
# 2. 检索(简化:关键词匹配)
results = []
for memory in self.memories.values():
if query.lower() in memory.content.lower():
# 更新访问记录
self.importance_scorer.record_access(memory)
results.append(memory)
# 按重要性分数排序
results.sort(key=lambda m: m.importance_score, reverse=True)
return results
def cleanup(self):
"""定期清理:淘汰低分+过期的记忆。"""
print(f"\n[清理] 当前记忆数: {len(self.memories)}")
# 1. 应用时间衰减
self._apply_decay_to_all()
# 2. 获取淘汰候选
candidates = self.importance_scorer.get_eviction_candidates(
list(self.memories.values()),
threshold=self.importance_threshold
)
# 3. 执行淘汰
removed = 0
for candidate in candidates:
if candidate.id in self.memories:
del self.memories[candidate.id]
removed += 1
print(f"[清理] 淘汰 {removed} 条记忆,剩余 {len(self.memories)} 条")
def _apply_decay_to_all(self):
"""对所有记忆应用时间衰减。"""
for memory in self.memories.values():
decayed_score = self.decay_manager.apply_decay(memory)
memory.importance_score = decayed_score
def get_memory_stats(self) -> Dict:
"""获取记忆库统计信息。"""
if not self.memories:
return {"total": 0}
scores = [m.importance_score for m in self.memories.values()]
return {
"total": len(self.memories),
"avg_score": sum(scores) / len(scores),
"min_score": min(scores),
"max_score": max(scores),
"restricted_count": sum(1 for m in self.memories.values() if m.sensitivity == SensitivityLevel.RESTRICTED)
}
# ========== 使用示例:完整流程 ==========
if __name__ == "__main__":
manager = SmartMemoryManager(half_life_days=30, max_memory_size=5)
print("=== 存储记忆 ===")
# 1. 正常记忆
manager.store("用户喜欢深色模式", memory_id="pref_1", explicit_mark=True)
manager.store("用户对响应速度要求高")
# 2. 敏感记忆(应被拦截或脱敏)
manager.store("用户密码: MyP@ssw0rd123")
manager.store("用户身份证号: 110101199001011234")
# 3. 更多记忆(触发清理)
manager.store("用户去年用过的一次性配置", memory_id="old_1")
manager.store("用户偶尔提到的一个边缘功能")
print("\n=== 检索记忆 ===")
results = manager.recall("用户")
for r in results[:3]:
print(f"[{r.importance_score:.2f}] {r.content[:40]}...")
print("\n=== 统计 ===")
stats = manager.get_memory_stats()
print(f"记忆统计: {stats}")
⚠️ 集成风险:SmartMemoryManager 的 recall() 使用了简化关键词匹配。生产环境替换为向量检索(如 Chroma/FAISS)时,需注意:① 遗忘操作需同步更新向量索引 ② 敏感过滤应在向量化前执行 ③ 清理后索引需重建
验证步骤:
bash
python -c "
from smart_memory_manager import SmartMemoryManager
m = SmartMemoryManager(max_memory_size=3)
m.store('test1'); m.store('test2'); m.store('test3')
# 触发清理
m.store('test4')
stats = m.get_memory_stats()
assert stats['total'] <= 3, '超出最大容量限制'
print(f'整合验证通过: 当前{stats[\"total\"]}条记忆')
"
八、与Loop Engineering和内容矩阵的关联
8.1 与Loop Engineering的关联
Loop Engineering中的**Hill Climbing Loop(第四层)**核心理念是"Agent越用越聪明"。遗忘机制是Hill Climbing的前提------如果Agent记住的全是垃圾信息,它越用越糊涂。
| Loop层级 | 与遗忘机制的关系 |
|---|---|
| Level 1 Agent Loop | 每次循环结束后,哪些中间状态该记住、哪些该遗忘? |
| Level 2 Verification Loop | 验证失败的尝试,记忆还是遗忘?(建议:短期记住避免重复犯错,长期遗忘) |
| Level 3 Event Driven Loop | 定时触发清理任务,淘汰过期记忆 |
| Level 4 Hill Climbing Loop | 基于使用反馈优化记忆策略(哪些记忆被频繁访问?哪些从未被访问?) |
8.2 与已有内容矩阵的闭环
Agent Memory架构选型(选型指南)------ 已有 ✅
↓ 向量+图谱+关系数据库的选择
OpenSPG构建Agent记忆(知识图谱实战)------ 已有 ✅
↓ 知识图谱作为记忆存储层
本文:Agent记忆遗忘机制 ------ 新增 ✅
↓ 记忆需要"会忘"才算智能
Loop Engineering(循环控制)------ 已有 ✅
↓ 循环中的状态持久化与遗忘策略
GB/Z 185(标准合规)------ 已有 ✅
↓ 敏感信息过滤对应6.2数据隐私条款
九、总结
本文是一篇讨论Agent记忆遗忘机制与实现的文章,核心交付:
| 层级 | 机制 | 核心代码 | 解决什么问题 |
|---|---|---|---|
| Layer 1 | 敏感过滤 | SensitiveMemoryFilter |
密码/隐私不进记忆库 |
| Layer 2 | 重要性评分 | ImportanceScorer |
常用记忆保留,冷门记忆淘汰 |
| Layer 3 | 时间衰减 | TimeDecayManager |
旧记忆自动"褪色" |
| 整合 | 智能管理器 | SmartMemoryManager |
三层机制协同工作 |
核心结论:
- Agent Memory不是"记住越多越好",不会遗忘的Agent是危险的
- 三层遗忘机制缺一不可:敏感过滤保安全、重要性评分保质量、时间衰减保鲜度
- 遗忘机制是Hill Climbing Loop的前提:只有定期清理垃圾记忆,Agent才能越用越聪明
- GB/Z 185 6.2数据隐私条款要求敏感信息分级存储,遗忘机制的第一层直接对应此要求
十、方案对比与选型建议
10.1 三种衰减模型对比
| 维度 | 指数衰减 | 阶梯衰减 | 幂律衰减 |
|---|---|---|---|
| 公式 | score * e^(-t/λ) | score * (1-0.1*t/7) | score * t^(-α) |
| 遗忘曲线 | 平滑连续 | 阶梯下降 | 前期快后期慢 |
| 适用场景 | 一般记忆(推荐默认) | 业务规则(按周过期) | 热点事件(快速降温) |
| 参数调优 | half_life_days 控制速率 | 阶梯步长控制 | α 控制衰减力度 |
| 实现复杂度 | ⭐ 低 | ⭐⭐ 中 | ⭐⭐⭐ 高 |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
10.2 三层遗忘机制选型指南
| 场景 | 必须启用 | 建议启用 | 可选启用 |
|---|---|---|---|
| 客服Agent | Layer 1(敏感过滤) | Layer 2(重要评分) | Layer 3(时间衰减) |
| 代码开发Agent | Layer 1(API Key过滤) | Layer 2 + Layer 3 | - |
| 企业知识库Agent | Layer 1(机密信息过滤) | Layer 3(版本过期) | Layer 2 |
| 个人助手Agent | Layer 1 + Layer 2 | Layer 3 | - |
10.3 适用边界说明
⚠️ Layer 1 不适用场景:纯公开数据集(如维基百科检索)、无需隐私保护的测试环境------正则检测会增加写入延迟
⚠️ Layer 2 不适用场景:记忆总量<100条(淘汰无意义)、所有记忆同等重要的场景(如配置项集合)
⚠️ Layer 3 不适用场景:永久有效的记忆(如法律条款、公司制度)、需要精确时间点回溯的场景
相关阅读:
- Agent Memory架构选型:向量数据库、知识图谱、关系数据库,你的Agent该用哪个?(记忆存储基础------本文的上篇)
- 用OpenSPG知识图谱构建Agent长期记忆(知识图谱记忆------遗忘机制可以作用于图谱层)
- Loop Engineering四层架构:从Agent Loop到生产级智能体循环(循环控制------遗忘与Hill Climbing的关系)
- 从标准到代码:GB/Z 185合规的Agent Loop设计(标准合规------敏感信息分级对应6.2条款)
你的Agent现在有遗忘机制吗? 是无限增长(hoarder),还是定期清理?评论区说说你的策略------如果记忆库已经膨胀了,是怎么处理的?我针对高频场景整理一份"Agent Memory清理脚本",直接复制就能跑。
收藏这篇Agent遗忘机制指南,设计Agent记忆系统时别忘了"会忘"比"会记"更重要。觉得有用的话点赞+收藏,收藏率决定算法推荐权重,让更多开发者看到这篇Agent记忆安全的完整方案。
📅 更新日志:
| 日期 | 更新内容 |
|---|---|
| 2026-07 | 初始发布(基于Python 3.10+ / 标准库) |
⚠️ 版本变更提示 :本文基于Python 3.10+ 标准库(re/math/datetime)实现。如果Python大版本升级(如3.14+),
datetime.now()仍保持兼容。三层遗忘策略的逻辑与编程语言无关,可直接移植到TypeScript/Go/Java。