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

LLM 天生就是无状态的。每次 API 调用都是从零开始。你在与 ChatGPT 聊天时感受到的"记忆",其实是一种错觉——每次请求都会重新发送整个对话历史。
这种技巧在闲聊中行得通。但当你真正构建一个 Agent 时,它就会崩溃。
以下是跳过记忆时立即出现的 7 种失败模式:
- 上下文遗忘: Agent 会询问你已经提供过的信息
- 零个性化: 每次交互都感觉千篇一律
- 多步骤任务失败: 中间状态在任务执行中悄然丢失
- 重复犯错: 没有情景记忆意味着同样的错误会永远重复
- 知识无法积累: 每次会话都从零开始
- 信息缺口导致幻觉: 当上下文溢出时,模型会凭空编造
- 身份崩塌: 没有连续性,就没有信任
最直接的反应是"塞入更多上下文"。这就是为什么 128K 和 200K 的 token 窗口看起来应该能解决一切问题。
但它们不能。
当相关信息位于长上下文的中间位置时,准确率会下降超过 30%。 这就是被充分记录的"中间迷失"效应。
上下文是一个共享预算:系统提示、检索到的文档、对话历史和输出都在争夺相同的 token。
即使有 100K 个 token,缺乏持久性、优先级和显著性,使得原始上下文长度仍然不够。

记忆不是把更多文本塞进提示词。它是关于结构化地组织 Agent 记住的内容,以便它能找到重要的信息。
真正有用的认知科学框架
Lilian Weng 在 2023 年提出的框架已成为默认标准:
Agent = LLM + 记忆 + 规划 + 工具使用。
四个同等重要的支柱。
她的分类法借鉴了认知科学,其中人类记忆分为三个系统:
- 感觉记忆 捕获原始感知输入,只保留几分之一秒。只有你注意到的部分才会被传递下去。
- 工作记忆 是进行主动思考的地方。它大约能同时容纳 7±2 个项目(Miller 1956 年的发现)。一旦分心,内容就会消失。
- 长期记忆 是持久存储,没有实际容量限制。检索才是瓶颈:你可以存储数百万件事,但仍然无法回忆起你需要的那一件。
每一个都直接对应现代 Agent 架构中的一个组件:

长期记忆本身又进一步细分:
- 情景记忆: 特定的过去事件("周二,PostgreSQL 集群宕机了")
- 语义记忆: 事实和概念("PostgreSQL 是一个关系型数据库")
- 程序记忆: 技能和工作流程("当用户要求退款时,首先检查购买日期")
情景记忆和语义记忆之间的桥梁是记忆巩固:重复的特定事件提炼成通用知识。一个在数十次交互中注意到"用户始终偏好执行摘要"的 Agent,应该将其转化为可复用的规则。没有巩固,你的 Agent 只是在重放单个事件,而不是从中学习。

