构建永不遗忘的 Agent

@akshay_pachaar
英语3个月前 · 2026年4月13日
625K
1.9K
305
46
4.1K

TL;DR

深入剖析 AI Agent 记忆系统,探讨如何从简单的列表演进为 Cognee 的开源图向量引擎,从而实现持久化与关系型推理。

从第一性原理理解 Agent 记忆:从 Python 列表到 Markdown 文件,再到向量搜索和图向量混合方案,最终找到一个简洁、开源的全能解决方案

Akshay 🚀 - inline image

LLM 天生就是无状态的。每次 API 调用都是从零开始。你在与 ChatGPT 聊天时感受到的"记忆",其实是一种错觉——每次请求都会重新发送整个对话历史。

这种技巧在闲聊中行得通。但当你真正构建一个 Agent 时,它就会崩溃。

以下是跳过记忆时立即出现的 7 种失败模式:

  1. 上下文遗忘: Agent 会询问你已经提供过的信息
  2. 零个性化: 每次交互都感觉千篇一律
  3. 多步骤任务失败: 中间状态在任务执行中悄然丢失
  4. 重复犯错: 没有情景记忆意味着同样的错误会永远重复
  5. 知识无法积累: 每次会话都从零开始
  6. 信息缺口导致幻觉: 当上下文溢出时,模型会凭空编造
  7. 身份崩塌: 没有连续性,就没有信任

最直接的反应是"塞入更多上下文"。这就是为什么 128K 和 200K 的 token 窗口看起来应该能解决一切问题。

但它们不能。

当相关信息位于长上下文的中间位置时,准确率会下降超过 30%。 这就是被充分记录的"中间迷失"效应。

上下文是一个共享预算:系统提示、检索到的文档、对话历史和输出都在争夺相同的 token。

即使有 100K 个 token,缺乏持久性、优先级和显著性,使得原始上下文长度仍然不够。

Akshay 🚀 - inline image

记忆不是把更多文本塞进提示词。它是关于结构化地组织 Agent 记住的内容,以便它能找到重要的信息。

真正有用的认知科学框架

Lilian Weng 在 2023 年提出的框架已成为默认标准:

Agent = LLM + 记忆 + 规划 + 工具使用。

四个同等重要的支柱。

她的分类法借鉴了认知科学,其中人类记忆分为三个系统:

  • 感觉记忆 捕获原始感知输入,只保留几分之一秒。只有你注意到的部分才会被传递下去。
  • 工作记忆 是进行主动思考的地方。它大约能同时容纳 7±2 个项目(Miller 1956 年的发现)。一旦分心,内容就会消失。
  • 长期记忆 是持久存储,没有实际容量限制。检索才是瓶颈:你可以存储数百万件事,但仍然无法回忆起你需要的那一件。

每一个都直接对应现代 Agent 架构中的一个组件:

Akshay 🚀 - inline image

长期记忆本身又进一步细分:

  • 情景记忆: 特定的过去事件("周二,PostgreSQL 集群宕机了")
  • 语义记忆: 事实和概念("PostgreSQL 是一个关系型数据库")
  • 程序记忆: 技能和工作流程("当用户要求退款时,首先检查购买日期")

情景记忆和语义记忆之间的桥梁是记忆巩固:重复的特定事件提炼成通用知识。一个在数十次交互中注意到"用户始终偏好执行摘要"的 Agent,应该将其转化为可复用的规则。没有巩固,你的 Agent 只是在重放单个事件,而不是从中学习。

Akshay 🚀 - inline image

最小化的 Agent,以及最先出问题的地方

去掉框架,Agent 就是一个循环:感知、思考、行动。

python
1class Agent:
2 """最小化 AI Agent:感知、思考、行动"""
3 def __init__(self):
4 self.client = anthropic.Anthropic()
5 self.model = "claude-sonnet-4-20250514"
6
7 def run(self, user_input: str) -> str:
8 response = self.client.messages.create(
9 model=self.model,
10 max_tokens=1024,
11 messages=[{"role": "user", "content": user_input}],
12 )
13 return response.content[0].text

告诉它"我有 4 个苹果",然后问"我吃了一个,还剩几个?",它完全不知道你在说什么苹果。每次调用都是孤立的。

第一层:Python 列表

每个人都会想到的第一个修复方案:

