第3章:Memory 存储底座 —— Vector / Graph / KV / Hybrid
向量库、图数据库、KV、全文索引的选型与组合,Mem0/Zep 的 Hybrid 架构,Graphiti 的 bi-temporal 模型详解
Memory 在物理上落到哪里?这一章把”存储底座”这件事讲透——单一介质都有短板:vector store 找不到”上次和谁聊过类似问题”,graph DB 写入慢、检索语义模糊,KV / 全文各有适用场景。真正生产级的 Memory 系统几乎都是 Hybrid——vector + graph + KV + 时间索引,每一层各自承担一类查询。本章详解每种底座的能力边界、Mem0 / Zep 的 Hybrid 架构,以及 Graphiti 的 bi-temporal 模型。
📑 目录
- 1. 单一介质的能力边界
- 2. Vector Store 选型
- 3. Graph DB:何时它是必需品
- 4. KV / 文件 / 全文(BM25)的角色
- 5. Hybrid 架构:Mem0 与 Zep 的设计
- 6. Bi-temporal 模型:Graphiti 详解
- 7. 选型决策树
- 自我检验清单
- 参考资料
1. 单一介质的能力边界
| 介质 | 擅长 | 不擅长 | 典型查询 |
|---|---|---|---|
| Vector Store | 语义相似度、模糊匹配 | 多跳关系、精确属性 | ”和’退款流程’相似的记忆” |
| Graph DB | 多跳推理、关系网络 | 全文模糊检索、低延迟点查 | ”用户 A 的朋友的朋友买过什么” |
| KV Store | 精确属性 lookup | 模糊查询、relation | ”user:123 的当前地址” |
| 全文(BM25) | 关键词搜索、长尾覆盖 | 同义/语义、多模态 | ”包含’订阅’关键词的对话” |
| 关系型(SQL) | 结构化、事务、聚合 | 高维语义 | ”今天创建了多少条 memory” |
🌟 核心结论:没有银弹。生产 Memory 系统几乎一定是 hybrid——选择哪几种、怎么组合,就是设计的核心权衡。
2. Vector Store 选型
Vector store 是 Memory 系统最常见的底座,负责”语义相似的 memory 召回”。主流选型对比:
| 数据库 | 类型 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|---|
| Qdrant | 独立服务 | Rust 高性能、payload 强 filter、production ready | 自托管运维 | Mem0 默认后端 |
| Weaviate | 独立服务 | 内置混合检索、模块化 | 资源占用大 | 需要 hybrid search |
| Milvus | 独立服务 | 大规模集群 | 部署复杂 | 亿级 vector |
| Chroma | embedded / 服务 | 轻量、开发友好 | 大规模性能弱 | 原型 / 单机 |
| pgvector | PostgreSQL 扩展 | 直接复用 RDBMS、事务、SQL | 大规模 ANN 性能弱 | 已有 PostgreSQL 栈 |
| FAISS | 库 | Facebook 出品、单机最快 | 无服务、无 metadata filter | 离线检索 |
| LanceDB | embedded | columnar 存储、分析友好 | 较新生态 | 数据分析场景 |
2.1 关键能力对比
Qdrant Weaviate Milvus Chroma pgvector
Hybrid (vec + BM25) ✓ ✓ (需配合 tsvector)
Payload Filter ✓✓ ✓✓ ✓ ✓ ✓
Sharding ✓ ✓ ✓✓ ✗ ✗
HNSW / IVF ✓ ✓ ✓✓ HNSW HNSW/IVF
Updates / Soft Delete ✓ ✓ ✓ ✓ ✓
Schema-less Payload ✓ ✓ partial ✓ ✗
Time Travel(snapshot) ✓ ✓ ✓ ✗ (Postgres MVCC)
2.2 给 Memory 用 vector store 的关键技巧
Payload 必带字段(以 Qdrant 为例):
{
"user_id": "u_123", # 强制 filter,隔离用户
"memory_type": "semantic", # 类型分桶
"created_at": 1714838400, # 时间索引
"valid_from": 1714838400,
"valid_to": null, # bi-temporal valid 区间
"importance": 0.85, # 用于 retrieval scoring
"source_msg_id": "m_456", # 溯源
"embedding_model": "text-embedding-3-large",
}
索引策略:
# Qdrant 创建带 filter 索引的 collection
client.create_collection(
collection_name="agent_memory",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
hnsw_config=HnswConfigDiff(m=32, ef_construct=200),
)
client.create_payload_index("agent_memory", "user_id", "keyword") # filter 加速
client.create_payload_index("agent_memory", "created_at", "integer")
避坑:绝不要忘了 user_id filter——一旦漏了,会把别人的 memory 召回到当前用户,既是 bug 也是合规事故。
3. Graph DB:何时它是必需品
Vector store 解决”模糊找一条相似 memory”,Graph DB 解决”沿着关系链找答案”。两个场景必须用图:
3.1 多跳推理
Q: 我老板的助理上次推荐的那家咖啡店在哪?
需要的查询路径:
user → boss → assistant → recommended → cafe → location
向量库根本无法表达”沿着多跳关系走”——这是 Graph DB 的本职工作。
3.2 复杂事实演化
“用户 A 在公司 X 工作 → 跳槽到 Y → 又跳槽到 Z”
事实之间有 supersede 关系,Graph 可以画出来:
A --works_at[t_valid: 2020-2022]--> X
A --works_at[t_valid: 2022-2024]--> Y
A --works_at[t_valid: 2024-now]--> Z
向量库存得了原始三句话,但回答”A 现在在哪”需要时间感知逻辑——硬塞给 LLM 容易出错。
3.3 主流 Graph DB 选型
| 数据库 | 类型 | 优点 | Memory 场景适配 |
|---|---|---|---|
| Neo4j | 老牌 native graph | 成熟、Cypher 表达力强 | Zep / Graphiti 默认后端 |
| Kùzu | embedded、columnar | 单机超快、列存 | 单 agent 单进程 |
| TigerGraph | 分布式 | 大规模、企业级 | 集团级 multi-agent |
| NebulaGraph | 国产分布式 | 大规模、中文社区 | 国内大集群 |
| NetworkX | Python 库 | 极轻量 | 原型、< 万节点 |
| Memgraph | in-memory | 流式、TimescaleDB 集成 | 实时图 |
🍎 生产首选:Neo4j——Zep 和 Graphiti 都基于它,生态成熟。
4. KV / 文件 / 全文(BM25)的角色
4.1 KV Store
适合”精确字段”——不模糊、不推理、毫秒级响应:
redis.set(f"user:{uid}:address", "上海浦东张江...")
redis.hset(f"user:{uid}:profile", mapping={"age": 30, "vip": "gold"})
Letta 的 core memory 就是 KV 风格——用 LLM 自己生成的 string 直接放在 prompt 里(human / persona blocks)。
4.2 文件 / 文档存储
适合大块内容:对话原文、上传的图片/PDF:
# Mem0 的对话原文存 S3,vector 只存 embedding 和 metadata
s3.put_object(Bucket="agent-memory-raw", Key=f"conv/{conv_id}.json", Body=...)
4.3 全文(BM25)
向量召回擅长”语义”,BM25 擅长”关键词”——两者互补:
| 查询 | 向量召回 | BM25 |
|---|---|---|
| ”我的猫不吃东西” | 召回养宠物相关 | 召回带”猫”的精确条 |
| ”SKU-001 的退货” | 弱(SKU 没语义) | 强(精确匹配) |
| “用户感觉很难过” | 强 | 弱 |
Hybrid 检索的标配:vector_score × α + bm25_score × (1-α),通常 α=0.6-0.7。
5. Hybrid 架构:Mem0 与 Zep 的设计
5.1 Mem0 的三层 Store
Mem0 把 Memory 拆成三个独立的 store,各自承担不同查询:
┌──────────────────────────────────────────────────────┐
│ Memory API │
└──────────────────────────────────────────────────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Vector │ │ Graph │ │ KV / SQL │
│ (Qdrant) │ │ (Neo4j) │ │ (SQLite) │
└──────────┘ └──────────┘ └──────────┘
语义相似度 关系/多跳 精确属性
写入时同时写入三层,检索时按 query 类型分发或并行查询后融合。
from mem0 import Memory
config = {
"vector_store": {"provider": "qdrant", "config": {...}},
"graph_store": {"provider": "neo4j", "config": {...}}, # 可选
"llm": {"provider": "openai", "config": {...}},
"embedder": {"provider": "openai", "config": {...}},
}
memory = Memory.from_config(config)
memory.add("我对乳糖不耐受", user_id="u123")
results = memory.search("早餐推荐", user_id="u123")
5.2 Zep 的架构
Zep 围绕单一 KG(Graphiti)展开,vector 是 KG 节点的属性而非独立 store:
┌──────────────────────────────────────────────────────┐
│ Zep Memory API │
└──────────────────────────────────────────────────────┘
↓
┌───────────────────┐
│ Graphiti │
│ (Neo4j-based) │
│ │
│ Nodes(Entity) │
│ Edges(Relation) │
│ + embeddings │
│ + bi-temporal │
└───────────────────┘
每个 node 和 edge 都带 vector embedding,既支持图遍历也支持向量检索——结构上更”原生 KG”,代价是查询复杂度高。
5.3 选 Hybrid 的两种风格
| 风格 | 代表 | 哲学 |
|---|---|---|
| Multi-store(各自专精) | Mem0 | 不同存储各司其职,通过应用层融合 |
| Single-store with hybrid index | Zep / Graphiti | 一套 KG 解决所有问题,内嵌 vector |
Multi-store 灵活但应用层逻辑重;Single-store 一致性强但 KG 表达不出的查询要绕路。
6. Bi-temporal 模型:Graphiti 详解
6.1 为什么需要 Bi-temporal
考虑场景:“我去年住在上海,今年搬到了北京。”
- Temporal 1(事实有效时间):住址在 2024 全年 = 上海,2025 至今 = 北京
- Temporal 2(系统记录时间):这条信息是今天(2025-05-04)才被告诉系统的
如果只记一个时间,会出现两类错误:
- 只记”事实何时为真”:回答不了”系统什么时候知道这个事实的”——审计时无法重现历史决策
- 只记”系统何时记录”:回答不了”用户当时实际在哪”——会错误地把今天才知道的事实回填到去年
6.2 Graphiti 的四时间戳模型
每条 edge 带 4 个时间戳:
| 字段 | 含义 |
|---|---|
t_valid | 事实开始为真的时间(用户去年搬来上海) |
t_invalid | 事实失效的时间(去年底搬走) |
t_created | 系统记录该事实的时间(今天告诉 Agent) |
t_expired | 系统将其标记为废止的时间(今天发现旧地址过期) |
形象示意:
事实时间轴(t_valid / t_invalid)
2024-01 ─────────────── 2024-12
│
↓ 用户搬家
2025-01 ─────────────── now
系统记录时间轴(t_created / t_expired)
2025-05-04 系统第一次知道两件事
↓
"去年住上海" t_created ──── ?
"今年住北京" t_created ──── ?
6.3 查询能力
有了 4 时间戳,可以回答:
| 查询 | SQL/Cypher 思路 |
|---|---|
| ”用户当前住址” | WHERE t_invalid IS NULL |
| ”用户 2024-06 时住址” | WHERE t_valid <= 2024-06 AND (t_invalid IS NULL OR t_invalid > 2024-06) |
| ”系统在 2025-04-01 时认为用户住哪” | WHERE t_created <= 2025-04-01 AND (t_expired IS NULL OR t_expired > 2025-04-01) |
| ”事实有冲突时谁覆盖谁” | 按 t_valid 排序,新的覆盖旧的;t_expired 标记废止 |
Zep 论文(arXiv 2501.13956)实测:开 bi-temporal vs 不开,准确率提升 18.5%,延迟降 90%(因为不再需要 LLM 二次推理”哪条是当前的”)。
6.4 工程实现要点
# Graphiti 添加事实(自动检测时间)
from graphiti_core import Graphiti
graphiti = Graphiti("bolt://localhost:7687", "neo4j", "password")
await graphiti.add_episode(
name="user_move",
episode_body="用户在 2025 年 1 月搬到北京",
source_description="客服对话",
reference_time=datetime(2025, 5, 4, 14, 30), # t_created
)
# Graphiti 内部:
# 1. LLM 抽取 entities + relations
# 2. 检测与已有 fact 冲突("住上海"还在 valid)
# 3. 把"住上海"的 t_invalid 设为 2025-01,t_expired 设为 now
# 4. 新增"住北京",t_valid=2025-01,t_invalid=NULL
7. 选型决策树
你的应用核心查询是什么?
│
├─ "找语义相似的 memory"(占比 > 80%)
│ └─ Vector Store(Qdrant / pgvector)
│ └─ 加 BM25 → Hybrid 检索
│
├─ "沿着关系网络多跳推理"
│ └─ Graph DB(Neo4j)
│ └─ 需要时间一致性 → Graphiti(bi-temporal)
│
├─ "精确属性 lookup"(用户档案、状态)
│ └─ KV(Redis) / SQL(Postgres)
│
├─ "复杂混合"(都用)
│ ├─ 自己集成 → Mem0 multi-store
│ └─ 全托管 → Zep / Letta
│
└─ "原型阶段,先跑通"
└─ Chroma + JSON / SQLite + 偶尔 Redis
🌟 生产经验:90% 的团队 Phase 1 都是 vector-only(Mem0 默认即可),Phase 2 加 KG(用户开始问多跳问题),Phase 3 加 bi-temporal(数据演化频繁,审计需求强)。
✅ 自我检验清单
- 介质边界:能列出 vector / graph / KV / 全文各自的擅长/不擅长场景,各举一例
- Vector store 选型:能根据”自托管 vs 云、规模、是否需要 hybrid”给出推荐
- Payload 字段:能默写 Memory 系统中 Qdrant payload 至少 6 个必带字段
- Graph DB 必要性:能给出 2 个具体场景”vector store 解决不了、必须用图”的例子
- Hybrid 两种风格:能对比 Mem0 multi-store 和 Zep single-store 的优劣
- Bi-temporal 必要性:能讲清”为什么 t_valid 和 t_created 必须独立”
- Graphiti 4 时间戳:能默写四个字段的含义和典型用途
- 冲突处理:能解释 Graphiti 如何处理”用户搬家”这类事实演化
- 决策树:面对 3 个不同业务,能给出存储底座方案
📚 参考资料
Vector Store
- Qdrant 官方文档:https://qdrant.tech/documentation/
- Weaviate vs Qdrant 对比(各家博客)
- pgvector:https://github.com/pgvector/pgvector
Graph DB
- Neo4j Graph Database:https://neo4j.com/
- Kùzu:https://kuzudb.com/
- NebulaGraph:https://www.nebula-graph.io/
Hybrid 与 Bi-temporal
- Mem0 Architecture:https://github.com/mem0ai/mem0
- Zep Paper (Rasmussen et al., 2025):arXiv 2501.13956
- Graphiti GitHub:https://github.com/getzep/graphiti
- Graphiti: Knowledge Graph Memory for an Agentic World —— Neo4j Blog:https://neo4j.com/blog/developer/graphiti-knowledge-graph-memory/
检索方法
- BM25 + 向量混合检索:Pinecone / Weaviate 文档均有详解
- Lost in the Middle (Liu et al., 2023):arXiv 2307.03172