
做一个会自己跑路的Agent项目最难受的时刻往往不是它不会写代码而是它跑到第12步开始和自己抬杠。明明几分钟前刚确认过的方案它翻个上下文窗口就忘得干干净净同一个工具调用反复触发、同一个决策反复推翻更夸张的情况是控制台直接抛agent execution terminated due to error整个任务在最后一步原地暴毙。这类翻车现场几乎在每一个Agent项目里都会出现根子不一定在模型能力也不全在提示词而往往出在上下文窗口的管理策略上。这篇是Agent系列的第4.2篇专门聊上下文窗口管理从为什么需要管、主流策略有哪些到一套可以直接拿去用的三层管理框架再到我实测下来踩过的坑。面向的人群是已经在做Agent开发、正在被长任务搞崩过的工程师和独立开发者。1. Agent项目跑到一半突然失忆问题通常出在上下文窗口1.1 从正常执行到反复横跳中间只隔了一轮巨大的工具返回先还原一个我实际遇到过的场景。当时在做的是一个需要“先调研、再写方案、最后改代码”的多步Agent前几步走得很顺模型读取了项目结构把技术选型列在了一个清单里还生成了第一版重构计划。到了第8轮模型调用了一个查询接口对方一次性返回了将近两万字的日志内容。本来这也不是什么大问题模型继续往下生成就行。但第9轮开始Agent突然像失忆一样它重新读取了一次项目结构重新分析了一遍已经被它分析过的文件然后把第5轮就确定好的方案推翻了一半。我翻看消息记录才发现那一大坨日志把更早的决策过程挤到了中间位置而模型在长上下文里对“旧信息”的注意力已经明显衰减。最要命的是等这轮工具调用再返回几万字整个请求直接超过了模型的上下文窗口上限控制台报错运行终止。这种问题你很难用调Prompt解决因为它不是某一次推理的质量问题而是上下文长度增长到一定阈值之后整个循环的“记忆”开始不稳定。跑得越久历史消息越积越多Agent的每一步推理都背负着越来越大的注意力负担最终不是出错就是超限。1.2 上下文放得下不等于上下文用得好很多刚接触Agent的同学有个误区觉得只要上下文窗口够大比如128K、200K甚至1M就不需要管理历史消息。看起来确实“放得下”但那只是容量层面的“放得下”。模型在超长上下文里推理时对同一段早期信息的关注度会显著下降尤其是当中间夹杂着大量工具返回、JSON片段、中间代码时早期关键指令很容易被稀释。我把上下文比作一个桌面。桌面很大本身是好事但如果你在上面堆了几十份文件你找东西的速度反而比桌面小、但文件归档整齐的情况更慢。模型每一轮都要在全部历史里重新做注意力计算历史越长越容易“翻不到”真正重要的内容。所以上下文窗口管理的本质不是“省空间”而是“控制注意力范围”——确保模型关注的区间里始终放着当前任务最需要的信息。另外还有一层很实际的原因成本。每次API调用都要把全部历史重新发给模型Token一涨钱就跟着涨。长任务的积累速度往往超出预期一个跑了20轮工具调用的Agent消息历史轻松突破10万Token其中很可能有70%是早已不用的旧中间产物。这些Token不仅拖慢响应速度还让每轮调用都变得更贵。上下文管理做得好的项目Token消耗通常能省一半以上同时执行稳定性反而更高。2. 动手管上下文之前先搞清两个隐藏规则2.1 输入Token和输出Token的预算并不是一回事很多平台的上下文窗口是“总窗口”也就是说输入占用的空间越多留给模型生成回复的空间就越少。做Agent时有个容易忽略的点Agent不是一次调用就结束它会多轮调用工具每一轮都需要模型生成内容。每轮生成内容都有输出长度上限max_tokens有些模型会从上下文总窗口里单独分一块给输出有些则是共享总窗口。我建议把上下文窗口想象成一条高速公路输入是已经开上路的车输出是给“生成动作”预留的应急车道。无论是车流把主路塞满还是应急车道被占掉最终结果都是交通瘫痪。在项目里做策略时至少要留出15%-20%的余量给输出否则会出现一种非常隐蔽的报错明明所有输入加在一起没超窗口但Agent一生成内容就碰到限制然后整个执行链路莫名其妙中断。我见过多次the agent execution provider did not respond in time这类超时问题排查了大半天根因其实就是上下文太长导致单次推理时间暴增。模型处理超长输入本身就是耗时大户如果上下文逼近上限计算时间会指数级拉长。很多Agent框架的默认超时阈值根本扛不住这种场景于是执行被判定失败。2.2 Lost in the middle为什么删中间信息比删开头信息更安全上下文窗口管理不光是“删哪些”还牵涉到模型对不同位置信息的敏感度差异。业界在做长文本评测时很早就发现一个现象模型对开头和结尾的内容记忆更牢固对中间部分的内容更容易忽略这就是常说的“lost in the middle”。放在Agent场景里这意味着如果你在历史消息中间堆了一大段不重要的工具日志而把核心指令放在很靠前或很靠后的位置模型的表现通常还凑合。但问题在于Agent执行过程中消息历史是在不断变长的。最开始位于末尾的“当前任务指令”随着工具调用越来越多会慢慢被顶到中间位置重要性随之下降。后台日志里会看到模型开始侧重处理尾部最新内容例如最新一次工具返回而对中间位置已经做过的方案规划、用户约束不那么上心。所以做上下文管理时必须明确一点删除或压缩的消息可以按策略处理但最好把“目标、约束、当前进度”这类内容以结构化形式固定在一个不会被顶到中间的位置。我的做法是维护一个独立的任务工作区每次都重新拼在系统提示之后、历史消息之前确保它始终出现在“开头区”。这样无论消息历史怎么增长关键信息都不会沉到注意力洼地里去。3. 上下文压缩的四条主流路径与交互取舍3.1 滑窗截断速度最快却最容易误伤关键信息滑窗截断的思路非常简单只保留最近N轮消息更早的整段丢弃。实现成本极低几乎不消耗额外Token执行也快很多早期Agent框架的默认策略就是它。但滑窗有个致命软肋它假设“最近的消息最重要”这个假设在Agent场景里经常不成立。用户可能在20轮前给过一个明确的格式要求也可能在15轮前出现过一条决定技术方案的关键信息。一旦被滑窗切掉Agent后续就会凭自己的“想象”继续工作行为开始漂移。所以如果只用滑窗我建议至少把窗口调大一些别为了省钱只留五轮十轮。同时要配合另外一个机制把历史里真正重要的信息提前抽出来放进工作区或摘要区防止被窗口切掉。滑窗适合作为最后一道防线不适合作为唯一的管理策略。3.2 摘要压缩保留主线但代价是细节失真摘要压缩是更聪明的做法定期把较早的对话交给模型做一轮归纳用一段几百字的摘要替换掉几万字的原始内容。这样Token占用大幅下降而任务主线——目标是什么、做了什么、下一步计划——仍然保留在上下文里。问题在于摘要本质上是“有损压缩”。我做过一个实验让摘要模型概括一段包含多个精确数字的讨论它把“用Python 3.11跑测试”概括成了“兼容Python”把“重试3次”概括成了“增加重试机制”。单独看摘要似乎没错但在执行层面精确参数丢失就意味着Agent会按错误的理解去写代码或调接口。因此摘要压缩不能只做一次。比较稳妥的做法是分级摘要全局摘要只管目标、决策和下一步计划同时额外维护一张“关键事实卡片”专门记录数字、文件名、路径、端口、命令这类不可模糊的信息。摘要负责讲“故事”事实卡片负责保“细节”两者配合才不容易出岔子。3.3 语义检索从“读全部历史”变成“按需找历史”把历史消息向量化后存储起来每次需要时按当前query做相似度检索只把最相关的一小段放回上下文。这种方案在小规模项目里效果不错可以保留几乎所有历史信息同时每轮只消耗少量Token。但做语义检索不是光接个向量库就完事。首先Agent执行过程中的query往往不够明确比如模型这轮只是在“继续推进任务”并没有生成很好的检索语句其次一段历史是否需要被召回跟当前问题的语义相关度有关而有些关键信息比如几个月前的用户偏好可能在语义上并不像“代码报错信息”那样容易被检索出来。我采用的做法是给检索增加一个“强制索引”每条消息在存入记忆库时会额外打上类型标签例如“用户偏好”“工具结果”“代码改动”等。检索时除了用当前query还会把任务工作区里的目标句一起作为上下文去搜索。这个组合检索比单纯拿最后一句用户消息去搜要可靠得多。3.4 结构化记忆与上下文编辑让Agent像人一样分门别类近两年不少框架开始引入更结构化的记忆机制把Agent的记忆拆成工作记忆、情景记忆、语义记忆几层对应不同生命周期和访问频率的信息。工作记忆就是当前执行期间必须随时可见的消息情景记忆就是过去执行过的完整任务记录语义记忆就是从历史里提炼出的用户偏好、领域知识、通用规则。这类思路的落地形式一是维护几个不同“舱位”的上下文区按调用时机拼进模型输入二是支持对上下文做“编辑”而不是只能“追加”。比如发现当前消息历史里有一段已经废弃的旧方案与其留着跟新方案打架不如直接做一次上下文编辑操作把旧方案替换掉。传统对话API里消息是只读追加的但在Agent框架里我们完全可以自己维护消息数组在合适时机重组、重写、移除内容然后再发送给模型。结构化记忆的代价是工程复杂度。你需要设计一套数据模型、一套存取逻辑、一套更新策略对一个Demo项目来说确实重。但对那些需要连续运行几天甚至更久的Agent服务来说这套投入是非常值得的。4. 落地一套三层上下文管理框架可以直接抄作业4.1 先给上下文做“分舱”与Token预算无论用上面哪种策略落地上都需要一个统一的框架。我把自己的方案总结成三层任务工作区、滚动摘要区、最近消息区。先定预算区域预算占比存放内容更新频率系统提示5%-8%角色规则、工具说明、输出格式几乎不变任务工作区10%-15%当前目标、约束、关键事实、下次计划每轮可能更新滚动摘要区15%-20%已完成步骤的高层归纳每N轮或触发时重写最近消息区50%-60%最近几轮对话及工具返回每次追加输出余量10%-15%留给模型生成本次回复每轮预留这套分舱有几个用意。第一系统提示不参与压缩让模型始终知道自己的角色和可用的工具。第二工作区是唯一允许“永久保留”的区域里面放的信息不允许被滑窗或摘要丢掉。第三最近消息区保留完整的原始信息避免过度压缩导致细节缺失。预算比例不用照抄但建议“输出余量”一定别省尤其在使用工具较多的Agent里每轮输出可能要几百甚至上千Token常常还要输出JSON结构。留得太少平台会截断Agent思考中层代码逻辑不明不白就断了。4.2 工作区刷新让Agent每轮都带着“当前事实”出发工作区是三层里最重要的一层也是大多数教程不会讲的细节。它的设计目标是在每一轮开始前把当前最应该被记住的内容重新组织一遍。具体字段通常包括objective任务总目标来自用户最开始的指令constraints用户明确说过的硬性约束例如“不要动数据库结构”“必须用中文写注释”facts关键事实卡片比如选型决定、路径、端口、版本号progress已经完成了哪些步骤当前执行到哪一步next_plan下一步准备做什么。这些字段不一定要每次都由模型重新生成可以设计成增量更新。比如某轮工具返回里出现了“测试端口是8080”这个信息就由Agent写一条“facts.append(测试端口: 8080)”的指令解析后更新到工作区。下一轮拼Prompt时工作区内容重新注入到消息最前面。这样即使最新消息是几万字的工具返回Agent依然知道“当前目标是什么做到哪了下一步怎么走”。我强烈建议不要让工作区的更新和正文推理共用一个模型调用。可以在Agent循环里额外插入一次轻量调用用低Token消耗专门做“历史扫描工作区更新”而不是在正常任务推理中间让模型顺便改状态。专注做状态更新的小模型往往比“边推理边维护状态”的主模型更稳定出错的概率更低而且不容易干扰主任务流。4.3 摘要触发与滚动摘要重写滚动摘要区不是只做一次就放着不动。随着任务推进已经完成的工作会不断沉淀进摘要而以前写的摘要又可能因为后续发展而需要修正。我的触发策略是每次工作区更新后统计当前全部消息的Token数。如果超过某个阈值比如预算的60%就触发一次摘要重写。哪部分需要被摘要所有位于“摘要区起点”和“最近消息区起点”之间的旧消息都可以交给摘要模型做归纳更新。举个例子如果最近消息区被定义为“保留最近6轮”那第7轮之前的所有内容除了工作区里明确保存的事实外都属于摘要压缩范围。每次压缩时摘要模型会接收到上一版摘要 新产生的旧消息 当前工作区输出一个新版摘要。这个过程要保证摘要永远覆盖到“上一个被压缩点”而不是只概括局部范围这样才能避免“中间漏了一段没概括进去”的洞。4.4 最近的完整消息保留多少轮最合理最近消息区是唯一保留原始完整消息的区域保留轮数要看Agent任务的性质。如果是偏向对话、交互频繁的任务保留15-20轮不奇怪如果是偏向程序执行、工具调用的任务每轮消息体量很大保留6-10轮就足够了。保留轮数一旦超出预算就把最早的一轮从“最近消息区”挪到“待摘要区”而不是直接删除。这样可以保证信息过渡平滑先被完整保留再进入摘要池信息始终有迹可循。有些框架直接删除早期消息后面排查问题的时候什么日志都对不上非常难受。Agent任务最好做到每一轮消息都有完整留存哪怕在大模型上下文里已经不放完整内容也得存到外部日志系统里。如果要更精细地控制还可以给不同消息类型设置不同的“保质期”。工具结果特别是大段日志往往保质期最短用户消息保质期最长Agent自己生成的中间计划次之。基于保质期来压缩比统一按轮数一刀切效果更接近人的记忆习惯。5. 实测中踩过的坑与对应的排查思路5.1 报错“execution terminated due to error”根因是上下文超限后的连锁反应第一次遇到这类错误时我第一反应是查代码逻辑结果查了半天也没发现问题。后来打开Token统计才看明白在一次工具调用里脚本把一个大文件的完整内容当作返回值传回了模型几万Token一下把上下文推到上限。平台在真实发送请求时发现超限直接判定agent处于异常状态终止了本轮执行。这类问题在竞品或内部系统的Agent框架中非常常见。排查思路是给Agent加“执行中Token观测”在每轮调用前计算待发送消息的总Token数超过上限时先触发压缩而不是硬着头皮发送。其次在调用工具前就对工具返回做截断或摘要。不要等工具结果回到上下文里才处理在工具侧就应该设置“单次返回内容最大长度”。如果一个工具可能返回很大体积的数据就给它套一层自动摘要的壳或者让它只返回字段清单由Agent决定下一步是否读取完整内容。5.2 摘要看似准确实则悄悄偷走了关键约束还有一次Agent中途需要重启再来一遍我设置了会话恢复功能把摘要重新组装成上下文。头几步跑得很顺结果到执行阶段Agent没有遵守“上传部署时不要覆盖线上配置”这个约束直接重写了线上配置。回查摘要发现摘要模型在概括用户原话时把“不要覆盖线上配置”压缩成了“确保部署配置正确”语义完全反转。从那以后我对摘要系统的原则改为“摘要负责压缩过程信息不碰约束信息”。约束必须原样保存在工作区里宁可多占几百Token也不要拿给摘要模型做归纳。这条原则救了我后面好几个项目每次出问题时排查出来的根因都不是摘要“归纳不够简洁”而是它在关键约束上做了有损变换。5.3 Agent“性格突变”滑窗把系统提示挤出去了有一次跑一个角色设定比较强的Agent一开始它的语气和人设保持得很好但跑了大概二十几轮后说话风格突然变得很“裸奔”——不再按照角色设定组织语言连工具调用格式都开始不规范。我一开始以为是模型抽风后来打印了那条实际发送给模型的请求才发现滑窗策略把系统提示挤出上下文了。原来那个项目的旧框架用了一种比较粗暴的拼接方式系统提示和消息历史共用同一个数组滑窗一滚动最早被挤掉的就是系统提示。修复方案是分舱管理——系统提示永远单独占一个固定位置甚至单独计费独立不允许参与任何截断。之后Agent的人设和格式稳定性显然恢复了。如果你也发现Agent跑着跑着突然变了个样先检查发送给模型的系统提示在不在不在的话赶紧拆开管别去优化Prompt。5.4 检索到了正确资料模型却没用上向量检索召回的内容如果注入位置不好很可能被模型当成“不重要的边角料”。比如有时我把检索结果放在历史消息的末尾模型可能正专注于处理最近一次工具返回根本没去读新塞进来的摘要。后来我做了个改动把检索结果放到当前用户消息的前面并明确标注为“相关资料”用分隔符和消息正文隔开模型通常就会把它当作重要信息参与推理。另外召回的内容与当前问题不相关时强塞进上下文反而会产生干扰。所以在把检索结果拼进上下文前我通常会加一道快速过滤在Prompt里指示模型对候选片段做一次相关性判定只保留判定为相关的内容。这样不仅能减少Token占用还能提高模型对真正关键信息的注意力。6. 越往后做越要提前想清楚的边界情况6.1 多Agent协同会话上下文不能共享一份在单Agent里上下文是围绕一个“我”的完整记忆。但多Agent协同的时候每个Agent必须有自己独立的上下文视图主Agent统筹全局子Agent只在被调用时启动执行完把结果交回主Agent然后它的完整过程不需要全部塞回主Agent的记忆里。如果所有Agent共享同一个大上下文Token消耗会迅速爆炸而且Agent之间会出现信息互相污染。我现在的架构是让每个子Agent只保留最小工作上下文入参说明、目标、相关工具、输出格式。结束后不管中间经历了多少轮返回给主Agent的只包括结论、关键文件和风险点。主Agent再把这份“精简报告”写进自己的工作区。这本质上就是“对话级上下文隔离 结论级信息汇总”在复杂工作流里比硬塞一份超长消息要高效得多。6.2 上下文临时失效后的恢复路径Agent执行到一半如果网络中断或者平台超时服务端往往已经没有完整上下文。这时候如果直接拿最后几条消息重新发起调用模型很容易忘记之前整个任务背景。恢复路径通常是从外部日志里找到最近一次工作区快照加载滚动摘要区内容读取最近几条系统关键变更记录重新组装出上下文再继续执行。所以从第一天起就要给Agent配上“会话快照”机制。每次工作区更新时把快照写一份持久化存储。这个成本非常低但在恢复场景里的价值极高属于典型的花小钱办大事。6.3 给下游省Token上下文管理的“事前”思维前面聊的上下文管理其实都是“事后处理”——消息已经产生了才去压缩、截断、摘要。但真正成熟的系统还要做“事前控制”。也就是说在Agent调用工具、操作外部系统时就应该设计好返回数据的“套餐规格”。能只返回状态码的就不要返回完整Body能只返回文件摘要的就不要把整个文件扔回来能分页查询的就不要一次性抓全量。这个思维转变很重要。上下文管理不只是Prompt组装层的责任它要在更上游的“工具设计层”就把问题消除掉。我前几天给一个内部工具加了一个文本截断参数让工具在被调用时先返回前500字加一个“是否查看完整内容”的选项Agent确认后再追加读取。单这一个改动就帮整个流程省了大量上下文空间而且Agent的执行效果没有下降——真正需要完整内容时它自己会去取。一些收尾的切身感受做Agent上下文管理这一年多最大的体会是不要追求某一种“万能策略”而是要把“分舱存储、按需注入、有损压缩只处理过程信息、无损保留约束与事实”这几个原则钉死在代码里。好用的上下文管理不是让Agent记住所有事而是帮它把应该记住的事放在最显眼的位置。如果你现在正被Agent长任务搞疯我建议第一步先别写压缩代码而是花半天时间把你当前Agent每轮实际发送给模型的消息完整打印出来一帧一帧看哪些内容被重复消费了几十轮、哪些真正关键的信息已经被淹没到看不见。看到那一条你就知道从哪个方向改最划算了。上下文管理策略没有银弹但一定是从修“当前最浪费的那一段消息”开始的。