Agent 记忆与状态
LLM 本身无状态:每轮对话它都是「第一次」遇见你。Agent 的智能上限,一半取决于把什么记住(写入),一半取决于用什么想起(检索)。Agent 架构通论 第四节给出了记忆的四层分类与上下文预算概览;本篇深挖工程落地:先判断需要哪级记忆,再按「写入 → 存储 → 检索 → 更新/过期」四环节设计系统,最后谈状态持久化、记忆安全与评测。
一、先判断:你的 Agent 需要哪级记忆
先纠正三个常见误解:
- 上下文 ≠ 记忆:上下文只是每次请求时「载入的工作副本」;关掉就没了;
- 会话历史 ≠ 长期记忆:它是原始素材,不是提炼后的知识;
- 记忆不是缓存:缓存存「原样数据」,记忆存「可复用的提炼结果」。
按需求分级,先别一律上向量库:
| 级别 | 典型场景 | 需要什么 | 复杂度 |
|---|---|---|---|
| L0 无状态 | 一次性问答/单任务工具 | 什么都不用 | 零 |
| L1 会话内 | 多轮对话、单次任务的进度 | 消息历史 + 状态结构 | 低 |
| L2 跨会话 | 记住用户偏好、常用设置、历史结论 | 长期存储 + 检索 | 中 |
| L3 跨任务积累 | 团队规范、项目知识、技能沉淀 | 长期存储 + 结构化 + 治理 | 高 |
- 判断一句:用户/任务是否期待 Agent 记住上次的事?不确定时默认 L1,出现「用户反复纠正同样的事」再升 L2;
- L2/L3 的收益衡量标准:记忆检索带来的成功率提升 vs 检索噪声/中毒带来的新失败,先加「记忆在什么场景该被用」的明确触发,不要全局注入历史(见第三节读取)。
二、记忆系统参考架构:四个环节
事件流(对话/工具结果/用户反馈)
│ ① 写入:提炼(总结/抽取事实/更新状态)——谁触发、提炼什么、校验可信度
▼
② 存储:短期工作区(消息/状态) + 长期存储(结构化事实 + 非结构化文本/向量)
│
▼
③ 检索:按需取回 → query 改写/重排 → 注入上下文(带来源与时间戳)
│
▼
④ 更新与过期:冲突处理、用户纠正优先、TTL/合并/衰减 —— 记忆库不治理=垃圾场- 每环的取舍:
| 环节 | 要点 | 常见错误 |
|---|---|---|
| ① 写入 | 提炼用专用 prompt;区分「事实 / 用户偏好 / 事件经过」;写入前校验来源可信度 | 全量对话都写库,噪声爆炸 |
| ② 存储 | 见下节选型;短期与长期分开存;敏感字段脱敏 | 一个库里混所有 |
| ③ 检索 | 按需注入 + 来源标注;查不到要能说「我不知道/我不记得」 | 每轮把所有记忆塞上下文 |
| ④ 更新 | 用户纠正 > 旧记忆;重复合并;过时衰减;定期治理 | 旧记忆与新事实打架 |
三、工作记忆与状态管理(会话内)
- 工作记忆 = 状态结构 + 上下文预算。把任务状态显式建模,而不是让它「漂在对话里」:
jsonc
// 显式状态结构(建议落盘而非只放上下文)
{
"task_id": "t-123",
"goal": "修复登录页在 iOS 上白屏的问题",
"steps": ["复现", "定位", "修复", "验证"],
"current_step": 2,
"findings": ["白屏仅 iOS Safari,疑似 -webkit- 前缀缺失"], // 关键结论
"ruled_out": ["不是字体问题(桌面端正常)"], // 排除项,防重复探索
"budget": { "steps_left": 8, "tokens_spent": 41230 }
}「哪些该进状态」的判断:目标/进度/关键结论/排除项/预算——会被后续步骤用到、且窗口装不下的;
- 上下文预算策略(方法详见 架构通论 第四节):截断、滚动摘要、结构化抽取、按需检索——组合使用,并给「哪些进窗口」定优先级:当前步骤相关 > 目标与关键结论 > 摘要历史 > 原始细节;
- 压缩会丢事实,做「摘要一致性抽查」:定期抽样比对摘要与原文,丢了关键约束(如「禁止用 X 方案」)要能发现;
- 状态持久化与恢复(生产必做):状态序列化到 DB/Redis/文件,带
task_id;- 恢复时机:进程重启、超时续跑、多实例切换、用户回来接着问;
- 恢复后第一件事:把状态与当前上下文对齐(用户可能改了主意,先确认目标没变);
- checkpoint 语义:关键里程碑(如验证通过)落一次盘,失败时可回退到最近 checkpoint 而不是从头跑。
四、长期记忆:存储什么、怎么存取
存储形态选型
| 形态 | 代表 | 适合 | 不适合 |
|---|---|---|---|
| 键值/文档 | Redis / Mongo 类 | 结构化偏好、简单 KV | 语义检索 |
| 向量库 | pgvector / Milvus 类 | 非结构化文本按语义召回 | 精确事实查询 |
| 知识图谱 | 图数据库 / 三元组 | 实体关系、推理可达 | 快速起步、模糊需求 |
| 组合 | 向量 + 元数据过滤 | 生产大多数 | 需要字段与向量一致治理 |
- 起步建议:结构化事实用文档/键值 + 非结构化用向量,加统一的元数据(用户 id、来源、时间、类型);知识图谱等出现「需要多跳推理/去重关系」再引入;
写入:质量 > 数量
- 触发时机:任务完成、里程碑、用户明确陈述偏好、用户纠正;不要在每轮对话末尾都写;
- 提炼 prompt 的要点:只抽取可验证事实与明确偏好、过滤情绪化表达与临时想法、标注「置信度/来源/时间」;
- 写入前校验可信度:来自网页/邮件检索的内容属于「外部事实」,要标来源与置信度;用户直接陈述 > 模型推断(宁可漏写不编造)。
读取:按需检索,不是全量灌入
- 检索触发:什么时候需要记忆?目标相关、用户提到「上次/之前/我记得」、任务需要历史上下文——由分类器/规则判断,平时不查;
- 检索 + 重排方法直接复用 RAG 的经验,区别:记忆检索要按用户/任务做范围过滤,且注入时标注时间与来源(「你上次(7-02)说过……」);
- 「记得住但想不起」比「没有记忆」更糟:检索不到就诚实说不知道,别用旧的过期记忆糊弄;
- 用户纠正优先:用户说「不是那样/改成 X」,必须先更新记忆再继续,且本次会话内立即生效。
更新与过期
- 冲突处理:新旧事实矛盾时,以用户纠正与最近时间为准,并合并去重;
- 过期策略:带 TTL 的事件型记忆自动衰减;长期偏好(如「我不用 PHP」)保留但要可被显式覆盖;
- 定期治理:重复项合并、失效项归档、敏感数据复查(见第六节安全)。
五、现成方案与协议:少自建就少踩坑
- MCP 的 memory 资源形态:把「记忆读写」做成一类标准工具/资源由协议承载(架构见 MCP),Agent 通过统一接口存取——工具层解耦、可替换;
- 托管记忆库/服务(如 Mem0、Zep 一类):内部一般做「提取 → 存储 → 带时间与实体权重的检索」,价值是把第四节的写/查/更新做成产品级;
- 适合:不想自己维护提炼与检索逻辑、跨会话记忆是核心卖点;
- 注意:它们仍要你定义「哪些该记」,且数据出境与 PII 合规要先确认(见 AI 工程化);
- 示例接口形态以各官方文档为准,本文不做具体版本断言。
- 何时自建:数据留在内部网、记忆需求简单(几个字段 + 少量历史)、已有 DB——三张表(users / memory_items / events)往往比引一个服务更快。
六、记忆安全与评测
- 记忆系统的三个评测面:
| 评测面 | 怎么测 |
|---|---|
| 提取正确性 | 人工标注「该记的」对照系统写的,算精确率/召回率 |
| 检索命中 | 构造「依赖上次信息才能答」的任务集,测成功率 |
| 遗忘与更新 | 测「改口后」是否按新事实回答;过期记忆是否不再被用 |
- 记忆中毒是最隐蔽的攻击面(详见 Agent 安全与治理):网页/邮件里的恶意文本 → 被写入长期记忆 → 之后每轮都「记得」要按恶意指令行事——写入环节必须做来源可信度分级,不能把外部不可信内容无差别提炼成事实。
七、踩坑清单
- 没判断就上向量库:L0 的需求配 L3 的方案,检索噪声带来新失败;
- 把上下文当记忆:任务一长/一断,Agent 就从零开始;
- 状态不落盘:进程重启、超时续跑全部从头;
- 全量写库:每轮对话都提炼,库被噪声淹没,检索质量崩;
- 写入即编造:模型「推断」的偏好没标置信度,当事实存了;
- 读取全量灌:每轮把全部记忆塞上下文,又贵又互相打架;
- 检索范围不隔离:多用户/多任务共用一个记忆池,串记忆(A 的话 B 看到);
- 用户纠正被吞:旧记忆压过新指令,用户反复纠正同样的事;
- 无过期治理:几个月前的过期事实天天被检索出来当新鲜事;
- 敏感数据裸存:对话中的 PII/机密进记忆库不脱敏(见 AI 工程化 合规);
- 记忆中毒不设防:外部不可信内容直接写入长期记忆,回灌成持久后门(见 Agent 安全与治理)。
关联阅读:记忆四层分类与上下文预算概览见 Agent 架构通论 第四节;检索/重排/向量库方法直接复用于记忆检索,见 RAG 与 Agent;记忆读写做成标准工具的协议层见 MCP;记忆中毒与数据安全见 Agent 安全与治理;记忆存取涉及 PII 的合规要求见 AI 工程化。