如何构建永不遗忘的Agent【译】
如何构建永不遗忘的Agent【译】
如何构建永不遗忘的Agent【译】 如何构建永不遗忘的Agent【译】 Modified April 15 你需要在单个记忆层中同时具备持续性、语义理解能力以及关系推理能力。 如果你打算自己做这一套,你要把向量数据库、图数据库、关系型存储、实体抽取器、去重管道和边权系统全部粘在一起。在你真正写一行 agent 逻辑之前,这已经是好几周的基础设施工作量了。 我一直在用一个能很干净填上这个空白的方案。它完全开源,在同一个系统里处理这三种存储范式,而且几分钟就能跑起来。下面聊聊 Cognee。 Cognee:三类存储,一套引擎,四次调用 Cognee 是一个专为智能体记忆打造的开源知识引擎。它把向量搜索、知识图谱和关系型 provenance layer 组合成了一个统一系统。 整个API接口仅包含四个异步调用: Code block Python Copy import cognee await cognee.add("Your document here") Ingest anything await cognee.cognify() Build knowledge graph + embeddings await cognee.memify() Self improve the memory await cognee.search("Your query") Retrieve with reasoning 在这四个调用背后,是一个三存储架构。 为什么是三个存储,而不是一个? 因为每个存储都捕获了另外两个做不到的一种知识维度: • Relational store → provenance:数据来自哪里、何时被摄取、谁有访问权限 • Vector store → semantics:内容意味着什么、它与什么相似 • Graph store → relationships:实体如何连接、因果如何关联、谁向谁汇报 把其中任意一个维度压平,都会丢掉对检索准确率有意义的信息。 默认栈是 SQLite + LanceDB + Kuzu,全部都是嵌入式、文件型。只要 pip install cognee,再加一个 LLM API key,就能跑起来。 不需要 Docker,也不需要外部服务。 到了生产环境,你可以把 SQLite 换成 Postgres,把 LanceDB 换成 Qdrant / Pinecone / pgvector,把 Kuzu 换成 Neo4j / FalkorDB / Neptune。 无论哪种后端,API 都还是同样的四个调用。 cognify 实际执行的功能是什么? cognee.cognify() 会运行一个多阶段 pipeline,把原始文本转换成结构化、彼此互联的知识: 1. 按类型和领域做文档分类 2. 做多租户访问控制的权限检查 3. 进行尊重段落结构的 chunk 提取,而不是固定大小硬切 4. 通过 LLM 做实体和关系抽取,并用内容哈希自动去重 5. 生成摘要,提升检索效率 6. 同时写入向量存储(embeddings)和图存储(edges) 去重这一步的重要性,比听上去还大。如果同一个实体出现在 50 份文档里,Cognee 会把它合并成一个图节点,并保留 50 条入边。你的 agent 不会再把 “Alice” 看成 50 个互不相干的陌生人。而且这条 pipeline 默认是增量式的:只有新增或更新的文件会被重新处理。 每一个图节点都会对应一个 embedding。这种双重表示才是核心技巧:你可以从向量进入,找到语义相近的内容,再从图里出去,沿着关系走到相关实体;也可以反过来。正是这个机制,让多跳查询成立,同时又不牺牲语义检索。 Memify:会学习的记忆 memify() 函数正是Cognee区别于所有“摄取并检索”类工具的核心所在。它会对知识图谱执行一轮受强化学习启发的优化处理: • 强化那些曾经导向良好检索结果的路径 • 剪除长期没有被用到的旧节点 • 根据真实使用情况自动调边权 • 通过识别隐含关系来添加派生事实 客服人员的知识图谱会自然强化指向产品文档和退款政策的路径,同时让极少被查询的人力资源相关关联边逐渐弱化。 随着时间推移,该图谱会形成自身的相关性判断逻辑。 十四种检索模式 Cognee 内置了 14 种搜索模式。真正高频会用到的,是下面这些: 利用Cognee记忆构建真实智能体 下面是把 Cognee 接进 perceive think act 循环里的完整模式: Code block Python Copy import cognee from cognee import SearchType class CogneeMemoryAgent: """Agent with graph vector hybrid persistent memory.""" 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 记忆循环:摄取、提取、存储、检索、响应、再次存储。每一轮循环都会丰富知识图谱,而增量处理意味着你只需为新增内容的索引付费。 Session memory 还能自动处理代词消解: Code block Python Copy await cognee.search(query text="Where does Alice live?", session id="conv 1") await cognee.search(query text="What does she do for work?", session id="conv 1") "she" resolves to Alice from session context 多租户架构在图层面内置实现,具备按数据集划分的权限(读取、写入、删除、共享)。并非命名空间隔离,而是真正的图层面隔离。 切实可行的发展路径 如果你今天正在构建 agent,真正应该先问的问题是:“我的 agent 需要记住什么?它又会回答什么类型的问题?” 如果你的查询只需要 similarity search,比如 “find conversations like this one”,那么只用向量 memory 就够了。只要问题要跨越实体边界,比如 “Was Alice's project affected by Tuesday's outage?”,你就需要 graph traversal。 你当然也可以自己把向量、图和关系型存储拼在一起。现实里,选择这条路的团队,通常会在基础设施上先烧掉几周时间,最后得到的 memory layer 还未必真的会从使用中学习。 Cognee 不一样。它的嵌入式默认配置让你几分钟内就能跑起来;而可切换后端(Postgres、Qdrant、Neo4j)又能让你不改 agent 代码就走到生产环境。 智能依赖的是结构,而不只是存储。关系型、向量型、图型这三种存储范式,并不是彼此竞争的选项。它们是同一个 memory system 的互补层。只有这样对待它们,才能把一个无状态的 LLM wrapper,变成真正会学习的东西。 你希望你的 agent 明天能记住、而它今天还会忘掉的下一件事是什么?从那里开始。 👉Cognee GitHub,给它点个 star,然后试着把它接进你的下一个 agent。 Cognee Cognee Cognee GitHub 你需要在单个记忆层中同时具备持续性、语义理解能力以及关系推理能力。 如果你打算自己做这一套,你要把向量数据库、图数据库、关系型存储、实体抽取器、去重管道和边权系统全部粘在一起。在你真正写一行 agent 逻辑之前,这已经是好几周的基础设施工作量了。 我一直在用一个能很干净填上这个空白的方案。它完全开源,在同一个系统里处理这三种存储范式,而且几分钟就能跑起来。下面聊聊 Cognee。 Cognee Cognee:三类存储,一套引擎,四次调用 Cognee 是一个专为智能体记忆打造的开源知识引擎。它把向量搜索、知识图谱和关系型 provenance layer 组合成了一个统一系统。 整个API接口仅包含四个异步调用: 在这四个调用背后,是一个三存储架构。 为什么是三个存储,而不是一个? 因为每个存储都捕获了另外两个做不到的一种知识维度: • Relational store → provenance:数据来自哪里、何时被摄取、谁有访问权限 • Vector store → semantics:内容意味着什么、它与什么相似 • Graph store → relationships:实体如何连接、因果如何关联、谁向谁汇报 把其中任意一个维度压平,都会丢掉对检索准确率有意义的信息。 默认栈是 SQLite + LanceDB + Kuzu,全部都是嵌入式、文件型。只要 pip install cognee,再加一个 LLM API key,就能跑起来。 不需要 Docker,也不需要外部服务。 到了生产环境,你可以把 SQLite 换成 Postgres,把 LanceDB 换成 Qdrant / Pinecone / pgvector,把 Kuzu 换成 Neo4j / FalkorDB / Neptune。 无论哪种后端,API 都还是同样的四个调用。 cognify 实际执行的功能是什么? cognee.cognify() 会运行一个多阶段 pipeline,把原始文本转换成结构化、彼此互联的知识: 1. 按类型和领域做文档分类 2. 做多租户访问控制的权限检查 3. 进行尊重段落结构的 chunk 提取,而不是固定大小硬切 4. 通过 LLM 做实体和关系抽取,并用内容哈希自动去重 5. 生成摘要,提升检索效率 6. 同时写入向量存储(embeddings)和图存储(edges) 去重这一步的重要性,比听上去还大。如果同一个实体出现在 50 份文档里,Cognee 会把它合并成一个图节点,并保留 50 条入边。你的 agent 不会再把 “Alice” 看成 50 个互不相干的陌生人。而且这条 pipeline 默认是增量式的:只有新增或更新的文件会被重新处理。 每一个图节点都会对应一个 embedding。这种双重表示才是核心技巧:你可以从向量进入,找到语义相近的内容,再从图里出去,沿着关系走到相关实体;也可以反过来。正是这个机制,让多跳查询成立,同时又不牺牲语义检索。 Memify:会学习的记忆 memify() 函数正是Cognee区别于所有“摄取并检索”类工具的核心所在。它会对知识图谱执行一轮受强化学习启发的优化处理: • 强化那些曾经导向良好检索结果的路径 • 剪除长期没有被用到的旧节点 • 根据真实使用情况自动调边权 • 通过识别隐含关系来添加派生事实 客服人员的知识图谱会自然强化指向产品文档和退款政策的路径,同时让极少被查询的人力资源相关关联边逐渐弱化。 随着时间推移,该图谱会形成自身的相关性判断逻辑。 十四种检索模式 Cognee 内置了 14 种搜索模式。真正高频会用到的,是下面这些: 利用Cognee记忆构建真实智能体 下面是把 Cognee 接进 perceive think act 循环里的完整模式: 记忆循环:摄取、提取、存储、检索、响应、再次存储。每一轮循环都会丰富知识图谱,而增量处理意味着你只需为新增内容的索引付费。 Session memory 还能自动处理代词消解: 多租户架构在图层面内置实现,具备按数据集划分的权限(读取、写入、删除、共享)。并非命名空间隔离,而是真正的图层面隔离。 切实可行的发展路径 如果你今天正在构建 agent,真正应该先问的问题是:“我的 agent 需要记住什么?它又会回答什么类型的问题?” 如果你的查询只需要 similarity search,比如 “find conversations like this one”,那么只用向量 memory 就够了。只要问题要跨越实体边界,比如 “Was Alice's project affected by Tuesday's outage?”,你就需要 graph traversal。 你当然也可以自己把向量、图和关系型存储拼在一起。现实里,选择这条路的团队,通常会在基础设施上先烧掉几周时间,最后得到的 memory layer 还未必真的会从使用中学习。 Cognee 不一样。它的嵌入式默认配置让你几分钟内就能跑起来;而可切换后端(Postgres、Qdrant、Neo4j)又能让你不改 agent 代码就走到生产环境。 Cognee 智能依赖的是结构,而不只是存储。关系型、向量型、图型这三种存储范式,并不是彼此竞争的选项。它们是同一个 memory system 的互补层。只有这样对待它们,才能把一个无状态的 LLM wrapper,变成真正会学习的东西。 你希望你的 agent 明天能记住、而它今天还会忘掉的下一件事是什么?从那里开始。 👉Cognee GitHub,给它点个 star,然后试着把它接进你的下一个 agent。 Cognee GitHub 只要四个 async 调用,一个 pip install,你就跑起来了。 That's a wrap! 如果你喜欢这篇内容: Find me →@akshay pachaar @akshay pachaar ✔️ 每天,我都会分享关于AI、机器学习和Vibe Coding最佳实践的教程和见解。 原帖链接:https://x.com/akshay pachaar/status/2043745099792953508 原帖链接:https://x.com/akshay pachaar/status/2043745099792953508 从第一性原理出发解析智能体记忆:从Python列表、Markdown文件,到向量检索、图向量混合架构,最终形成一套简洁易用的开源完整解决方案。 LLM 在设计上就是无状态的。每一次 API 调用都会重新开始。你在和 ChatGPT 对话时感受到的那种“记忆”,其实只是通过在每次请求里重新发送整段对话历史制造出来的一种幻觉。 这个技巧对于日常闲聊是够用的。但一旦你开始尝试构建一个真正的 agent,它就会立刻失效。 只要你跳过 memory,下面这 7 种失败模式马上就会出现: 1. Context amnesia:agent 会反复问你已经给过它的信息 2. Zero personalization:每一次交互都显得很泛化 3. Multi step task failure:中间状态会在任务过程中悄悄丢失 4. Repeated mistakes:没有 episodic recall,就意味着同样的错误会无限重复 5. No knowledge accumulation:每个 session 都从零开始 6. Hallucination from gaps:当上下文溢出时,模型就会开始编造 7. Identity collapse:没有连续性,也就没有信任 最直观的回应通常是:“那就多塞点上下文进去。” 这也是为什么 128K 和 200K token 窗口会让人感觉它们本来应该能解决一切。 但它们并不能。 当相关信息位于长文本上下文的中间位置时,准确率会下降超过30%。这就是已被充分证实的“中间丢失”效应。 上下文如同一项共享资源:系统提示词、检索文档、对话历史以及输出内容都会争夺相同的令牌配额。 即便上下文长度达到10万个token,由于缺乏持续性、优先级划分与信息显著性处理,单纯的上下文长度依然不足以满足需求。 Memory 的核心,不是往 prompt 里塞更多文本。而是把 agent 要记住的东西组织起来,让它能够找到真正重要的内容。 真正能发挥作用的认知科学框架 Lilian Weng 在 2023 年提出的公式,已经成了默认框架: Agent = LLM + Memory + Planning + Tool Use. 这是四个并列的支柱。 她的分类借用了认知科学中的思路,人类记忆被拆分成三套系统: • Sensory memory 会捕捉原始感知输入,并把它保留极短的一瞬间。只有你真正注意到的那部分,才会被继续传递下去。 • Working memory 是主动思考发生的地方。它一次大约只能容纳 7±2 个项目(Lilian Weng 的相关综述)。一旦失去注意力,里面的内容就会消失。 Lilian Weng 的相关综述 • Long term memory 是持久存储,几乎没有实际容量上限。真正的瓶颈在检索:你可以存下数百万条信息,但依然可能想不起真正需要的那一条。 它们都能直接映射到现代 agent 架构中的组件: Long term memory 本身还可以继续拆分: • Episodic:具体发生过的事件,“周二的时候 PostgreSQL 集群挂了” • Semantic:事实和概念,“PostgreSQL 是一个关系型数据库” • Procedural:技能和流程,“当用户要求退款时,先检查购买日期” 情景记忆与语义记忆之间的桥梁是记忆巩固: 重复发生的具体事件会提炼为通用知识。一个智能体若在数十次交互中发现“用户始终偏好执行摘要”,就应将其转化为可复用规则。若缺乏巩固过程,智能体只会重复回放单个事件,而非从中学习。 最简智能体,以及最先失效的部分 抛开各类框架不谈,智能体本质上就是一个循环:感知、思考、执行。 你先告诉它 “I have 4 apples”,再问它 “I ate one, how many left?”,它根本不知道你在说什么苹果。因为每一次调用都是孤立存在的。 第一层:Python列表 每个人首先会尝试的修复方式是这个: 现在,多轮对话可以正常工作了。苹果那个问题能答对,是因为每次调用时,整段对话历史都会被重新发送。 但两个问题会很快出现: • 这个列表会无限增长。大概到第 200 轮时,你就会碰到上下文天花板,最早的消息会被静默丢弃。第 1 轮里的用户名会比昨天那句无关紧要的玩笑更早消失。没有任何优先级,只有严格按时间排序。 • 一切都只活在内存里。只要 Python 进程一结束,agent 就完全不知道你是谁。 第二层:用于持久化存储的Markdown文件 下一步,自然就是把 memory 写到磁盘。Markdown 是一种很自然的选择:人类可读、对 Git 友好,而且 agent 也可以把它当作普通文本重新读回来。Claude Code 采用的正是这种模式,用 CLAUDE.md 和 MEMORY.md 文件来做。 持久化问题已解决。脚本重启后,对话仍然留在磁盘上。你也可以另外维护一个事实文件,让 agent 逐步从对话里抽取内容: 你可以用任意编辑器打开该文件,清晰查看智能体掌握的所有信息,并手动进行修正。这对于原型开发非常实用。 在仅有4条信息的情况下,这种方式运行得十分顺畅。将整个文件加载到上下文窗口中,大语言模型就能解答所有关于萨拉、她所在公司及其所属行业的问题。 时间快进至三个月后,你的智能体已提取出2000条信息,同时积累了200条对话日志。这些内容以Markdown格式存储在磁盘上,token数超过50万,而你的上下文窗口仅为12.8万token。 此时你已无法将所有内容一次性加载,需要有选择性地仅提取与当前查询相关的信息。而在使用普通文本文件的情况下,你唯一的办法就是进行关键词检索: 在小规模下,Markdown 文件是可行的。但一旦到了真实规模,它们就会把你逼回关键词检索,而关键词无法处理同义词、转述,也无法处理跨越多条事实的关系连接。 信息确实在磁盘上。 但你没法把它全部加载进来,而关键词搜索又过于脆弱,找不到真正应该取出的那一部分。 如果你用过 OpenClaw,你就见过这种情况。它把 memory 存成 Markdown checkpoint 文件,但在持续几周的日常使用后,随着上下文不断累积与压缩,更早的 facts 会悄悄滑落。存储还在。检索不在。 没有智能检索的存储,就像一个没有目录的图书馆。 第三层:向量检索及其面临的瓶颈 添加嵌入功能。将你的Markdown内容分块,对这些分块进行嵌入处理,通过余弦相似度进行检索。如今“数据库”能够匹配到“PostgreSQL”,这是因为它们的向量在嵌入空间中位置相近。同义词问题就此迎刃而解。 然后你又会撞上一堵新的墙。想象一下,你的向量数据库里有下面三条事实: 用户问:“Was Alice's project affected by Tuesday's outage?” 这个查询提到了 Alice 和 Tuesday's outage,所以向量搜索会把第一条和第三条排得很高。但真正关键的桥梁,“Project Atlas uses PostgreSQL”,既没提 Alice,也没提 Tuesday。它才是把问题连起来的关键,却正好最不容易被召回。 每一条事实在 embedding space 里都只是一个孤立点。把它们串联起来的那层 connective tissue,对向量来说是不可见的。 这并非极端特例,而是现实场景中问题的常见形态。业务知识本身就具有关联性:人员隶属于团队,团队负责项目,项目依赖系统,系统会出现故障。任何涉及两层及以上关联的问题,都超出了普通向量检索所能解答的范围。 能力矩阵 每一层都会修复上一层的痛点,但同时暴露出一个更深层的问题: