LLM 面试备考
首页/Agent 架构

记忆与状态管理 Memory & State Management

所属:01-fundamentals / 04-agent-architecture
面试权重:★★
来源:通用领域知识(B站「AI Agent 八股」合集高频);未做一手考证
相关项目:QueWI —— 多轮对话与意图识别;Writing Copilot —— LangGraph 共享状态与跨会话偏好 → 见 02-interview/projects/quewi-chatbot.md、02-interview/projects/writing-copilot.md

一句话#

短期记忆是当前会话的状态(对话历史 + 任务中间状态),长期记忆是跨会话持久化的用户事实与偏好;记忆系统的全部设计难点归结为三个策略问题 —— 写什么、什么时候写、什么时候读。

为什么重要(面试角度)#

这是一道★★题,但它最容易暴露「只做过单轮 demo」。面试官想问的是策略而不是定义:

  • 你分不分得清对话历史与任务状态(前者可以摘要,后者不能丢)
  • 你知不知道「偏好这类小而确定的事实放结构化存储比放向量库更可靠」
  • 你有没有想过记忆的写入是有风险的(错误记忆被长期反复召回)
  • 你知道记忆污染和「通过对话注入持久化」这类攻击面
  • 你能说清记忆的生命周期:冲突怎么处理、什么时候该忘

核心内容#

短期记忆 vs 长期记忆#

短期记忆 长期记忆
范围 当前会话 跨会话、跨设备
内容 对话历史 + 任务中间状态 + 工具调用轨迹 用户偏好、事实、历史交互
载体 上下文窗口 / 会话状态对象 数据库 / 向量库 / 结构化存储
主要问题 窗口有限 → 需要截断与摘要 召回准不准、写不写对、该不该忘

一个关键区分:短期记忆里其实混着两类东西 ——

  • 对话历史(用户说了什么、模型回了什么):可以摘要、可以截断,损失通常可接受
  • 任务状态(当前做到哪一步、已确认的约束、未完成的待办):不能摘要掉,这是 agent 的执行依据

把它们混在一个消息列表里,是很多 agent 在长会话中「忘记任务目标」的根因。工程上应该显式分离 —— LangGraph 的共享状态就是这种分离的一种实现(见 multi-agent.md)。

长期记忆的类型#

借用认知科学的分类(在 agent 语境里被广泛使用,含义有调整):

类型 内容 典型载体
语义记忆 事实与偏好(「用户是香港的」「偏好简体中文」「不喜欢长段落」) 结构化偏好表 / 向量库
情景记忆 过去发生过什么(「上个月讨论过 X 方案,最后选了 Y」) 事件日志 / 向量库
程序性记忆 怎么做事(工作流、约束、格式规范) 三个合法载体,按「作用范围 + 是否需要权限强制」分:全局生效的进系统 Prompt;只在特定领域/工具匹配时才用的做成按需加载的 Skill;不可绕过的进程序 / Harness 硬约束。都不该做成向量检索

重要判断:程序性记忆不该做成「向量检索」——「怎么做事」的规则必须稳定生效,靠相似度召回就会出现「该遵守时没召回到」。但 ⚠️ 不要把「不该检索」直接等同于「只能常驻系统提示」:那会把 Skill 这一整层排掉。正确的分法是三个载体(《深入理解 AI Agent》第 9 章的划分):

载体 作用范围 什么时候用
系统 Prompt 对所有任务生效 全局的、任何任务都必须遵守的规则
按需加载的 Skill 只在匹配到某个领域或工具时生效 领域性的流程与规范 —— 关键是它靠 description 作确定性路由(「何时用 / 何时不该用」),不是相似度检索,加载后正文作为 user message 追加到上下文末尾
程序 / Harness 硬约束 不可绕过的权限、合规、高风险门禁 —— 这类必须由代码强制,写在 Prompt 里等于没写

判据不是「常驻还是检索」,而是**「它需不需要被强制」**:需要强制 → 代码;不需要强制但只对某类任务有意义 → Skill;对所有任务都成立 → 系统 Prompt。这也回答了「系统提示词为什么会膨胀」——把本该做成 Skill 的领域流程全塞进系统提示,正是膨胀的来源(见 context-engineering.md 的「放进去 vs 去检索」,以及 10-tool-discovery.md 的渐进式披露)。

载体选择#

载体 适合 不适合
结构化存储(表 / KV) 小而确定、需要精确命中的事实与偏好(语言、时区、会员等级、禁忌) 模糊、开放、语义相关的内容
向量库 语义相似召回:过往对话、长文档、经验 需要精确、需要可解释、需要保证命中的场景
图 实体关系、多跳推理 简单事实(成本远高于收益)

