为什么没有记忆的 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 也一样。

遗忘机制有几种常见做法:

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 对你的"了解"就深一分。这正是"金鱼"和"老朋友"的区别。


六、几条实战经验

最后是我踩坑攒下的几条,比理论更实用:

  1. 日记用追加,绝不覆盖:会话日志一定用 append 模式写。我吃过亏——一次误用覆盖写,把一整天的上下文冲没了。
  2. 重要性打分让模型自己做:写入记忆时,顺手让模型给这条信息打个 0-1 的重要性分,比你手写规则灵活得多。
  3. 先用文件,再上数据库:原型阶段用 Markdown 文件存记忆完全够用,调试还直观。等架构稳定、数据量上来了,再迁移到向量库。
  4. 召回数量宁少勿多:召回 5 条精准的,胜过 20 条噪音。多出来的记忆不仅占窗口,还会干扰模型判断。

写在最后

记忆系统是 Agent 从"工具"进化成"伙伴"的分水岭。

没有记忆的 Agent,每次都在重新认识你;有记忆的 Agent,会越用越懂你。

三层架构(工作/短期/长期)、混合检索、遗忘机制——这套组合拳搭起来,你的 Agent 就有了真正的连续性。