2026/10/6 14:53:02

AI代理记忆管理实战:从失忆到长期记忆的完整设计

AI代理记忆管理实战:从失忆到长期记忆的完整设计 写这个系列到第4篇我心里一直憋着一股劲。前几篇我们讲了怎么搭agent的骨架、怎么接工具、怎么设计工作流但很多同学照着做完之后跑来问我同一个问题为什么我的agent聊着聊着就“失忆”了答案就藏在记忆管理里。这一篇我准备拿自己在维护的一个工作代理workbuddy来当例子完整拆解它的记忆系统是怎么设计的。workbuddy的主要工作是帮我跟进项目进度、整理会议纪要、提醒重要事项并跨会话记住我的偏好和决策。听起来不复杂但真正做下去你会发现记忆管理是整个ai-agent项目里最容易被低估的环节。这篇教程我会从记忆的分类、存储结构、读写流程到踩坑排查一步步把workbuddy的实现细节讲清楚。相比前面几篇这篇会更偏工程落地也会多出不少数据结构和代码片段但我会尽量把每个设计决策背后的“为什么”一起讲明白。你不需要完全照抄我的方案重点是把思路带走。1. AI代理为什么非要“记忆管理”不可1.1 workbuddy早期是怎么被记忆坑的先说个真实经历。workbuddy第一版的时候我根本没有正经记忆系统所有对话都靠模型的上下文窗口硬扛。那会儿它的表现用一个词形容就是“人格分裂”。举个例子我周一告诉它“本周三之前要交付客户报告报告格式用PDF封面要加公司logo”周二我让它跟进一件事它会煞有介事地回我“好的我会跟进”但它其实已经把报告这个任务忘得干干净净。字面上看它还在正常对话实际上大脑一片空白只是礼貌地在应和。更离谱的是等上下文窗口被塞满之后它会开始胡编乱造。我把这个现象叫做“记忆幻觉”它不是真的记得你而是基于当前对话片段强行拼一个看起来合理的回答。早期workbuddy甚至把上一个项目的截止时间安到另一个项目头上差点让我错过客户对接。后来我总结出来了没有记忆管理的代理本质上只是一个“无状态的函数调用器”。它每次收到输入都在独立地生成输出前后之间没有任何依赖。模型本身的注意力机制只对当前窗口内的文本有效窗口之外的一切都是虚空。想要让代理具备连续工作的能力必须有专门的设计来负责记忆的写入、存储与读取。1.2 记忆管理到底管的是什么一句话说记忆管理就是解决“代理该记什么、记在哪里、怎么取出来、什么时候该忘”这四件事。听起来像在说一个记事本但人脑的记事实在比记事本复杂太多。人和人对话的时候会同时依赖工作记忆刚才说什么、语义记忆世界常识、情景记忆我们上次聊过什么和程序记忆怎么做某件事。AI代理面对的情况也是一样的只是我们得用工程手段把这几种记忆具象化。具体到workbuddy我把它需要的记忆分成了三类短期记忆当前这轮会话里的上下文比如刚才讨论到哪了、用户最新的指令是什么。长期记忆跨会话存在的稳定信息比如用户偏好、历史项目、关键决策。情景记忆已经完成的事件与过程记录比如某次会议的结果、某次任务的完整闭环。有经验的同学可能已经看出来了这三类记忆在技术上对应的分别是上下文窗口管理、向量数据库/结构化存储、以及事件日志。但先别急着写代码我说一个更扎心的观察大部分人做agent记忆都死在第一个问题上——不知道什么时候该记住什么。你要先搞清楚自己的agent到底需要什么记忆然后再想技术选型。2. 先把记忆拆成三个抽屉2.1 短期记忆上下文窗口与装箱策略短期记忆是所有记忆里最直观的因为模型每处理一次请求都会读取当前上下文窗口里的内容。你的用户说了什么、系统给它的提示词、它上一步生成的输出全部塞在窗口里。问题在于窗口是有限的。以目前主流的模型为例长上下文可能有一两百万字符听上去很多但真的跑业务逻辑的时候你会非常快地消耗它。一个200页的项目文档可能就有几十万字符再加上系统提示词、历史对话、工具返回结果窗口早就爆了。workbuddy的短期记忆策略是“分层装箱”。我维护了一个消息列表每一轮对话、每一次工具调用返回值都会追加进去。在发起下一次模型请求前先检查这个列表的长度。如果长度超过阈值我会做三件事按顺序执行优先压缩工具返回的冗余内容比如只保留结构化结果丢弃原始响应的日志。将更早的普通对话记录做摘要合并保留核心意图与结论。如果摘要仍然太长就裁剪最早的对话只保留最近几轮。这段逻辑看起来简单但实际写起来很容易踩坑。很多人图省事直接对历史消息做暴力截断结果模型丢失了关键信息。workbuddy的原则是能摘要就不删能删日志就不删对话能删旧对话就不删新对话。有朋友问过我模型不是支持超长上下文吗为什么还要做这么多复杂操作我的回答是支持归支持成本可不是开玩笑的。上下文越长单次请求的token开销越高响应延迟也越明显。尤其当你是在自己的服务器上跑推理长上下文直接影响吞吐量。给workbuddy做装箱策略后单次请求成本大概降了将近一半响应也快了不少。2.2 长期记忆从“记住事实”到“用得上事实”长期记忆负责跨会话存储稳定信息。workbuddy里长期记忆分两层第一层是实体事实比如“用户是产品经理”“用户偏好PDF格式的报告”第二层是行为偏好比如“用户在每周五下午需要总结周报”“用户不喜欢在回复里堆细节”。实体事实我用结构化的方式存相当于给workbuddy建了一套用户档案。行为偏好则偏向量化因为偏好很少是精确的、静态的更多时候是一种风格得靠相似度匹配才能找回来。我举个例子。用户某天跟workbuddy说“以后回复我的时候先给结论再给理由”这句话如果按关键词存成结构化数据会变成preference: reply_style: conclusion_first。下次要检索“用户喜欢什么回复风格”直接查这个字段就行。但如果用户说的是比较模糊的话比如“我感觉你每次解释太啰嗦了能不能干脆一点”这句话就不太适合硬编码。我会让workbuddy把这句转成向量存到向量库里同时跟一个预设的偏好标签做关联。下次用户提到类似语境时向量检索能把它召回来跟预设标签一起参与判断。这里的选型逻辑也很重要。workbuddy用的是混合方案精确事实型记忆全走SQLite/JSON文件这类结构化存储模糊风格型记忆走向量库。只做向量库、不支持结构化检索会在遇到“用户到底叫什么名字”这种精确问题时抓瞎只做结构化、完全忽略向量又没法捕捉语义相似度。两者结合才是比较稳的路子。2.3 情景记忆过程和结果的档案情景记忆在早期workbuddy里是被我忽视的后来发现它恰恰是让代理看起来“真的有经验”的关键。举个场景这周你让workbuddy帮你整理一个客户提案你提供了一批资料它产出了一版大纲。下周你又接手了一个类似的新客户项目你想让workbuddy参考上次的经验。如果没有情景记忆workbuddy只知道“用户给了一些资料我给了大纲”这个孤立事件具体资料结构、大纲套路、你事后怎么改的全部丢失。有了情景记忆它会记录“上次整理客户提案时使用的是三段式结构用户改动最多的是第二段最终版本用户自己加了预算表格”。这种记录价值极大等于给代理留了“过往项目的复盘档案”。下次处理类似任务时它能主动把上次的完整流程调出来而不是从零开始。workbuddy的情景记忆实现上没有搞很复杂的东西就是一张事件表每条记录包含时间戳、项目ID、任务名称、输入摘要、输出摘要、用户后续的修改内容、完成状态。这样既方便按项目维度检索也方便做时间线回溯。2.4 三个抽屉为什么要分开管可能有人觉得既然都是记录统一定义一个“历史记录表”不就行了吗我当初也这么想后来被打脸了。短期记忆的特点是高频、易变、与当前上下文强相关。长期记忆则需要稳定、去重、经得起时间检验。情景记忆介于两者之间它是事件级别的既不像短期记忆那样很快被压缩又不像长期记忆那样需要提炼出稳定的实体关系。三者的读写逻辑、时效性、存储模型都有差异硬塞在一个桶里很快就会互相干扰。最典型的例子是短期对话里出现一句“我这次可不想再用PDF了”如果被误当成长期记忆写进去下次workbuddy就会一直认为你喜欢非PDF格式而这句话其实只是针对当前项目的一次性吐槽。workbuddy最后定下的规则是短期记忆只存在于会话内会话结束时要么被提炼成长期记忆要么被丢弃情景记忆记录事件并保留原始细节长期记忆只存“经过确认、有稳定价值”的信息写入流程需要额外判断。3. workbuddy记忆管理实操记录3.1 记忆存储层怎么搭我先交代一下workbuddy的技术背景整体是Python写的长记忆部分用了一个轻量的向量库Chroma结构化部分直接用的SQLite。为了演示下面这段是一个简化版的数据模型定义。# memory_models.py from dataclasses import dataclass from datetime import datetime from typing import Optional dataclass class LongTermMemory: id: str user_id: str memory_type: str # fact / preference key: str # 记忆的名称如 report_format value: str # 记忆的值如 pdf source: str # 来源用于追溯 confidence: float # 置信度0 ~ 1 created_at: datetime updated_at: Optional[datetime] None last_accessed_at: Optional[datetime] None dataclass class EpisodicMemory: id: str user_id: str project_id: str task_name: str input_summary: str output_summary: str user_feedback: str # 用户后续修改内容 status: str # completed / cancelled / draft created_at: datetime这个数据模型里有几个字段值得展开说。source字段特别重要。workbuddy写入长期记忆时必须记下这段记忆是从哪来的比如“用户在对话里明确说过”“从某个历史项目里提炼出来的”。有了来源遇到记忆冲突时就能回溯哪条记忆更可信、要不要更新都更好判断。confidence也很好用。比如workbuddy从一篇历史项目中推测“用户喜欢在汇报前先看提纲”这个推测的置信度只有0.6但用户某天自己明说“以后凡是汇报都先给提纲”这个置信度就能提到0.95。检索记忆时置信度高的条目可以给更大的权重不会出现一条猜测和一条事实同等地位的情况。存储结构定了之后我对SQLite里的长期记忆表加了唯一约束以(user_id, key)做唯一键。这样每类记忆在用户维度下最多一条更新时直接覆盖而不是不断追加否则跑上三个月同一项偏好会出现几十条版本根本没法用。3.2 写入记忆的流程设计写入是最容易“写脏”的环节。workbuddy最初的版本是一股脑全写只要对话里出现有点信息量的话就往记忆里塞。结果记忆库里塞满了噪音检索出来的往往不是最有价值的信息。后来我把写入流程改成了三层筛子。第一层是判断“信息是否值得记录”第二层是判断“该记录到哪个记忆类型”第三层是“是否与已有记忆冲突需要合并或更新”。先说第一层。我给workbuddy定义了一个记忆触发条件大致如下# memory_writer.py def should_save_as_long_term(user_input: str, context: dict) - bool: # 规则1包含明确偏好关键词 preference_keywords [我喜欢, 我习惯, 不喜欢, 以后都, 记住, 不要太] for kw in preference_keywords: if kw in user_input: return True # 规则2包含明确个人事实 fact_keywords [我是, 我叫, 我在, 我的职位, 我负责] for kw in fact_keywords: if kw in user_input: return True # 规则3包含明确的项目决策 decision_keywords [决定了, 最终选, 不用再讨论, 就这样定] for kw in decision_keywords: if kw in user_input: return True # 规则4置信度阈值低于此值不落库 if context.get(confidence, 0.5) 0.7: return False return False这个函数在初版看起来很简陋但实际用下来几个硬关键词已经覆盖了80%的核心场景。剩下的长尾场景workbuddy会结合一个很小的分类模型来判断要不要写入。判断出该写之后进入第二层分类。如果用户说的是“以后汇报先给结论”这就是偏好型记忆。如果是“我叫XX”就是事实型记忆。分类决定了它走结构化表还是向量库。第三层冲突检测很关键。写入前workbuddy会把同key已有记忆调出来看新旧值是不是一致。如果不一致不会直接覆盖而是标记为“待确认”。有的系统直接让新值覆盖旧值但用户上一句说“不要PDF”下一句可能只是在吐槽某个特定情况。workbuddy的做法是降置信度、保留两个值、在下一次对话时询问用户。3.3 检索记忆的流程设计记忆写得好不好最终要看取的效率。workbuddy检索长期记忆的方式我总结为“关键槽位预填充 按需检索”两步走。关键槽位预填充的意思是每次开始新会话时workbuddy会把用户的高置信度长期记忆比如名字、常用文件格式、沟通偏好统统加载到系统提示词里。这个成本很低因为这类记忆数量不多但收益很高模型在生成任意回答前就已经知道这些上下文。按需检索则是在任务执行过程中根据当前输入动态查询。这里通常走向量检索把当前用户问题作为query到Chroma里找出最相关的记忆条目。我给workbuddy设定的是TopK3也就是最多取3条太多会把模型注意力带偏。检索时我还会对结果做一次排序加权。大致逻辑是# memory_retriever.py def rank_memories(memories: list, query_embedding: list) - list: weighted [] for m in memories: score m.get(similarity, 0) score m.get(confidence, 0.5) * 3 # 置信度权重 if m.get(recently_accessed, False): score 1.0 # 近期访问奖励 weighted.append((m, score)) weighted.sort(keylambda x: x[1], reverseTrue) return [m for m, score in weighted[:3]]这里加近期访问奖励是为了照顾那些“刚提过的事”的上下文延续。比如用户昨天刚提过的一个项目名称今天再次提到时应该优先被感知到。情景记忆的检索则简单一些。workbuddy采用“项目维度拉取”策略也就是进入某个项目时把该项目最近N条事件记录整体加载到上下文里。这其实比逐条检索更高效因为一个项目内的事件天然是连续的、顺序相关的。逐条向量检索反而会把时间线打乱导致agent分不清先后顺序。3.4 记忆更新和遗忘机制成年人的记忆有个特点该忘的得忘。AI代理也一样而且遗忘这件事做得越不好后面踩的坑越多。workbuddy的遗忘机制分成三层第一层是“主动遗忘”。用户明确要求“忘掉刚才那句话”“不要再提这个项目”这些指令会触发硬删除。删除时不仅删内容还会连带删除相关的关联标记避免半条残留在系统里。这个操作不走确认属于最高优先级。第二层是“有效期过期”。我给每类记忆设置了不同的TTL有效期。比如临时的项目偏好可能只保留30天用户画像类的长期事实保留180天。到期后不会直接物理删除而是标记为“待复核”如果近期用户有新的行为覆盖了旧记忆就彻底归档。第三层是“低置信度自动降权”。记忆库里那些置信度低于阈值、且长期没有被命中的数据workbuddy会每月做一次清理把它们放入冷存储或者直接删除。这一步能有效防止记忆库无限膨胀。很多同学会忽略记忆更新的时序问题我说一个真实发生的细节。某次workbuddy判断用户偏好发生了变化新旧记忆产生了冲突它做了标记。结果用户连续几周没有再提及这个偏好系统就把旧记忆降权了。这里有个原则冲突未确认时默认以最新数据为准但旧数据不立即删留一个灰度期。灰度期过了、没有反向信号才真正拿下。4. 记忆系统踩坑实录与排查清单4.1 最常见的4个记忆问题我在维护workbuddy的过程中遇到过非常多记忆相关的bug。这里列出四个最典型、也最容易被复现的问题它们几乎覆盖了90%的agent记忆异常。第一个是“记忆串台”。不同项目之间的记忆互相污染workbuddy把项目A的客户偏好带到了项目B的对话里。根因在于记忆表没有加命名空间区分所有项目共用一套key。解决办法是给每条记忆加project_id字段检索时强制过滤。第二个是“记忆永远不更新”。用户明明在最新对话里修改了自己的偏好workbuddy依然拿着一个月前的记忆来回答问题。根因是写入策略过于保守只走“待确认”流程而用户根本没有进行二次确认。我后来加了规则如果用户在同一话题里重复表达两次相同的新偏好自动跳过确认直接更新。第三个是“长上下文被历史记忆塞满”。系统提示词里加载了太多记忆导致真正有用的新对话被挤占了空间。根因是预填充的记忆槽位没有限制数量。我把预填充的记忆数量限制在10条以内且只能加载高置信度、近期有访问的记录。第四个是“记忆写入被琐碎噪声淹没”。agent很容易把“用户今天心情不错”这种场景性信息当成长期记忆存下来。解决办法是我给写入函数加了一道“重要性评分”低于阈值就丢弃或者只保留在情景记忆里不升级到长期记忆。4.2 排查记忆问题的通用步骤如果你在跑自己的ai-agent时遇到记忆相关的问题我建议按下面这个顺序排查。先看输入侧。检查用户输入的文本有没有真的触发记忆写入条件。很多时候不是记忆系统坏了而是压根没进入写入流程。你可以在写入函数入口加一条debug日志打印“是否满足触发条件”和“当前置信度”。再看存储侧。查记忆库里到底存了什么。直接打开SQLite表或者向量库手工检索一下相关条目看看里面的内容和预期是否一致。我排查过很多诡异问题最后发现都是脏数据写进去了比如value字段存了一整段对话原文而不是提炼后的结论。再看检索侧。查一下检索时的query向量是不是构建正确。曾经有个bug因为embedding模型切换过新老向量维度不一致查询出来的相似度全是乱的。这种属于基础设施问题排查时别忘了对比向量维度。最后看模型侧。有些问题不是记忆系统的锅而是模型本身没有正确利用记忆。比如记忆已经加载进系统提示词但由于提示词太长模型“忽略”了其中一部分信息。这种情况可以做一个测试把记忆内容放在系统提示词的最前面看看回答质量是否明显提升。4.3 我的调试口诀这几条经验我每次调试记忆系统时都会默念一遍也分享给你们。先说“可追溯才有可修复”。我建议在每条记忆上都留下来源和更新时间。这样线上出了问题能快速定位是哪一次对话、哪一条信号把这个记忆写进去的。再说“先隔离后联调”。我给workbuddy加了一根环境变量开关可以一键停用长期记忆检索。遇到“有记忆比没记忆表现更差”的情况时先用这个开关把记忆系统摘掉确认问题出在记忆侧还是模型侧再针对性处理。最后说“冷启动不可怕冷启动乱写才可怕”。一个新用户第一次使用workbuddy时记忆库是空的此时agent应该主动少依赖记忆多依赖通用能力。很多新手开发者在冷启动阶段就给agent预填了一堆“用户画像”测试数据结果真实用户一进来记忆全都对不上反而干扰了正常对话。我的建议是冷启动时记忆系统保持沉默直到累积了足够真实交互再启用。个人实操体会这套记忆管理系统在workbuddy上跑了几个月最大的体感变化是用它处理跨周、跨月的项目不用再重复交代背景了。它知道我叫什么、平时习惯怎么看报告、哪个项目的优先级最高甚至能在新任务开始前主动提醒“上次这个客户对格式有额外要求”。但我同样想提醒正在做ai-agent的同学记忆管理不是一次性的功能开发而是一个需要持续迭代的系统工程。我前前后后改了六七版才勉强达到“可用”的状态。你现在看到的这套设计依然有坑比如高并发下的记忆写入一致性、多设备间的记忆同步这些都还没有完全解决。不过话说回来看到自己的agent真的“记住了事儿”那种感觉还是挺上头的。希望这篇教程能帮你在做记忆管理的路上少踩几个坑。如果你有别的思路或更好的方案欢迎在评论区交流。