为什么没有记忆的 Agent 是个「金鱼」
你有没有这种体验:和一个 AI 助手聊得好好的,关掉窗口再打开,它把你是谁、刚才聊了啥忘得一干二净。
这就是大多数 Agent 的真实状态——它们是"金鱼记忆",每次对话都是从零开始。
对简单问答这没什么。但只要你想做一个能长期陪你工作的 Agent——记得你的偏好、积累过往的经验、跨会话延续任务——记忆系统就成了绕不过去的核心模块。
这篇我把一套可落地的记忆系统从头拆一遍:分几层、怎么存、怎么取、怎么忘,配上能直接参考的代码。
一、记忆的三层架构
人的记忆不是一个整体——有此刻脑子里的念头,有这几天的事,也有刻进骨子里的东西。Agent 的记忆也该这么分层。我把它拆成三层:
| 层级 | 对应人类 | 存什么 | 存在哪 |
|---|---|---|---|
| 工作记忆 | 当下的注意力 | 当前这一轮的输入和推理 | 上下文窗口 |
| 短期记忆 | 这几天的事 | 当前会话的对话历史 | 内存 / 会话缓存 |
| 长期记忆 | 长久的认知 | 跨会话的偏好、事实、经验 | 向量库 + 持久化文件 |
这三层不是孤立的,而是有流动的:工作记忆里产生的重要信息,会沉淀进短期记忆;短期记忆里反复出现、有长期价值的,再固化进长期记忆。就像人睡一觉,把白天的经历整理进长期记忆一样。
二、长期记忆:向量库是怎么工作的
三层里最难、也最关键的是长期记忆。它要解决一个问题:几万条历史记忆,怎么在一瞬间找出和当前最相关的那几条?
答案是向量检索。核心思路是把每条记忆变成一个向量(一串数字),相似的内容在向量空间里距离也近,于是"找相关记忆"就变成了"找空间里最近的点"。
# memory_store.py —— 长期记忆的核心存取
import chromadb
class LongTermMemory:
def __init__(self, agent_id):
self.client = chromadb.PersistentClient(path=f"./memory/{agent_id}")
self.collection = self.client.get_or_create_collection("memories")
def remember(self, text, metadata=None):
"""写入一条长期记忆"""
self.collection.add(
documents=[text],
metadatas=[metadata or {}],
ids=[f"mem_{self._next_id()}"]
)
def recall(self, query, top_k=5):
"""根据当前问题,召回最相关的几条记忆"""
results = self.collection.query(
query_texts=[query],
n_results=top_k
)
return results["documents"][0]
remember 负责存,recall 负责取。注意 recall 不是把所有记忆都拿出来,而是只召回 top_k 条最相关的——这正是上一篇讲的"检索得准"在记忆系统里的体现。
三、混合检索:光靠向量还不够
只用向量检索,会漏掉一些情况。比如用户问"我上次说的那个项目编号是多少"——这种带精确关键词的查询,向量检索反而不如老老实实的关键词匹配。
所以成熟的记忆系统通常用混合检索:向量召回 + 关键词召回,再把两路结果融合排序。
def hybrid_recall(self, query, top_k=5):
# 第一路:语义向量召回(找意思相近的)
semantic = self.vector_recall(query, top_k * 2)
# 第二路:关键词召回(找字面命中的)
keyword = self.keyword_recall(query, top_k * 2)
# 融合:用 RRF(倒数排名融合)把两路结果合并重排
merged = self.reciprocal_rank_fusion(semantic, keyword)
return merged[:top_k]
再加一个时间衰减因子会更好——越近的记忆权重越高,这符合"最近发生的事更可能相关"的直觉。
四、遗忘机制:会忘,才是好记忆
很多人做记忆系统只想着"怎么记得更多",却忽略了一个反直觉的真相:一个不会遗忘的记忆系统,最终会被垃圾淹没。
想象一下,如果你记得人生中每一秒的每个细节,你反而会被淹没,找不到真正重要的东西。Agent 也一样。
遗忘机制有几种常见做法:
- 基于时间:超过一定时长且从未被再次召回的记忆,降权或归档;
- 基于频率:长期没被
recall命中的记忆,判定为低价值,定期清理; - 基于重要性:写入时给每条记忆打一个 importance 分数,清理时优先保留高分的。
def forget(self, days_threshold=30):
"""归档:长期未被访问的低价值记忆"""
for mem in self.collection.get_all():
idle_days = (now() - mem.last_accessed).days
if idle_days > days_threshold and mem.importance < 0.3:
self.archive(mem) # 不是删除,是移到冷存储
注意我用的是归档而非删除——把低频记忆移到冷存储,而不是直接抹掉。万一哪天需要,还能捞回来。这点和很多人的第一反应不同,但在生产里更稳妥。
五、一个完整的记忆循环
把上面几块串起来,一个 Agent 处理请求时的记忆循环是这样的:
用户提问
↓
① 召回:从长期记忆 hybrid_recall 出相关记忆
↓
② 组装:相关记忆 + 当前会话历史 → 拼进上下文
↓
③ 生成:模型基于完整上下文作答
↓
④ 写入:把这轮的关键信息 remember 进短期记忆
↓
⑤ 固化:会话结束时,把有长期价值的提炼进长期记忆
↓
⑥ 遗忘:定期 forget,归档低价值记忆
这个循环每跑一轮,Agent 对你的"了解"就深一分。这正是"金鱼"和"老朋友"的区别。
六、几条实战经验
最后是我踩坑攒下的几条,比理论更实用:
- 日记用追加,绝不覆盖:会话日志一定用 append 模式写。我吃过亏——一次误用覆盖写,把一整天的上下文冲没了。
- 重要性打分让模型自己做:写入记忆时,顺手让模型给这条信息打个 0-1 的重要性分,比你手写规则灵活得多。
- 先用文件,再上数据库:原型阶段用 Markdown 文件存记忆完全够用,调试还直观。等架构稳定、数据量上来了,再迁移到向量库。
- 召回数量宁少勿多:召回 5 条精准的,胜过 20 条噪音。多出来的记忆不仅占窗口,还会干扰模型判断。
写在最后
记忆系统是 Agent 从"工具"进化成"伙伴"的分水岭。
没有记忆的 Agent,每次都在重新认识你;有记忆的 Agent,会越用越懂你。
三层架构(工作/短期/长期)、混合检索、遗忘机制——这套组合拳搭起来,你的 Agent 就有了真正的连续性。