python
1class Agent:
2 def __init__(self):
3 self.client = anthropic.Anthropic()
4 self.messages = [] # 整个"记忆"就是一个列表
5
6 def chat(self, user_input: str) -> str:
7 self.messages.append({"role": "user", "content": user_input})
8 response = self.client.messages.create(
9 model="claude-sonnet-4-20250514",
10 max_tokens=1024,
11 messages=self.messages, # 每次发送完整历史
12 )
13 reply = response.content[0].text
14 self.messages.append({"role": "assistant", "content": reply})
15 return reply

多轮对话现在可以工作了。苹果问题能正确回答,因为每次调用都会重新发送完整的对话。

但两个问题很快就会出现:

  • 列表会无限制增长。 大约在第 200 轮时,你会达到上下文上限,最旧的消息会悄然丢失。用户在第一轮提到的名字,远在昨天那个无关紧要的笑话之前就消失了。没有优先级排序,只有严格的时间顺序。
  • 所有内容都存在于 RAM 中。 一旦 Python 进程结束,你的 Agent 就不知道你是谁了。

第二层:用于持久化的 Markdown 文件

下一步是将记忆写入磁盘。Markdown 是天然的选择:人类可读、对 Git 友好,而且 Agent 可以将其作为纯文本读回。Claude Code 正是使用这种模式,配合 CLAUDE.mdMEMORY.md 文件。

python
1class MarkdownMemoryAgent:
2 def __init__(self):
3 self.client = anthropic.Anthropic()
4 self.history_file = Path("memory/conversation_history.md")
5 self.facts_file = Path("memory/known_facts.md")
6
7 def save_to_disk(self, role: str, content: str) -> None:
8 with open(self.history_file, "a") as f:
9 f.write(f"### {role} at {datetime.now().isoformat()}\n{content}\n\n")
10
11 def load_history(self) -> str:
12 if self.history_file.exists():
13 return self.history_file.read_text()
14 return ""
15
16 def chat(self, user_input: str) -> str:
17 self.save_to_disk("user", user_input)
18 history = self.load_history()
19 response = self.client.messages.create(
20 model="claude-sonnet-4-20250514",
21 max_tokens=1024,
22 system=f"Previous conversation:\n{history}",
23 messages=[{"role": "user", "content": user_input}],
24 )
25 reply = response.content[0].text
26 self.save_to_disk("assistant", reply)
27 return reply

持久化问题解决了。重新启动脚本,对话仍然在磁盘上。你还可以维护一个单独的事实文件,Agent 可以随时间从中提取信息:

text
1- 用户的名字是 Sarah
2- Sarah 管理 Acme Corp 的后端团队
3- Acme Corp 是一家 B2B SaaS 公司
4- 目前正在将生产数据库迁移到新的 AWS 区域

你可以在任何编辑器中打开这个文件,准确看到 Agent 知道什么,并手动修正。对于原型开发来说非常有用。

当只有 4 个事实时,这完美运行。将整个文件加载到上下文中,LLM 可以处理任何关于 Sarah、她的公司或行业的问题。

现在快进三个月。你的 Agent 有 2,000 个提取的事实和 200 个对话日志。磁盘上有 500K+ 个 token 的 Markdown 内容,而你的上下文窗口只有 128K。

你无法再加载所有内容了。 你需要有选择地只检索与当前查询相关的事实。使用平面文件时,你唯一的选择是关键词搜索:

python
1# 用户问:"我们的云迁移进展如何?"
2grep("cloud migration", facts_file)
3# 返回:[]
4# 磁盘上的事实写着"将生产数据库迁移到新的 AWS 区域。"
5# 但"cloud migration"这两个词完全没有出现。
6
7# 用户问:"哪个团队在负责数据库工作?"
8grep("database team", facts_file)
9# 返回:[]
10# 一个事实说 Sarah"管理后端团队"。另一个说团队正在
11# "迁移生产数据库"。但没有一行同时包含
12# "database"和"team"。

在小规模下,Markdown 文件可以工作。在真实规模下,它们强制使用关键词检索,而关键词无法处理同义词、改写或跨事实的连接。

信息在磁盘上。但你无法加载全部内容,而关键词搜索又太脆弱,无法找到正确的部分。

如果你用过 OpenClaw,你就会看到这种情况。它将记忆存储为 Markdown 检查点文件,经过数周的日常使用,随着上下文的积累和压缩,早期的事实会悄然消失。存储是存在的。但检索不是。

没有智能检索的存储,就是一个没有目录的图书馆。

第三层:向量搜索及其遇到的瓶颈