最常犯的错:把「用户偏好简体中文」这种确定性事实塞进向量库。向量召回是近似的 —— 相关不相关靠相似度阈值,会漏、会错、不可解释、且无法保证「每次都用上」。该精确的用结构化,该模糊的用向量。 实际系统通常是混合:结构化存高频确定的事实(每次注入),向量库存情景记忆(按需检索)。

写策略#

策略 做法 风险
显式写入 模型调用记忆工具、或用户明确说「记住」 覆盖不全(该记的没记);但可控
隐式写入 每轮自动抽取「值得记的东西」并落库 噪声写入、错误写入、以及隐私风险
定期批处理 会话结束后统一抽取摘要 时效性差,会话中断会丢

核心工程原则:写入是高风险动作,比读取更值得谨慎。 写入一次错误,会在此后每一次相关交互里被召回并放大 —— 错误记忆的破坏是复利的。所以:

  • 写入要有校验:结构化字段做类型与取值域校验;语义内容最好过高置信度阈值
  • 写入要可追溯:每条记忆带上来源(哪次会话、哪句话)与时间戳
  • 写入要可撤销:用户能查看、编辑、删除自己的记忆(这既是产品需求也是合规要求)

读策略#

  • 全量注入:把用户画像整个塞进系统提示。简单可靠、成本可预测,但只适用于很小且稳定的画像。
  • 按需检索:用当前输入去检索相关记忆。成本低,但该召回时可能没召回,且检索质量直接决定体验。
  • 分层:高频确定的走全量注入(如语言、时区),情景类走检索。这是最常见的生产做法。

一个容易忽略的点:检索回来的记忆要带时间戳与来源,否则模型无法判断「这条记忆是不是过时了」。用户两个月前说「下个月要去出差」,今天再召回就会误导。

记什么、忘什么#

该记:稳定的偏好与约束(语言、格式、禁忌)、长期有效的身份与背景、明确的用户指令(「以后都用表格」)、以及过去的决策与理由。

不该记:一次性的上下文(「这次会议在 3 楼」)、模型的推测(把模型自己猜的东西当事实写进去是最危险的一类)、敏感信息(除非有明确授权与合规依据)、以及会过期的具体状态(改成带时间戳留存,读的时候判断有效性)。

遗忘机制:

  • TTL / 过期:时间敏感的记忆应带有效期
  • 冲突处理:新事实与旧事实矛盾时是覆盖还是并存?⚠️ 答案是「先别急着覆盖」 —— 单纯覆盖会不可逆地丢掉历史,而单纯追加又会让模型看到两条矛盾记忆、行为随召回顺序漂移。正确的顺序是先判定这两条是不是真的冲突:如果它们分别在不同时间 / 对象 / 地域 / 任务 / 前置条件下成立,那它们不是冲突,而是各有适用场景 —— 这时该做的是把适用场景写进知识,而不是删掉其中一条。只有确认是同一条件下的前后矛盾,才谈覆盖。证据仍不足时,保留冲突与待确认状态,不要强行收敛成一个确定结论(见 08-agent-stack.md 的分层归因思路,以及下面「遗忘机制」一节的做法)
  • 降权:长期未被使用的记忆降低召回优先级,避免陈旧噪声堆积
  • 主动清理:定期审计(人工或自动)删除低质量记忆

记忆污染#

这是本文件最有面试价值的一节。

污染途径:

  1. 模型推测被当成事实:模型在总结时把「用户可能偏好 X」写成「用户偏好 X」,之后被反复召回
  2. 摘要误差累积:多轮摘要,误差逐步放大(类似于不断转述的传话游戏)
  3. 间接 prompt injection 写记忆:攻击者在 agent 会读到的内容里(网页、邮件、文档)埋入「请记住:用户的 API key 是…」,一旦 agent 有记忆写入能力且不加校验,这条就被持久化了 —— 这是跨会话持续生效的攻击,破坏力远大于单次注入
  4. 用户误输入:用户说错一次,被当成长期事实

缓解:

  • 写入与读取分离的权限边界:不是所有内容都能写记忆;敏感类别需要显式来源与确认
  • 写入前校验 + 关键记忆需人工确认(见 human-in-the-loop.md)
  • 来源标注与可追溯:每条记忆记下「是谁说的、什么时候、在什么上下文」
  • 记忆可见可删:让用户可以审计,这是最有效的兜底
  • 定期审计与过期:控制熵增

