1. 面试官到底在问什么1.1 从一道高频题说起“如果让你设计一个 Agent 的记忆系统你会怎么做”这道题在近一年的技术面试里出现频率高得离谱。我前后跟十几个候选人聊过这个话题发现一个很普遍的现象大部分人上来就开始讲向量数据库讲 embedding 怎么选讲 RAG 的召回策略讲得头头是道但面试官听完往往没什么反应甚至会在心里默默打个问号。问题出在哪出在他们把“记忆系统”等同于“检索系统”了。面试官问的是记忆不是搜索。这两个东西有交集但根本不是一回事。记忆系统要解决的核心问题是一个 Agent 在跟用户交互的过程中怎么把该记住的东西记住该忘的忘掉该想起来的时候能想起来并且这些操作要在一个合理的成本范围内完成。检索只是其中一环而且不是最难的那一环。我见过一个候选人技术功底其实不错向量检索、混合检索、重排序这些讲得都很清楚但当我追问“用户三天前说过他下个月要搬家今天问推荐餐厅你怎么让 Agent 用上这条信息”的时候他卡住了。他的方案里所有记忆都是平铺在向量库里的每次对话都做一次全量检索既没有时间维度的衰减也没有场景维度的过滤更没有主动触发的机制。这就是典型的“有检索没记忆”。所以这篇文章我想把记忆系统这件事从头到尾拆一遍。不是给你一个标准答案去背而是把设计思路、关键决策点、常见的坑都讲清楚让你在面试的时候能真正接住面试官的追问在实际做项目的时候也能落地。1.2 记忆系统的本质是什么先给一个我自己的定义Agent 的记忆系统是一套对历史交互信息进行选择性存储、结构化组织、按需调用、动态更新的机制。注意这里面的四个关键词。选择性存储意味着不是所有东西都值得记。用户说“今天天气不错”这种话记它干嘛但用户说“我对花生过敏”这条信息可能一辈子都有用。区分这两者是记忆系统的第一道关卡。结构化组织意味着记忆不能是一锅粥。人的记忆分情景记忆、语义记忆、程序记忆Agent 的记忆也需要类似的分类。不同类型的记忆存储方式、检索方式、生命周期都不一样。按需调用意味着不是每次对话都把全部记忆塞进上下文。上下文窗口是有限的token 是要花钱的把不相关的记忆塞进去不仅浪费还会干扰模型的判断。动态更新意味着记忆不是写完就不动了。用户的口味会变地址会变关系会变。一条三个月前的记忆今天可能已经过时了系统需要有能力识别和处理这种过时。把这四件事想清楚你的方案就有了骨架。接下来我们一层一层往里填。2. 记忆的分层设计2.1 为什么一定要分层我刚开始做 Agent 项目的时候也想过偷懒把所有历史对话存一个表每次检索 top-k 塞进 prompt 不就完了实测下来问题一大堆。首先是成本。一个用户跟 Agent 聊了三个月积累了几千条对话每次都要在这几千条里做向量检索延迟和费用都受不了。其次是精度。几千条记忆里真正跟当前问题相关的可能就两三条检索出来的大部分是噪音。最后是冲突。用户上个月说喜欢川菜这个月说在减肥两条记忆都检索出来了模型该听谁的分层的本质是用不同的存储和检索策略来匹配不同性质的信息。就像你家里的东西常用的放抽屉里随手拿重要的放保险柜不常用的塞储藏室。全堆在客厅看着热闹用起来抓瞎。我一般会把记忆分成四层工作记忆、情景记忆、语义记忆、程序记忆。这个分法借鉴了认知科学的框架但在工程上做了简化落地性比较强。2.2 四层记忆各自的职责工作记忆就是当前这轮对话的上下文。它的生命周期最短就是这一次会话。存储方式最简单直接放在 prompt 里就行。它的作用是维持对话的连贯性让 Agent 知道刚才聊了什么。这一层不需要什么复杂的设计但要注意上下文窗口的管理聊太长了要做摘要压缩。情景记忆是“什么时候发生了什么”。比如“上周三用户说他要去上海出差”“昨天用户问了一个关于 Python 装饰器的问题”。这类记忆的特点是带时间戳跟具体的事件绑定。它的价值在于让 Agent 能回忆起具体的交互片段适合用在“你上次说的那个事怎么样了”这种场景。语义记忆是“关于用户和世界的稳定事实”。比如“用户是后端工程师”“用户偏好简洁的回答风格”“用户所在团队用的是 Go 语言”。这类记忆不带具体时间是对信息的抽象和提炼。它的生命周期最长也最需要维护因为事实会变。程序记忆是“怎么做某件事”。比如“用户习惯用 pytest 写测试”“用户要求代码示例必须能直接运行”。这类记忆影响的是 Agent 的行为模式而不是它知道什么。很多方案会忽略这一层但我觉得它挺重要的因为它直接决定了 Agent 的输出风格是否符合用户预期。这四层不是孤立的它们之间有转化关系。情景记忆积累多了可以提炼成语义记忆。比如用户连续三次问 Go 相关的问题就可以提炼出“用户关注 Go 语言”这条语义记忆。语义记忆和程序记忆又会反过来影响工作记忆的构建决定当前这轮对话该带哪些背景信息。2.3 分层带来的工程收益分层之后最直接的好处是检索范围大幅缩小。当前问题需要哪层记忆就只查哪层不用全库扫描。比如用户问“帮我写个函数”这明显需要程序记忆和语义记忆情景记忆基本用不上检索范围一下就砍掉一大半。第二个好处是更新策略可以差异化。语义记忆需要定期校验和更新情景记忆基本是只读的程序记忆需要用户显式确认才能改。如果全混在一起你没法针对性地做维护。第三个好处是冲突处理有依据。当语义记忆和情景记忆冲突时比如语义记忆说“用户喜欢川菜”情景记忆说“用户昨天说在吃清淡的”你可以设计优先级规则近期的情景记忆优先于旧的语义记忆或者触发一次确认。有了分层冲突才有得谈。实操心得分层不要太细。我见过有人分了七八层结果每层的边界都模糊维护成本极高。四层是我试下来比较平衡的粒度再细就容易过度设计。3. 存储与检索的关键决策3.1 存什么写入策略的设计记忆系统的第一道难关不是检索是写入。什么该记什么不该记这个判断做不好后面全是垃圾进垃圾出。我的做法是双通道写入一条是规则通道一条是模型通道。规则通道处理那些明确的、低成本的判断。比如用户显式说“记住我喜欢喝美式”这种带“记住”指令的直接写入。再比如包含特定模式的句子像“我是XX”“我住在XX”“我习惯XX”这些可以先用正则或者关键词匹配捞出来作为候选。模型通道处理那些需要理解的判断。每轮对话结束后用一个轻量模型做一次抽取问它三个问题这轮对话里有没有关于用户的稳定事实有没有值得记住的事件有没有影响后续交互的偏好模型返回结构化的结果再决定写入哪一层。这里有个坑要注意不要让模型决定“记不记”让它决定“记什么”。我早期试过让模型判断一条信息值不值得记结果它太保守很多有用的信息被漏掉了。后来改成让模型先抽取候选再用规则做过滤召回率明显提升。过滤规则我一般设这么几条长度太短的丢弃纯情绪表达丢弃跟已有记忆高度重复的丢弃时效性太强的比如“今天下雨”丢弃。这几条下来能过滤掉大部分噪音。3.2 怎么存向量库不是唯一选择很多人一提记忆存储就想到向量数据库其实这是个思维定式。向量库擅长的是语义相似度检索但记忆系统需要的检索方式远不止这一种。我一般会用混合存储结构化字段用关系库或者文档库语义检索用向量库两者通过 ID 关联。具体来说每条记忆至少包含这些字段内容文本、类型情景/语义/程序、时间戳、来源哪轮对话、置信度、访问次数、最后访问时间。这些结构化字段放在普通数据库里方便做时间过滤、类型过滤、置信度排序。向量只存内容文本的 embedding用来做语义召回。检索的时候先用结构化条件缩小范围再在缩小后的集合里做向量检索。这样既保证了精度又控制了成本。举个例子。用户问“推荐个周末去的地方”。检索流程是这样的先按类型过滤只要语义记忆和情景记忆再按时间过滤情景记忆只看最近一个月的然后在这个子集里做向量检索找出跟“周末”“出行”“休闲”相关的记忆最后按置信度和访问时间排序取 top-3 塞进 prompt。这套流程比单纯的全库向量检索精准得多而且快。3.3 怎么取检索策略的层次检索策略我习惯分三层粗筛、精排、组装。粗筛就是上面说的用结构化条件把候选集从几千条降到几十条。这一步的关键是条件设计条件太松没效果太紧会漏掉有用信息。我的经验是类型过滤和时间过滤是必须的其他条件看场景。精排是在候选集里做语义相似度计算同时考虑其他信号。我一般会用一个加权公式最终得分 语义相似度 × 0.6 时间衰减 × 0.2 访问频率 × 0.1 置信度 × 0.1。权重可以根据业务调但这个结构基本通用。时间衰减这块值得说一下。我用的是指数衰减半衰期设成 30 天。也就是说一条 30 天前的记忆时间得分是 0.560 天前是 0.25。这样既不会让旧记忆完全失效又能让新记忆有优势。组装是把选出来的记忆格式化成 prompt 能用的形式。这里要注意两点一是要给每条记忆标注来源和时间让模型知道这条信息的可信度和时效性二是要控制总量我一般限制在 5 条以内太多会稀释注意力。注意检索出来的记忆一定要带时间戳。我踩过这个坑模型拿到一条没有时间的记忆会默认它是当前有效的结果用了一条三个月前的过时信息闹了笑话。4. 记忆的生命周期管理4.1 遗忘机制不是所有记忆都该留着人的记忆会遗忘Agent 的记忆也需要。无限增长的记忆库不仅成本高还会拖累检索精度。但遗忘不是简单删除要分情况处理。我一般把遗忘分成三种硬删除、软遗忘、降权。硬删除针对的是明确的垃圾信息比如用户说“刚才说错了当我没说”这种直接删。还有一些明显无效的记忆比如重复写入的、格式错误的也直接删。软遗忘针对的是过时但可能有历史价值的信息。比如用户换了工作旧的工作信息不该再被检索到但保留着可以做历史追溯。做法是给这条记忆打个“已失效”的标记检索时默认过滤掉但需要的时候还能查到。降权针对的是那些不确定是否还有效的信息。比如用户半年前说喜欢某个乐队你不知道他现在还喜不喜欢。这种不删除但降低它的时间得分和置信度让它在检索排序里往后排。这三种策略配合使用能让记忆库保持一个健康的规模。我实测下来一个活跃用户用一年记忆条数能控制在几百条以内检索延迟基本没变化。4.2 更新机制事实变了怎么办更新比遗忘更难因为你要先发现“变了”。我的做法是冲突检测 主动确认。每次写入新记忆之前先检索一下有没有相关的旧记忆。如果有且内容冲突就触发更新流程。冲突分两种。一种是明确冲突比如旧记忆说“用户在北京”新记忆说“用户在上海”。这种直接更新把旧记忆标记为失效写入新记忆。另一种是模糊冲突比如旧记忆说“用户喜欢川菜”新记忆说“用户最近在吃清淡的”。这种不一定是冲突可能是用户口味变了也可能是暂时的。这种我会保留两条但在检索时给新记忆更高的权重同时在一个合适的时机让 Agent 主动问一句“您最近口味是不是变了”。主动确认这个机制我觉得挺重要的。它让记忆系统有了“自我修正”的能力而不是被动地等用户来纠正。但要注意频率不能每轮对话都问那样很烦。我的经验是只在检测到明确冲突且影响较大的时候才触发。4.3 压缩机制长对话怎么处理长对话是记忆系统的一个大挑战。用户跟 Agent 聊了两个小时几百轮对话工作记忆根本放不下。这时候就需要压缩。压缩我一般分两级。一级压缩是摘要把最近 N 轮对话压缩成一段摘要保留关键信息丢弃细节。N 我一般设成 10也就是每 10 轮做一次摘要。摘要用模型生成prompt 里明确要求保留事实、决策、待办事项丢弃寒暄和重复内容。二级压缩是提炼把摘要进一步提炼成记忆条目。摘要还是偏叙述性的提炼则是抽取成结构化的记忆。比如摘要里说“用户提到他下周要去杭州出差顺便想见个朋友”提炼出来就是两条一条情景记忆“用户下周去杭州出差”一条语义记忆“用户在杭州有朋友”。这两级压缩配合能把长对话的信息密度提上去同时控制住 token 消耗。我实测下来两个小时的对话压缩完核心信息能控制在 500 token 以内基本不丢东西。5. 面试中的高频追问与应对5.1 追问一记忆冲突怎么处理这是面试官最爱追问的点之一。上面讲了更新机制但面试的时候要答得更结构化。我会这么答首先在写入阶段做冲突检测用语义相似度找出可能冲突的旧记忆。然后分三类处理明确冲突直接覆盖模糊冲突保留双份但调权重潜在冲突记录待确认。最后在检索阶段做二次过滤确保返回给模型的记忆不自相矛盾。如果面试官继续追问“怎么判断是明确冲突还是模糊冲突”我会说看两个记忆的语义距离和实体重叠度。如果实体相同但属性值不同比如都是“居住地”但值不一样那是明确冲突。如果实体不同或者属性不同比如一个是“饮食偏好”一个是“当前饮食状态”那是模糊冲突。这个判断可以用规则做也可以用模型做看成本要求。5.2 追问二怎么评估记忆系统的好坏这个问题很多人答不上来因为它不像检索有明确的指标。我的答法是分两个维度离线评估和在线评估。离线评估看三个指标召回率也就是该记住的信息有没有记住准确率也就是记住的信息是不是真的有用冲突率也就是记忆库里自相矛盾的比例。这三个指标可以用人工标注的测试集来算。在线评估看两个指标任务完成率也就是 Agent 在需要用到记忆的任务上表现如何用户纠正率也就是用户需要重复说明或者纠正 Agent 的比例。这两个指标从实际交互日志里统计。如果面试官追问“怎么构造测试集”我会说找一批真实对话人工标注哪些信息应该被记住然后跑一遍写入流程看召回和准确。冲突率则是定期抽样人工检查。5.3 追问三成本和延迟怎么控制这是工程落地必须回答的问题。我的答法是分层控制。写入侧规则通道零成本模型通道用轻量模型而且不是每轮都跑可以攒几轮跑一次。检索侧粗筛用结构化查询成本极低精排才用向量计算而且只在粗筛后的子集上做。组装侧严格控制记忆条数一般不超过 5 条。延迟方面写入是异步的不阻塞对话。检索是同步的但因为做了粗筛实际计算量很小我实测下来 P99 延迟在 50ms 以内。如果对延迟极度敏感还可以做缓存把常用的记忆组合缓存起来。实操心得不要追求实时写入。我早期做过实时写入每轮对话结束就同步写库结果对话延迟明显增加。后来改成异步批量写入攒 5 轮或者 30 秒写一次延迟问题就解决了而且对记忆的时效性几乎没有影响。5.4 追问四多用户场景怎么隔离这个问题看似简单但答不好会暴露工程经验的不足。我的答法是逻辑隔离 物理隔离结合。逻辑上每条记忆都带 user_id所有查询都强制带这个条件。物理上如果用户量大可以按 user_id 分库分表避免单库过大。向量库这边可以用命名空间或者分区来做隔离。还要考虑一个点跨用户的知识共享。有些记忆是通用的比如“Python 的 GIL 是什么”这种不需要每个用户存一份。我的做法是分两层用户私有记忆和全局共享记忆。检索的时候先查私有再查共享合并排序。这样既保证了隔离又避免了重复存储。6. 落地时的几个真实坑6.1 坑一embedding 模型换了记忆全废这个坑我踩得最惨。项目跑了一段时间觉得 embedding 模型效果不够好换了个新的。结果发现所有历史记忆的向量都是旧模型生成的跟新模型的向量不在一个空间里检索全乱套了。解决办法有两个。一是预留重嵌入通道换模型的时候批量重新生成所有记忆的向量。这需要你存原始文本而且要有批量处理的脚本。二是版本化 embedding每条记忆记录用的是哪个模型检索时按模型分组。但这样维护成本高不推荐。我的建议是项目初期就选一个靠谱的 embedding 模型尽量别换。如果非要换一定要提前设计好重嵌入流程。6.2 坑二记忆写入太频繁成本失控早期我没做过滤每轮对话都让模型抽取记忆结果一个用户聊一天写入几百条记忆token 成本高得吓人而且大部分是噪音。后来加了过滤规则又改成批量写入成本降了大概 80%。具体做法是规则通道先过滤掉明显不值得记的模型通道攒 5 轮跑一次抽取出来的候选再用规则过滤一遍。这样下来一个用户一天大概写入 10 到 20 条有效记忆成本可控。6.3 坑三检索出来的记忆模型不用这个坑比较隐蔽。你检索了一堆记忆塞进 prompt但模型就是不用还是按自己的理解回答。原因可能是记忆的格式不对或者位置不对。我的解决办法是在 prompt 里明确指示模型使用记忆。比如加一句“以下是与当前问题相关的历史信息请在回答时参考”并且给每条记忆编号方便模型引用。另外记忆要放在 prompt 的靠前位置不要放在最后因为模型对开头和结尾的信息更敏感。还有一个技巧是在记忆前面加一个简短的说明比如“用户偏好用户喜欢简洁的回答”。这样模型一眼就知道这条记忆是干嘛的用起来的概率高很多。6.4 坑四时间戳处理不当时区混乱这个坑比较低级但很常见。用户在不同时区记忆的时间戳如果统一用 UTC检索的时候做时间过滤就会出错。比如用户说“明天提醒我”这个“明天”是用户所在时区的明天不是 UTC 的明天。我的做法是存储用 UTC展示和计算用用户时区。每条记忆记录用户当时的时区检索和展示的时候转换过去。这样既保证了存储的一致性又保证了用户体验。7. 一套可复用的方案骨架7.1 整体架构把上面的东西串起来一套完整的记忆系统大概长这样写入侧对话结束后触发抽取流程。规则通道先跑捞出显式记忆和模式匹配的候选。模型通道攒批跑抽取情景、语义、程序三类记忆。两路候选合并后做去重和冲突检测然后写入存储。存储侧结构化字段进关系库内容向量进向量库通过 ID 关联。每条记忆带类型、时间戳、置信度、访问统计等元数据。检索侧对话开始时触发。先按类型和时间做粗筛再在子集上做向量精排最后按加权得分排序取 top-k 组装进 prompt。维护侧定期跑冲突检测和压缩任务。冲突检测发现矛盾记忆就触发更新压缩任务把长摘要提炼成记忆条目。遗忘策略按硬删除、软遗忘、降权三种方式执行。7.2 关键参数参考下面这张表是我在实际项目里用的参数可以直接参考但要根据自己的场景调。参数建议值说明工作记忆轮数10超过就做摘要压缩情景记忆保留期90 天超过后降权但不删除语义记忆校验周期30 天定期检查是否过时时间衰减半衰期30 天指数衰减的参数检索返回条数3-5 条太多会稀释注意力语义相似度阈值0.7低于这个值不召回批量写入间隔5 轮或 30 秒平衡时效和成本冲突检测相似度阈值0.85高于这个值才检查冲突这些参数不是拍脑袋定的是我在不同项目里调出来的。比如时间衰减半衰期我试过 7 天、14 天、30 天、90 天最后发现 30 天对大多数场景比较合适。太短了旧记忆失效太快太长了新记忆优势不明显。7.3 从零搭建的步骤如果你现在要动手做我建议按这个顺序来第一步先把工作记忆管好。这是最基础的也是立刻能看到效果的。做好上下文窗口管理长对话做摘要压缩。第二步搭存储层。选一个关系库存结构化字段选一个向量库存 embedding。先把写入和读取的通道打通不用管策略。第三步做写入策略。先上规则通道把显式记忆和模式匹配跑通。再上模型通道做抽取和过滤。第四步做检索策略。先做粗筛再做精排最后做组装。每一步都可以单独测试和调优。第五步做生命周期管理。先做遗忘再做更新最后做压缩。这三块可以逐步加不用一次到位。第六步做评估和监控。离线评估看召回、准确、冲突率在线评估看任务完成率和用户纠正率。有了数据才能持续优化。这个顺序的好处是每一步都有可交付的成果不会做了一半发现方向错了。我见过有人一上来就搞全套结果每个模块都半成品调都没法调。8. 一些零散但重要的经验8.1 记忆的可解释性记忆系统最好能做到可解释。也就是说当 Agent 用了一条记忆来回答问题时你能追溯它是从哪条记忆来的这条记忆是什么时候写入的置信度多少。这个能力在调试的时候特别有用。用户反馈“Agent 怎么知道这个的”你能立刻查出来。面试的时候如果提到这一点也是加分项因为它说明你考虑过系统的可维护性。实现方式很简单就是在组装 prompt 的时候给每条记忆带上 IDAgent 的输出里如果引用了记忆就带上这个 ID。日志里记录 ID 到记忆的映射查询的时候一查就知道。8.2 冷启动问题新用户没有历史记忆记忆系统等于空转。这时候怎么办我的做法是主动引导。在对话初期Agent 可以主动问一些能产生记忆的问题比如“您平时用什么编程语言”“您希望我回答得详细还是简洁”。这些问题不是为了寒暄是为了快速建立初始的语义记忆和程序记忆。另外可以预设一些通用的程序记忆比如“回答要准确”“代码要能运行”这些对所有用户都适用不用等用户说。8.3 隐私和边界记忆系统会存储大量用户信息隐私是个绕不开的问题。我的原则是只存必要的能匿名就匿名给用户控制权。只存必要的就是前面说的选择性存储不相关的信息不存。能匿名就匿名比如用户说“我在某公司工作”存的时候可以只存“用户在某行业工作”不存具体公司名。给用户控制权就是提供查看、修改、删除记忆的接口让用户能管理自己的记忆。这些不光是合规要求也是产品体验的一部分。用户知道自己的信息被怎么用了才会更信任这个 Agent。8.4 面试时的表达技巧最后说点面试技巧。这道题的回答我建议用总-分-总的结构。开头先给一个定义把记忆系统的四个核心能力点出来让面试官知道你抓到了本质。中间分模块展开每讲一个模块先说设计思路再说为什么这么设计最后说实际效果。结尾可以提一下评估和迭代显示你有工程闭环的思维。回答的时候多用具体的例子少讲抽象的概念。比如讲分层就举“用户问推荐餐厅”的例子把四层记忆怎么协作讲清楚。讲冲突处理就举“用户换工作”的例子。具体的例子能让面试官觉得你真的做过而不是背的。还有一点遇到不会的问题不要硬答。可以说“这块我实际经验不多但我的思路是……”然后给出一个合理的推测。面试官更看重你的思考方式而不是你什么都知道。记忆系统这个方向说实话没有标准答案不同的业务场景有不同的最优解。但只要你抓住了“选择性存储、结构化组织、按需调用、动态更新”这四个核心再结合具体的工程实践去展开基本就能答到点子上。面试官想听的从来不是一个完美的方案而是你对这个问题的理解深度和落地能力。
阅读完成 · 觉得有帮助?