Appearance
面试官: Memory 系统怎么设计?短期和长期记忆分别怎么管理?
你的回答:
- Memory 系统分短期和长期两层。短期记忆管理当前对话历史,长期记忆持久化保存重要信息。
- 短期记忆用滑动窗口,保留最近 5-10 轮对话,旧的自动丢弃。同时做 Token 预算管理,给不同类型信息分配额度,超额就压缩。我们还会把关键信息(任务目标、用户偏好)置顶到 System Prompt,保证不被截断。
- 长期记忆用向量数据库存储用户画像、历史对话摘要、领域知识。用户新提问时,用问题向量检索最相关的记忆注入上下文,这个机制和 RAG 是一样的。同时用 MySQL 存储结构化信息,比如用户档案、任务记录。
- 摘要压缩方面,我们用分层摘要:最近 5 轮保留完整对话,6-15 轮保留摘要,15 轮以前只保留关键事实。同时做渐进式压缩,每次新增对话检查 Token 数,超阈值就把最旧的几轮压缩成摘要。
- 我们还解决了记忆污染问题。记忆有时间衰减,越旧的权重越低。同时把记忆分成永久偏好、临时偏好、历史行为三类,临时偏好优先级最高,避免旧信息干扰新任务。
面试官追问: 记忆越多越好吗?怎么防止旧信息干扰?
你的回答:
- 记忆不是越多越好。历史越长,Token 成本越高,LLM 越容易被无关信息干扰,答案质量反而下降。而且旧信息可能和当前任务无关,甚至产生误导。
- 我们用三个方法防止旧信息干扰。
- 第一是时间衰减,检索记忆时除了语义相似度还考虑时间因素。最近 7 天的记忆权重 1.0,30 天以前的权重 0.1。
- 第二是记忆分类。永久偏好(过敏信息)、临时偏好(这次想吃什么)、历史行为分开管理,临时偏好优先级最高。用户明确说「我现在想吃日料」,就覆盖之前「喜欢粤菜」的记忆。
- 第三是相关性过滤。不是把所有记忆都塞给 LLM,而是根据当前问题检索最相关的
Top-5,无关记忆不注入。我们统计过,注入 5 条相关记忆比注入 20 条历史记忆效果更好,成本还降低 60%。
可以把 Agent Memory 系统设计成一个分层、分级、可检索、可衰减的架构,核心目标不是“存得越多越好”,而是在有限上下文里,让最该出现的信息稳定出现,让不该出现的旧信息尽量别干扰推理。
这个方案可以直接拆成六部分:总体架构、短期记忆、长期记忆、写入机制、读取机制、抗污染机制。
一、总体架构
整个 Memory 系统建议分成四层:
1.工作记忆层(Working Memory)
这是最靠近当前推理的一层,直接服务本轮任务执行。
里面放的是:
- 当前任务目标
- 当前轮用户输入
- 当前子任务状态
- 当前工具调用结果
- 当前约束条件
它的特点是实时、短生命周期、高优先级。
一般直接拼进 prompt,或者放进 agent runtime state。
2.短期记忆层(Short-term Memory)
用于维护最近几轮对话和上下文连续性。
里面放的是:
- 最近 N 轮对话
- 最近几步推理结论
- 最近工具调用摘要
- 当前会话内形成的临时偏好
这层适合做成滑动窗口 + 分层压缩。
3.长期记忆层(Long-term Memory)
用于跨会话持久化。
里面放的是:
- 用户长期偏好
- 用户画像
- 历史任务经验
- 关键事实
- 领域知识
- 历史总结
这层不能直接全塞给模型,而是要通过检索召回进入上下文。
4.元数据与治理层(Memory Governance)
这一层负责控制记忆质量。
包含:
- 记忆分类
- 权重管理
- 时间衰减
- 冲突检测
- 失效机制
- 删除与更新策略
- 隐私与权限控制
真正让系统可用的,往往不是“存储”,而是这层治理能力。
二、短期记忆怎么设计
短期记忆的核心不是完整保存所有聊天记录,而是让当前任务所需信息在 Token 限额内保持连续性。
方案一:滑动窗口
保留最近 5 到 12 轮对话。
比如默认保留最近 5 轮。
但不能只是机械保留,因为不同消息长度差别很大,所以更推荐:
- 按轮数限制
- 再叠加 Token 预算限制
例如:
- 最近对话窗口上限:10 轮
- 短期记忆总预算:3000 tokens
- 超过预算后触发压缩
方案二:分层保留
短期记忆建议按“新鲜度”分层:
- 最近 1 到 5 轮:保留原文
- 6 到 15 轮:保留摘要
- 更早内容:只保留关键事实
这比纯窗口删除更稳,因为它不会把重要上下文直接丢光。
方案三:置顶关键上下文
有些信息不能随着窗口滚动丢失,需要置顶到固定槽位,比如:
- 当前任务目标
- 成功标准
- 用户明确约束
- 当前偏好
- 当前会话中的关键决策
这些内容不要和普通聊天历史混在一起,而要单独作为:
- pinned memory
- session state
- system slot
这样可以避免长对话后“任务目标漂移”。
三、长期记忆怎么设计
长期记忆的重点是:持久化 + 可检索 + 可更新。
通常建议分成两类存储:
1.向量存储
适合放非结构化内容:
- 历史对话摘要
- 用户表达过的偏好
- 经验片段
- 领域知识片段
- 任务复盘结论
每条记忆可以存成一个 memory chunk,字段类似:
- memory_id
- user_id
- text
- summary
- embedding
- type
- importance
- confidence
- created_at
- last_access_at
- expires_at
- source
- scope
这部分适合放在向量数据库里,供语义检索。
2.结构化存储
适合放稳定字段:
- 用户档案
- 永久偏好
- 黑名单/禁忌
- 历史任务记录
- 会话索引
- 记忆版本
- 冲突关系
这部分建议放 MySQL / PostgreSQL 一类关系型数据库。
原因很简单:
结构化信息更新、覆盖、查询、约束都更容易做,不能全靠向量库。
四、记忆的分类设计
长期记忆不要放成一个大桶,至少要分成下面几类:
1.永久偏好
长期稳定,不轻易变化,比如:
- 语言偏好
- 表达风格偏好
- 长期职业背景
- 不可违反的禁忌条件
这类权重高、有效期长。
2.临时偏好
只在一段时间内成立,比如:
- 这次项目想用 React
- 今天想吃日料
- 这轮任务里先要中文输出
这类记忆必须带 TTL 或会话范围。
否则最容易污染后续任务。
3.历史行为
是用户做过什么,不代表现在还想这么做,比如:
- 以前选过某个方案
- 曾经关注某个方向
- 做过某类任务
这类信息只能做辅助特征,不能直接当当前约束。
4.任务知识
来自过去任务的经验:
- 哪种方案成功率高
- 某类 bug 的修复经验
- 某项目的上下文摘要
适合做跨任务复用。
5.事实记忆
稳定客观事实,比如:
- 用户学校
- 技术栈
- 常用平台
如果是高置信长期事实,可以写入 profile 表并做版本管理。
五、写入机制怎么设计
不是每轮对话都要写长期记忆。
Memory 系统最重要的是写入门控。
写入原则
只有满足以下条件的信息才值得入长期记忆:
- 跨会话可能有价值
- 对未来任务有帮助
- 相对稳定
- 重要性高
- 非瞬时噪声
写入流程
第一步:候选提取
从当前对话中抽取候选记忆:
- 用户偏好
- 用户约束
- 新增事实
- 关键任务结论
- 经验性规则
这一步可以让 LLM 做 extraction(提取),也可以规则+模型结合。
第二步:分类与打分
给候选记忆打标签:
- type:永久/临时/行为/事实/经验
- importance:1-5
- confidence:0-1
- ttl:是否过期
- scope:全局/会话/任务
第三步:去重与冲突检测
写入前先检查:
- 是否与已有记忆重复
- 是否与已有记忆冲突
- 是否需要覆盖旧值
- 是否需要保留版本
比如: 旧记忆:用户喜欢粤菜
新记忆:我现在想吃日料
这不是事实冲突,而是临时偏好覆盖当前场景,所以处理方式应是:
- 不删除长期偏好
- 在当前 session 写入高优先级临时偏好
- 读取时临时偏好优先
第四步:持久化
根据类型分别写入:
- 结构化字段 → SQL
- 非结构化摘要 → 向量库
六、读取机制怎么设计
Memory 的价值不在存,而在取。
读取建议采用“多路召回 + 重排序 + 注入控制”的流程。
第一步:查询理解
先从当前用户请求中提取检索意图,例如:
- 当前任务主题
- 用户偏好需求
- 是否涉及历史任务
- 是否需要事实类记忆还是经验类记忆
第二步:多路召回
不要只做一次向量检索,而是做分通道召回:
- 通道 A:永久偏好
- 通道 B:临时偏好
- 通道 C:历史任务摘要
- 通道 D:相关经验片段
- 通道 E:会话内短期记忆
这样召回结果可控,不会全靠一个向量相似度。
第三步:混合排序
记忆排序不要只按 embedding 相似度,而要用综合评分:
[
Score = \alpha \cdot Similarity + \beta \cdot Importance + \gamma \cdot Recency + \delta \cdot Confidence + \epsilon \cdot TypePriority
]其中:
- Similarity:语义相似度
- Importance:重要程度
- Recency:新近性
- Confidence:置信度
- TypePriority:类型优先级
比如:
- 临时偏好优先于历史行为
- 永久禁忌优先于普通经验
- 最近 7 天权重大于 30 天前
第四步:Top-K 控制
不要把所有召回结果都注入模型。
通常只取 Top-3 到 Top-8。
经验上:
- 注入 5 条高相关记忆,往往比注入 20 条历史记忆效果更好
- 成本更低
- 噪声更小
- 推理更稳定
第五步:上下文注入
注入时也不要原样堆进去,而要结构化组织:
- 当前任务目标
- 当前会话约束
- 检索到的相关偏好
- 检索到的历史经验
- 必须遵守的事实约束
也就是说,Memory 注入应该是“整理后的上下文包”,不是“原始记忆拼盘”。
七、摘要压缩怎么做
摘要压缩是短期记忆和长期记忆的中间桥梁。
推荐用分层摘要 + 渐进式压缩。
分层摘要
把历史对话按层保存:
- L0:原始消息
- L1:会话摘要
- L2:关键事实摘要
- L3:长期经验摘要
渐进式压缩
每次新增对话时检查上下文 Token 数:
- 未超限:不处理
- 接近阈值:把最旧的若干轮压成摘要
- 继续增长:把旧摘要再压成关键事实
这种方式比“一刀切总结全部历史”更稳定,因为它避免了大规模信息损失。
八、怎么防止旧信息干扰
这部分是面试里最关键的点,因为真正难的不是“有记忆”,而是“记忆不污染”。
1.时间衰减
越旧的记忆默认权重越低。
例如:
- 7 天内:1.0
- 8 到 30 天:0.6
- 30 天以上:0.1
但这不是绝对规则。
某些永久偏好、禁忌类记忆,即使旧也应该保持高权重。
所以更准确的做法是:
时间衰减 × 类型修正
2.按类型隔离
不同类别记忆不要混用:
- 永久偏好:长期有效
- 临时偏好:高优先级,短时有效
- 历史行为:仅参考
- 经验知识:按主题召回
这能避免“曾经做过”被误判成“现在要做”。
3.相关性过滤
只注入与当前问题真正相关的记忆。
不相关的记忆即使重要,也不要进入本轮上下文。
4)冲突检测
对新旧记忆建立冲突关系。
例如:
- 旧:默认用英文输出
- 新:这次用中文输出
则系统需要知道这是“局部覆盖”,不是永久删除。
读取时根据 scope 选择更近、更局部的版本。
5.TTL 与失效机制
临时偏好、任务状态、会话缓存都应该设置失效时间。
例如:
- 本轮项目技术栈选择:会话结束失效
- 今天的餐饮偏好:24 小时失效
- 长期技能背景:长期有效
6.用户显式覆盖优先
当用户明确表达新意图时,应允许覆盖旧记忆。 比如用户说:
- “这次不要按我以前的风格来”
- “先忽略之前方案”
- “我现在改主意了”
系统应立刻把当前声明视为高优先级上下文,而不是仍被长期记忆牵着走。
九、一个可落地的数据结构示例
每条记忆可以设计成这样:
json
{
"memory_id": "mem_001",
"user_id": "u_123",
"content": "用户当前希望本次项目优先使用 React 技术栈",
"summary": "本次项目技术栈偏好:React",
"type": "temporary_preference",
"scope": "session",
"importance": 4,
"confidence": 0.95,
"created_at": "2026-03-24T10:00:00Z",
"last_access_at": "2026-03-24T10:10:00Z",
"expires_at": "2026-03-25T10:00:00Z",
"source": "dialogue_extraction",
"embedding": "...",
"status": "active"
}结构化表可以再单独存:
user_profileuser_preferencestask_historymemory_conflictssession_state
十、一个典型请求的运行流程
当用户发来一个新问题时,系统流程可以这样走:
- 解析当前问题,识别任务目标与约束
- 从短期记忆中取最近对话和当前会话状态
- 从长期记忆中分通道召回候选记忆
- 用相似度、时间、重要性、类型做混合重排
- 选 Top-K 记忆注入 prompt
- 模型生成回答
- 回答结束后抽取新的候选记忆
- 做分类、去重、冲突检测、持久化
- 更新短期窗口与会话摘要
这就是一个完整闭环。
十一、这个方案的核心设计原则
这个 Agent Memory 系统最重要的原则可以总结成四句:
短期记忆保证连续性,长期记忆保证可积累;
写入要有门槛,读取要有筛选;
旧记忆不能无条件保留,新记忆不能无条件覆盖;
真正有效的记忆系统,不是“记得多”,而是“该想起时想得对”。
十二、面试中可以直接落地成一句总结
如果你要把它压成比较像面试里的方案表达,可以这样说:
我会把 Agent Memory 设计成短期记忆、长期记忆和治理层三部分。短期记忆用滑动窗口加分层摘要,保证当前任务上下文连续;长期记忆用向量库存非结构化记忆、关系库存结构化档案,通过语义检索加时间衰减、类型优先级、重要性重排来做 Top-K 注入;同时在写入侧做候选提取、分类、去重和冲突检测,在读取侧做相关性过滤和 TTL 控制,避免旧信息污染当前推理。

评论区