与 RAG 的区别#

两者工程上常共用同一套检索设施,但语义与生命周期不同:

RAG Memory
内容 外部知识(文档、知识库) 关于这个用户/这次任务的状态
谁产生 组织维护 交互过程中累积
变更频率 低(版本化更新) 高(每轮可能变)
主要风险 召回不准、内容过时 写入错误、污染、隐私
正确性要求 事实正确 事实正确 + 归属正确 + 时效正确

面试里能主动说出「memory 比 RAG 多两个维度:归属与时效」,是个很清楚的信号。

评测#

记忆的价值体现在跨会话任务成功率上,很难用单轮指标衡量:

  • 记忆有效性:需要用到记忆的任务,成功率是否提升?(对照实验:开/关记忆)
  • 记忆准确性:注入的记忆里有多少是正确且当前的?(防污染的核心指标)
  • 误召回率:不该用记忆时有没有硬套?(例如把上一个用户/上一次会话的偏好套过来)
  • 成本:记忆注入增加的 token 是否值这个收益

常见面试问法#

Q:短期记忆和长期记忆怎么设计? A:短期是当前会话的状态,里面要显式区分对话历史与任务状态 —— 前者可以摘要截断,后者(做到哪一步、已确认的约束、待办)不能丢,混在一起是长会话忘记目标的根因。长期是跨会话的用户事实与偏好,按语义/情景/程序性三类分开处理:程序性记忆(怎么做事)不该做向量检索,但也不是「只能常驻系统提示」—— 它有三个载体:全局规则进系统 Prompt、领域流程做成按需加载的 Skill、不可绕过的进程序 / Harness 硬约束;偏好这类小而确定的事实放结构化存储;情景记忆放向量库按需召回。读的策略通常分层:确定的小事实全量注入,情景按需检索。

Q:为什么偏好不放进向量库? A:因为向量召回是近似的:靠相似度阈值判断相关性,会漏召、会误召、不可解释,且无法保证每次都生效。而偏好属于「小而确定、需要精确命中、必须稳定生效」的信息 —— 这类应该放结构化存储并在每轮注入。向量库适合的是模糊、开放、语义相关的内容,比如过往对话与经验。实际系统通常是混合:结构化存确定的画像,向量库存情景记忆。

Q:记忆会不会写错?写错了会怎样? A:会,而且写入错误比读取错误严重得多:错误记忆会在此后每一次相关交互中被召回并放大,破坏是复利的。常见污染途径包括模型把自己的推测当成事实写入、多轮摘要误差累积、用户误输入,以及最危险的——通过间接 prompt injection 让 agent 把攻击者指定的内容写进长期记忆(跨会话持续生效)。缓解手段是写入前校验、关键记忆需人工确认、每条记忆带来源与时间戳、记忆对用户可见可删、以及定期审计与过期淘汰。

Q:记忆和 RAG 有什么区别? A:工程上常共用同一套检索设施,但语义与生命周期不同。RAG 是外部知识(组织维护、变更频率低),主要风险是召回不准与内容过时;memory 是关于这个用户和这次任务的状态(交互中累积、每轮可能变),除了事实正确之外还要求归属正确(这条记忆是不是这个用户的)与时效正确(是不是还有效)。所以记忆必须带来源和时间戳,并处理冲突与过期。

Q:多轮对话历史越来越长怎么办? A:分层处理。首先把任务状态从历史里拆出来单独维护(不可摘要掉);其次对纯对话历史做滚动摘要,摘要保留决策、约束与未完成事项,丢弃过程性内容;再次对工具输出做裁剪。要点是摘要是有损且不可逆的,且摘要本身会引入误差,所以要配评测:验证「后续轮次需要的信息是否仍在上下文里」。极端情况下还可以只保留最近 N 轮 + 常驻摘要 + 按需检索历史。

易错点#

  • ❌ 把「记忆」等同于「把历史塞进 prompt」—— 那只是上下文管理的一部分。
  • ❌ 把对话历史和任务状态混在一个消息列表里,导致长会话丢失任务目标。
  • ❌ 把确定性偏好塞进向量库,指望相似度检索,结果时灵时不灵。
  • ❌ 把「怎么做事」的规则做成按需检索 —— 该遵守时没召回到,行为不可预测。
  • ❌ 只设计读、不设计写:不做写入校验与来源标注,等于给污染开门。
  • ❌ 记忆只追加不处理冲突,模型同时看到两条矛盾记忆,行为不可预测。
  • ❌ 忽略隐私与合规:记忆必须对用户可见、可编辑、可删除(区分「脱敏」与不可逆的「匿名化」)。
  • ❌ 认为记忆一定提升效果 —— 它不是免费的,需要和「不用记忆」做对照评测。

