做Agent应用的时间越久越会发现一个真相真正卡住Agent上限的往往不是模型能力而是上下文窗口。我见过太多这样的场景——一个看起来逻辑很完整的多工具Agent跑到第8轮调用时就开始“失忆”把用户几分钟前明确提到的约束给忘了或者一个对话助手在处理完第一批文档后第二批进来的内容直接挤占了第一批的Token空间导致前面所有结论都变成不可追溯的“黑历史”。这不是模型笨是上下文管理没做好。Agent上下文压缩与记忆策略本质上是在回答三个问题什么东西必须留在眼前、什么东西可以放到仓库、什么东西应该直接扔进垃圾桶。这篇文章会围绕5种上下文压缩方式、3类记忆形态、4条选型红线展开再结合我对比过的MemGPT、ACON、Context Editing三类主流思路把一套能落地的记忆系统讲清楚。适合正在做Agent应用、RAG系统、复杂对话机器人以及被Token成本和上下文衰减折磨的开发者看完可以直接拿去调整自己的方案。1. 上下文为什么会失控先看清问题的本质1.1 Agent任务里Token是怎么被吃掉的很多人以为Agent的上下文膨胀是“聊天记录太多”导致的实际上聊天记录只是很小一部分。拆开一次典型的Agent任务调用链你会发现Token消耗的大头在这几个地方系统提示词和角色设定、工具调用产生的结构化参数输入与原始返回、中间推理过程、过往对话历史、临时性记忆块。以一个常见的“查询天气并安排日程”任务为例工具返回的JSON可能有数百Token而它真正有用的信息可能只有“明天晴温度22度”这十几个字。如果Agent要连续操作五个工具返回的原始数据叠加起来窗口的一半就没了。更麻烦的是很多框架会把工具返回的完整内容原样塞进后续Prompt因为“万一后面要用呢”。这种“舍不得删”的心态是上下文膨胀的根源之一。模型推理时的注意力是会被稀释的一段20000 Token的历史里关键信息可能埋在第17000 Token的位置模型要么漏掉要么为了“回忆”而花掉大量推理时间。实测下来上下文利用率超过70%之后Agent的指令遵循度会肉眼可见地下降。1.2 五种压缩方式横向对比总有一款能对症把目前社区和工业界常见的上下文压缩手段梳理一遍可以归纳为五种各有各的适用场景我直接放在一张表里对比压缩方式核心思路优点缺点适用场景丢弃式压缩直接删掉最旧或最不重要的内容零成本、实现简单可能误删关键信息工具返回的临时日志、重复性输出摘要式压缩用LLM把多轮内容提炼成一段摘要保留语义主干可大幅降Token有信息损失可能产生事实漂移历史对话、已完成的子任务检索式压缩全量内容存外部按需召回Top-K片段信息保真度高只付出查询成本检索质量影响回复效果长期记忆、知识库型Agent结构化提取将自由文本改写为字段化的记录体积小、便于程序处理无法保留叙事细节用户意图、实体参数、状态记录学习式压缩用小模型或门控网络学会“留什么丢什么”智能化、可适配不同任务需要训练/微调调试成本高大规模生产环境、特定领域场景这五种方式不是选一个用到底而是可以叠加组合。比如对工具调用结果用“结构化提取丢弃式”对多轮对话用“摘要式检索式”对早期历史用“分层摘要”——我会在第5章给出具体组合方案。2. 三类记忆形态先分清楚“记什么”“存哪里”做记忆策略之前先得把“记忆”这个词拆清楚。我习惯把Agent的记忆分成三类工作记忆、长期记忆、程序记忆。别看它们都叫“记忆”存储位置、读写方式、生命周期完全不同混为一谈必出乱子。2.1 工作记忆窗口里最贵的临时空间工作记忆对应的是当前上下文窗口里正在被模型直接看到的全部内容包括本轮用户输入、最近的工具结果、当前任务目标、还没有完成的步骤清单。可以把它类比成CPU的寄存器或高速缓存速度最快、容量最小、断电即失。工作记忆里的东西必须是最新的、最相关的任何过时的内容留在这里都是浪费。实操中需要刻意控制工作记忆的体积。我给自己定过一条规矩如果一段内容在后续三到五步推理中不会再被用到就必须被移出工作记忆。比如用户问“帮我查一下附近的餐厅”工具返回了10家店的完整信息等用户说“选评分最高的那家”之后那10家店的信息里只需要保留评分最高那家店的名称和地址其余9家的原始JSON可以直接丢弃。很多Agent产品体验差就是因为工作记忆里堆了一堆已经用完的数据把真正重要的用户意图挤到了注意力边缘。2.2 长期记忆从向量库到知识图谱长期记忆是Agent的硬盘存的是跨会话需要保留的、不会频繁变化的内容用户的偏好、历史事实、项目背景、已完成任务的结论。这类记忆的特点是“写入一次、多次读取”对读取效率和准确性的要求远高于写入速度。常见载体包括向量数据库按语义相似度召回、键值存储按ID精确读取、图数据库按实体关系遍历。选择哪种载体取决于你的查询模式。如果是“用户上次提到喜欢喝什么”——向量库合适如果是“这个订单关联的用户和商品是什么”——图数据库更合适。这里特别提醒一点长期记忆一定要和原始上下文解耦不能混在同一个列表里。我会为每条记忆标记来源片段和可信度防止检索到一条“用户说他是北京的”之后就永远认为用户是北京人哪怕用户后来多次纠正已经搬到上海。长期记忆的更新要谨慎“记忆覆盖”是比“记忆缺失”更危险的问题。2.3 程序记忆真正让Agent变聪明的部分程序记忆存的是“怎么做”的知识某个任务的处理流程、某个工具的调用规范、某种异常情况下的应对策略。它不是给模型讲一次就行的常识而是通过沉淀可复用的Prompt片段、Few-shot示例、规则引擎、工具调用模板来固化。程序记忆解决的是每次对话都要重新“教”一遍的问题。比如你的Agent每次处理退款时都需要按照“验证订单—检查退款政策—确认入账方式—执行退款”四步走这四步放在系统提示词里属于程序记忆某次执行中学到一个新技巧——“发卡银行在夜间维护时退款会失败应该先重试三次再走人工流程”——这个技巧可以被提炼成一条规则写回程序记忆下次直接套用。程序记忆的质量决定Agent能走多远我见过太多Agent“单次对话表现惊艳、跨任务水平为零”根因就是程序记忆没有沉淀。3. 三个思路MemGPT、ACON与Context Editing理论框架讲完来看具体的方法论。MemGPT、ACON、Context Editing是三个很有代表性的方向分别对应了“系统级调度”“智能选择”“精准修改”三种不同的解题思路。我把它们放在一起对比是因为它们在真实项目里经常需要配合使用。3.1 MemGPT像操作系统管理内存一样管理上下文MemGPT的核心思想是借鉴操作系统的虚拟内存管理机制把LLM的上下文窗口看作“物理内存”把外部存储看作“虚拟内存”由系统调度器负责在两者之间做换入换出。它定义了两类上下文主上下文main context始终给模型看到外部上下文external context按需调入。真正巧妙的是它的“自编辑记忆”机制Agent可以调用工具来主动修改自己的记忆内容比如某个目标完成后通过工具把相关的临时条目从主上下文移到外部上下文或者干脆删除。这跟操作系统里的进程管理非常像——进程结束后释放内存而不是一直占着。这套思路适合解决“单一长任务”和“多会话持久任务”。我实际体会是MemGPT的调度策略不能原封不动照搬因为它默认模型具备很强的函数调用能力而你的Agent不一定有。落地时可以只借鉴它的分层思想把上下文拆成“常驻区”和“换入区”常驻区放系统提示词、用户核心目标、最近一轮交互换入区放历史摘要、工具结果、中间推理用时再取。3.2 ACON让一个轻量模型决定“留什么、扔什么”ACON在这类讨论里通常指自适应上下文网络Adaptive Context Network这一大类方法核心是训练一个轻量级的选择器/门控网络让它学会判断上下文里哪些内容重要、哪些可以压缩、哪些可以删而不是靠人工写死规则。我把它理解为“压缩策略的智能化和个性化”。举个例子规则式的压缩方式是“超过10轮对话就开始摘要”ACON方式则会让选择器观察当前任务特征判断“这一轮工具调用结果的详细参数对后续步骤是否有关键影响”如果有就保留没有就压缩成一句话。它不改变主模型只是给主模型配了一个“文秘”帮它过滤信息。ACON这类思路的最大优势是可以针对特定领域做优化。比如在金融领域选择器会被训练成对“金额、日期、账号”这些字段极度敏感哪怕上下文再长也要保住在闲聊场景选择器则会偏向保留情感色彩词汇。缺点是训练门槛和调试成本确实高如果你只有一个简单Agent不需要上ACON用第5章里的阈值规则就够了。但如果你维护的是高频次、大规模的生产AgentACON能显著降低Token消耗并提升指令遵循率。3.3 Context Editing精准修改记忆中“过时的那几行”Context Editing上下文编辑是另一个容易忽略但极其重要的能力它不以“压缩”“删除”“追加”的方式管理上下文而是直接修改上下文中的具体条目。听起来和MemGPT的自编辑有点相似但Context Editing更强调“精准命中某一行再修改”而不是大范围重写。最典型的场景是用户信息纠偏。用户一开始说“我在北京”Agent记住了后来用户改口“最近我搬到上海了”。普通方案只能追加一条新记忆导致检索时“北京”和“上海”两条并存模型很容易调错。Context Editing的做法是定位到原来那条“用户在北京”的记录把地点字段改为“上海”同时保留历史版本供审计。它避免了一种很隐蔽的问题——记忆冲突。在实际项目里我会把Context Editing设计成独立于对话链的记忆写接口。任何一次对话或工具执行都可能触发一个编辑操作包括纠正过期事实、删除临时缓存、更新任务状态、合并重复记忆。编辑操作的记录要写进操作日志原因我放在第4章讲。3.4 三种思路怎么组合用这三个方向不是互斥的我见过比较健康的组合方式是用MemGPT的分层思想搭骨架把上下文划分为常驻区和换入区用Context Editing负责换入区里具体条目的增删改保证记录永远是最新的用ACON选择器作为更上层的“智能调度员”决定哪些条目从常驻区降级到换入区、哪些从换入区直接丢弃。三层各管一段清晰不打架。如果团队和算力有限我建议的最小组合是“MemGPT分层 Context Editing”ACON可以后续再上。先保证记忆不膨胀、事实不走样再追求压缩选择的自动化。4. 四条选型红线判断方案能不能用的底线方案选型是最容易出问题的环节。很多人拿到压缩工具或记忆库后只盯着“Token省了多少”却忽略了更底层的约束。总结这些年踩过的坑我提炼出四条红线任何一条被突破方案都应该直接否掉。4.1 保真度红线压缩之后任务还得能完成压缩的本质是丢信息。丢多少、丢哪些必须由任务要求决定而不是由Token预算决定。所谓“保真度红线”指的是压缩后仍然必须保留完成任务所必需的关键约束条件。举例来说用户要求“找一家评分不低于4.5的江浙菜餐厅人均不要超过150”摘要式的压缩如果只留下“找江浙菜餐厅”过滤条件就丢了后续推荐必然跑偏。保真度必须用测试来验证不能靠感觉。我会在项目里准备一组“关键约束集测试用例”把用户原话里的每个约束点都显式列出来跑完压缩流程后检查每个约束是否仍能从压缩结果中推理得到。任何一条丢失说明压缩策略过激应该回调保留比例或换成检索式压缩。这条红线的底线是压缩后的内容加上模型自己的推理仍然可以复现出压缩前能完成的全部决策。4.2 成本红线压缩的开销必须小于收益压缩本身是有成本的。用LLM做摘要意味着要额外发起一次甚至多次模型调用消耗Token、产生延迟用检索式压缩意味着要维护向量索引每次查询都有计算开销和延迟。如果压缩10000 Token省下来的钱被摘要调用消耗的5000 Token和额外2秒延迟抵消大半那这次压缩就是负收益。我常用一个公式来算账净节省Token 压缩减少的Token数 − 压缩过程消耗的Token数净延迟变化 压缩过程耗时 − 因上下文缩短而减少的解码耗时。只有当两项都为正时压缩才值得做。延迟尤其关键用户在等回复结果Agent先花3秒把历史记录压缩了一遍体验是灾难性的。所以摘摘要式压缩更适合在任务间隙、用户输入之前这样的“空闲窗口”做而不是每次请求都现场压缩。4.3 可回滚红线记忆操作必须有迹可循这条红线的背景是我吃过的一次大亏。有一版Agent的记忆模块把历史覆写当成简单的“删旧写新”结果一次误操作把用户的关键偏好给覆盖了还不留任何日志。复盘时想找回原始记录都做不到因为旧值直接被覆盖掉了。可回滚红线要求两条一是所有记忆写操作都要记录操作日志包括时间、操作前的值、操作后的值、触发该操作的对话ID二是关键记忆在覆盖前必须保留快照或版本号。设计上我倾向于“追加而不是原地覆写”新记录写入时携带版本号和覆盖链指针读取时默认取最新版本必要时可以按版本回退。这不是为了炫技而是因为Agent的推理结果会被用于真实业务决策出问题时要能定位到“是哪一条错误记忆导致了这次错误输出”。4.4 安全红线隔离、脱敏、最小权限一个不能少Agent记忆系统最容易忽视的是权限和隔离。多用户场景下记忆存储必须按用户维度做严格隔离任何检索和编辑都不能跨用户这个要求听起来无比正确但做到却很难——很多团队把用户ID作为元数据塞进向量库检索时过滤条件写漏一个就会串数据。更隐蔽的是脱敏上下文压缩前工具返回里的身份证号、地址、银行卡信息要先做脱敏再进入记忆库否则一次日志泄露就可能把用户隐私全暴露了。安全红线还包含“记忆访问最小化”某个Agent子模块不应该能读取一切记忆只应读取完成当前任务所需的那部分。我会给每条记忆打上访问级别标签读取时强制匹配。这条红线在单Agent场景下可以放低一点但一旦Agent同时处理多个用户或者接入第三方工具就必须严格执行。安全关系的是能不能上线其他三条关系的是做得好不好优先级最高。5. 落地配置一套能跑的轻量记忆系统前面讲完理论和红线这章给一套可以直接抄作业的轻量配置。我用一个简单的Python伪代码描述记忆系统的核心结构具体框架不限制重点是结构。5.1 记忆表与读写接口的设计我把记忆设计成一张带版本的表字段包括记忆ID、类型、内容、向量、访问权重、创建时间、最后访问时间、覆盖链指针。类型用于区分工作记忆/长期记忆/程序记忆访问权重用于后续压缩决策覆盖链指针负责可回滚。class MemoryEntry: def __init__(self, mem_id, mem_type, content, embeddingNone): self.mem_id mem_id # 记忆唯一ID self.mem_type mem_type # working / long_term / procedural self.content content # 记忆内容 self.embedding embedding # 向量用于检索式召回 self.access_weight 1.0 # 访问权重越常用权重越高 self.created_at time.now() # 创建时间 self.last_accessed_at time.now() self.chain None # 覆盖链指向被本条覆盖的旧版本 class MemoryStore: def __init__(self): self.entries {} self.log [] def write(self, entry, override_chainNone): if override_chain: entry.chain override_chain self.entries[entry.mem_id] entry self.log.append((WRITE, entry.mem_id, time.now())) def read(self, mem_id): entry self.entries.get(mem_id) if entry: entry.access_weight 0.1 entry.last_accessed_at time.now() return entry这里要解释一个关键设计我把读写都定义成显式接口不让Agent直接动存储目的是让所有访问都能被日志记录、被权限控制拦截。实际项目里我不建议把记忆接口暴露给Agent去自由调用那会让调试变成一场噩梦。5.2 压缩触发策略与阈值经验压缩不是等窗口满了才做而是提前在“危险水位”就介入。我常用的触发策略是混合水位判断当上下文使用超过总量的60%时标记为“预警水位”开始对工作记忆中的临时条目执行丢弃式压缩超过70%时进入“压缩水位”对早期对话执行摘要式压缩并将摘要结果存入长期记忆超过85%时进入“紧急水位”强制将最近的完整对话换入外部存储只在窗口中保留最近3轮摘要。这个60/70/85的三档阈值是我实测下来比较稳的经验值但不同模型、不同任务会有差异。高推理型任务比如代码生成的窗口红线要更低因为模型注意力对上下文残留极其敏感短对话型任务可以稍微宽放。调试方法是逐步压紧阈值每档都在“关键约束集测试用例”上验证保真度。5.3 一段完整流程演示假设一个Agent要执行“帮用户对比三款笔记本电脑给出推荐”的任务。完整流程如下第一步用户输入进入工作记忆系统初始化任务清单。第二步Agent调用比价工具工具返回三大段原始规格数据我直接将这三段数据标记为“临时工具结果”只在窗口保留时会用到的字段型号、价格、核心配置、用户关心的三个维度。第三步对比推理完成后将推理结论写入长期记忆把临时工具结果执行丢弃式压缩。第四步任务结束系统将“用户预算区间”“用户对便携性的偏好”这两条从对话摘要里提取出来的长期记忆写入持久化存储更新覆盖链。第五步下班前检查上下文水位若超过70%则对早前的对比过程做摘要移出窗口。这套流程的核心是“临时数据早扔结论数据早存摘要数据迟留”。实际操作时我还会加一个“memory budget”字段给每条记忆分配一个目标Token开销超过就触发压缩跟项目预算管理一个道理。6. 踩坑记录真实项目中最常遇到的问题最后分享几个我在实际项目里踩过、也帮别人排查过的典型问题基本能覆盖压缩与记忆系统上线后80%的故障。6.1 摘要压缩后的“事实漂移”摘要式压缩最常见的问题是“事实漂移”。现象是摘要本身看起来流畅自然但丢失了关键数字、否定词或限定条件。比如原文“用户说不需要购买延长保修”摘要变成“用户考虑延长保修”一正一反后面整个推荐方向就反了。我排查这类问题时会在压缩器Prompt里加一条硬性要求“必须完整保留否定词、数字、时间、人名地名和条件状语”。但靠Prompt约束不可靠更稳妥的方案是对摘要结果做一个“关键信息抽取复核”——将摘要喂回模型让它抽取出其中的实体和约束再和原文的约束剪集做对比不一致就触发重写或改用检索式保留原文。6.2 检索噪声与记忆污染检索式压缩的最大风险是“召回错了但系统不知道”。向量检索返回的Top-K片段往往有语义相近但实际无关的内容Agent把它们当成根据就会一本正经地胡说八道。我调研这类问题时常发现根源是记忆写入时的切分太粗糙把两段无关语义塞进了同一个向量块里。解决方法是写入时按语义边界切分加一个“必须与查询的相似度大于阈值否则拒绝召回”的底线更重要是在系统提示词里告诉模型“检索到的记忆只能作为参考如果没有直接证据不得臆测”。6.3 级联压缩导致的雪崩遗忘级联压缩是摘要方案特有的隐形杀手。第一次压缩时细节A被摘掉了第二次压缩是在第一次的摘要上做的细节B又丢了等到第三、第四次原始信息基本只剩骨架。你根本不知道是哪一级丢了关键信息因为原始内容早就删掉了。对抗手段有两个一是长期记忆里保留“一级摘要原文链接”不允许多级摘要直接叠加每一轮摘要都可以追溯到上一级原文二是执行压缩时把被压缩的原文放入一个“冷藏区”低成本存储不参与检索如果需要追溯原始细节可以从冷藏区恢复。这个设计让我在多次线上事故里都没有陷入“找不回老数据”的绝境。6.4 并发更新时的记忆冲突Agent一旦使用并行工具或同时处理多个异步任务记忆并发冲突就会冒出来。典型现象是两条执行链同时读到一个旧版本的记忆各自修改后写回后写的把先写的覆盖了导致“明明两个任务都完成了状态却丢了一半”。我项目里用的方案是给记忆写入加上条件更新Compare-and-Swap写入时必须携带期望版本号版本不匹配就重读重写。另外对于频繁更新的记忆字段尽量在顶层做成Redis式的原子操作不直接让Agent操纵长字段。“串行是一切一致性的根源”这句话在记忆系统里尤其适用。做Agent记忆不是堆功能也不是抄某个框架就能一劳永逸。我个人的体会是先把“5种压缩方式、3类记忆形态、4条选型红线”在自己的场景里逐个过一遍再决定哪些组合值得引入优先级从保真度开始排序——一条丢失关键约束的记忆方案省再多Token也只能在Demo里好看进不了生产。而真正生产级的效果永远是“丢得起与丢不起之间”的取舍做对的结果。