Agent 记忆系统学习笔记(二):从 Zep 看懂时间图谱记忆
用 Zep / Graphiti 作为第二个样本,理解 Context Graph、episode、fact、valid_at / invalid_at、Context Block 和时间图谱召回链路。
写在前面:为什么第二篇看 Zep
上一篇我用 Mem0 拆了一遍 Agent 记忆系统的基本链路:对话如何被抽取成 memory,memory 如何被存储,未来又如何通过语义、关键词、实体、时间、衰减和 rerank 找回来。
这一篇换一个视角:如果记忆不是一条条事实,而是一张会随时间变化的图,会发生什么?
这就是 Zep 最值得分析的地方。它的核心不是“给 memory 加一个图增强”,而是把用户、业务数据和 Agent 工作记录组织成一张 temporal Context Graph。在这张图里,实体是节点,事实和关系是边,原始数据是 episode,边上还带着事实何时开始有效、何时失效的时间信息。
所以这篇不是 Zep 接入教程,而是继续这个系列的学习目标:用 Zep 作为样本,理解时间图谱记忆这条技术路线。
一、Zep 的核心问题:长期记忆为什么需要图
在 Mem0 那篇里,我把 Mem0 理解成一个“记忆排序系统”。它的基本单元是 memory fact:一条被抽取出来、未来可以召回的事实。
但有些问题用单条 fact 很难表达清楚。
比如:
用户原来在 A 公司。
用户后来加入 B 公司。
用户和 Alice 一起负责 Q1 路线图。
Alice 后来转去负责定价项目。
Q1 路线图因为预算问题延期。如果只是把这些都存成独立 memory,当然也能搜。但真正的难点是:这些事实之间有关系,而且关系会随时间变化。
你问:
用户现在和 Alice 还在同一个项目吗?这不是单纯的语义相似问题。系统需要知道:
用户是谁
Alice 是谁
他们之前有什么关系
这个关系从什么时候开始
是否已经结束
Q1 路线图和定价项目分别是什么
哪个事实代表当前状态Zep 的答案是:不要只存一堆 memory,而是构建一张带时间的 Context Graph。
从这个角度看,Zep 不是单纯的 memory store,而是一个把交互数据、业务数据和历史状态持续编织成图的系统。
二、Zep 的核心抽象:Context Graph
Zep 文档里反复提到一个概念:Context Graph。
可以把它理解成某个用户、对象或业务主体的一张时间知识图谱。
这张图里主要有三类东西:
Entity Nodes:实体节点
节点代表实体,比如用户、公司、地点、项目、产品、账号、概念。
和普通图谱不同的是,Zep 的实体节点不只是一个名字。它还会维护围绕这个实体的叙事摘要,比如这个用户是谁、这个项目发生了什么、这个账号有哪些历史状态。
Entity Edges / Facts:事实边
边代表两个实体之间的关系,而 fact 存在边上。
比如:
Emily 的账号因为支付失败被暂停。这里可能有两个实体:
Emily 的账号
支付失败关系边上保存事实:
User account Emily0e62 has a suspended status due to payment failure.更重要的是,这条 fact 不是一个静态字符串,它带时间属性。
Episodes:原始证据
Episode 是开发者交给 Zep 的原始数据:聊天消息、文本片段、JSON 记录、邮件、工单、CRM 数据等。
这点很重要。Zep 并不是抽取完 fact 就把原始数据扔掉。Episode 会被保留,作为事实背后的证据。以后如果 Agent 需要引用原话、找上下文、追溯来源,就可以回到 episode。
所以 Zep 的结构不是:
对话 → memory而更像:
episode → entities + facts + summaries + observations → context block三、写入链路:episode 如何变成时间图谱
Zep 支持几种数据入口。
一种是聊天消息:
thread.add_messages另一种是业务数据:
graph.add(message / text / JSON)这说明 Zep 的目标不只是“记住聊天”,还包括把业务系统里的动态数据也放进同一张图。
这里最关键的概念是 episode。
每次你往 Zep 里加一段消息、文本或 JSON,它都会先成为一个 episode。然后系统从 episode 里抽取实体、关系和事实,并把它们写进 Context Graph。
举个例子,输入一条业务 JSON:
{
"event": "payment_failed",
"account": "Emily0e62",
"reason": "Card expired",
"amount": 99.99,
"created_at": "2024-09-15T00:00:00Z"
}Zep 可能从里面抽出:
账号 Emily0e62 发生了一笔 99.99 的失败交易。
失败原因是 Card expired。
这个事件发生在 2024-09-15。这些不是简单存在一个列表里,而会进入图:账号、交易、失败原因可能成为实体或关系的一部分,事实被挂在边上,时间字段也进入事实生命周期。
这就是 Zep 和普通 memory fact 系统的第一个大差异:Zep 从源头上把记忆当成图结构来生成,而不是事后给 memory 做实体连接。
四、Zep 的时间模型:事实不是覆盖,而是失效
Zep 最值得学习的设计,是它对事实变化的处理。
很多系统遇到状态变化时会做 update:
旧事实:用户喜欢 Adidas
新事实:用户喜欢 Puma然后旧事实被覆盖。
但 Zep 的思路不是简单覆盖,而是让事实带生命周期。
Zep 的 fact 边上有四个时间字段:
| 时间字段 | 含义 |
|---|---|
| created_at | Zep 什么时候知道这个事实 |
| valid_at | 这个事实在现实中什么时候开始为真 |
| invalid_at | 这个事实在现实中什么时候不再为真 |
| expired_at | Zep 什么时候知道这个事实失效 |
这个设计非常重要,因为它区分了两个时间:
现实中什么时候发生
系统什么时候知道比如用户 6 月 1 日搬家,但 6 月 10 日才告诉 Agent。那么:
valid_at = 6 月 1 日
created_at = 6 月 10 日如果 9 月 1 日又搬走,但 9 月 5 日才告诉 Agent:
invalid_at = 9 月 1 日
expired_at = 9 月 5 日这比“最新事实覆盖旧事实”细得多。它让系统可以回答:
用户现在住在哪里?
用户 8 月时住在哪里?
系统是什么时候知道用户搬家的?这也是 Zep / Graphiti 路线最有价值的地方:它不是只记住事实,而是记住事实的时间边界。
五、召回链路:Zep 怎么把图谱变成上下文
Zep 的召回和 Mem0 很不一样。
Mem0 更像:
query → 搜 memory → 返回 top memories → 应用注入 promptZep 更像:
query 或最近消息 → 搜 Context Graph → 选择多种 context type → 组装 Context Block → 直接放进 promptZep 有两种常见使用方式。
第一种是高层方法:
thread.get_user_context()它会根据当前 thread 最近几条消息,在整个 User Graph 里找相关上下文,然后返回一个 prompt-ready 的 Context Block。
第二种是低层方法:
graph.search()你可以指定要搜 facts、entities、episodes、observations、thread summaries,或者使用 scope="auto" 让 Zep 自己跨类型选择并组装上下文。
这背后用到几类信号:
semantic similarity:语义相似
BM25 full-text:关键词精确匹配
Breadth-first search:从指定节点出发做图连接相关性
RRF / MMR / cross encoder / node distance 等 reranker
时间过滤:created_at、valid_at、invalid_at、expired_at所以 Zep 的召回不是简单“图遍历”,而是混合检索:语义、关键词、图距离、时间过滤、跨类型 rerank 一起工作。
六、Context Types:Zep 召回的不只是事实
Zep 还有一个很值得学习的点:它把上下文拆成多种形态。
这比“只返回 memory list”更细。
Facts:精确事实
适合回答具体问题,比如:
用户账号为什么被暂停?
用户什么时候开始使用某个工具?
某个项目的决定是什么?Facts 的优势是精确,而且有时间范围。
Entities:实体摘要
适合回答围绕某个实体的问题,比如:
我们知道 Alice 的哪些信息?
这个项目过去发生过什么?
这个账号的整体状态是什么?实体摘要不是某一条 fact,而是围绕一个节点的叙事。
Episodes:原始证据
适合需要原话和来源的时候。
比如 Agent 不能只说“用户投诉过登录问题”,它还需要引用当时用户怎么说的,就应该回到 episode。
Thread Summaries:会话摘要
适合恢复某个历史会话。
它回答的是:这条 thread 里发生过什么,而不是全局用户画像。
Observations:跨事实形成的稳定模式
这是 Zep 比较有意思的一层。Observation 不是单条 fact,而是从多个 facts 和 entities 中总结出的稳定模式、承诺、决策或状态变化。
比如:
用户经常在账单失败后需要人工支持。
用户对支付可靠性非常敏感。
某个项目持续被预算问题影响。这种信息如果只看单条 fact,会显得很碎;但作为 observation,就能表达“长期模式”。
User Summary:用户基线画像
Zep 的默认 Context Block 会包含 user summary。它提供一个用户的长期基线画像,让 Agent 不用每次都从零理解用户是谁。
这套 context type 的拆分,说明 Zep 的目标不是简单召回,而是做 context assembly:根据当前问题选择合适形态的上下文。
七、Context Block:Zep 不只是返回检索结果
Zep 的一个关键产品化设计是 Context Block。
它不是让你拿到一堆搜索结果后自己拼 prompt,而是直接返回一个可以塞进系统提示词的字符串。
典型结构类似:
<USER_SUMMARY>
用户是谁、长期偏好是什么、当前账户状态如何……
</USER_SUMMARY>
<FACTS>
- 某条相关事实。(Date range: from - to)
- 某条相关事实。(Date range: from - to)
</FACTS>这件事很重要,因为很多 memory 系统只解决“搜到什么”,但没有解决“怎么把搜到的东西喂给模型”。
而 Zep 直接把召回和 prompt 组装连在一起:
检索结果 → context selection → 格式化 → 字符预算控制 → Context Block这对应用开发者很友好,但也有取舍:默认 Context Block 更方便,定制性则需要通过 context template 或 advanced context construction 来做。
我理解它的定位是:
Zep 不只是 graph retrieval,而是面向 Agent 的上下文工程系统。
八、和 Mem0 的关键差异
看完 Zep,再回头看 Mem0,两者差异会很清楚。
| 维度 | Mem0 | Zep / Graphiti |
|---|---|---|
| 核心抽象 | memory fact | temporal Context Graph |
| 图的角色 | 实体连接,用于 ranking boost | 图是主要记忆结构 |
| 时间处理 | retrieval ranking 中的时间信号 | fact 边自带 valid_at / invalid_at |
| 原始证据 | memory 和历史记录 | episode 是一等对象 |
| 召回结果 | memories | Context Block / facts / entities / episodes 等 |
| 更适合 | 个性化偏好、长期事实、轻量 memory 层 | 关系变化、状态演化、多源业务数据、时间查询 |
简单说:
Mem0 更像一个产品化 memory ranking layer。
Zep 更像一个 temporal graph + context assembly layer。这不是谁更好,而是抽象层不同。
如果你的应用主要是记用户偏好、历史任务、简单事实,Mem0 的 mental model 更直接。
如果你的应用里有大量实体关系、业务事件、状态变化、跨系统数据,Zep 的图谱模型会更自然。
九、我对 Zep 的判断
我觉得 Zep 最值得学习的不是“用了知识图谱”,而是它把长期记忆里的几个难题都放进了图模型:
第一,事实不是孤立的,而是实体之间的关系。
第二,事实不是永远为真,而是有时间边界。
第三,原始数据不能丢,episode 要作为证据保留。
第四,召回不是单一结果类型,而是按任务选择 facts、entities、episodes、observations、summary。
第五,最终目标不是返回数据库记录,而是组装出 LLM 能直接使用的 Context Block。
它适合的场景是:
客户支持:账号、工单、支付、历史问题持续变化
销售 / CRM:人、公司、机会、会议、承诺之间关系复杂
医疗 / 教育:用户状态和偏好随时间演化
企业 Agent:业务系统 JSON、文档、聊天记录要汇入同一上下文
长周期项目管理:项目状态、负责人、决策和风险不断变化它不一定适合的场景是:
只需要轻量用户偏好记忆
不需要关系建模和时间有效性
希望完全自托管且不想接入托管服务
需要自己控制每个图数据库细节另外要注意一点:Zep 是商业托管产品,Graphiti 是它背后的开源 temporal knowledge graph 框架。研究技术路线时可以把 Zep 当成“产品化图谱记忆”,把 Graphiti 当成“开源图谱引擎”。如果后面要自建,Graphiti 会是更值得深入看的部分。
十、这一篇之后,我的分析框架更新了
上一篇 Mem0 让我看到一个 memory layer 需要回答:
怎么抽取 memory?
怎么多信号召回?
怎么排序?
怎么注入上下文?这一篇 Zep 让我加上几个新问题:
这个系统有没有显式实体和关系?
事实是否有时间生命周期?
原始证据是否被保留?
它返回的是 memory list,还是 prompt-ready context?
它能否表达跨事实的长期模式?所以,到目前为止,这个系列里可以形成一个初步判断:
轻量长期记忆看 memory ranking;复杂长期记忆看 temporal graph;真正面向 Agent 的系统,最后都要走向 context assembly。
下一篇我会看 Letta / MemGPT。它和 Mem0、Zep 又不一样:它关注的是 Agent 如何主动管理自己的 core memory 和 archival memory。