Zep - Temporal Knowledge Graph Memory · essay

03. Zep:用时序知识图谱做 Agent Memory

论文:ZEP: A Temporal Knowledge Graph Architecture for Agent Memory
链接:https://arxiv.org/pdf/2501.13956
代码:https://github.com/getzep/zep

本地原文:

![[papers/03-Zep-2501.13956.pdf]]

论文定位与阅读路径

核心定位

Zep 把 Agent 记忆从“文本片段列表”升级成“带时间的知识图谱”,特别适合处理用户事实变化、关系变化和长期会话。

原文结构导读

原文章节读法你要抓住的工程问题
1 Introduction看问题定义为什么普通向量记忆难处理变化事实
2 Knowledge Graph Construction精读,论文核心会话如何变成图结构
2.1 Episodes看原始事件层为什么不能直接丢掉原始会话证据
2.2 Entities and Facts看实体/关系抽取节点、边、事实三者如何拆分
2.2.3 Temporal Extraction and Edge Invalidation重点事实何时生效、何时失效,怎样避免旧事实污染
2.3 Communities看图聚合多个事实如何形成更高层主题
3 Memory Retrieval精读search + reranker 如何从图里取证据
4 Experiments看 Deep Memory Retrieval 和 LongMemEval图记忆是否比片段记忆更能处理长历史
Appendix Prompts复现时再读LLM 抽取实体、事实、时间的 prompt 边界

Zep 的阅读重点是“证据链可追溯”。一条回答最好能回到具体 episode,再回到抽取出的 entity/fact/temporal edge。否则图谱看起来高级,实际只是把向量库换了个名字。

证据链阅读

证据点读什么工程判断
Graph constructionepisode、entity、fact、community 如何生成构图过程是否可追溯、可重跑
Temporal invalidation旧事实如何失效而不是被删除解决长期记忆里最常见的“过时事实污染”
Deep Memory Retrieval图检索是否改善深层历史查询判断图结构是否带来真实召回收益
LongMemEval 对比与 MemGPT/长记忆 baseline 的差异看 Zep 强在事实变化还是长文本压缩

研究背景与问题定义

为什么需要图谱

向量库擅长找“语义相似文本”,但不擅长表达关系。

比如:

代码块TEXT · 4 行收起展开
小明现在住在广州。
小明以前住在深圳。
小明是小红的同学。
小红喜欢摄影。

这些信息如果只是文本,很难回答:

  • 小明现在住哪里?
  • 小明和小红是什么关系?
  • 小明以前的住址是否还有效?

知识图谱会把它们变成节点和边:

graph TD
    A[小明] -->|现在住在| B[广州]
    A -->|曾经住在| C[深圳]
    A -->|同学| D[小红]
    D -->|喜欢| E[摄影]

方法框架与核心机制

核心结构

Zep 的记忆大致分三类:

记忆工程解释
Episodic Memory某次对话或事件发生了什么
Semantic Memory抽取出的事实和关系
Community Memory图中一组相关节点形成的主题社区

工作流程

flowchart TD
    A[会话消息] --> B[抽取实体和关系]
    B --> C[写入时序图谱]
    C --> D[检测冲突和重复]
    D --> E[旧边失效或降权]
    F[用户新问题] --> G[混合检索]
    G --> H[语义相似度]
    G --> I[BM25关键词]
    G --> J[图遍历]
    H --> K[Rerank]
    I --> K
    J --> K
    K --> L[构造成上下文]

关键概念

1. Temporal

Temporal 就是“带时间”。
记忆不是永远正确的,事实会变。

例如:

代码块TEXT · 2 行收起展开
2024: 用户在深圳工作
2026: 用户在广州读书

如果没有时间和失效机制,Agent 可能把旧事实当成当前事实。

2. Edge Invalidation

当新事实和旧事实冲突时,不一定物理删除旧边,而是把旧边标记为失效。

这很像数据库里的软删除:

代码块SQL · 1 行收起展开
valid_from, valid_to, is_active

