把会话窗口当记忆用是 Agent 一个常见的设计误区——上下文窗口是有限、昂贵、不可扩展的。长期记忆必须落到外部存储里,常见做法是”向量库 + 知识图谱 + 事件日志”三层架构,配合 RAG(Retrieval-Augmented Generation)按需召回。在企业客服 Agent 上线第一周就翻车后,我把记忆层从”塞上下文”改成”外部持久化 + 智能检索”,跨会话用户偏好命中率显著提升。下面把记忆分层、存储选型、检索策略拆开讲清。
一、为什么上下文窗口撑不起长期记忆
很多人第一版 Agent 会把历史对话”全塞进 prompt”——遇到核心用户用了 3 个月,prompt 长度轻松突破 10 万 token。这条路有三个硬伤:
- 成本:每轮推理按 token 计费,10 万 token 的 prompt 调用一次主流大模型,费用是几百 token 的几十倍。
- 延迟:长 prompt 拉长 prefill 阶段,首 token 延迟从毫秒级跳到秒级。
- 衰减:上下文超长后模型会出现”迷失中段”问题,关键事实被淹没在噪声中。
Mem0 在 LOCOMO 基准上的公开数据:使用托管长期记忆模块的 Agent,回复准确率比”context-stuffing”提升 26%,token 消耗下降超过 90%。这组数据不是营销话术——它精确刻画了”长期记忆外置”对成本与质量的双重收益。
二、长期记忆的分层架构
成熟的 Agent 长期记忆通常分四层,每层解决不同问题:
- 工作记忆(Working Memory):当前会话的上下文,由上下文窗口承担。会话结束即释放。
- 情景记忆(Episodic Memory):历史事件和操作记录,按时间序列存储。回答”上周用户问了什么”这类问题靠它。
- 语义记忆(Semantic Memory):抽象出的事实、概念、用户偏好,以知识库或结构化文档形式存在。
- 程序记忆(Procedural Memory):可复用的执行模式,例如”对技术用户用代码示例,对非技术用户用类比”。
四层不是非此即彼,生产系统通常四层都用。MemGPT、LangGraph、Letta 等框架已经在工程上把这四层做成了”分层记忆栈”。
三、存储选型:向量库、知识图谱、事件日志
不同类型的记忆需要不同的存储。下面是各层对应的存储选型与典型产品。
3.1 向量库(语义记忆主力)
向量库存储高维 embedding,支持相似度检索。主流选项分两类:
- 托管型:Pinecone。企业级 SLA,免运维,按存储和 QPS 计费。
- 开源型:Weaviate(混合检索强)、Milvus(大规模场景)、Chroma(轻量、本地友好)、Qdrant(Rust 写、延迟稳)、FAISS(Meta 出,库而非服务)。
选型核心考量是规模与运维能力。10 万级向量、本地开发用 Chroma/FAISS 够用;千万级以上、跨区域高可用,Pinecone 或 Milvus 更稳。
3.2 知识图谱(结构化推理)
向量相似检索搞不定多跳推理(”用户工作的城市附近有哪些餐厅”),这种关系查询必须靠图:
- Neo4j:老牌成熟图数据库,Cypher 查询语言生态完整。
- Memgraph:高性能、兼容 Cypher,适合实时分析。
- Graphiti / Zep:专门为时序知识图谱设计,记录实体关系随时间的演变。
- GraphRAG:Microsoft 出,把图谱检索融入 RAG 流程。
知识图谱的强项是”实体-关系-时间”三元组的精确查询;弱项是写入慢、构建成本高。常见组合是:向量库放原始记忆、知识图谱放结构化洞察。
3.3 事件日志(审计与回放)
事件日志负责”不可篡改的真相源”。LangGraph checkpoint、PostgreSQL + Kafka、EventStoreDB 都能做。事件日志的用途不是检索,是审计、调试和回放——一旦 Agent 出问题,可以从日志中精确还原某一步的状态。
| 记忆层 | 存储类型 | 典型产品 | 检索方式 |
|---|---|---|---|
| 语义记忆 | 向量库 | Pinecone / Milvus / Chroma | Embedding 相似度 |
| 结构化关系 | 知识图谱 | Neo4j / Zep / Graphiti | Cypher / 图查询 |
| 时序事件 | 事件日志 | Kafka + PostgreSQL / LangGraph checkpoint | 时间窗口 + 字段过滤 |
| 会话级 | 关系数据库 | PostgreSQL | 主键查询 |
四、检索策略:把记忆喂回 Prompt
存好之后,怎么取回是核心问题。
4.1 RAG 三步走
- 查询向量化:用户 query 用同一 embedding 模型转成向量。
- 向量检索:在向量库中找出 top-K 最相关的记忆块。
- 重排与注入:用重排模型(cross-encoder)过滤噪声,剩余记忆块拼到 prompt 的 system 段。
4.2 混合检索
纯向量检索在精确关键词、专有名词上容易漏检。混合检索把”语义相似度 + 关键词匹配 + 元数据过滤”叠起来:
- 语义检索:找”概念相关”的内容。
- 关键词检索(BM25):找”字面相关”的内容。
- 元数据过滤:按时间、来源、标签做硬约束。
Pinecone、Weaviate 都内置混合检索 API。生产经验是:先 metadata 过滤缩小候选集,再做向量检索——又快又准。
4.3 Memory-First 架构
传统 RAG 是”先检索外部文档 → 生成”。Memory-First 反过来:先查 Agent 自己的长期记忆,命中就不再调外部 RAG,节省延迟与 API 成本。这种架构对”个性化”场景尤其有效——用户偏好等私有记忆几乎肯定在内部,外部文档反而是噪声源。
五、四层记忆栈的工程实践
给一段最简化的 LangGraph 长期记忆示例代码,演示工作记忆 + 长期记忆如何配合:
from langgraph.graph import StateGraph
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.store.memory import InMemoryStore
# 短期记忆:checkpointer 负责跨 step 状态持久化
checkpointer = PostgresSaver.from_conn_string("postgresql://...")
# 长期记忆:InMemoryStore 负责跨 thread 持久化(生产换 Redis 或 PG)
store = InMemoryStore()
builder = StateGraph(dict)
# ... 添加节点 ...
graph = builder.compile(
checkpointer=checkpointer,
store=store,
)
# 跨会话检索用户偏好
def retrieve_user_context(state, config, *, store):
user_id = config["configurable"]["user_id"]
facts = store.search(("facts", user_id), query="preferences")
episodes = store.search(("episodes", user_id), query="resolved", limit=5)
return {"user_facts": facts, "episodes": episodes}
效果上,checkpointer 保证单次对话的 step 之间状态可恢复;store 保证跨 session 的”事实”和”事件”可查询。两层配合就是”短期可断点续跑、长期可历史追溯”的最小可行实现。
工程上有几条经验值得记:
- 块大小(chunking):文档别整篇存,按”语义完整的段落/小节”切,控制在 200~500 token 一块。整篇存会大幅降低检索精度。
- 元数据丰富度:每块都附 source、date、author、topic_tag 标签,过滤检索场景必备。
- 去重与摘要:定期合并相似记忆、淘汰长期未访问的项,防止向量库膨胀拖累检索质量。
- 写权限隔离:长期记忆里的敏感字段要做脱敏和访问控制,Agent 记忆被攻击的后果比模型本身被攻击更严重。
到这里,长期记忆的”为什么 / 存什么 / 怎么存 / 怎么取”就闭环了。
常见问题(FAQ)
Q1:上下文窗口能解决长期记忆吗?
不能。窗口有限且昂贵;超过约 32K 后模型注意力会显著衰减。
Q2:向量库和知识图谱必须二选一吗?
不必。生产环境常做”向量存原文 + 图存结构化关系”的混合架构。
Q3:记忆更新频率怎么定?
按事件触发写入,定期做合并/摘要;高频小更新会导致向量库膨胀。