Graphiti 深度解读
“给知识图谱加上时间轴——让记忆知道’什么时候成立、什么时候失效’”
一句话定位
Zep 团队开源的时序知识图谱引擎,核心创新是给每条边加上”生效时间”和”失效时间”,让图谱能自动处理信息随时间变化的问题,是 7 个项目中唯一在时序维度做到深度的方案。
设计哲学上最精彩的地方
Graphiti 的核心洞察是:记忆不是静态快照,而是随时间演化的流。当用户说”我住在北京”,三个月后说”我搬到上海了”,传统向量检索会返回两条矛盾的记忆。Graphiti 的做法是标记”住在北京”这条边为”已失效”,而不是删除它——因为过去住在北京本身仍然是有价值的历史信息。
日常类比:传统记忆系统像一块白板,新信息直接擦掉旧信息。Graphiti 像一本日记——旧的不擦,而是在旁边标注”已更新为…“。信息的”死亡”和它的”诞生”一样,都是值得记录的事件。
双时间轴模型——Bi-temporal 的精妙
每条边有两个时间维度:valid_at 是事实在现实中何时成立(”2024 年 3 月住在北京”),created_at 是这条边何时被写入图谱(”2024 年 6 月的对话中提到的”)。
这种区分在处理”回忆性描述”时至关重要。用户在 6 月对话中说”我去年 3 月搬到了北京”——created_at 是 6 月,valid_at 是去年 3 月。如果不区分这两个时间,时序推理就会出错。DB-Agent-Memory 目前没有显式的时间处理机制,这是 Graphiti 最值得借鉴的设计点。
Cross-Encoder 重排——两阶段检索架构
Graphiti 的检索不是简单的向量 cosine 排序。它先用向量检索拿到候选集,再用 cross-encoder 模型精排。Cross-encoder 把查询和候选拼在一起编码,能捕捉更细粒度的语义匹配,但推理速度比 bi-encoder 慢一个数量级。Graphiti 的”先粗筛再精排”是信息检索领域的经典两阶段架构。
同时,它利用 Neo4j 内建的向量索引做语义检索,用 Cypher 做图遍历。一次检索可以同时利用”语义相似性”和”图结构关系”——查”小明的同事喜欢什么”时,向量检索找语义相关的边,图遍历从”小明”节点出发经过”同事”关系到达目标,两者交集得到最终结果。
局限性——Neo4j 强依赖
Graphiti 对 Neo4j 的强依赖是最大限制。Neo4j 是重量级图数据库,部署和维护成本远高于 SQLite。如果 Agent 只需要简单记忆,用 Graphiti 就像用大炮打蚊子。此外,Neo4j + cross-encoder 组合在复杂图谱上可能消耗数百毫秒——对交互式 Agent 来说,这个延迟可能不可接受。代码量小(~200 文件)但生态依赖重。
本地代码结构
graphiti/
graphiti_core/
graphiti.py # 核心入口类
nodes.py # 实体节点定义
edges.py # 时序边定义(valid_at / invalid_at)
search/
search.py # 混合检索(向量 + 图遍历)
search_utils.py # 检索工具函数
llm_client/
openai_client.py # LLM 调用(实体提取、关系识别)
embedder/
openai_embedder.py # 向量嵌入
cross_encoder/
cross_encoder_client.py # Cross-encoder 重排
utils/
maintenance/
temporal_operations.py # 时序边失效/更新操作
tests/
examples/
本地关键文件
| 文件 | 用途 |
|---|---|
graphiti_core/graphiti.py |
核心入口,add_episode / search 等主方法 |
graphiti_core/edges.py |
时序边定义,包含 valid_at / invalid_at 双时间字段 |
graphiti_core/nodes.py |
实体节点,包含属性和向量嵌入 |
graphiti_core/search/search.py |
混合检索,向量 + 图遍历 + cross-encoder 精排 |
graphiti_core/cross_encoder/ |
Cross-encoder 重排器,两阶段检索的第二阶段 |
graphiti_core/utils/maintenance/temporal_operations.py |
时序边维护,旧事实失效 + 新事实写入 |