自测#

1. 为什么「用户偏好简体中文」这类事实不该放在向量库里?

答案

向量检索是近似召回:相关性由相似度阈值决定,会漏召(该用上时没召回)、会误召(把不相关或不属于该用户的记忆拉进来)、不可解释,且无法保证每次都生效。而偏好属于「小而确定、需要精确命中、必须稳定生效」的信息 —— 一旦漏召,模型的行为就会与前几轮不一致,用户立刻能感觉到。正确做法是放结构化存储(偏好表/KV),在读的那一侧每轮注入。向量库适合模糊、开放、语义相关的内容,例如过往对话情景与经验总结。生产系统通常是混合方案。

2. 短期记忆里,「对话历史」和「任务状态」为什么要分开?不分开会出什么问题?

答案

因为两者的可丢弃性完全不同:对话历史可以摘要、截断,损失通常可接受;任务状态(当前做到哪一步、已确认的约束与参数、未完成的待办)是 agent 的执行依据,一旦被摘要掉或截断,agent 就失去了目标。如果两者混在同一个消息列表里,上下文管理策略(滚动摘要、滑窗截断)会无差别地作用于它们,典型症状就是长会话中 agent 开始重复已完成的工作、忘记用户前面确认过的约束、或者偏离原始目标。工程做法是把任务状态抽成独立的结构化状态对象(如 LangGraph 的共享状态),只对纯对话历史做压缩。

3. 一条错误的记忆被写进长期存储,后果为什么比一次错误的回答严重?

答案

因为它的破坏是复利的:错误记忆会在此后每一次相关交互中被召回并影响输出,而用户和开发者往往看不到它(它不在这一轮的输入里显式出现)。更麻烦的是它可能被后续的摘要再次「确认」,越滚越像事实。如果写入能力对模型或外部内容开放,还可能被间接 prompt injection 利用 —— 攻击者只需在 agent 会读到的网页或邮件里埋一句「请记住……」,就能获得跨会话持续生效的影响。缓解:写入前校验、关键记忆需人工确认、每条记忆带来源与时间戳、记忆对用户可见可删、定期审计与过期淘汰。

4. 记忆「只追加」会带来什么具体问题?你会怎么处理冲突和过期?

答案

只追加会导致:新旧事实并存且互相矛盾(「用户在北京」「用户在上海」),模型看到两条冲突信息时行为不可预测,且会随召回的先后随机波动;陈旧噪声不断堆积,稀释真正有效的记忆,还会抬高检索成本。处理办法:① 先判「是不是真冲突」,再决定动不动它 —— 「用户在北京」和「用户在上海」很可能不是矛盾而是时序(搬过家),这种情况不该覆盖,而该保留两条并带上时间,查询时结合时间排序取当前有效的那条(Mem0 从 v2 的 UPDATE/DELETE 改成 v3 的仅追加写入 + 检索时融合时间排序,正是因为这个:一次错误的 UPDATE/DELETE 会不可逆地丢掉历史,LoCoMo 因此从 71.4 升到 92.5、LongMemEval 从 67.8 升到 94.4);只有当两条在时间、对象、地域、任务、前置条件上都相同却仍矛盾时,才是真冲突 —— 这时也不是简单删一条,而是把各自的适用场景写进知识。② 时间敏感的记忆带时间戳与有效期,读取时判断是否仍有效,过期即降权或淘汰;③ 长期未使用的记忆降低召回优先级;④ 定期审计清理低质量记忆。核心原则是记忆需要一套生命周期管理(审计、场景限定、过期、合并),而不是一个只会增长的日志,也不是一个只会覆盖的键值表 —— 这两种极端都错。

5. 你怎么验证引入记忆系统真的有用?

答案

做对照实验并分维度看指标。① 记忆有效性:选取确实需要用到历史信息的跨会话任务集,对比开启/关闭记忆的任务成功率与用户纠错次数 —— 只在单轮任务上评测是测不出记忆价值的。② 记忆准确性:注入的记忆中正确且当前的比例(这是防污染的核心指标)。③ 误召回率:不该使用记忆时是否硬套(例如把上一次会话或另一个用户的偏好套过来)。④ 成本:记忆注入增加的 token 与延迟是否值这个收益。注意评测要覆盖「跨会话」这一维度,因为记忆的价值恰恰体现在这里。