最小化的 Agent,以及最先出问题的地方
去掉框架,Agent 就是一个循环:感知、思考、行动。
1class Agent:2 """最小化 AI Agent:感知、思考、行动"""3 def __init__(self):4 self.client = anthropic.Anthropic()5 self.model = "claude-sonnet-4-20250514"67 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 列表
每个人都会想到的第一个修复方案:
1class Agent:2 def __init__(self):3 self.client = anthropic.Anthropic()4 self.messages = [] # 整个"记忆"就是一个列表56 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].text14 self.messages.append({"role": "assistant", "content": reply})15 return reply
多轮对话现在可以工作了。苹果问题能正确回答,因为每次调用都会重新发送完整的对话。
但两个问题很快就会出现:
- 列表会无限制增长。 大约在第 200 轮时,你会达到上下文上限,最旧的消息会悄然丢失。用户在第一轮提到的名字,远在昨天那个无关紧要的笑话之前就消失了。没有优先级排序,只有严格的时间顺序。
- 所有内容都存在于 RAM 中。 一旦 Python 进程结束,你的 Agent 就不知道你是谁了。
第二层:用于持久化的 Markdown 文件
下一步是将记忆写入磁盘。Markdown 是天然的选择:人类可读、对 Git 友好,而且 Agent 可以将其作为纯文本读回。Claude Code 正是使用这种模式,配合 CLAUDE.md 和 MEMORY.md 文件。
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")67 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")1011 def load_history(self) -> str:12 if self.history_file.exists():13 return self.history_file.read_text()14 return ""1516 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].text26 self.save_to_disk("assistant", reply)27 return reply
持久化问题解决了。重新启动脚本,对话仍然在磁盘上。你还可以维护一个单独的事实文件,Agent 可以随时间从中提取信息:
1- 用户的名字是 Sarah2- Sarah 管理 Acme Corp 的后端团队3- Acme Corp 是一家 B2B SaaS 公司4- 目前正在将生产数据库迁移到新的 AWS 区域
你可以在任何编辑器中打开这个文件,准确看到 Agent 知道什么,并手动修正。对于原型开发来说非常有用。
当只有 4 个事实时,这完美运行。将整个文件加载到上下文中,LLM 可以处理任何关于 Sarah、她的公司或行业的问题。
现在快进三个月。你的 Agent 有 2,000 个提取的事实和 200 个对话日志。磁盘上有 500K+ 个 token 的 Markdown 内容,而你的上下文窗口只有 128K。
你无法再加载所有内容了。 你需要有选择地只检索与当前查询相关的事实。使用平面文件时,你唯一的选择是关键词搜索:
1# 用户问:"我们的云迁移进展如何?"2grep("cloud migration", facts_file)3# 返回:[]4# 磁盘上的事实写着"将生产数据库迁移到新的 AWS 区域。"5# 但"cloud migration"这两个词完全没有出现。67# 用户问:"哪个团队在负责数据库工作?"8grep("database team", facts_file)9# 返回:[]10# 一个事实说 Sarah"管理后端团队"。另一个说团队正在11# "迁移生产数据库"。但没有一行同时包含12# "database"和"team"。
在小规模下,Markdown 文件可以工作。在真实规模下,它们强制使用关键词检索,而关键词无法处理同义词、改写或跨事实的连接。
信息在磁盘上。但你无法加载全部内容,而关键词搜索又太脆弱,无法找到正确的部分。
如果你用过 OpenClaw,你就会看到这种情况。它将记忆存储为 Markdown 检查点文件,经过数周的日常使用,随着上下文的积累和压缩,早期的事实会悄然消失。存储是存在的。但检索不是。
没有智能检索的存储,就是一个没有目录的图书馆。
第三层:向量搜索及其遇到的瓶颈
加上嵌入。将你的 Markdown 分块,嵌入这些块,通过余弦相似度进行搜索。现在"database"可以匹配"PostgreSQL",因为它们的向量在嵌入空间中位置相近。同义词问题解决了。
然后你遇到了新的瓶颈。考虑向量数据库中的这三个事实:
1- "Alice 是 Project Atlas 的技术负责人"2- "Project Atlas 使用 PostgreSQL 作为其主要数据存储"3- "PostgreSQL 集群在周二经历了一次宕机"
用户问: "Alice 的项目受到周二宕机的影响了吗?"
查询提到了 Alice 和周二的宕机,所以向量搜索将第一个和第三个事实排在前面。但关键的桥梁——"Project Atlas 使用 PostgreSQL"——既没有提到 Alice 也没有提到周二。它是连接各部分的纽带,但却是最不可能被呈现出来的那一个。
每个事实都是嵌入空间中的一个孤立点。连接它们的纽带对向量来说是不可见的。

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