Zep 不只用 embedding,还组合:

  • 语义相似度。
  • BM25 关键词。
  • 图遍历。
  • reranker。

这很工程化,因为单一检索经常不稳。

概念校准

向量库像“按意思找相似笔记”。知识图谱像“人物关系表 + 时间轴”。如果你要做一个真的长期认识你的 Agent,只靠相似文本不够,它还需要知道事实之间的关系和时效性。

工程映射与实践启发

工程落地方案

Java 后端可以这样拆:

模块作用
EntityExtractor从对话中抽实体
RelationExtractor抽取实体关系
GraphMemoryStore保存节点和边
TemporalValidator判断新旧事实冲突
HybridRetriever向量 + BM25 + 图搜索
ContextConstructor把图结果转成 prompt 文本

表结构可以先简化:

代码块SQL · 2 行收起展开
memory_node(id, user_id, name, type)
memory_edge(id, user_id, from_node, to_node, relation, valid_from, valid_to, active)

相关工作与方法对比

它和 Mem0 的关系

Mem0-G 也走图增强记忆路线。Zep 的特色更强调时序知识图谱和图社区。

维度ZepMem0
核心时序知识图谱生产级长期记忆框架
强项关系、时间、冲突记忆抽取、更新、API 化
适合用户事实变化、复杂关系工程接入和产品化

局限、对比与阅读检查

局限与边界

  • 图谱构建依赖抽取质量。
  • LLM 抽实体和关系有成本。
  • 图数据库和向量检索结合增加系统复杂度。
  • 冲突判断不总是简单。

阅读重点

  1. 它如何把对话转成图。
  2. 它如何处理时间和事实失效。
  3. 它如何混合检索。
  4. 它如何把图结果构造成上下文。
  5. 它和普通向量库记忆相比强在哪里。

实践启发

如果你的 Agent 需要长期记住“人、项目、偏好、关系、时间变化”,就不要只做 memory_text + embedding。至少要考虑:

  • 实体。
  • 关系。
  • 时间。
  • active/invalid 状态。
  • 多路召回。

原文精读补充

1. 为什么 Zep 强调 Temporal

普通知识图谱可能只表达:

代码块TEXT · 1 行收起展开
UserA -> uses -> FrameworkA

但真实世界里的事实有时间:

代码块TEXT · 2 行收起展开
2026-07-04:用户 A 从 FrameworkA 项目转向 Agent 学习。
2026-07-05:用户 A 要求 Agent Memory 论文逐篇拆解。

如果没有时间维度,Agent 会把旧事实、新事实、阶段性偏好混在一起。

2. 三层图结构

Zep/Graphiti 的核心可以按三层理解:

存什么类比
Episodic原始事件、消息、JSON、交互片段日记
Semantic从事件抽取出的实体和关系知识图谱
Community高连接实体的聚类摘要章节总结

这比单纯向量库强在:它能把“发生过什么”和“抽象事实是什么”同时保留。

3. 边的失效比删除更重要

长期记忆最容易遇到冲突:

代码块TEXT · 2 行收起展开
用户以前住在 A 城。
用户现在搬到 B 城。

如果直接删除旧事实,会丢失历史;如果两个都 active,会答错。

Zep 的启发是:

代码块TEXT · 1 行收起展开
保留历史事实,但给关系加有效期/失效标记。

后端里这通常对应:

代码块SQL · 1 行收起展开
valid_from, valid_to, invalidated_at, invalidated_by

4. 检索为什么要混合

Zep 的检索不是单一路线。你可以理解成:

  1. 语义向量找相似内容。
  2. BM25 找关键词强匹配。
  3. 图遍历找相关实体关系。
  4. Reranker 决定最终上下文。

这比单一 embedding 更适合长期记忆,因为很多问题是实体关系问题,不只是文本相似问题。

5. 实验怎么看

