Pydantic 修好了我的 Agent Memory
Akshay 🚀 (@akshay_pachaar)
2026-05-26
D
原文
---
title: "Pydantic 修好了我的 Agent Memory"
author: "Akshay 🚀 (@akshay_pachaar)"
source_url: "https://x.com/akshay_pachaar/status/2058976178908885210"
published_at: "2026-05-25T18:19:09.000Z"
fetched_at: "2026-07-02T15:31:30Z"
updated_at: "2026-07-02T15:31:30Z"
language: "zh"
review_status: "draft"
---

# Pydantic 修好了我的 Agent Memory
你的 agent 什么都记得,却什么都不理解。
Agent memory 一开始靠的是向量数据库。把事实存成 chunk,再按相似度取回。
只要查询需要跨 chunk 连接事实,它就会失效。问题不在相似度,而在结构。
Knowledge graph 曾经是修法。Entity 作为 node,relationship 作为 edge,用遍历替代匹配。
但大多数团队会撞上另一堵墙。
当你给 agent 一张 knowledge graph 作为 memory 时,默认行为是:负责抽取的 LLM 自己决定结构。
它自己选择 entity 类型、relationship label 和 attribute。
结果很泛。
比如,你正在构建一个客服 agent。你喂给它 50 段支持对话,里面涉及客户、工单、功能和升级历史。
你问:*“哪些企业客户还有未关闭的 sev-1 工单?”*
Graph 里有这些数据。但每个支持工单都被存成了一个 “Topic” node。每个客户都是一个 “Object”。每条 relationship 都是 “RELATES_TO”。
你没法按类型、严重程度或套餐层级过滤。查询返回的是噪音。
Agent 不是忘了什么。只是没人告诉它该关注什么。

