这是 Agent 学习笔记的第 2 篇,讲记忆系统。回忆一下我卡住的那句:
记忆冲突怎么办?时间戳优先、置信度加权、显式确认;低风险自动更新,高风险追问。
背是能背,但面试官追问"如果用户只是去上海出差,不是搬家,你怎么判断?"就答不上来了。这篇就是把这个坑填上。
本系列导航
- 00 开篇:Agent 是什么?与 Chatbot / Workflow / RAG 的边界
- 01 推理范式:ReAct / Plan-and-Execute / Reflexion
- 02 记忆系统:四类记忆与冲突处理(本篇)
- 03 工具调用:Schema 设计与工具幻觉防护
- 04 可靠性工程:循环、假终止、多 Agent 与安全
- 05 成本、上下文、评测与项目故事
一、四类记忆:先分清"放在哪、放多久"
| 类型 | 是什么 | 例子 |
|---|---|---|
| 短期记忆 | 当前会话上下文 | 最近 10 轮对话 |
| 长期记忆 | 跨会话持久化 | 用户偏好、历史订单 |
| 情景记忆 | 具体的经历 | “上次退款调了 refund API 成功” |
| 语义记忆 | 抽象的事实 | “退款需要订单号” |
我用自己的话重新归纳了一版(更好记):
短期 = 草稿纸(这轮对话);
长期 = 档案柜(跨会话的身份与偏好);
情景 = 日记(做过什么、结果如何);
语义 = 常识/规则(这个系统的世界是怎样的)。
情景记忆和语义记忆的区别特别值得记:情景是"我经历过什么",语义是"我知道什么"。前者用来避免重复踩坑,后者用来做判断。
图:短期与长期描述保留范围和时间尺度,情景与语义描述内容类型,因此分类可以交叉:事件记录与知识都可以长期保留。图中的四张卡是理解视角,不是必须分别建设的四个互斥数据库。
二、记忆冲突:问题长什么样
记忆冲突 = Agent 之前记住的事,和现在用户说的事,矛盾了。
例子:
- 去年用户说:“我住北京。”
- 今天用户说:“我在上海,帮我推荐附近的餐厅。”(注意:用户没有说"我搬到上海了")
如果直接检索记忆,Agent 可能会推荐北京餐厅——这就错了。
于是就有了四种处理策略。
三、四种处理策略(逐条 + 局限)
① 时间戳优先:新的覆盖旧的
每条记忆都记时间,新的覆盖旧的:
记忆1:住北京,时间 2024-01-01 记忆2:住上海,时间 2025-06-01 → 选 2025-06-01 的"住上海"局限(重要):如果用户只是去上海出差呢?所以不能无脑覆盖。
② 置信度加权:谁说的更可信
每条记忆带一个置信度:用户明确说的置信度高,Agent推测的置信度低。
用户明确说:"我搬到上海了" → 置信度 0.95 Agent 推测:"用户可能住上海" → 置信度 0.6 → 选高置信度的这里的关键是:"我在上海"和"我搬到上海了"不是同一个信息。前者是位置,后者是居住地变更。置信度差异来自于"表述的明确程度"。
③ 显式确认:拿不准就直接问
当新旧记忆矛盾、且不好判断时,直接问用户:
Agent:"您之前提到住北京,现在是在上海吗?" 用户:"对,我搬过来了。" → 更新记忆④ 按风险分级:低风险自动更新,高风险必须追问
| 风险 | 例子 | 处理 |
|---|---|---|
| 低风险 | 口味、喜欢的颜色、常点的外卖 | 自动更新 |
| 高风险 | 医疗过敏、法律身份、金融账户 | 必须追问确认 |
举例:
- 用户上次说"不吃辣",这次说"最近开始吃辣了" → 口味是低风险,自动更新;
- 用户上次说"对青霉素过敏",这次说"好像不过敏了" → 医疗高风险,必须追问:“您确认青霉素不过敏吗?这很重要。”
四、回答那道追问:出差还是搬家?
面试官追问"用户只是出差怎么办",我现在会这样答(分三层):
- 先看表述的明确性:如果用户说"我搬到上海了"→ 高置信度的居住地变更;如果只是说"我在上海"→只记一条"情景记忆:当前在上海",不动"居住地=北京"这条长期记忆;
- 再看时效与场景:推荐餐厅这种查询,只用"当前地点",不需要改长期画像;
- 拿不准就问:如果后续行为持续指向上海(多次定位/多次"在上海"),再触发显式确认:“您现在是常住上海了吗?”
一句话总结:
不要用"位置"去覆盖"居住地",要用"情景记忆"记录临时状态,只有"显式表述 + 高置信度 + 持续一致"才更新长期画像。
这就是"时间戳优先"不能无脑用的原因——冲突要先分清是"哪一类记忆"的冲突。
图:明确的出差消息应记录为有时效的事件,保留原常住地;只有获得真正长期变更的明确依据后才更新长期信息,语义含糊时先询问。来源、时间与适用范围应与记忆一起保存,图中A、B仅为抽象代号。
五、记忆检索排序:公式怎么读
score = relevance × exp(-λ × age) × importance三个因子:
| 因子 | 含义 | 直觉 |
|---|---|---|
relevance | 与当前问题的相关性 | 越相关越靠前 |
exp(-λ × age) | 时间衰减,越旧越小 | λ 越大,衰减越快 |
importance | 这条记忆本身的重要性 | 重要的事不该被时间淹没 |
举例:用户问"推荐餐厅"
- “不吃辣”:重要性高——虽然旧,仍排在前面;
- “上次去了川菜馆”:相关性低——时间再新也不一定排前面。
我对这个公式的理解是:乘法的意义是"一票否决"。任何一项接近 0,这条记忆就不会被选中——相关性为 0 就完全没用;时间极旧会指数衰减;重要性极低也一样。
而
importance的存在,正是为了解决"时间优先把重要信息冲掉"的问题(比如过敏史)。
遗忘机制(我补的扩展)
面试还可能问"记忆太多怎么办"。常见做法:
- 时间衰减:
exp(-λ×age)天然实现"旧记忆降权"; - 容量上限 + LRU:每类记忆设条数上限,超出淘汰最久未用的;
- 摘要压缩:把多条情景记忆总结成一条语义记忆(“该用户近 3 个月常问退款”);
- 重要性保护:高
importance的记忆不参与淘汰(过敏史、法务标记)。
六、面试口述版
“记忆冲突是新旧信息矛盾。处理分三层:第一,时间戳优先,新的覆盖旧的;第二,置信度加权,用户明确说的优先于系统推测的;第三,显式确认,矛盾且高风险时追问用户。低风险如口味自动更新,高风险如医疗必须追问。我们系统用向量相似度大于 0.9 且内容矛盾来检测冲突。”
如果被追问"出差怎么办",就补上第四节那句结论——先分记忆类型,再决定要不要覆盖。
七、我的复盘
这一篇最大的收获是**“记忆不是一个池子”**:
把记忆分成短期/长期/情景/语义之后,“冲突"这件事就不再是"选新还是选旧”,而是"这次冲突发生在哪一类记忆上、该怎么处理"。
我整理了一张决策表,面试时可以直接讲:
| 冲突类型 | 处理 |
|---|---|
| 临时状态 vs 长期画像(我"在"上海 vs 我"住"北京) | 写情景记忆,不动长期画像 |
| 低风险偏好变更(口味) | 时间戳优先,自动更新 |
| 高风险事实变更(过敏、账户) | 显式追问+ 人工确认 |
| 推测 vs 明确表述 | 置信度加权,明确优先 |
| 持续多次一致 | 升级为长期记忆 |
下一篇讲工具调用:Schema 到底该怎么写(“写什么时候用,不是写是什么”)、工具幻觉的四层防护(白名单 / Schema 校验 / Prompt 约束 / Few-shot),以及"参数错误"和"工具结果过长"这两类真实故障怎么处理。