七、记忆与上下文管理
这一章考察的是"上下文窗口之外"的工程能力:历史怎么裁、记忆怎么存、长程信息怎么不丢。答题关键在于给出量化的触发条件(窗口水位、轮数、压缩率)和写入 / 去重 / 衰减的具体策略,只背"短期记忆、长期记忆"两个名词是拿不到分的。
73. 短期记忆和长期记忆分别怎么实现?各自存什么内容、用什么存储?
必刷·基础
参考答案
结论:短期记忆 = 当前会话内随请求携带的上下文(本质是 Prompt 的一部分),长期记忆 = 会话外持久化、按需检索回注的信息(本质是一个存储 + 检索系统)。
短期记忆:
- 存什么:当前会话的消息列表(user / assistant / 工具调用结果)、会话内临时变量(当前任务状态、已确认的参数);
- 怎么存:结构上就是消息数组,物理上放 Redis(带 TTL,典型 1~7 天)即可支撑会话续接;
- 怎么用:每次请求拼 Prompt 时注入,受上下文窗口约束,超了就要裁剪或摘要(见下一题)。
长期记忆:
- 存什么:用户画像(偏好、约束)、事实型记忆("用户叫什么、做什么工作")、事件历史(上次咨询的问题、结论)、沉淀的知识结论;
- 怎么存:主流是向量库(按语义召回,如 Milvus / Qdrant / pgvector)+ 结构化字段(画像属性存 KV 或关系表,需要精确过滤)混合;事件流水可存文档库带时间戳;
- 怎么用:请求前按当前 Query 检索 top-k(经验值 3~10 条)注入 system 或独立记忆段,Token 预算单列。
一句话区分:短期记忆决定"这轮对话接不接得上",长期记忆决定"隔天回来还认不认得你"。两者的失败现象也不同——前者表现为指代混乱,后者表现为重复追问已知信息。
面试官可能追问
- 短期记忆放 Redis 还是放数据库?(活跃会话 Redis + TTL,落库做审计和跨会话恢复,双写)
- 长期记忆为什么不能全塞进上下文?(成本随条数线性涨、噪声稀释相关性、还有 Lost in the Middle 问题)
74. 对话历史超过上下文窗口怎么办?截断、摘要、检索式记忆各有什么问题?
必刷·中等
参考答案
三种主流方案及各自的问题:
滑动窗口截断:只保留 system + 最近 N 轮(经验值 10~20 轮起,按 Token 预算动态算)。
- 问题:窗口外的信息硬丢失,用户引用很早说过的内容时直接"失忆";切分点也可能切断一轮完整的问答,造成半截对话。
- 落地要点:按轮次整体裁剪而不是按 Token 硬切;system 和关键参数(如已收集的用户信息)单独锁定,不参与裁剪。
滚动摘要:历史超过水位线(如窗口的 60%~70%)时,把早期对话压成摘要,用摘要 + 近期原文拼接。
- 问题:有损压缩,摘要模型会丢细节(数字、否定语义、约束条件最容易被丢);多轮反复摘要会累积误差(摘要的摘要);额外引入一次 LLM 调用的延迟和成本;摘要触发时机不当还会造成卡顿。
- 落地要点:摘要里显式要求保留"数字、人名、已确认的决策、未完成的待办";控制摘要频率(每 N 轮或超阈值才触发,而不是每轮都摘)。
检索式记忆:全部历史 embedding 入库,每轮按当前 Query 召回相关片段注入。
- 问题:召回是语义相关的,不一定是决策相关的——"我三分钟前说过别加香菜"这种时序强相关内容,向量相似度未必高;依赖 Query 质量,口语化 Query 召回不准;冷启动(第一轮没有可召回内容)无效。
- 落地要点:检索结果和最近 K 轮原文叠加使用而不是二选一;召回片段带上轮次标签帮助模型判断时效。
生产实践是三者组合:最近轮次保原文 + 超窗部分滚动摘要 + 跨会话内容走检索。单独依赖任何一种都有明确短板。
面试官可能追问
- 摘要触发条件你定多少?(Token 水位 60%~70% 触发,留出检索内容 + 输出的余量,避免触发即超限)
- 怎么验证压缩没有丢关键信息?(构造含关键事实的长对话评估集,压缩后做事实召回测试,对比原文回答的一致性)
75. 什么是 Lost in the Middle?它对 Prompt 里内容的排布有什么实践指导?
高频·中等
参考答案
Lost in the Middle 是 Liu et al. 2023 年论文《Lost in the Middle: How Language Models Use Long Contexts》揭示的现象:把相关信息放在长上下文的不同位置让模型回答,效果呈 U 形曲线——开头和结尾的信息利用率高,中间部分明显下降,即使模型声称支持超长窗口。也就是说窗口"装得下"不等于"用得好"。
对 Prompt 排布的实践指导:
- 关键信息放两端:指令、关键约束、重要事实放 system(开头)或紧贴用户问题(结尾);长篇参考资料放中段;
- 检索结果的排序策略:RAG 场景 rerank 后不要简单按相关度从高到低排,常见做法是最优的放头尾(如第 1、2 名放开头和结尾,其余居中),或至少把最相关片段放最前面;
- 控制上下文总长:塞满窗口不仅贵、慢,还在主动制造 Lost in the Middle。经验上单请求上下文控制在窗口的 30%~50% 以内,效果和成本更平衡;
- 显式指路:用"请优先参考以下第一条资料"这类指针性指令,引导模型注意力到关键片段;
- 重排而非硬塞:内容多时分批处理(map-reduce)或提高召回精度减少 top-k,优于全部平铺。
注意两点:一是不同模型、不同上下文长度下 U 形程度不同,新模型有改善但问题仍存在,上线前应在自己业务上实测;二是它和"长上下文模型还需不需要 RAG"直接相关——这是窗口扩大后依然要做检索式注入的核心论据之一。
面试官可能追问
- 怎么在自己的模型上测这个效应?(构造关键事实位置可控的探针问答集,扫位置画曲线,观察中段准确率跌幅)
- 除了位置,还有哪些因素影响长上下文利用率?(指令与材料的距离、重复次数、材料间相似度干扰)
76. 长期记忆怎么设计?什么时候写入、怎么去重更新、怎么遗忘过时信息?
高频·中等
参考答案
长期记忆设计的核心是写入要有门槛、更新要有策略、遗忘要有机制,三个环节缺一不可。
什么时候写入:不能每轮都写(噪声大、成本高)。常用触发条件:
- 显式信号:用户陈述稳定偏好或事实("我对花生过敏"、"我住杭州");
- 隐式信号:同类信息在多轮 / 多会话中重复出现(出现 2~3 次再落为画像);
- 任务边界:会话结束、任务完成时批量沉淀结论;
- 实现上可用小模型或规则做"记忆抽取" pass,只对候选轮次跑,控制成本。
怎么去重更新:写入前先检索(拿新记忆的 embedding 在库里找近邻),相似度超阈值(经验值 0.85~0.92 起,需按业务校准)判定为同一事实,走更新而非新增:字段型画像直接覆盖并记录版本;事件型记忆保留多条带时间戳;冲突值单独处理(见记忆冲突消解)。可选"重写合并":让 LLM 把新旧两条合成一条更完整的表述再写入。
怎么遗忘过时信息:
- 显式遗忘:用户说"别记这个了"、行使删除权,硬删并同步所有副本与索引;
- 衰减:记忆带访问时间与最后命中时间,长期未被召回的降权或归档(半衰期按业务定,事件类 30~90 天,画像类不衰减或长周期);
- 过期淘汰:时效性记忆("本周促销是 X")带有效期,到期自动失效;
- 容量治理:单用户记忆条数设上限(如数百条量级),超限按重要度 + 新鲜度挤出。
整体架构 = 记忆抽取器 + 向量 / 结构化混合存储 + 写前去重 + 读时按相关度 × 新鲜度排序。上线前重点评估两个指标:写入精确率(别把闲聊噪声存成记忆)和召回命中率(该想起来的能不能想起来)。
面试官可能追问
- 记忆抽取用什么模型?(小模型 + 固定 Schema 抽取够用,效果不够再升级,抽取本身也要做评估)
- 衰减参数怎么定?(按业务节奏:客服场景记忆生命周期短,陪伴 / 助手类场景长,用线上召回效果回归来调)
77. 用户画像类记忆和事件类记忆如何区分存储和召回?混合召回怎么融合?
中频·较难
参考答案
先给区分维度再讲融合。两类记忆的信息性质不同:画像是慢变的、可覆盖的;事件是 append-only 的、带时序的,混在一张表里必然互相污染。
画像类记忆(用户偏好、稳定属性、长期约束):
- 存储:结构化为主(用户 ID + 属性键值,如
饮食禁忌: 花生、所在地: 杭州),存关系表或 KV;语义模糊的偏好("喜欢简洁的解释风格")可辅以向量; - 召回:按用户 ID 整体加载,不走语义检索——画像就该全量在场(几百 Token 内),注入 system;
- 更新:新值覆盖旧值,保版本历史用于审计与回滚。
- 存储:结构化为主(用户 ID + 属性键值,如
事件类记忆(某次会话发生了什么、某天问了什么、结论是什么):
- 存储:向量库 + 元数据(时间戳、会话 ID、主题标签),append-only;
- 召回:按当前 Query 语义检索 top-k,排序融合相关度与新鲜度(如
score × 0.7 + recency × 0.3,权重按业务调); - 特点:单条价值低、组合价值高,量大(每用户可达成百上千条)。
混合召回融合:
- 画像全量注入 system(稳定、便宜、必命中);
- 事件按 Query 语义检索 + 时间衰减排序,取 top-k(3~10 条)注入记忆段;
- 冲突时画像优先级高于事件("长期吃素"覆盖"上周吃过一次烤肉"的推断);
- Token 预算分层:画像固定预算 + 事件弹性预算,超限时先裁低分事件。
判断某条信息归哪类的准则:会随时间失效、需要时间戳佐证的是事件;可被更新覆盖、描述"这个人是谁"的是画像。边界情况(如"最近在减肥",是临时画像)可加有效期字段做画像的时效衰减。
面试官可能追问
- 画像和事件冲突怎么自动检测?(画像属性作为过滤条件对事件召回做一致性校验,冲突走消解流程)
- 事件记忆规模大了怎么办?(按主题 / 会话先聚类建两级索引,先路由再向量检索,或对旧事件做周期性归并摘要)
78. 上下文压缩有哪些做法?压缩会不会丢关键信息?怎么权衡压缩率和信息保真度?
中频·中等
参考答案
压缩必然是有损的,工程目标不是"不丢"而是丢得可控、丢得可测。做法分层:
规则级(无模型):
- 裁剪:按轮次滑窗,删工具调用的原始冗长输出(只留结论)、去重重复的系统日志;
- 特点:零成本、确定性,但只能处理"结构性冗余",压不动语义内容。
抽取级:用 LLM 从原文抽取结构化要点(事实、决策、待办、数字),丢弃寒暄与过程。
- 特点:压缩率高(长对话可压到 10%~20%),但依赖抽取质量,数字和否定句最容易被抽丢。
摘要级:滚动摘要(见第 74 题),保留叙事连续性,适合"只需要大概背景"的场景。
硬压缩(提示词压缩研究 / 工具):如 LLMLingua 系思路,用小模型逐 Token 评估信息量、删除低困惑度片段。
- 特点:便宜、快,但可能切碎语法,对关键短实体(人名、编号)误删风险高,生产使用要谨慎评估。
权衡压缩率与保真度的落地做法:
- 关键内容白名单不参与压缩:数字、金额、日期、人名、用户明确确认过的决策、未完成待办,用规则先抽出保护,剩余部分再压缩;
- 压缩 Prompt 显式列出必须保留的类别,并在输出 Schema 里强制这些字段;
- 度量保真度:建"事实核查"评估集——压缩前模型能答对的问题,压缩后仍要答对,以此算事实保持率,作为压缩方案的准入门槛;
- 分级压缩:先规则裁剪(便宜),不够再语义压缩(贵),按水位逐步升级;
- 经验水位:触发压缩的阈值设在上下文预算的 60%~70%,单次目标压到 40% 以下留出缓冲。
一句话:压缩率是成本指标,事实保持率是质量指标,在事实保持率达标的前提下最大化压缩率,顺序不能反。
面试官可能追问
- 工具调用的长输出怎么压?(优先留"结论 + 关键字段",原始 payload 落存储给引用,上下文里只放摘要)
- 摘要的摘要误差累积怎么治?(保留一份基准摘要 + 增量合并新内容,而不是反复重摘全量)
79. 多轮对话的状态管理用什么方案?纯消息列表够用吗?什么时候要引入显式状态机?
中频·较难
参考答案
结论:消息列表只承载"对话内容",不承载"任务状态"。任何超过闲聊复杂度的应用都需要在消息之外引入显式状态。
纯消息列表适用场景:开放闲聊、通用问答助手。状态隐式存在于历史里,模型自己"读"出来,实现最简单。
失效场景:
- 消息被裁剪 / 摘要后,隐式状态随内容一起丢;
- 关键槽位(订单号、已确认的日期)依赖模型从历史里重新解析,每轮都有解析出错概率,长对话错误累积;
- 业务流程有硬约束(必须先验证身份才能查账单),靠 Prompt 描述流程模型可能跳步;
- 需要持久化恢复、审计、多端同步时,"状态藏在消息里"无法序列化。
显式状态管理的层次(按复杂度递进):
- 槽位结构(Slot / Session State):会话级 KV(当前意图、已收集参数、任务进度),随消息一起存 Redis,拼 Prompt 时显式注入。覆盖大多数多轮任务;
- 有限状态机(FSM):任务有明确阶段流转(如售前咨询:需求收集 → 方案推荐 → 报价 → 转人工),每轮先做意图 / 槽位迁移判断再决定动作,跳步和回退都受控;
- 图编排(如 LangGraph):状态是共享数据结构,节点是步骤,边是条件路由,天然支持中断恢复与人机协同,适合长流程 Agent。
工程要点:
- 状态迁移的判定可用"小模型分类 / 规则"承担,不必每轮都问大模型,降低成本和抖动;
- 状态变更要落持久化(每次迁移写 checkpoint),会话恢复时重建状态而不是让模型从历史里重新猜;
- 消息列表和状态分存储、同会话 ID 关联,消息可压缩,状态永不压缩。
判断标准一句话:当"下一步该做什么"存在业务正确答案时,就把这个答案从模型手里拿回来,放进显式状态。
面试官可能追问
- 状态机会不会太死板,用户跳着说怎么办?(迁移函数允许跳跃和回退边,或混合"FSM 定阶段 + 模型定话术")
- 状态和消息不一致怎么办?(以状态为准重定向对话,或触发一次澄清,禁止静默分叉)
80. 跨会话记忆怎么做(用户隔天回来还能"记得")?隐私合规上有什么要求?
低频·较难
参考答案
跨会话记忆 = 会话结束时把值得长期保留的信息沉淀到用户级存储,新会话开始时按需回注。链路四步:
- 沉淀:会话结束(或任务完成)触发记忆抽取,产出候选记忆(事实 / 偏好 / 事件),带来源会话 ID 和时间戳;
- 写入治理:写前去重(语义近邻阈值判断,见第 76 题)、冲突检测(与既有记忆矛盾走消解)、分级(画像 / 事件,见第 77 题);
- 回注:新会话首条请求时,画像全量 + 按 opening Query 召回相关事件 top-k,注入 system 记忆段,并明确标注"以下是历史记忆,可能过时";
- 展示与反馈:记忆对用户可见、可编辑可删除(前端"记忆管理"入口),误记忆可被纠正——这既是体验也是合规要求。
隐私合规要求(以国内《个人信息保护法》为主要依据,GDPR 思路类似):
- 知情同意:开启长期记忆前需明示并取得同意,记忆用途、范围写清楚;敏感个人信息(健康、金融、行踪)要单独同意;
- 最小必要:只存完成功能所必需的记忆,闲聊内容不沉淀,抽取环节就要过滤;
- 可删除 / 可撤回:用户有权查看、更正、删除全部记忆,撤回同意后既删记忆也要同步向量索引副本和备份链路(删除要可验证);
- 存储与传输安全:记忆关联用户身份,加密存储、访问鉴权、按用户隔离(多租户场景严禁串号——这是记忆系统最严重的事故类型);
- 用于模型训练的限制:用户记忆未经单独授权不得拿去训练;调用第三方 API 时确认数据不留存条款,或做脱敏后回注;
- 未成年人等特殊主体:从严或默认关闭长期记忆。
工程上的配套:记忆写入和删除都要有审计日志;删除操作设计成幂等且覆盖全副本(主存、向量索引、缓存、备份)。
面试官可能追问
- 回注记忆引入 Prompt 注入风险怎么办?(记忆内容当不可信数据隔离,格式上与指令分离,生成前做注入检测)
- 用户删号了,向量库里的 embedding 算不算个人信息?(算派生个人信息,删除要连 embedding 一起删,这也是选支持按 ID 删除的向量库的原因)
81. 记忆冲突怎么消解?用户先说喜欢 A、后说喜欢 B,系统应该存什么?
低频·较难
参考答案
先给结论:默认"新值覆盖旧值"(last-write wins),但要区分冲突类型分别处理,且被覆盖的值不物理删除、转为历史版本。逐类展开:
偏好反转(先喜欢 A 后喜欢 B):
- 大概率是偏好改变 → 新值生效,旧值标记
superseded留版本链; - 小概率是口误或情境差异("平时喜欢辣,今天嗓子疼不吃辣")→ 依赖上下文判断。落地做法:反转写入时看当前会话语境,带情境条件的可存为条件记忆(
健康时: 偏辣),无条件冲突才覆盖; - 无法判断时保守做法:存新值但降低置信度,观察后续是否反复——短期内反复翻转(如 3 次以上)说明这是情境依赖偏好,不该落成全局画像。
- 大概率是偏好改变 → 新值生效,旧值标记
事实矛盾(先说住杭州后说住上海):
- 可能是搬迁(新值对)也可能是口误。带时间戳双条保留 + 新值优先召回,冲突事件本身也入记忆("用户 8 月更新了城市"),生成侧可自然表达"你之前在杭州,现在在上海对吧"。
画像 vs 事件冲突:画像优先级高于单次事件推断(见第 77 题),事件不覆盖画像,只作为画像更新的候选证据(多次事件一致才触发画像变更)。
消解机制的设计要点:
- 冲突检测前置:写入前的近邻检索不只做去重,同时做矛盾判定(可用 LLM 判断新旧两条是否语义互斥,输出"一致 / 更新 / 冲突 / 无关"四分类);
- 版本化而非覆盖:记忆表带
version与valid_to,可回滚可审计,也支撑"时间旅行"式查询(上个月用户的偏好是什么); - 置信度与证据计数:记忆带置信分,重复出现加分、被用户纠正降分,召回排序时融合;
- 用户可干预:冲突无法自动判定时可主动澄清一次("以后都按 B 来吗?"),用户回答是最高优先级信号;
- 生成侧兜底:注入记忆时附时间戳,让模型对陈旧信息保持不确定表达,避免拿旧值与用户抬杠。
一句话总结:冲突消解的本质是把"信哪个"从模型推理问题变成存储层的版本管理问题——靠 Schema 和版本链兜底,模型只做辅助判断。
面试官可能追问
- 为什么不建议直接问模型"该存哪个"就完事?(无版本链不可审计、不可回滚,且模型单次判断不可靠,需要证据累积)
- 两个 Agent / 设备同时写入冲突记忆怎么办?(写入带来源与时间戳做因果排序,用户显式操作 > 新会话 > 后台推断)
