原文:Build Agents that never forget by @akshay_pachaar |翻译整理 by@Liwx
一、核心问题:为什么 Agent 必须拥有记忆?
1.1 LLM 的天然缺陷------无状态
每次调用 LLM 的 API,都是一次全新的开始。ChatGPT 给人"有记忆"的错觉,仅仅是因为它每次请求时,都把整段对话历史重新塞进了提示里。
这种把戏对付 casual chat 还行,一旦你要构建真正的 Agent,立刻就会崩塌。
1.2 不做记忆的 7 大灾难
- 上下文失忆:反复问你已经给过的信息
- 零个性化:每次对话都像第一次见面
- 多步骤任务崩坏:中间状态悄无声息地丢失
- 重复犯错:没有情景记忆,同样的错误犯一辈子
- 知识无法积累:每次会话从零开始
- 缺口幻觉:上下文溢出时,模型开始瞎编
- 身份崩塌:没有连续性,就没有信任
1.3 砸钱买长上下文有用吗?
128K、200K 的上下文窗口听起来很美,但有两个致命问题:
- "Lost in the Middle" 效应:关键信息放在长上下文的中间位置,准确率会下降超过 30%
- 上下文是共享预算:系统提示、检索文档、对话历史、模型输出,全在抢同一块 Token
结论:记忆不是把更多文本塞进提示,而是结构化地组织信息,让 Agent 能找到真正重要的东西。
二、认知科学视角:Agent 记忆的三大支柱
Lilian Weng 在 2023 年提出的公式已成为业界默认框架:
Agent = LLM + Memory + Planning + Tool Use
人类的记忆系统可以直接映射到 Agent 架构上:
| 人类记忆 | 特点 | Agent 对应 |
|---|---|---|
| 感官记忆 | 捕获原始感知,只保留零点几秒 | 原始输入数据 |
| 工作记忆 | 活跃思考区,同时容纳 7±2 个项目 | 当前上下文窗口 |
| 长期记忆 | 容量几乎无限,检索是瓶颈 | 持久化存储系统 |
长期记忆又细分为三类:
- 情景记忆:具体事件("上周二 PostgreSQL 集群挂了")
- 语义记忆:事实和概念("PostgreSQL 是关系型数据库")
- 程序记忆:技能和流程("用户要求退款时,先查购买日期")
记忆巩固(Memory Consolidation)
反复出现的情景记忆会蒸馏成语义记忆。Agent 如果在几十次交互中发现"用户总是偏爱执行摘要",就应该把它变成一条可复用的规则,而不是每次重放原始对话。
没有巩固,Agent 只是在重播录像,而不是在学习。
三、四层技术演进:从玩具到生产级
Layer 0:裸 LLM(每次调用孤立)
你先问"我有 4 个苹果",再问"吃了一个还剩几个"------它根本不知道你在说什么。每次调用都是孤岛。
Layer 1:Python 列表(对话历史全量发送)
最直觉的修复:用一个列表存下所有对话,每次请求全量发送。
python
class Agent:
def __init__(self):
self.messages = [] # 全部记忆就是一个列表
def chat(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
# 每次把完整历史发给模型
response = self.client.messages.create(
messages=self.messages,
)
两个致命问题:
- 列表无限增长:到 200 轮左右就会碰到上下文天花板,最早的消息无声消失------没有优先级,只有严格的时间顺序
- 一切在 RAM 里:Python 进程一结束,Agent 就不认识你了
Layer 2:Markdown 文件持久化
下一步:把记忆写到磁盘。Markdown 天然适合------人类可读、Git 友好、Agent 能直接读回纯文本。Claude Code 的 CLAUDE.md 和 MEMORY.md 就是这个思路。
小规模时完美,大规模时崩塌:
- 3 个月后:2000 条事实 + 200 条对话日志 = 50 万+ tokens
- 128K 上下文根本装不下
- 只能用关键词搜索(grep),但关键词无法处理同义词、改写、跨事实关联
举个典型失败:
python
# 用户问:"云迁移进展如何?"
grep("cloud migration", facts_file) # 返回空
# 磁盘上写的是"把生产数据库迁移到新 AWS 区域"
# "cloud migration"这几个字根本没出现
存储没有智能检索,就像图书馆没有目录。
Layer 3:向量搜索(语义相似度)
把 Markdown 切成块,做 embedding,用余弦相似度搜索。现在 "database" 能匹配到 "PostgreSQL",因为它们的向量在嵌入空间里距离很近。
但又撞上一堵新墙:多跳关系查询。
假设向量库里有三条事实:
- "Alice 是 Project Atlas 的技术负责人"
- "Project Atlas 用 PostgreSQL 做主存储"
- "PostgreSQL 集群周二出了故障"
用户问:"Alice 的项目受周二故障影响了吗?"
向量搜索会给第 1 条和第 3 条打高分(因为它们分别提到了 Alice 和周二故障),但关键的桥梁------"Project Atlas 用 PostgreSQL"------既没有 Alice 也没有周二,所以它浮不上来。
向量空间里的每个事实都是孤立点,连接它们的组织关系对向量来说是不可见的。
能力矩阵总结:
| 层级 | 解决了什么 | 暴露了什么 |
|---|---|---|
| 裸 LLM | 无 | 完全无状态 |
| Python 列表 | 多轮对话 | 无限增长、进程结束即丢失 |
| Markdown 文件 | 持久化 | 关键词检索脆弱、无法语义匹配 |
| 向量搜索 | 语义相似 | 无法处理跨实体多跳关系 |
你需要的是:持久化 + 语义理解 + 关系推理,三者合一。
四、Cognee:三存储合一的解决方案
Cognee 是一个开源知识引擎,专门为 Agent 记忆设计。它把三种存储范式整合到一个系统里。
4.1 为什么需要三种存储?
| 存储类型 | 负责什么 | 为什么不可替代 |
|---|---|---|
| 关系存储 | 数据来源、摄入时间、访问权限 | 可追溯、可审计 |
| 向量存储 | 语义:内容含义、相似度 | 同义词、语义匹配 |
| 图存储 | 关系:实体如何连接、因果链 | 多跳推理、跨实体查询 |
把任何一个拍扁,都会丢失对检索准确性至关重要的信息。
默认技术栈:SQLite + LanceDB + Kuzu ,全部嵌入式、文件级。pip install cognee 加一个 LLM API key 就能跑,不需要 Docker,不需要外部服务。
生产环境可无缝替换:Postgres + Qdrant/Pinecone + Neo4j,API 完全不变。
4.2 四个核心调用
python
import cognee
await cognee.add("Your document here") # 摄入任何内容
await cognee.cognify() # 构建知识图谱 + 嵌入向量
await cognee.memify() # 自我优化记忆
await cognee.search("Your query") # 带推理的检索
4.3 cognify 的六阶段流水线
cognify() 把原始文本转换成结构化、互联的知识:
- 文档分类:按类型和领域自动分类
- 权限检查:多租户访问控制
- 智能分块:尊重段落结构,不是固定长度切割
- 实体与关系提取 :LLM 提取实体和关系,通过内容哈希自动去重
- 同一个实体在 50 个文档中出现 → 合并成一个图节点,带 50 条入边
- 摘要生成:为高效检索生成摘要
- 双索引:同时写入向量存储(embedding)和图存储(边关系)
每个图节点都有对应的嵌入向量。 这是核心技巧:
- 可以从向量入口进入(找到语义相似的内容)
- 从图出口离开(沿关系遍历到关联实体)
- 也可以反过来
多跳查询就这样被解决了。
4.4 memify:会学习的记忆
memify() 是受强化学习启发的优化过程,让记忆系统从自身使用中学习:
- 强化那些带来好检索结果的路径
- 剪枝长期未访问的陈旧节点
- 自动调优边权重,基于真实使用频率
- 添加推导事实,识别隐式关系
一个客服 Agent 的图会自然强化"产品文档 → 退款政策"路径,同时让很少被查询的 HR 边衰减。
图会自己发展出相关性直觉。
4.5 14 种检索模式
Cognee 内置 14 种搜索模式,覆盖从纯语义搜索到复杂图推理的各种场景。
4.6 会话记忆与多租户
- 会话记忆:自动处理代词消解。先问"Alice 住哪?",再问"她是做什么工作的?"------"她"会自动从会话上下文中解析为 Alice
- 多租户:图级别的隔离,不是简单的命名空间分离,而是真正的权限控制(读、写、删、共享)
4.7 完整 Agent 集成示例
python
import cognee
from cognee import SearchType
class CogneeMemoryAgent:
def __init__(self, session_id: str = "default"):
self.llm_client = OpenAI()
self.session_id = session_id
async def ingest(self, text: str, dataset: str = "main"):
await cognee.add(text, dataset)
await cognee.cognify([dataset])
async def recall(self, query: str) -> str:
results = await cognee.search(
query_text=query,
query_type=SearchType.GRAPH_COMPLETION,
session_id=self.session_id,
)
return results[0] if results else ""
async def chat(self, user_input: str) -> str:
context = await self.recall(user_input)
messages = [
{"role": "system", "content": "You are helpful. Use memory context."},
{"role": "system", "content": f"Memory context:\n{context}"},
{"role": "user", "content": user_input},
]
response = self.llm_client.chat.completions.create(
model="gpt-4o-mini", messages=messages
)
reply = response.choices[0].message.content
# 把本轮对话也存入记忆
await cognee.add(
f"User: {user_input}\nAssistant: {reply}",
"conversations"
)
await cognee.cognify(["conversations"])
return reply
记忆循环:摄入 → 提取 → 存储 → 检索 → 响应 → 再存储。每轮对话都在丰富知识图谱,增量处理意味着你只需要为新增内容付费。
五、如何为你的 Agent 选记忆方案?
核心问题:我的 Agent 需要记住什么?会回答什么样的问题?
| 你的查询类型 | 推荐方案 |
|---|---|
| 只需要相似性搜索("找类似这段对话的") | 纯向量存储就够了 |
| 问题跨越实体边界("Alice 的项目受周二故障影响了吗?") | 必须图遍历 |
| 需要来源追溯、权限控制、审计日志 | 关系存储不可少 |
自建的代价:向量库 + 图库 + 关系库 + 实体提取器 + 去重管道 + 边权重系统 = 几周基础设施工作,而且记忆层还不会从自身使用中学习。
Cognee 的价值:四个 API 调用,嵌入式默认分钟级启动,可替换后端直接上生产,不需要改 Agent 代码。
六、一句话总结
智能需要结构,而不只是存储。 关系、向量、图三种存储范式不是互相竞争的选项,而是同一记忆系统的互补层。只有把它们当作互补层来处理,才能把无状态的 LLM 包装层变成真正能学习的东西。
你的 Agent 明天需要记住什么、今天却忘了的东西?从那里开始。
本文配套资料、后续更新会优先发布在在AI的旷野思考平台,欢迎关注