一、为什么 Agent 需要记忆?
1.1 从一个尴尬的对话说起
想象你有一个 AI 助手,对话是这样的:
plaintext
你:我叫小明,是一名后端工程师,最近在学 Agent 开发。
AI:你好小明!很高兴认识你。
(------ 第二天 ------)
你:帮我推荐一些适合我的学习资料。
AI:请问您是做什么工作的呢?想学习什么方向?
是不是很崩溃?这就是没有记忆的 Agent ------ 每次对话都像初次见面,用户体验极差。
1.2 LLM 的本质缺陷:无状态(Stateless)
大语言模型(LLM)本身是无状态的:它就是一个"输入文本 → 输出文本"的函数,不会自动保存任何对话历史。
我们平时用 ChatGPT 觉得它"记得"上文,其实是应用层每次都把完整的历史对话重新拼进 Prompt 塞给模型。这就引出了记忆系统要解决的三个核心问题:
| 问题 | 说明 |
|---|---|
| 上下文窗口有限 | 模型一次能"看"的文本有上限(如 128K token),对话太长塞不下 |
| 成本随长度暴涨 | 每轮都传全部历史,token 消耗是 O(n²) 级增长 |
| 跨会话失忆 | 关掉窗口重新打开,之前聊的全没了 |
记忆系统的本质:在有限的上下文窗口内,让 Agent 在正确的时机"想起"正确的信息。
二、记忆的分类:借鉴人类认知科学
Agent 记忆的设计大量借鉴了认知心理学,先建立一个总体框架:
2.1 短期记忆(Short-term Memory)
是什么:当前会话内的上下文,直接放在 Prompt 里传给模型。
-
对话缓冲区(Conversation Buffer):最近 N 轮的原始对话
-
工作记忆(Working Memory / Scratchpad):Agent 执行任务过程中的中间思考、工具调用结果(比如 ReAct 模式里的 Thought/Action/Observation 轨迹)
特点:容量小、精度高、随会话结束而消失。
2.2 长期记忆(Long-term Memory)
是什么:持久化存储在外部(数据库/文件),跨会话存在,需要时被"检索"回来。
按内容性质细分为三类(这个分类非常重要,生产系统基本都按这个思路设计):
| 类型 | 存储内容 | 类比人类 | 举例 |
|---|---|---|---|
| 情景记忆 (Episodic) | 发生过的具体事件 | "我记得上周二和他吃过饭" | "用户 3 月 5 日咨询过退款流程,最终选择了换货" |
| 语义记忆 (Semantic) | 提炼出的事实与结论 | "我知道巴黎是法国首都" | "用户是后端工程师,偏好 Java,讨厌啰嗦的回答" |
| 程序性记忆 (Procedural) | 做事的方法和技能 | "我会骑自行车" | "处理报销问题的标准流程是:先查发票→再核金额→最后提交" |
一个直观的对照图:
💡 记忆系统的核心循环就是这张图 :短期记忆用完即弃,有价值的部分沉淀 为长期记忆;长期记忆在合适的时机被检索回短期记忆(Prompt)中使用。
三、通用技术方案:从简单到复杂
下面按由浅入深的顺序介绍主流技术方案,每种都说明原理、优缺点和适用场景。
3.1 方案一:全量缓冲(Full Buffer)
原理:把所有历史对话原封不动拼进 Prompt。
plaintext
System: 你是一个助手
User: 你好,我叫小李
Assistant: 你好小李!
User: 我想学 Agent 开发 ← 每轮都带上前面所有内容
-
✅ 实现最简单,信息零丢失
-
❌ 对话一长就爆上下文窗口;token 成本高
-
适用场景:短对话场景(客服快问快答、单次任务型对话)、Demo 原型
3.2 方案二:滑动窗口(Sliding Window)
原理:只保留最近 N 轮对话,更早的直接丢弃。
-
✅ 成本可控、实现简单
-
❌ 早期关键信息会"断片"(用户开头说了姓名,10 轮后 Agent 就忘了)
-
适用场景:话题连续性弱的闲聊、对早期信息不敏感的场景
3.3 方案三:摘要压缩(Summarization)
原理:老对话不丢弃,而是用 LLM 压缩成摘要,摘要 + 最近几轮原文一起放进 Prompt。
常见变体:
-
递归摘要:每次窗口快满时,把"旧摘要 + 新溢出的对话"再压缩一次
-
分层摘要:会话级摘要 → 天级摘要 → 用户级摘要,逐层抽象
-
✅ 兼顾成本与信息保留;对话可以"无限长"
-
❌ 摘要有信息损耗,细节(数字、专有名词)容易丢;每次压缩多一次 LLM 调用
-
适用场景:长对话助手、陪伴类应用、需要"大致记得聊过什么"的场景
3.4 方案四:向量检索记忆(Vector Retrieval Memory)
原理 :把每条记忆做成向量(Embedding)存入向量数据库;需要时用当前问题做语义检索 ,只把最相关的几条记忆注入 Prompt。这是目前长期记忆的主流方案。
-
✅ 记忆容量近乎无限;按需精准取用,token 高效
-
❌ 检索质量依赖 Embedding 效果;"相关"不等于"重要";时间顺序信息弱
-
适用场景:跨会话个性化助手、需要记住大量零散事实的场景
⚠️ 常见误区:向量检索按"语义相似度"排序,但有时最相关的记忆并不是语义最像的。生产系统通常用复合打分 :
得分 = 语义相似度 × 时间衰减因子 × 重要性权重(这正是斯坦福著名的 Generative Agents 小镇论文的做法)。
3.5 方案五:结构化记忆(实体/画像记忆)
原理 :不存原文,而是用 LLM 从对话中抽取结构化信息,维护成一份"用户档案"或实体知识卡。
json
{
"user_profile": {
"name": "小李",
"job": "后端工程师",
"skills": ["Java", "Spring"],
"learning": "Agent 开发",
"preferences": ["回答简洁", "喜欢有代码示例"],
"allergies": ["海鲜"]
}
}
-
✅ 精确、紧凑、可直接注入 System Prompt;便于人工审查和修改
-
❌ 需要设计 Schema;抽取有出错风险;只适合"可结构化"的信息
-
适用场景:用户画像、CRM 场景、需要稳定注入的核心事实
3.6 方案六:知识图谱记忆(Graph Memory)
原理 :把记忆存成"实体---关系---实体"的图结构,能表达向量检索难以表达的关联关系。
当用户问"我同事负责的系统最近有什么问题",向量检索可能找不到,但图谱可以沿着 小李→同事→老王→负责→支付系统 的路径多跳推理。
-
✅ 支持多跳推理、关系查询;记忆之间有结构
-
❌ 构建和维护成本高;实体消歧困难
-
适用场景:复杂关系场景(企业组织、人物关系)、需要推理链的记忆
3.7 方案对比总结
| 方案 | 实现复杂度 | 成本 | 信息保真度 | 容量 | 典型场景 |
|---|---|---|---|---|---|
| 全量缓冲 | ⭐ | 高 | 100% | 极小 | Demo、短对话 |
| 滑动窗口 | ⭐ | 低 | 近期100% | 小 | 弱连续性闲聊 |
| 摘要压缩 | ⭐⭐ | 中 | 中 | 中 | 长对话助手 |
| 向量检索 | ⭐⭐⭐ | 中 | 高(原文) | 极大 | 跨会话个性化 |
| 结构化画像 | ⭐⭐⭐ | 中 | 高(关键点) | 中 | 用户画像 |
| 知识图谱 | ⭐⭐⭐⭐⭐ | 高 | 高 | 大 | 复杂关系推理 |
💡 生产系统几乎从不单选一种,而是组合使用:滑动窗口(短期)+ 摘要(中期)+ 向量检索 & 画像(长期)是最常见的"三件套"。
四、生产级解决方案
Demo 和生产的差距,就是"能跑"和"能扛"的差距。这一节讲生产级记忆系统怎么设计。
4.1 生产级记忆系统的完整架构
几个关键设计决策:
① 读写分离:写记忆必须异步
记忆提取要调 LLM,耗时 1~3 秒。如果同步做,用户每句话都要多等几秒。正确做法:先响应用户,对话内容丢进消息队列,后台 Worker 慢慢提取记忆。
② 记忆写入不是"只增不减":CRUD 决策
新信息进来时,不能无脑追加,否则记忆库会充满矛盾("用户住北京"和"用户住上海"并存)。业界主流做法(Mem0 的核心思路)是让 LLM 对每条新事实做四选一决策:
③ 检索不只看相似度:复合打分公式
plaintext
score = w1 · 语义相似度 + w2 · 时间新近度(指数衰减) + w3 · 重要性评分 + w4 · 访问频次
-
时间衰减 :
recency = e^(-λ·Δt),越久远的记忆权重越低 -
重要性:写入时让 LLM 给记忆打 1~10 分("用户对海鲜过敏"=9 分,"用户今天说了句你好"=1 分)
-
访问频次:常被用到的记忆更重要(模仿人脑的"越用越牢")
时间衰减
设定参数
-
当前检索时刻:2024-05-20 14:00
-
衰减速率 λ_λ_:设为
0.1 /小时(意味着半衰期约 6.9 小时,适合日间助理场景) -
公式:recency=e−0.1×Δt(小时)
-
e 是自然常数(Euler's number),一个无理数,约等于 2.71828。
四条记忆的得分计算
| 记忆内容 | 生成时间 | Δt (小时) | 计算过程 | Recency 得分 | 直觉解读 |
|---|---|---|---|---|---|
| A. "13:50 用户说把3点会议改到4点" | 13:50 | 0.17 | e−0.1×0.17 | 0.98 | 刚刚发生,几乎满分,必须优先召回 |
| B. "12:00 用户确认了下午2点的评审会" | 12:00 | 2.0 | e−0.1×2.0 | 0.82 | 两小时前,仍然很新鲜 |
| C. "昨天18:00 用户提到明天有个客户拜访" | 昨日18:00 | 20.0 | e−0.1×20.0 | 0.14 | 过了一天,得分骤降,但没归零 |
| D. "上周三用户设置了每周例会提醒" | 7天前 | 168.0 | e−0.1×168.0 | ≈ 0.000005 | 极旧记忆,新近度维度几乎无贡献 |
④ 分层存储选型
| 层次 | 存储 | TTL | 存什么 |
|---|---|---|---|
| 短期 | Redis | 会话级/小时级 | 最近对话、会话摘要 |
| 长期-结构化 | PostgreSQL/MySQL | 长期 | 用户画像、实体卡片 |
| 长期-语义 | Milvus/Qdrant/PGVector | 长期+衰减 | 事实记忆向量 |
| 长期-关系 | Neo4j(可选) | 长期 | 实体关系图 |
| 原始归档 | S3/OSS/数仓 | 永久 | 全量对话原文(合规审计用) |
4.2 主流开源/商业方案对比
| 方案 | 定位 | 核心特点 | 适合谁 |
|---|---|---|---|
| Mem0 | 记忆中间件 | ADD/UPDATE/DELETE 智能记忆管理,混合存储(向量+图+KV),API 简单 | 想快速给 Agent 加上生产级长期记忆 |
| Letta(原 MemGPT) | 记忆操作系统 | 把上下文窗口当"内存"、外部存储当"磁盘",Agent 自己调用工具做记忆换页 | 研究自主记忆管理、白盒可控 |
| LangGraph Memory | 框架内置 | 短期记忆=Checkpointer(线程内状态持久化),长期记忆=Store(跨线程 KV+语义检索) | 已用 LangChain/LangGraph 技术栈 |
| Zep | 记忆服务 | 时序知识图谱(Graphiti),自动构建实体关系并带时间有效性 | 需要"何时发生了什么"的时序记忆 |
| 自研 | --- | 按上面 4.1 架构搭 | 数据敏感、深度定制需求 |
Letta / MemGPT 的思路值得单独一讲
它把操作系统的虚拟内存思想搬到了 LLM 上------上下文窗口 = 物理内存(小而快),外部存储 = 磁盘(大而慢),由 Agent 自己决定何时"换页":
关键洞察:记忆管理本身也可以是 Agent 的工具调用------不是框架被动地帮 Agent 管记忆,而是 Agent 主动"决定记住什么、忘掉什么、想起什么"。
4.3 生产环境的坑与最佳实践
-
记忆污染:用户开玩笑说"我是奥特曼",被当成事实存了。→ 提取时加置信度评估,低置信度记忆隔离或标注。
-
记忆冲突:不同时间的记忆互相矛盾。→ 记忆带时间戳,冲突时"新者胜",或保留时间线(Zep 的时序图谱思路)。
-
隐私与合规 :记忆里全是个人信息。→ 必须支持用户查看/删除自己的记忆(GDPR"被遗忘权");敏感字段脱敏存储;多租户严格隔离(记忆表必须带
user_id分区,检索时强制过滤)。 -
成本控制:每轮都做记忆提取太贵。→ 批量提取(会话结束时统一处理)、用小模型做提取、先规则粗筛再 LLM 精提。
-
可观测性:记忆系统是黑盒会让排障噩梦。→ 记录每次"检索到了哪些记忆、注入了什么",做成可回放的 trace。
-
评估:上线前用 LongMemEval、LOCOMO 等长记忆基准测试;上线后监控"记忆命中率""用户纠正次数"等指标。
五、场景化选型指南
最后给一张"按场景抄作业"的决策图:
一句话总结各典型产品的记忆配方:
-
智能客服:滑动窗口 + 会话摘要 +(跨会话)工单结构化记录
-
个人助理/陪伴应用:三件套全上------画像 + 向量记忆 + 摘要,重点做好时间衰减和记忆更新
-
代码助手:工作记忆(当前任务轨迹)+ 项目级语义记忆(代码规范、架构约定)
-
多 Agent 系统 :额外需要共享记忆(黑板模式)供 Agent 间协作
六、总结
-
LLM 天生无状态,记忆的本质是"在有限窗口内,正确时机注入正确信息"。
-
记忆分短期 (缓冲区、工作记忆)与长期 (情景/语义/程序性),核心循环是"沉淀 ↔ 检索"。
-
技术方案由浅入深:全量缓冲 → 滑动窗口 → 摘要压缩 → 向量检索 → 结构化画像 → 知识图谱,生产上组合使用。
-
生产级系统的关键:异步写入、CRUD 式记忆管理、复合打分检索、分层存储、隐私合规、可观测可评估。
-
现成方案优先看 Mem0 (简单通用)、Letta (自主记忆管理)、LangGraph Memory (框架原生)、Zep(时序图谱)。