加上嵌入。将你的 Markdown 分块,嵌入这些块,通过余弦相似度进行搜索。现在"database"可以匹配"PostgreSQL",因为它们的向量在嵌入空间中位置相近。同义词问题解决了。

然后你遇到了新的瓶颈。考虑向量数据库中的这三个事实:

text
1- "Alice 是 Project Atlas 的技术负责人"
2- "Project Atlas 使用 PostgreSQL 作为其主要数据存储"
3- "PostgreSQL 集群在周二经历了一次宕机"

用户问: "Alice 的项目受到周二宕机的影响了吗?"

查询提到了 Alice 和周二的宕机,所以向量搜索将第一个和第三个事实排在前面。但关键的桥梁——"Project Atlas 使用 PostgreSQL"——既没有提到 Alice 也没有提到周二。它是连接各部分的纽带,但却是最不可能被呈现出来的那一个。

每个事实都是嵌入空间中的一个孤立点。连接它们的纽带对向量来说是不可见的。

Akshay 🚀 - inline image

这不是一个边缘情况。这是现实世界问题的常态。业务知识本质上是关系型的:人属于团队,团队拥有项目,项目依赖系统,系统会发生事故。任何跨越两个或更多跳的问题,都超出了平面向量检索的能力范围。

能力矩阵

每一层都修复了之前的痛点,但揭示了一个更深层次的问题:

Akshay 🚀 - inline image

你需要在单个记忆层中同时拥有持久化、语义理解和关系推理能力。

自己构建这个意味着要拼接向量数据库、图数据库、关系存储、实体提取器、去重管道和边权重系统。在写一行 Agent 逻辑之前,这需要数周的基础设施工作。

我一直在使用一个能干净地填补这个空白的解决方案。它完全开源,在一个屋檐下处理所有三种存储范式,而且你可以在几分钟内让它运行起来。让我们来谈谈 Cognee。

Cognee:三个存储,一个引擎,四个调用

Cognee 是一个为 Agent 记忆构建的开源知识引擎。它将向量搜索与知识图谱和关系溯源层结合到一个单一系统中。

整个 API 接口只有四个异步调用:

python
1import cognee
2
3await cognee.add("Your document here") # 摄入任何内容
4await cognee.cognify() # 构建知识图谱 + 嵌入
5await cognee.memify() # 自我改进记忆
6await cognee.search("Your query") # 通过推理进行检索

在这四个调用的背后,是一个三存储架构。

Akshay 🚀 - inline image

为什么是三个存储而不是一个?

每个存储捕获了其他存储无法捕捉的知识维度:

  • 关系存储 → 溯源:数据来自哪里,何时被摄入,谁有访问权限
  • 向量存储 → 语义:内容意味着什么,它与什么相似
  • 图存储 → 关系:实体之间如何连接,什么导致什么,谁向谁汇报

扁平化其中任何一个,你都会丢失对检索准确性至关重要的信息。

默认栈是 SQLite + LanceDB + Kuzu,完全嵌入式且基于文件。pip install cognee 加上一个 LLM API 密钥,你就可以运行了。

无需 Docker,无需外部服务。

对于生产环境,将 SQLite 换成 Postgres,LanceDB 换成 Qdrant/Pinecone/pgvector,Kuzu 换成 Neo4j/FalkorDB/Neptune。

无论哪种方式,都是相同的四个调用 API。

cognify 到底做了什么?

cognee.cognify() 运行一个多阶段管道,将原始文本转换为结构化、互联的知识:

  1. 文档分类,按类型和领域
  2. 权限检查,用于多租户访问控制
  3. 块提取,尊重段落结构(不是固定大小的切割)
  4. 实体和关系提取,通过 LLM 进行,并通过内容哈希自动去重
  5. 摘要生成,用于高效检索
  6. 双重索引,进入向量存储(嵌入)和图存储(边)

去重步骤比听起来更重要。如果同一个实体出现在 50 个文档中,Cognee 会将其合并为一个具有 50 条入边的图节点。你的 Agent 不再将"Alice"视为 50 个不同的陌生人。而且管道默认是增量的:只有新的或更新的文件才会被重新处理。

Akshay 🚀 - inline image

每个图节点都有一个对应的嵌入。 这种双重表示是核心技巧:通过向量进入(找到语义相似的内容),通过图退出(跟随关系到连接的实体),或者反过来。这就是使多跳查询能够工作而不牺牲语义搜索的原因。