重点看 LongMemEval 这类长期记忆 benchmark。你读时问:

  1. 问题是否需要跨会话回忆。
  2. 是否涉及时间变化。
  3. 图结构是否比纯向量检索更准。
  4. 构图成本和查询延迟是否值得。

6. 工程落地提醒

如果以后做 Obsidian Agent,不要一开始就把所有关系都塞进复杂图数据库。可以先做轻量结构:

代码块TEXT · 3 行收起展开
entity(name, type)
memory_event(time, source, content)
memory_relation(subject, predicate, object, valid_from, valid_to)

先把时间和失效机制做对,比盲目上知识图谱更重要。

深度精读:Zep/Graphiti 的关键是“时间化的事实图”

1. 为什么企业记忆需要 temporal graph

企业或长期个人场景中,事实经常变化:

代码块TEXT · 4 行收起展开
用户以前在项目 A。
用户现在切到项目 B。
某 API 过去用 v1,现在迁移到 v2。
某人以前负责后端,现在转去算法。

普通向量库只会存多段文本,很难判断哪个事实当前有效。Temporal KG 的价值是把事实表示成:

代码块TEXT · 1 行收起展开
(subject, relation, object, valid_from, valid_to, source)

这样系统可以回答:

代码块TEXT · 4 行收起展开
现在是什么?
当时是什么?
这个事实从什么时候开始有效?
哪个新事实让旧事实失效?

这就是 Zep 比普通 memory store 更强的地方。

2. Episodic -> Semantic -> Community 的流水线

Zep 的三层对应记忆抽象层级:

代码块TEXT · 3 行收起展开
Episodic: 原始事件
Semantic: 从事件抽出来的事实关系
Community: 高层主题/群组摘要

例子:

代码块TEXT · 9 行收起展开
Episodic:
  2026-07-05 用户说“论文讲解太短,要更深入”

Semantic:
  UserA -> prefers -> deep paper explanations
  UserA -> dislikes -> shallow summaries

Community:
  AI Agent 学习偏好:要求原文支撑、逐章拆解、工程落地

这比直接把用户原话放向量库更适合长期使用。

3. 构图为什么需要 LLM

从文本到图,需要做几件事:

  1. 识别实体。
  2. 识别关系。
  3. 判断关系类型。
  4. 提取时间。
  5. 判断是否与旧边冲突。

长期对话很口语,LLM 更灵活。但风险也大:

风险后果
实体抽错图节点污染
关系抽错检索走错路径
时间抽错旧事实被误判有效
冲突判断错新旧事实同时存在

所以生产系统必须保留 source episode,方便回查。

4. 边失效机制比删除高级

假设:

代码块TEXT · 2 行收起展开
2025:用户使用 MySQL。
2026:用户项目迁移到 PostgreSQL。

删除 MySQL 这条边会丢历史;保留两条 active 又会答错。正确做法:

代码块TEXT · 2 行收起展开
UserA -> uses -> DatabaseA        valid_to = 2026-xx-xx
UserA -> uses -> DatabaseB        valid_from = 2026-xx-xx

这对学习记录也有用:

代码块TEXT · 1 行收起展开
“当前在学后端框架” 后来会变成 “当前转向 AI Agent 工程”

5. 检索不是“图 or 向量”,而是混合

长期记忆问题有多种形态:

问题更适合的召回
“我之前说过什么?”语义向量
“某个项目现在用什么数据库?”实体图
“上周做了哪些任务?”时间过滤
“和 MCP 有关的笔记有哪些?”关键词 + 图邻居

一个工程版检索可以是:

代码块TEXT · 5 行收起展开
candidate = vector_search(query)
          + keyword_search(query)
          + graph_expand(entities)
          + time_filter(range)
rerank(candidate)

6. Constructor 为什么重要

图检索返回的是节点和边,但 LLM 需要读文本。所以要有 constructor 把结构化结果变成上下文:

代码块TEXT · 4 行收起展开
Entity: UserA
Current relation: studies -> AI Agent
Evidence: 2026-07-04 started backend-to-Agent route
Invalidated relation: studies -> backend framework only