你需要在单个记忆层中同时拥有持久化、语义理解和关系推理能力。
自己构建这个意味着要拼接向量数据库、图数据库、关系存储、实体提取器、去重管道和边权重系统。在写一行 Agent 逻辑之前,这需要数周的基础设施工作。
我一直在使用一个能干净地填补这个空白的解决方案。它完全开源,在一个屋檐下处理所有三种存储范式,而且你可以在几分钟内让它运行起来。让我们来谈谈 Cognee。
Cognee:三个存储,一个引擎,四个调用
Cognee 是一个为 Agent 记忆构建的开源知识引擎。它将向量搜索与知识图谱和关系溯源层结合到一个单一系统中。
整个 API 接口只有四个异步调用:
1import cognee23await cognee.add("Your document here") # 摄入任何内容4await cognee.cognify() # 构建知识图谱 + 嵌入5await cognee.memify() # 自我改进记忆6await cognee.search("Your query") # 通过推理进行检索
在这四个调用的背后,是一个三存储架构。

为什么是三个存储而不是一个?
每个存储捕获了其他存储无法捕捉的知识维度:
- 关系存储 → 溯源:数据来自哪里,何时被摄入,谁有访问权限
- 向量存储 → 语义:内容意味着什么,它与什么相似
- 图存储 → 关系:实体之间如何连接,什么导致什么,谁向谁汇报
扁平化其中任何一个,你都会丢失对检索准确性至关重要的信息。
默认栈是 SQLite + LanceDB + Kuzu,完全嵌入式且基于文件。pip install cognee 加上一个 LLM API 密钥,你就可以运行了。
无需 Docker,无需外部服务。
对于生产环境,将 SQLite 换成 Postgres,LanceDB 换成 Qdrant/Pinecone/pgvector,Kuzu 换成 Neo4j/FalkorDB/Neptune。
无论哪种方式,都是相同的四个调用 API。
cognify 到底做了什么?
cognee.cognify() 运行一个多阶段管道,将原始文本转换为结构化、互联的知识:
- 文档分类,按类型和领域
- 权限检查,用于多租户访问控制
- 块提取,尊重段落结构(不是固定大小的切割)
- 实体和关系提取,通过 LLM 进行,并通过内容哈希自动去重
- 摘要生成,用于高效检索
- 双重索引,进入向量存储(嵌入)和图存储(边)
去重步骤比听起来更重要。如果同一个实体出现在 50 个文档中,Cognee 会将其合并为一个具有 50 条入边的图节点。你的 Agent 不再将"Alice"视为 50 个不同的陌生人。而且管道默认是增量的:只有新的或更新的文件才会被重新处理。

每个图节点都有一个对应的嵌入。 这种双重表示是核心技巧:通过向量进入(找到语义相似的内容),通过图退出(跟随关系到连接的实体),或者反过来。这就是使多跳查询能够工作而不牺牲语义搜索的原因。
Memify:会学习的记忆
memify() 是 Cognee 区别于所有"摄入并搜索"工具的地方。它运行一个受强化学习启发的图优化过程:
- 强化 导致良好检索的有用路径
- 修剪 未被触及的陈旧节点
- 自动调整 基于实际使用的边权重
- 添加派生事实,通过识别隐含关系
一个客户支持 Agent 的图会自然地强化通过产品文档和退款策略的路径,同时让很少被查询的 HR 边衰减。图会随着时间的推移发展出自己的相关性感知。

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

使用 Cognee 记忆构建真正的 Agent
以下是将 Cognee 接入感知-思考-行动循环的完整模式:
1import cognee2from cognee import SearchType34class CogneeMemoryAgent:5 """具有图向量混合持久记忆的 Agent。"""67 def __init__(self, session_id: str = "default"):8 self.llm_client = OpenAI()9 self.session_id = session_id1011 async def ingest(self, text: str, dataset: str = "main"):12 await cognee.add(text, dataset)13 await cognee.cognify([dataset])1415 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 ""2223 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=messages32 )33 reply = response.choices[0].message.content34 await cognee.add(35 f"用户:{user_input}\n助手:{reply}",36 "conversations"37 )38 await cognee.cognify(["conversations"])39 return reply
记忆循环:摄入、提取、存储、检索、响应、再次存储。每一轮都会丰富知识图谱,增量处理意味着你只需为索引新内容付费。
会话记忆会自动处理代词解析:
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 最佳实践的教程和见解。