修法很直接:**预先定义 schema。** 告诉抽取模型,你的领域里有哪些 entity 类型,哪些 relationship 是有效的,以及每种 entity 带哪些 attribute。
这套组织蓝图叫 **ontology**。你可以把它理解成 **agent 大脑的 schema**。
我们来看为什么这件事重要,没有它会坏在哪里,以及如何用一个 [**100% 开源方案**](https://github.com/getzep/graphiti) 实现它。
## 为什么扁平检索会在多跳推理上失效
基于向量的 memory 会把事实存成文本 chunk,并按语义相似度取回。直到一个查询需要连接不在同一个 chunk 里的事实,它都会看起来可用。
看三个关于某个项目的事实。
- Alice manages Project Atlas
- Project Atlas runs on PostgreSQL
- The PostgreSQL cluster went down Tuesday
像 “was Alice’s project affected by Tuesday’s outage” 这样的查询需要三个事实全都参与。

向量搜索只会取回事实 1 和事实 3,因为它们都提到了相关词。事实 2 是把 Alice 通过 Project Atlas 连接到 PostgreSQL 的桥,但它既没有提到 Alice,也没有提到 Tuesday。相似度搜索会漏掉它。
Knowledge graph 把 entity 存成 node,把 relationship 存成 edge。它不是匹配文本,而是遍历连接。
这条链(Alice → manages → Project Atlas → runs on → PostgreSQL)就是多跳推理能够成立的原因,而它对扁平向量检索是不可见的。
## Memory pipeline,以及 extraction 在哪里发挥作用
每个基于 graph 的 agent memory 系统都会遵循一条常见 pipeline:
1. **Ingest:** 原始数据进入系统(对话消息、文档、JSON 业务数据)
2. **Extract:** LLM 读取原始数据,并决定有哪些 entity、哪些 relationship 连接它们,以及哪些 attribute 重要
3. **Store:** 抽取出的 entity 变成 node,relationship 变成 edge,全部持久化到 graph 里
4. **Retrieve:** 查询时,系统搜索 graph 并组装相关事实
5. **Deliver:** 取回的事实被格式化成 context block,并注入 agent 的 prompt
Extraction 这一步决定一切。它决定你的 graph 包含什么、结构如何、下游能查询什么。

问题在于,在大多数框架里,这一步是个黑盒。你传入文本,LLM 抽出 “entities” 和 “relationships”,你得到 node 和 edge。类型、label、attribute 都由 LLM 自己决定。
你完全无法控制它分类什么、怎么分类。
我们来理解怎么修。
## [用 Pydantic 定义 schema](https://github.com/getzep/graphiti)
修法和 AI stack 里到处都在用的模式一样。
- FastAPI endpoint 有 Pydantic response model。
- Function calling tool 有 Pydantic schema。
- 在 Zep 里,agent memory 也是同样的方式。
使用 EntityModel(Pydantic 的 BaseModel 子类)定义自定义 entity 类型,配合 EntityText 字段和描述来引导抽取模型。
```python
from zep_cloud.external_clients.ontology import EntityModel, EntityText
from pydantic import Field
class Project(EntityModel):
"""
Represents a specific software project, application,
or codebase that the user is building or contributing to.
"""
project_status: EntityText = Field(
description="Current status: active, completed, paused, or archived.",
)
project_type: EntityText = Field(
description="Type of project: web app, mobile app, API, CLI tool, etc.",
)
```
这里,docstring 和字段描述很重要,因为带具体例子的好描述能给抽取器足够信号,让它准确分类。
上面的 Pydantic 描述不只是分类指令。它们会教抽取器它原本不知道的词汇。
Technology entity 也遵循同样模式。
```python
class Technology(EntityModel):
"""
Represents a programming language, framework, library,
database, or tool that the user works with.
"""
tech_category: EntityText = Field(
description="Category: programming language, framework, database, etc.",
)
```
Edge 类型使用 EdgeModel,并且带有自己的 attribute。
```python
from zep_cloud.external_clients.ontology import EdgeModel
class WorksOn(EdgeModel):
"""The user is currently working on, building, or contributing to a project."""
role: EntityText = Field(
description="User's role: lead developer, contributor, maintainer, etc.",
)
class UsesTechnology(EdgeModel):
"""The user actively uses or works with a specific technology."""
proficiency: EntityText = Field(
description="Proficiency level: beginner, intermediate, advanced, or expert.",
)
```
最后,用 EntityEdgeSourceTarget 把这些接入 graph,并设置 source/target 约束。它定义了哪些 entity 类型可以通过哪些 edge 类型连接:
```python
from zep_cloud import EntityEdgeSourceTarget
client.graph.set_ontology(
entities={"Project": Project, "Technology": Technology},
edges={
"WORKS_ON": (
WorksOn,
[EntityEdgeSourceTarget(source="User", target="Project")],
),
"USES_TECHNOLOGY": (
UsesTechnology,
[EntityEdgeSourceTarget(source="User", target="Technology")],
),
},
)
```
这段代码会强制保证:
- WORKS_ON 只能连接 User 和 Project
- USES_TECHNOLOGY 只能连接 User 和 Technology。
- 任何不符合这些约束的 relationship,都不会产生 typed edge。
总结一下,到目前为止,我们得到了这些:

## 底层发生了什么
当 schema 已经启用时,一段对话被 ingest 之后,Zep 的 extraction pipeline 会跑五步:
1. **Entity extraction** 识别文本里的命名 entity
2. **Entity resolution** 合并重复项(”Nexus” 和 “the Nexus project” 会变成同一个 node)
3. **Fact extraction** 识别 relationship,并把它们输出成 typed edge
4. **Fact resolution** 检测矛盾,并让过时事实失效(同时保留历史)
5. **Temporal extraction** 解析时间引用,并把它们映射到每条 edge 的有效时间窗口上
<video autoplay loop muted playsinline src="https://video.twimg.com/tweet_video/HJKyJvDakAA1jt8.mp4"></video>
你的 Pydantic schema 会引导第 1 步和第 3 步。Entity 类型告诉抽取器该找什么。带约束的 edge 类型告诉它该把哪些 relationship 分类出来。Resolution 和 temporal processing 会自动发生。
## 实际 walkthrough:看起来是什么样子
我们 ingest 一段对话,里面一位名叫 Alex 的开发者讨论了自己的工作:一个名为 Nexus 的活跃 Web app、他的技术栈以及熟练程度。

查询 Project node 时,会返回 Nexus,并填好 project_status 和 project_type attribute。

这个 node 不是泛泛的 “Topic” 或 “Object”。它是一个 Project,带着 schema 里定义好的结构化字段。
Edge 也是 typed。
- WORKS_ON 带有 role: lead developer

- USES_TECHNOLOGY 带有 proficiency:Python 和 Docker 是 advanced,TypeScript 是 intermediate。

现在,你就可以按 status 过滤 project,按 category 过滤 technology,并用精确答案查询 “which active projects use PostgreSQL”。
## Context template
最后一块是 context template,它会把 typed fact 组装成一个可以直接放进 prompt 的 block。
你可以定义要包含哪些 edge 类型和 entity 类型,Zep 会把它们连同 temporal annotation 格式化成一个字符串,注入 agent 的 prompt。
```python
client.context.create_context_template(
template_id="dev-context",
template="""# PROJECTS
%{edges types=[WORKS_ON] limit=5}
# TECH STACK
%{edges types=[USES_TECHNOLOGY] limit=10}
# PROJECT DETAILS
%{entities types=[Project] limit=5}
# TECHNOLOGIES
%{entities types=[Technology] limit=10}""",
)
```
它看起来像这样:

最终 context block 里的每一项都是 typed、有 temporal annotation,并携带你定义的 attribute。保存一次 template,然后在 agent 调用里用 ID 引用它。
## 10/10/10 约束,以及 schema 作为推理边界
Zep 强制限制最多 10 种自定义 entity 类型、10 种自定义 edge 类型,以及每种类型最多 10 个字段。

这是有意设计的,目的是迫使开发者思考一个领域里到底什么重要,而不是把所有东西都建模进去。
Source/target 约束也会充当 agent 被允许记住什么的 guardrail。如果 schema 里没有包含连接 Project 和 Competitor 的 edge 类型,那么即使对话同时提到了两者,抽取模型也不会创建那条 relationship。
Schema 定义了有效 memory 的空间。
这和 typed function calling 背后的原则一样:我们约束 LLM 的输出空间,让它不能产生无效参数。Memory schema 把同一个约束应用到了 agent 存储的内容上。
从 3-4 种 entity 类型和 3-4 种 edge 类型开始,先覆盖你领域逻辑的 80%,再逐步增加复杂度。
---
没有 schema discipline 的 agent memory,就是一张表现得像 vector store 的 graph。
某种意义上,你付出了构建 graph 的成本,却没有获得结构化检索的收益。
Schema 是把这个收益拿回来的方法,而它用的是 Pydantic,意味着你不需要学新的东西。
对领域特定应用来说,这一点尤其成立。LLM extraction 在通用知识上表现还算合理,但一旦你的领域有内部术语、和常见词撞名的产品名,或者训练数据里没有的 jargon,不受引导的 extraction 就会产生胡话。Schema 填补了这个缺口。它把领域词汇直接带进 extraction 步骤,所以 LLM 不需要以前见过你的术语。它只需要你写下的定义。

[**你可以在这里找到 Zep 的 GitHub repo →**](https://github.com/getzep/graphiti)(别忘了点 star 🌟)
感谢阅读!