如果 constructor 写得差,检索再准,模型也可能读不懂。

7. 工程最小版本

不需要一开始上 Neo4j。可以用关系表模拟:

代码块SQL · 24 行收起展开
memory_episode(
  id,          -- episode ID,对应一次对话、事件或资料片段。
  source,      -- 来源,例如 chat、note、tool_result。
  content,     -- 原始事件文本。
  happened_at  -- 事件发生时间,不一定等于写入时间。
);

memory_entity(
  id,    -- 实体 ID。
  name,  -- 实体名称,例如人名、地点、项目名。
  type   -- 实体类型,用于过滤和消歧。
);

memory_edge(
  id,                -- 关系边 ID。
  subject_entity_id, -- 主体实体。
  predicate,         -- 关系谓词,例如 lives_in、works_on。
  object_entity_id,  -- 客体实体。
  source_episode_id, -- 这条关系来自哪次 episode。
  valid_from,        -- 关系开始生效时间。
  valid_to,          -- 关系失效时间;为空表示当前仍有效。
  confidence,        -- 抽取可信度。
  status             -- active / superseded / deleted 等状态。
);

等关系复杂后,再迁移图数据库。

8. 读实验时的真实关注点

Zep 这类系统分数高时,你还要问:

  1. 构图成本是多少。
  2. 每次写入是否都要调 LLM。
  3. 查询延迟能否接受。
  4. 错误记忆如何人工修正。
  5. 多租户权限如何隔离。

这是开发岗读论文必须补的工程问题。

论文精读主线:问题-方法-验证-局限-启发

1. 核心结论

Zep/Graphiti 把长期记忆建成带时间属性的知识图谱,让 Agent 不只保存事实,还能表示事实何时成立、何时失效、被什么新事实取代。

本篇的阅读重心是:长期记忆中的事实关系必须带时间和证据,否则旧事实会污染当前回答。读完后要能说清它改变的是 runtime、存储、检索、更新还是评测层;否则只是记住了论文名。

2. 问题背景

向量记忆能找到相似文本,但难以表达实体关系和事实变化。用户偏好、项目状态、学习方向都会随时间变化;如果旧事实和新事实同权存在,长期记忆越多越容易答错。

把问题具体化为失败样例:用户项目从 A 技术栈迁到 B 技术栈后,向量库仍召回旧记录;系统无法判断哪个事实现在有效。用这个样例反推论文动机,比泛泛说“上下文不够”或“记忆不准”更有判断力。

3. 核心方法

系统从 episode 中抽取实体和关系,写入 temporal knowledge graph。关系边带 valid_from、valid_to、evidence 等字段。检索时结合图搜索、向量搜索和关键词搜索,既找语义相似片段,也沿实体关系找最新事实。

flowchart TD
    E[Episode 原始事件] --> X[实体/关系抽取]
    X --> G[Temporal KG]
    G --> V[有效期与失效边]
    Q[查询] --> H[Hybrid Search]
    H --> G
    H --> I[向量/关键词索引]
    G --> R[带时间和证据的结果]

方法部分按数据流读:episode 保存原始证据,抽取实体和关系,写入带 valid_from/valid_to 的边,检索时结合图路径和最新有效边。每一步都要能标出输入、输出、控制点和失败后该记录的日志。

4. 技术细节

技术重点是边的生命周期。普通图只说 A 和 B 有关系;Zep 还要记录关系从何时开始、是否仍有效、由哪条 episode 支持、被哪条新事实失效。这样才能同时支持“当前是什么”和“过去发生过什么”。

代码块JAVA · 15 行收起展开
final class TemporalGraphMemoryService {
    private final EpisodeStore episodeStore; // 保存原始对话/事件,作为所有图事实的证据源
    private final EntityResolver entityResolver; // 实体消歧,判断 AI Agent、Agent 工程等是否同一概念
    private final RelationWriter relationWriter; // 写入带 validFrom/validTo 的关系边,并处理旧边失效
    private final HybridRetriever hybridRetriever; // 融合图扩展、向量相似和关键词精确匹配

