Pydantic 修好了我的 Agent Memory
Pydantic 修好了我的 Agent Memory
Pydantic 修好了我的 Agent Memory Pydantic 修好了我的 Agent Memory Modified May 27 底层实际发生了什么 当对话在激活模式下m被摄取,Zep 的提取流水线会运行五个步骤: 1. 实体提取: 识别文本中的命名实体 2. 实体解析: 合并重复项,例如「Nexus」与「the Nexus project」会变为同一个节点 3. 事实提取: 识别关系,并将其输出为带类型的关系 4. 事实解析: 发现矛盾并使过期事实失效,同时保留历史 5. 时间提取: 解析时间引用,并将其映射为每条关系的时间有效期 你的 Pydantic 结构会引导步骤 1 和步骤 3。实体类型告诉提取器应该寻找什么;带约束的关系类型告诉它应当把哪些关系分类出来。解析和时间处理会自动发生。 实际效果示例 我们摄取一段对话,其中一位叫 Alex 的开发者介绍自己的项目:一个名为 Nexus 的Web 应用,以及他的技术栈与熟练程度。 查询项目节点时,会返回 Nexus,并填充项目状态与项目类型属性。 这个节点不再是一个泛化的「主题」或「对象」,而是带有结构定义字段的项目。 关系同样是带类型的: • WORKS ON 携带角色:lead developer • USES TECHNOLOGY 携带熟练程度:Python 和 Docker 为高级,TypeScript 为中级 现在系统可以按状态过滤项目、按类别过滤技术,并精确回答「哪些活跃项目使用 PostgreSQL?」 上下文模板 最后一块是上下文模板:它会把带类型的事实组装成可直接放进提示词的块。 你可以定义需要包含哪些关系类型和实体类型,Zep 会附带时间标注进行格式化,生成一个字符串并注入 Agent 的提示词。 Code block Python Copy client.context.create context template( template id="dev context", template=""" 项目 %{edges types=[WORKS ON] limit=5} 技术栈 %{edges types=[USES TECHNOLOGY] limit=10} 项目详情 %{entities types=[Project] limit=5} 技术 %{entities types=[Technology] limit=10}""", ) 生成的效果如下: 最终上下文块中,每一条条目都有类型、时间标注,并携带结构中定义的属性。模板只需保存一次,在 Agent 调用时按 ID 引用即可。 10 / 10 / 10 限制,以及作为推理边界的模式 Zep 有一个硬性限制:最多 10 个自定义实体类型、10 个自定义关系类型、每种类型最多 10 个字段。 这是刻意设计的:它迫使开发者思考领域里真正重要的东西,而不是试图建模一切。 来源/目标约束同样会成为 Agent 被允许记住什么的护栏。如果结构没有定义一种把项目连接到竞争对手的关系类型,即使对话同时提到了这两者,提取模型也不会创建这条关系。 模式定义了有效记忆的空间。 这与类型化函数调用背后的原则相同:我们约束大语言模型的输出空间,使它无法产生无效参数。记忆模式则把同样的约束应用到 Agent 要存储的内容上。 从能覆盖你 80% 领域逻辑的 3 4 个实体类型与 3 4 个关系类型开始,再逐步增加复杂度。 缺少模式约束的 Agent 记忆,是一张行为像向量存储的图。 换句话说,你支付了构建图的成本,却没有获得结构化检索的收益。 这个模式会把这份收益带回来,而它采用 Pydantic,意味着你并不需要学习新的东西。 这在特定领域的应用中尤为重要。大语言模型提取在通用知识上通常表现不错;但一旦你的领域中有内部术语、与常用词冲突的产品名,或训练数据里没有出现过的术语,不受引导的提取就会生成毫无意义的结果。该模式能弥合这道差距:它直接把领域词汇引入提取步骤,因此大语言模型不必此前见过你的术语,只需要读取你写下的定义。 Zep 的 GitHub 仓库在这里 →(别忘了点 star) 感谢阅读! Zep 的 GitHub 仓库在这里 → 底层实际发生了什么 当对话在激活模式下m被摄取,Zep 的提取流水线会运行五个步骤: 1. 实体提取: 识别文本中的命名实体 2. 实体解析: 合并重复项,例如「Nexus」与「the Nexus project」会变为同一个节点 3. 事实提取: 识别关系,并将其输出为带类型的关系 4. 事实解析: 发现矛盾并使过期事实失效,同时保留历史 5. 时间提取: 解析时间引用,并将其映射为每条关系的时间有效期 你的 Pydantic 结构会引导步骤 1 和步骤 3。实体类型告诉提取器应该寻找什么;带约束的关系类型告诉它应当把哪些关系分类出来。解析和时间处理会自动发生。 实际效果示例 我们摄取一段对话,其中一位叫 Alex 的开发者介绍自己的项目:一个名为 Nexus 的Web 应用,以及他的技术栈与熟练程度。 查询项目节点时,会返回 Nexus,并填充项目状态与项目类型属性。 这个节点不再是一个泛化的「主题」或「对象」,而是带有结构定义字段的项目。 关系同样是带类型的: • WORKS ON 携带角色:lead developer • USES TECHNOLOGY 携带熟练程度:Python 和 Docker 为高级,TypeScript 为中级 现在系统可以按状态过滤项目、按类别过滤技术,并精确回答「哪些活跃项目使用 PostgreSQL?」 上下文模板 最后一块是上下文模板:它会把带类型的事实组装成可直接放进提示词的块。 你可以定义需要包含哪些关系类型和实体类型,Zep 会附带时间标注进行格式化,生成一个字符串并注入 Agent 的提示词。 生成的效果如下: 最终上下文块中,每一条条目都有类型、时间标注,并携带结构中定义的属性。模板只需保存一次,在 Agent 调用时按 ID 引用即可。 10 / 10 / 10 限制,以及作为推理边界的模式 Zep 有一个硬性限制:最多 10 个自定义实体类型、10 个自定义关系类型、每种类型最多 10 个字段。 这是刻意设计的:它迫使开发者思考领域里真正重要的东西,而不是试图建模一切。 来源/目标约束同样会成为 Agent 被允许记住什么的护栏。如果结构没有定义一种把项目连接到竞争对手的关系类型,即使对话同时提到了这两者,提取模型也不会创建这条关系。 模式定义了有效记忆的空间。 这与类型化函数调用背后的原则相同:我们约束大语言模型的输出空间,使它无法产生无效参数。记忆模式则把同样的约束应用到 Agent 要存储的内容上。 从能覆盖你 80% 领域逻辑的 3 4 个实体类型与 3 4 个关系类型开始,再逐步增加复杂度。 缺少模式约束的 Agent 记忆,是一张行为像向量存储的图。 换句话说,你支付了构建图的成本,却没有获得结构化检索的收益。 这个模式会把这份收益带回来,而它采用 Pydantic,意味着你并不需要学习新的东西。 这在特定领域的应用中尤为重要。大语言模型提取在通用知识上通常表现不错;但一旦你的领域中有内部术语、与常用词冲突的产品名,或训练数据里没有出现过的术语,不受引导的提取就会生成毫无意义的结果。该模式能弥合这道差距:它直接把领域词汇引入提取步骤,因此大语言模型不必此前见过你的术语,只需要读取你写下的定义。 Zep 的 GitHub 仓库在这里 →(别忘了点 star) Zep 的 GitHub 仓库在这里 → 感谢阅读! 原帖链接:https://x.com/akshay pachaar/status/2058976178908885210 原帖链接:https://x.com/akshay pachaar/status/2058976178908885210 你的 agent 什么都记得,却什么都理解不了。 智能体的记忆始于向量数据库。将事实存储为数据块,通过相似度进行检索。 这个方法一直能用,一旦需要跨数据块关联各类信息,系统就会出现问题。问题不在于相似度匹配,而在于数据结构本身。 知识图谱就是解决方案。实体作为节点,关系作为边,采用遍历而非匹配。 但多数团队会撞上另一堵墙。 当你把知识图谱给智能体作为记忆时,默认行为是处理提取的大语言模型(LLM)自行决定结构。 它会选择实体类型、关系标签和属性。 结果往往非常泛化。 例如,你在构建一个 customer support agent。你喂给它 50 段支持对话,内容涵盖客户、工单、功能和升级历史。 你问:“哪些企业客户有未解决的一级严重程度(sev 1)工单?” 图表中包含数据。但每个工单都被存储为一个“主题”节点。每个客户都是一个“对象”。每个关系都是“关联到”。 你没有办法按类型、严重程度或计划层级进行筛选。查询返回的是无用信息。 Agent 并没有忘记任何东西。只是从来没人告诉它,应该关注什么。 解决方法很简单:预先定义模式。告诉提取模型你的领域中存在哪些类型的实体、哪些关系是有效的,以及每个实体具有哪些属性。 这种组织蓝图被称为本体。可以将其视为你的智能体大脑的模式。 下面会说明为什么它重要、缺少它时系统会在哪里坏掉,以及怎样使用一套 100% 开源方案 来实现。 为什么平面检索在多跳推理中会失效 向量记忆把事实存成文本数据块,再依据语义相似度检索。当查询需要连接不在同一个数据块中的事实时,这套机制就不够用了。 假设系统存着关于某个项目的三条事实: • Alice 管理 Project Atlas • Project Atlas 运行在 PostgreSQL 上 • PostgreSQL cluster 在周二宕机 类似“周二的故障影响到 Alice 的项目了吗?”这样的查询,需要把三条事实都连起来。 向量搜索往往只检索到第一条和第三条,因为它们包含相关词汇。第二条事实是把 Alice 通过 Project Atlas 连接到 PostgreSQL 的桥梁,但它既没提到 Alice,也没提到周二。相似度匹配会漏掉它。 知识图谱把实体存为节点,把关系存为边。系统不再匹配文本,而是沿连接关系遍历。 这条链路(Alice → 管理 → Project Atlas → 运行 → PostgreSQL)正是多跳推理能工作的原因,而平面向量检索看不到它。 记忆流水线及提取所处的位置 每套基于图谱的 Agent 记忆系统都遵循相似的流水线: 1. 摄取: 输入原始数据,例如对话消息、文档或 JSON 格式的业务数据 2. 提取: 大语言模型读取原始数据,判断存在哪些实体、哪些关系连接它们,以及哪些属性重要 3. 存储: 提取出的实体变为节点,关系变为边,并持久化到图谱中 4. 检索: 查询到来时,系统搜索图谱并组装相关事实 5. 交付: 将检索出的事实格式化为上下文块,注入 Agent 的提示词 提取步骤决定了一切:图谱里有什么、采用什么结构,以及之后能查询什么。 问题就在这里。大多数框架中,这一步是黑箱。你传入文本,大语言模型抽出所谓的「实体」和「关系」,然后你得到节点和边。类型、标签和属性全由大语言模型自己决定。 你无法控制它如何分类,也无法控制它分类什么。 下面看看如何修复。 使用 Pydantic 定义模式 使用 Pydantic 定义模式 解决方式与 AI 技术栈中很多地方采用的模式一致: • FastAPI 端点有 Pydantic 响应模型 • 函数调用工具获取 Pydantic 模式 • 在 Zep 中,Agent 记忆也可以采用同样方式 使用 EntityModel(Pydantic BaseModel 的子类)定义自定义实体类型,并通过 EntityText 字段与描述来引导提取模型。 这里的文档字符串和字段描述很重要,因为具体且清楚的描述,才能给提取器足够信号,使它准确分类。 上面的 Pydantic 描述不只是分类指令。它们也在教提取器认识原本不懂的领域词汇。 Technology 实体模式: 关系类型使用 EdgeModel,并携带自身属性: 最后,通过 EntityEdgeSourceTarget 规定来源/目标约束,把这些类型接入图谱。它定义哪些实体类型可以通过哪些关系类型建立连接: 这段代码强制规定: • WORKS ON 只能将用户连接到项目 • USES TECHNOLOGY 只能将用户连接到技术 • 任何不符合这些约束的关系,都不会生成带类型的关系 到这里,结构如下: