首页 / 资讯中心 / 文章详情

AI Agent记忆系统实战:从上下文窗口到分层记忆架构

AI Agent记忆系统实战:从上下文窗口到分层记忆架构 ★ FEATURED ARTICLE
1. “更大的窗口”这个思路一开始就搞反了问题最近半年几乎每隔几天就有人拿着某个模型的新版本截图来找我聊“你看上下文窗口已经 100 万 token 了以后 Agent 是不是就不用做记忆系统了所有东西都塞进上下文里它不就什么都能记住了吗”我每次的回答都一样上下文窗口解决的是“能装多少”的问题而 Agent 的记忆问题本质上是“如何组织、检索、遗忘”的问题。这完全是两码事。拿人类来类比就很容易理解。你家里的书房可以很大大到能放下几万本书这叫“存储容量”。但如果你每次写文章时需要把书房里所有书都摊在书桌上才能找到一句引用的话那你很快就会被淹没。书桌才是真正的“工作台”书房里的书架是“仓库”。上下文窗口就是那张书桌不管它怎么扩大它都改变不了“你需要在几万本书里找到关键那一页”这件事本身。很多人在搭建 AI Agent 时踩的最大的坑就是把“记忆”等同于“上下文”。项目一遇到 Agent 失忆、答非所问、逻辑混乱第一反应是换一个上下文更大的模型或者给 API 传更多历史消息。结果呢token 账单翻了几倍效果反而可能更差。原因很直接大窗口并不等于大记忆反而会让 Agent 在大海里捞针时更容易捞到水草。这篇文章我会从根上讲明白记忆系统该怎么做为什么上下文窗口靠不住、Agent 需要哪几种记忆、一个可落地的分层架构长什么样、检索召回怎么做以及实际工程里大家会踩的坑。内容偏实战适合正在搭 Agent 的开发者、架构师也适合刚入行但想搞明白“为什么我的 Agent 总是记不住事”的朋友。先说几个我实测下来最有体感的数字。把 10 万 token 的历史记录直接塞给模型让它从里面找一条 3 天前用户提到的偏好模型的定位准确率会随着对话轮数增加急剧下降而且当你的 token 总量逼近窗口上限时几乎所有模型都会出现“注意力稀释”——它不是记不住是记住了太多导致真正重要的那条信息权重被摊薄了。上下文窗口越接近满这种退化越明显而不是等到真的满了才出问题。2. 为什么上下文窗口扩容救不了你的 Agent成本、噪音与注意力稀释三重绞杀2.1 先算一笔账注意力机制不是靠“地方大”就能撑住的Transformer 的核心是自注意力机制它的计算复杂度跟序列长度的关系是近似二次方的。也就是说窗口翻一倍计算量和内存占用的增长不是一倍而是接近四倍。这还只是理想状态下的理论值加上 KV Cache 之后长序列的推理延迟和显存占用会更夸张。我举一个实际数据。某个模型在 8K 上下文下首 token 延迟大约是 200 毫秒左右换到 200K 上下文即使中间很多内容是填充的首 token 延迟也可能放大到 2 到 3 秒。对 Agent 来说这意味着什么意味着它每一次工具调用、每一次思考循环都要背着几十万字的历史包袱去算。一本 20 万字的小说你不可能每读一句都要从头把之前所有字扫一遍——但 Transformer 在长上下文上恰恰就是这么干的除非你引入额外的稀疏机制。所以很多人说的“上下文不够用”其实有两层含义。一层是真的装不下了另一层是装得下但算不动。更大窗口只缓解第一层第二层反而更严重。你想解决记忆问题结果先被延迟和成本打垮了。2.2 长上下文会引入“信息稀释”而不是“信息增强”我记得有一个很经典的现象当上下文里塞入了大量无关或者弱相关的历史信息后模型的输出质量反而比只给它少量但精准的信息时更差。这不是玄学深度学习里有一个概念叫“注意力稀释”信息多了以后模型分配给每条信息的权重会变得更平均真正关键的那条信息没办法拿到足够高的注意力。做过 RAG检索增强生成的朋友应该都有体会。给模型检索回来 10 段文档如果里面 9 段都是噪音模型经常会被带偏甚至从噪音里“脑补”出答案。你可能会说那我优化 prompt告诉它“只依赖第一条消息其余仅作参考”行不行实测效果有限因为模型不是按照人类指令去“选择性忽略”的它是按照注意力权重去做统计推断的。当噪音占绝对多数的时候一条 prompt 拉不回来。这就揭示了一个残酷的事实把全部历史塞进上下文本质上是在用容量换精度而且换来的往往是负优化。上下文窗口里真正能被模型有效利用的信息比例远没有你想象得那么高。2.3 大窗口能掩盖问题但不能消灭问题现在很多平台把百万 token 窗口当作卖点我也理解开发者的心情谁不想省事呢但我要提醒一点窗口变大之后上面说的成本升高和信息稀释问题一个都没消失只是被延后了。你原来 8K 窗口跑 5 轮就“失忆”现在 100 万窗口可以跑 50 轮才失忆但失忆的本质原因并没有变——它依然是一个没有结构的“大口袋”。更重要的是窗口是无状态记忆它不知道什么重要、什么不重要也不做任何抽象和归纳。用户 1 小时前让你记住“他不吃香菜”这个信息藏在 500 条对话记录中间和一个包含了 500 条无关闲聊的记录对于模型来说没有区别。窗口只会按顺序排列不会按重要性筛选。所以核心结论就一句话上下文窗口是工作记忆的物理载体它替代不了记忆系统。要做真正能长期工作的 Agent必须引入结构化的记忆架构而不是在窗口大小上内卷。3. Agent 需要的记忆远不止“聊天记录”把记忆分型架构才有的谈3.1 模拟人类的记忆分类工作记忆、情景记忆、语义记忆、程序记忆我第一次给 Agent 设计记忆系统的时候也犯过“把所有历史丢进一个数组”的错。直到后来我意识到人脑的记忆并不是一个单一的容器而是分成了几种功能完全不同的记忆而 Agent 需要的记忆结构也恰恰是这么分的。我总结了四种最核心的记忆类型工作记忆相当于当前任务进行中的临时状态比如正在处理的用户请求、目前已经拿到中间结果。对应到技术上就是当前上下文窗口通常不需要持久化任务结束即可释放。情景记忆记录“发生过什么事”保留了时间、地点、人物和事件顺序。比如“昨天下午用户让我帮他写了一封邮件邮件主题是关于季度汇报的”。这类记忆的核心特征是时间线适合用事件日志或时序数据库来存。语义记忆从具体事件里抽象出来的通用知识或用户画像。比如“用户是产品经理”“用户偏好简洁的文风”“用户的公司是做跨境电商的”。放一个中性例子从多次对话中归纳出“用户喜欢用 Markdown 回复”。这类记忆的特点是剥离了具体时间表达的是稳定事实。程序记忆关于“怎么做一件事”的经验和技能比如“给用户写邮件时先列提纲再写正文”“遇到用户情绪化表达时先共情再解决问题”。这类记忆在 Agent 里对应的是技能库、流程模版、经验规则。如果你把记忆系统设计成只有一层“历史消息”那你实际上只实现了工作记忆和部分情景记忆语义记忆和程序记忆完全缺失。后果就是Agent 能回忆起用户上周说过什么前提是你查得到但无法形成“这个用户长期偏好什么”的稳定认知也无法沉淀“这类任务应该怎么处理更好”的方法论。3.2 不同记忆类型对存储和检索的要求完全不同分了型之后你会发现每种记忆背后的技术选型差异很大。我整理了一个对照表这个表在我做架构设计时一直挂在眼前记忆类型典型内容推荐存储检索方式一致性要求工作记忆当前任务上下文内存/上下文窗口直接传递实时准确情景记忆用户行为事件、对话历史时序数据库/事件日志按时间线查询、向量检索高写入吞吐语义记忆用户画像、实体关系、偏好知识图谱/向量库图查询、向量召回、规则抽取高准确率程序记忆技能模板、经验规则配置库/流程引擎按场景匹配强一致、可审计这张表的价值在于它逼着你把“记忆系统”这个问题拆成四个子问题来设计而不是笼统地说“我要做个记忆库”。比如你要处理用户偏好就不可能靠单纯的 SQLite 全表扫描解决因为偏好是会变化的而且可能存在冲突用户上个月喜欢长文这个月喜欢短文。这时候你需要一个带时间衰减或版本控制的语义记忆层而不是一个简单的 key-value。3.3 你真正缺的可能是“遗忘机制”我在帮朋友调一个客服 Agent 的时候发现了一个反直觉的现象把用户的每一条历史消息都记下来效果反而不如只保留最近 20 轮加一个用户画像摘要。原因很简单情景记忆太多会把语义记忆的提取难度提高一个量级。就好像一个档案室里的卷宗堆成了山你再想从中找到“这个用户是否投诉过”就变得非常困难。所以一个设计良好的记忆系统必须包含“遗忘”机制。遗忘不是把数据删掉而是把数据从高优先级层移到低优先级层。比如 7 天内的对话保留在活跃存储30 天前的对话做摘要后压缩90 天前的原始记录直接离线归档。遗忘的目的是减少检索时的干扰项避免让 Agent 面对一堆历史包袱。很多人做记忆系统时只想着“怎么记住”从来没想过“怎么忘掉”。我说句难听的你的 Agent 记不住事儿很多时候不是你记得太少而是你让它在太多不重要的历史里找那一条重要的。4. 一套可落地的分层记忆架构写入、检索、遗忘三链路设计4.1 总览三条数据链路贯穿整个系统我在实际项目里沉淀下来的一套架构核心思路就是分层 三个独立链路。先看整体结构再逐个拆解。写入链路产生的对话、事件、状态变化经过清洗、抽取、归纳之后分别写入对应的记忆层。检索链路Agent 在每次行动前根据当前任务从记忆层中召回相关信息组装成工作记忆注入上下文。遗忘与压缩链路定期对高层的活跃数据做摘要、压缩、迁移或删除控制记忆总量的膨胀。这套架构的核心原则是写入要快检索要准遗忘要稳。三条链路互相独立可以分别扩展和优化。4.2 短期记忆层滚动窗口 摘要压缩的组合拳短期记忆层对应的就是工作记忆它的设计目标是让 Agent 在当前对话里不“失忆”同时控制传给模型的 token 量。我常用的方案是滚动窗口加摘要压缩的组合滚动窗口是一个固定长度的消息队列比如保留最近 20 轮对话。超过 20 轮的部分不会直接被丢弃而是进入摘要流程。摘要流程会把这部分消息总结成一段话作为“压缩记忆”保留。这样Agent 的上下文里永远包含两部分——最近 20 轮的原始消息加上之前所有内容的压缩摘要。这套组合的好处是模型既能准确知道“上一句用户说了什么”又能大致记得“这个对话是从什么话题开始的”。代价是摘要会丢失细节所以我在设计摘要模板的时候会明确要求模型保留用户的明确要求、已完成的动作、未完成的事项、用户表达出的偏好。这样摘要就不会只是泛泛的“用户聊了一些工作内容”而是“用户要求写一份季度总结提到了数据要包含 Q3 增长率尚未提供具体数字”。4.3 长期记忆层向量库、知识图谱与事件日志的分工长期记忆层承担的是情景记忆和语义记忆。我强烈建议不要把两种记忆混在一个存储里。我的分工是这样的情景记忆用事件日志。每完成一次有意义的交互就记录一条结构化事件发生时间、事件类型、涉及的实体、结果状态。比如“2025-06-01 10:23用户创建了项目 X项目状态为草稿”。事件日志适合按时间线查询也适合做统计。存储上我用过 ClickHouse也用过 PostgreSQL 加 JSONB小规模项目其实 SQLite 就够了。语义记忆用向量库加知识图谱的混合。向量库负责把“用户画像”“偏好描述”“实体关系说明”编码成向量用于相似度检索。知识图谱负责保存实体和实体之间的显式关系比如“用户 A 是项目 X 的创建者”“用户 B 是用户 A 的同事”。我为什么需要图谱而不完全靠向量因为向量检索擅长“语义相近”但不擅长精确的路径查找。你问“这个用户参与过哪些和‘电商’相关的项目”向量库可以给你相近的结果但图谱能给你精确的、可解释的答案。我在线上项目里语义记忆的写入不是实时逐句做的而是跑一个周期性的“记忆抽取任务”每隔一段时间比如每 10 轮对话或每小时把新增的对话交给一个专门的模型让它抽取实体、关系、偏好并输出结构化的更新指令再写入图谱和向量库。这样做的好处是写入成本可控且抽取质量更高坏处是记忆更新会有几分钟的延迟。对大多数场景来说这个延迟完全可接受。4.4 写入策略不是所有对话都值得进长期记忆很多人把所有对话一股脑全写入长期记忆结果记忆库越来越脏检索效果越来越差。写入策略我觉得三个过滤器就够了重要性过滤器只有涉及用户明确偏好、项目关键信息、任务状态变更的对话才写入长期记忆。闲聊、寒暄、重复确认直接丢进短期滚动窗口到期就忘。冲突检测器如果新抽取的信息和已有记忆矛盾用户说“我不喝咖啡”后又说“帮我点杯拿铁”不要直接覆盖而是把新信息标记为待确认识别状态在后续对话里观察是特例还是偏好改变。抽象归纳器单次对话的细节更适合作为情景记忆保留而要沉淀为语义记忆必须经过归纳。比如“用户在第 1 轮提到喜欢简洁第 5 轮提到不要太口语化第 9 轮夸奖了你上次的书面风格”这一串事件应该被归纳成一条“用户偏好正式、简洁的书面表达”而不是三条孤立的记录。这层策略听上去很重但实现起来无非是给记忆写入任务加了几条 prompt 指令和判断逻辑。我做过的版本里用 GPT 级别的模型做抽取2000 条对话的记忆抽取成本大约相当于 10 次完整对话的 token 消耗性价比是很高的。5. 从“记住”到“想起来”检索召回是记忆系统真正的分水岭5.1 纯向量检索远远不够为什么必须做混合检索记忆系统光存得好没用关键是在需要的时候能“想起来”。很多人的第一版检索就是“把用户当前问题转成向量在向量库里算相似度取 top-k”。实测下来这种方案在记忆场景里经常翻车。为什么因为记忆检索不是文档检索它有很强的多模态特征用户问“我上次让你改的文案好了吗”这个问题本身是一个模糊指代它依赖“上次”这个时间概念。向量模型对时间的编码能力非常弱。用户问“你记得我女朋友喜欢什么吗”这个问题里藏了一个实体关系“用户-女朋友”。如果向量库里只存了“女孩喜欢吃芒果”而没有把实体关系建出来单纯靠向量相似度未必能召回。记忆场景里高频出现的是“命名实体”问题。用户会问“我之前说的那个李总的事”这个问题里“李总”是一个精确实体精确实体匹配用关键词或图谱查询远比向量召回可靠。所以我的方案是混合检索先用关键词和实体识别做一次精确召回再用向量做一次语义召回最后把两部分结果合并去重按分数排序。简单说精确查询保住下限语义召回拔高上限。5.2 时间衰减与重要性权重让记忆排序更像人脑光做混合检索还不够因为候选集里可能有一大堆时间不同、重要性不同的记忆。比如用户问你他的文档偏好你召回了两条“一周前用户喜欢 Markdown 格式”和“一年前用户喜欢 Word 格式”这时候模型应该信哪条显然时间更近的那条更重要。我每次做记忆排序都会加两个权重时间衰减因子和重要性权重。时间衰减好理解每条记忆的得分乘以一个衰减系数比如指数衰减 exp(-lambda * age_days)其中 lambda 是可调的衰减速率。重要性权重则需要给记忆打一个“重要级别”比如“涉及用户的核心业务”为 10 分“随口闲聊”为 1 分。最终排序分数 检索相似度 * 时间衰减 * 重要性权重。这个公式看起来很朴素但它的效果非常明显。我给一个在线客服 Agent 加上这两个权重后用户满意度评分提升了将近 20%原因很简单模型老是引用三个月前的旧状态来回答今天的问题谁都不愿意跟这种 Agent 聊天。5.3 检索失败也要有兜底Agent 不能只会“嘴硬”我发现一个很多 Agent 的通病检索不到相关记忆时模型会硬编一个回答。有些模型甚至会把“没有记忆”包装成一个看起来合理的假设。这种“嘴硬”在客服场景里是灾难级的信任杀手。我处理这个问题有几招在系统 prompt 里明确告诉模型如果检索结果为空或置信度低必须回答“我暂时没有找到相关记录请告诉我更多信息”禁止编造历史。给记忆检索模块加一个“置信度阈值”。召回结果的得分低于阈值时不注入上下文而是注入一条提示“未找到相关记忆”。更狠的一招是做一个“记忆缺失反馈回路”当 Agent 频繁回答“找不到”时系统记录这个失败事件触发记忆补写流程。比如用户告诉你“我上周发过一封邮件”Agent 没检索到那就主动触发一次邮箱记录的再抽取把漏掉的事件补进记忆库。这些兜底逻辑不复杂但决定了你的 Agent 在边缘情况下是“可靠”还是“搞笑”。6. 上下文窗口用完了怎么办token 预算管理与上下文压缩实战6.1 给上下文设预算而不是等它爆掉很多人的使用习惯是等到模型报错“context length exceeded”才想起来要清理。这个思路不对好的做法是你在一开始就给自己定一个 token 预算。我的经验是把模型上下文窗口的 50% 到 60% 作为硬上限预留空间给当前轮次的工具结果和生成输出。举个例子如果模型支持 128K 上下文我一般让历史记忆部分最多占 60K留 60K 左右给当前的对话状态、工具返回结果和模型输出。这样做的原因是工具调用函数调用返回的数据有时候会很庞大比如一个数据库查询返回 100 条记录一下就能吃掉 5K token。如果你把窗口用到 95%那模型基本没有空间去生成结构化输出而且很容易因为输出被截断而引发一连串错误。6.2 压缩是策略问题不是模型问题四种压缩手法当历史记录逼近预算上限时有几种常用手法我按推荐优先级列一下关键帧保留保留最近几轮完整消息再往前的内容全部用摘要替代。适合大多数场景实现简单效果稳定。结构化抽取不是保留原始对话而是抽取成“用户目标、当前状态、已完成步骤、下一步计划”的结构化条目。适合任务型 Agent比如你让 Agent 帮你操作网页、填表格、做数据分析。窗口分层裁剪把历史按重要性分层低优先级的打包成一行“此处省略了 X 条普通对话”高优先级的保留原文。适合偏好驱动的助手型 Agent。主动遗忘对超过一定时长且没有引用的记忆直接标记为“已过期”不再注入上下文。注意这里不是真的删库只是让它不出现在上下文里。压缩到底该压到什么程度核心判断标准是压缩之后Agent 回答当前问题的能力不能下降超过可接受的范围。这个范围怎么测我后面会讲评测方法。6.3 一个可复用的上下文组装流程最后分享一个我反复使用的上下文组装伪代码它的逻辑很清晰先注入系统指令和用户画像再注入相关记忆和工具定义最后注入当前对话状态。def build_prompt(current_query, memory_store, config): # 1. 系统指令固定不变 messages [{role: system, content: config.system_prompt}] # 2. 检索相关长期记忆混合检索时间衰减加权 related_memories memory_store.hybrid_search( querycurrent_query, top_kconfig.top_k, time_decayTrue ) messages.append({ role: system, content: f[相关记忆]\n{format_memories(related_memories)} }) # 3. 装配短期记忆最近消息 压缩摘要 recent_messages memory_store.get_recent(kconfig.recent_k) for msg in recent_messages: messages.append(msg) # 4. 注入当前用户问题 messages.append({role: user, content: current_query}) return messages实际部署的时候我还会加一个 token 计数器每轮对话组装完都估算总 token 数。超过预算就触发上面说的压缩策略。这个流程看起来平淡无奇但正是这些细节决定了 Agent 在跑一天之后是依然清醒还是已经疯掉。7. 工程落地选型与避坑心得从 Rust 到 Django存储与框架怎么配合7.1 为什么我用 Rust 写过一套记忆引擎又换回了 Python最近 Rust 在大模型生态里热度很高我也跟风用 Rust 写过一套记忆引擎。体验是性能确实很猛单机 QPS 能扛很高内存占用也稳。但我要说一个很现实的感受Rust 在 AI Agent 场景的问题不是“能不能写”而是“生态太薄”。模型调用、向量嵌入、知识图谱、各类 SDKPython 里几行 pip install 就能搞定Rust 里可能要自己造轮子。如果你的记忆系统是一个高性能的独立服务比如要支撑很多 Agent 实例同时访问的共享记忆层Rust 是个好选择。但如果你是在做单体 Agent 应用我建议用 Python 或 TypeScript 快速迭代核心性能瓶颈并不在记忆层而在于模型推理本身。7.2 存储选型没有银弹只有搭配在我经手的项目里不同数据量级选的存储差异很大数据规模推荐方案理由个人工具/小应用用户量 100SQLite json零部署读写够用备份方便中型应用用户量 1000 级PostgreSQL pgvector事务能力强向量检索和关系查询一体化大型系统用户量百万级Redis Elasticsearch 专用向量库高并发访问混合检索能力强Redis 通常用来放工作记忆的缓存因为它的读写速度极快TTL 机制天生适合做短期记忆的过期管理。向量库我分别用过 Milvus 和 QdrantMilvus 适合大数据量Qdrant 部署更轻量。配置上都支持 hybrid search对做多路召回很友好。7.3 Django 项目里接入记忆系统的一个完整路径热搜词里有人提到用 Django 开发 AI Agent我顺手把记忆系统的接入流程写一下因为这个问题在社区里被问得很多。假设你已经有一个 Django 项目我的接入路径是这样的先建一个memoryapp定义好 ORM 模型EventLog事件日志、SemanticMemory语义记忆、MemorySetting每用户阈值配置。在 View 层接入写入链路每当 Agent 完成一轮对话异步任务里调用memory_extract()抽取结构化记忆。配置 Celery 定时任务每小时跑一次记忆归纳和遗忘压缩。在 Agent 的 prompt 构造器里调用memory_search()做混合检索。最后用 Django Admin 或者自建简单面板做记忆的审计和手动修正。很多人在 Django 项目里遇到的一个实际问题是Agent 是在异步任务里跑的而 Django ORM 在异步环境里用起来很别扭。我的建议是记忆写入和检索统一走一个单独的函数层用sync_to_async包一层不要直接在各个业务代码里操作 ORM不然维护起来会疯掉。7.4 记忆系统的评测别只看召回率一个指标最后一个大多数人都忽略的点记忆系统怎么做评测。很多人只关心“检索召回率”但这远远不够。我自己的评测维度有五个召回准确率检索到的记忆和当前问题相关且信息不冲突。端到端任务完成率加了记忆系统之后Agent 在目标任务上的完成质量是否有提升。上下文压缩失真率摘要压缩后Agent 回答问题的关键信息是否丢失。记忆污染率因为记忆引入的错误信息导致 Agent 答错的占比。成本指标平均每轮对话的 token 消耗、检索延迟。我强烈建议在你上线记忆系统之前先准备一组“记忆问答测试集”把用户可能问的“你还记得……吗”这类问题整理成几十条系统化地测试。不做评测就上线的记忆系统大概率是一个等到线上才爆雷的系统。8. 写在最后的经验记忆系统是“整理术”不是“扩容术”我这几年搭过不少 Agent也拆过不少别人踩坑的案例。我最大的体会是记忆系统的核心困难不是技术而是取舍。你得想清楚什么该记、什么该忘、什么优先级高、什么该压缩这些决策比选哪个向量库难多了因为技术方案错了可以重写取舍策略错了你的 Agent 会以你完全想不到的方式“发疯”。举个例子我有个项目早期没有做遗忘机制用户的记忆库越堆越大。三个月后Agent 开始频繁地把很久以前跟用户的开玩笑内容当成正式需求来响应用户被气得不行。后来我加了时间衰减和重要性权重效果立竿见影。这个问题的根源不是模型笨而是我的记忆系统让太多低质量历史参与决策了。所以我通常给团队的建议是先别急着上向量库先把记忆的写入策略、分层结构、压缩逻辑想清楚。哪怕第一版用最简单的 SQLite 加 JSONB只要分层设计和遗忘机制是合理的效果不会比你上来就搞 Milvus 差。反过来存储再先进如果你的 Agent 分不清“重要事实”和“随口闲聊”一样会翻车。最后分享一个我在记忆系统设计里最常用的一句话做收尾上下文窗口决定你的 Agent 能看多远记忆系统决定它能不能真正看懂。前者是硬件后者是脑力。把精力花在后者上你的 Agent 才能从“看起来能记住”进化为“真的能记对”。
阅读完成 · 觉得有帮助?
咨询建站