    MemoryResult handle(MemoryInput input) {
        // 1. 保存原始对话/事件,作为所有图事实的证据源
        // 2. 实体消歧,判断 AI Agent、Agent 工程等是否同一概念
        // 3. 写入带 validFrom/validTo 的关系边,并处理旧边失效
        // 4. 融合图扩展、向量相似和关键词精确匹配
        // 返回结果时必须带 evidence、confidence、traceId,方便以后追查记忆为什么生效。
        return MemoryResult.withTrace(input.traceId());
    }
}

字段检查重点是 entityIdpredicatevalidFromvalidTosupersededByepisodeId 是时序图谱的核心。这些字段不是装饰,它们决定后续能否检索、审计、回滚、限权和做离线评测。

5. 实验验证

验证要分层看:实体抽取是否准,关系边是否准,失效机制是否返回最新事实,最终回答是否因此更对。只看最终 QA 分数不够,因为提升可能来自更强 prompt 或数据集实体关系天然清晰。

实验阅读重点:不要只看 QA 分数;要单独看实体消歧、关系抽取、事实失效、时间查询四个环节。如果论文没有直接给出这层证据,复现时就要自己补机制级指标。

6. 局限与启发

构图成本高,LLM 抽取错误会结构化放大。启发是 Obsidian Agent 可以先对少量关键实体建图:用户、项目、笔记、主题、偏好、任务;不要一开始把所有文本都强行图谱化。

迁移到你的项目时,结论是:Obsidian Agent 可以先为用户、项目、技术栈、任务建立小图;不必把所有笔记一次性图谱化。先把边界做对,再追求更复杂的模型或更大的记忆规模。

7. 关键术语表

术语在论文里的意思工程落地时要看什么
Temporal KG带时间属性的知识图谱节点、边、有效期、证据源
Episode原始事件或对话片段图事实的可追溯证据
Edge invalidation旧关系失效机制valid_to、invalidated_by 字段
Hybrid search多检索方式融合图、向量、BM25 互补

8. 复现难点

实体消歧是最大坑,合并错会污染图,不合并会证据分散。关系 schema 也要受控,否则 predicate 会膨胀。失效判断要区分覆盖、补充、阶段变化和冲突待确认。

复现入口应从失败集开始:构造“旧偏好被新偏好覆盖”“项目路径变更”“同名实体歧义”三类 case,检查返回的是当前事实还是历史事实。只有先证明旧方案会坏,才知道新机制是否真的解决了问题。

9. 代码框架建议

小规模先用关系表模拟图:entity_noderelation_edgeepisodeedge_evidence。写入时先生成 GraphMutation,再 normalize,再 apply;不要让 LLM 直接写数据库。

代码验收门槛:LLM 只能提出 GraphMutation,真正写库前要做 schema 约束、实体归一化和冲突确认。这条门槛应写进测试或 trace 字段里,避免实现只停留在 demo。

10. 相关论文对比

Zep 和 Mem0 都关心生产记忆,Mem0 强调操作决策,Zep 强调时序事实图。A-MEM 更像卡片网络,HippoRAG 更偏多跳检索,Zep 更适合用户事实随时间变化的场景。

横向比较时抓这个角度:Zep 强在事实演化;A-MEM 强在记忆卡片链接;HippoRAG 强在图路径辅助召回。比较对象不是谁“更先进”,而是谁负责哪一层失败。

11. 批判性思考

结构化事实看起来更可信,也更危险。一条错误边会影响很多查询。必须保留证据、时间、置信度和撤销能力,否则图谱会把模型猜测伪装成数据库事实。

批判边界:图谱会给错误事实披上结构化外衣;没有证据、置信度和撤销日志的边不能进入核心记忆。这不是否定论文,而是把论文结论放回真实系统的成本、权限和恢复约束里。