Memify:会学习的记忆

memify() 是 Cognee 区别于所有"摄入并搜索"工具的地方。它运行一个受强化学习启发的图优化过程:

  • 强化 导致良好检索的有用路径
  • 修剪 未被触及的陈旧节点
  • 自动调整 基于实际使用的边权重
  • 添加派生事实,通过识别隐含关系

一个客户支持 Agent 的图会自然地强化通过产品文档和退款策略的路径,同时让很少被查询的 HR 边衰减。图会随着时间的推移发展出自己的相关性感知。

Akshay 🚀 - inline image

十四种检索模式

Cognee 提供了 14 种搜索模式。你真正会用到的有:

Akshay 🚀 - inline image

使用 Cognee 记忆构建真正的 Agent

以下是将 Cognee 接入感知-思考-行动循环的完整模式:

python
1import cognee
2from cognee import SearchType
3
4class CogneeMemoryAgent:
5 """具有图向量混合持久记忆的 Agent。"""
6
7 def __init__(self, session_id: str = "default"):
8 self.llm_client = OpenAI()
9 self.session_id = session_id
10
11 async def ingest(self, text: str, dataset: str = "main"):
12 await cognee.add(text, dataset)
13 await cognee.cognify([dataset])
14
15 async def recall(self, query: str) -> str:
16 results = await cognee.search(
17 query_text=query,
18 query_type=SearchType.GRAPH_COMPLETION,
19 session_id=self.session_id,
20 )
21 return results[0] if results else ""
22
23 async def chat(self, user_input: str) -> str:
24 context = await self.recall(user_input)
25 messages = [
26 {"role": "system", "content": "你很有帮助。请使用记忆上下文。"},
27 {"role": "system", "content": f"记忆上下文:\n{context}"},
28 {"role": "user", "content": user_input},
29 ]
30 response = self.llm_client.chat.completions.create(
31 model="gpt-4o-mini", messages=messages
32 )
33 reply = response.choices[0].message.content
34 await cognee.add(
35 f"用户:{user_input}\n助手:{reply}",
36 "conversations"
37 )
38 await cognee.cognify(["conversations"])
39 return reply

记忆循环:摄入、提取、存储、检索、响应、再次存储。每一轮都会丰富知识图谱,增量处理意味着你只需为索引新内容付费。

会话记忆会自动处理代词解析:

python
1await cognee.search(query_text="Alice 住在哪里?", session_id="conv_1")
2await cognee.search(query_text="她做什么工作?", session_id="conv_1")
3# "她"从会话上下文中解析为 Alice

多租户在图级别内置,具有每个数据集的权限(读取、写入、删除、共享)。不是命名空间隔离,而是实际的图级别隔离。

实际的推进路径

如果你今天正在构建一个 Agent,真正的起点问题是:"我的 Agent 需要记住什么,以及它将回答什么样的问题?"

如果你的查询只需要相似性搜索("找到像这样的对话"),仅使用向量的记忆就足够了。一旦查询跨越实体边界("Alice 的项目受到周二宕机的影响了吗?"),你就需要图遍历。

你可以自己将单独的向量、图和关系存储连接起来。走这条路线的团队通常会在基础设施上花费数周时间,最终得到的记忆层仍然无法从自身的使用中学习。

Cognee 将其压缩为四个 API 调用。嵌入式默认值让你在几分钟内就能运行起来。可替换的后端(Postgres、Qdrant、Neo4j)可以带你进入生产环境,而无需更改你的 Agent 代码。

智能需要结构,而不仅仅是存储。 三种存储范式(关系型、向量、图)不是竞争选项。它们是同一个记忆系统的互补层。将它们视为如此,才能将一个无状态的 LLM 包装器转变为真正会学习的东西。

你希望你的 Agent 明天能记住什么,而今天它却忘记了?从那里开始。

👉 在 GitHub 上查看 Cognee →,给它点个星,并尝试将其接入你的下一个 Agent。

四个异步调用,一个 pip install,你就可以运行了。

以上就是全部内容!

如果你喜欢阅读本文:

找到我 →@akshay_pachaar ✔️

每天,我都会分享关于 AI、机器学习和 Vibe Coding 最佳实践的教程和见解。

一键保存

使用 YouMind AI 深度阅读爆款文章

保存原文、追问细节、总结观点,并在一个 AI 工作空间里把爆款文章沉淀成可复用笔记。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章