尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Agent上下文窗口管理策略:Token预算、裁剪与摘要压缩实战

发布时间:2026/9/5 20:03:39

资讯中心
01
ARTICLE

Agent上下文窗口管理策略:Token预算、裁剪与摘要压缩实战

Agent上下文窗口管理策略:Token预算、裁剪与摘要压缩实战
1. 上下文窗口管理Agent长跑中的隐形瓶颈如果只用一个指标来评判一个Agent实不实用我的答案不是它能调用多少工具也不是它的提示词写得有多精巧而是——它能不能在一件需要持续十分钟以上的真实任务上稳稳跑完不跑偏。我做过一个粗糙但典型的小实验让同一个Agent去调研一个开源仓库的代码结构然后输出一份技术报告。任务本身不算难前期几步也执行得行云流水。但随着它读过的文件越来越多、调用的搜索工具次数越来越频繁到了后半程它开始“失忆”——把仓库里已经确认过的接口说成另一个版本反复调用同一类工具返回相同结果甚至在总结时漏掉最开始定下的核心目标。起初我以为是模型的指令遵循能力不行后来把整个会话日志拉出来一看原因特别直白上下文窗口已经被中间过程塞满了真正重要的信息和最开始的指令早被挤到了模型的“视线”之外。这就是上下文窗口管理的意义。它不是简单地“控制token数量”而是当Agent面对一个开放式、多步骤、需要持续累积信息的真实任务时你能不能保证整个决策链路始终围绕目标运转、关键证据始终可被访问、必要的历史经验始终在线。这也是我在这套Agent系列里单独把“上下文窗口管理策略”列为一节的原因——在架构、工具、记忆之外窗口管理处理的是Agent运行时最基础也最容易翻车的资源调度问题。无论你是在做RAG流程编排、写代码生成Agent还是搭一个能自主完成调研的通用助手只要任务时长超过几步这个话题就绕不过去。这篇文章会从我在实际项目中遇到的“失控现场”讲起拆开上下文膨胀的三种典型成因再把一套从预算规划到动态裁剪、再到结构化记忆的完整管理策略摆到台面上最后给一份可直接复用的代码实现和踩坑记录。内容偏实战适合已经在做Agent开发、正被“跑着跑着就乱了”折磨的开发者参考。2. 上下文窗口为何失控三类资源劫持的真实场景要看懂管理策略先得知道窗口里那些token到底被谁吃掉了。我拆过几份跑崩的任务日志发现所谓的“上下文爆炸”并不是平均膨胀而是集中在三个来源。它们各自的特征和解决方式完全不同混在一起处理一定会出问题。2.1 系统提示词慢慢“发福”的初始区系统提示词是最容易被忽视的。刚开始做Agent的时候大家都倾向于把规则写得尽量完整角色定义、任务说明、工具使用规范、输出格式要求、禁止事项……第一版500字跑了几次发现模型偶尔会漏步骤于是再补一段“你必须要先分析、再调用工具、最后输出”后来又发现格式偶尔不稳定于是把JSON示例也塞进去。半年下来系统提示词可能已经膨胀到几千字。它不会像工具日志那样突然暴涨但它像一块压在窗口底部的石头——永远占着地方而模型每一次生成都要带着它“负重前行”。更要命的是系统提示词里的信息权重并不是均等的。模型对提示词开头和结尾的内容更敏感堆在中段的冗长规则容易变成“摆设”。有一段时间我发现Agent频繁违反“先确认参数再调用工具”的约束查了很久才发现这条规则被埋在了一大段工具说明中间早就不起作用了。后来我把这份提示词拆了最核心的一句话目标放在开头后置的格式说明全部挪到最终输出阶段再注入体积降了一半规则遵守率反而明显提升。这里想说明的是窗口管理不是只盯着动态增长的那部分静态提示词同样需要做瘦身和位次优化。2.2 工具调用日志真正的体积大户真实Agent跑任务时的上下文增长主力几乎永远是工具调用记录。每调用一次工具至少要产生四份文本Assistant发起的调用请求、工具返回的结果、模型对结果的理解或反思、以及下一轮它基于结果做出的新决策。这个循环下来一次工具交互的token开销经常在1000到3000之间。当Agent要检索30份文档、执行20次搜索时光是这些记录就能轻松突破窗口的中段区域。我见过一个很典型的场景Agent调用了一个代码搜索工具返回了整个文件内容8000多token。它本意只是确认一个函数名但这份结果被原封不动丢进了上下文。紧接着下一步它又搜索另一个符号结果再次拉进来一个完整文件。三轮下来窗口就被大量只有局部相关性的原始内容占满了。模型在处理后续任务时不得不从这么一堆庞杂内容里大海捞针准确率自然急剧下滑。工具日志管理的本质是你要替模型决定“哪些信息值得完整保留、哪些信息只需要留一个摘要、哪些信息压根不该进入窗口”。2.3 中间观察与多轮历史无差别堆积的缓存陷阱第三个来源最隐蔽Agent在中间过程产生的自我反思、阶段性计划、临时笔记以及跟环境交互时的无关对话历史。很多框架默认“把上下文完整传给模型”这在短任务里没问题但任务一旦拉长早期步骤的中间思考对当前决策往往已经没有参考价值了。它们就像后台运行的旧进程不释放内存越积越多。我之前排查过一个线上Agent的异常行为它在执行中期开始回复得越来越“敷衍”后期干脆只输出工具调用参数而不再解释判断逻辑。翻日志发现这个任务在两小时里累积了超过5万token的历史消息而系统提示词早被淹没在第2万token之后的位置。模型的注意力被大量早期低价值内容分散既找不到任务目标也找不到关键约束最后进入了“机械执行工具调用”的降智状态。这说明一个核心道理上下文窗口不是缓存它应当被当作一块需要动态分配资源的工作空间。什么阶段放什么内容、旧内容什么时候退场、重要信息通过什么方式常驻——都需要一个主动的管理者。3. 分层管理策略预算、裁剪、摘要与结构化记忆吃了上面这些亏之后我开始把上下文窗口当作CPU中的L1缓存来设计空间有限、访问极快、但内容必须精准。围绕这个思路我把管理策略拆成四个层面分别是预算、裁剪、摘要和结构化记忆。前两个负责“节流”后两个负责“保真”。3.1 先做Token预算表别等窗口失控才补救很多Agent项目的上下文管理是“救火式”的发现窗口满了才写一段裁剪逻辑把旧消息删掉。这种做法治标不治本因为你不知道哪些内容正在被消耗、哪些内容是可以被牺牲的。我现在的做法是在Agent启动前先定义一份预算表给窗口内每个内容区域划定上限像给项目排期一样给token分配额度。这份预算表大概长这样区域预算占比内容构成系统提示词与核心目标5%以内角色、任务目标、关键约束任务线索与计划10%以内当前计划、已完成事项列表、TODO关键上下文与证据30%-40%工具返回的重要事实、代码片段、数据短期交互历史20%-25%最近几次推理与行动记录摘要缓冲15%-20%旧过程的压缩摘要预留空间20%模型输出与临时内容注意一个细节预留空间往往被人忽略。模型的每次输出都要占用窗口空间如果上下文已经堆到了90%那模型生成内容时就会变得极其局促甚至出现输出被截断的情况。我一般会把动态预留控制在20%左右宁可少放一些历史内容也要保证模型有足够的“书写空间”。预算表的另一个作用是定义各类操作的触发阈值。比如“当前使用量超过总窗口70%”时启动整理程序“最近3轮工具调用超过8000 token”时立刻对工具日志执行压缩。量化指标负责驱动决策管理者不用每次去估算上下文状态让阈值自动触发即可。3.2 裁剪要有顺序先动结构性内容再动关键依据裁剪是整个策略里最容易踩坑的一环。很多新手实现会用最粗暴的方式对话历史超过N条就弹掉最旧的一条。但Agent上下文里每类内容的价值和可替代性完全不同一刀切裁剪很可能把现在还依赖的关键证据给切掉。我的裁剪顺序是这样的从“先牺牲”到“最后动”中间过程的推理碎语。Agent在自主思考时产生的“嗯这个结果看起来不太对我再看看另一个接口”——这些话当时有用但一旦行动完成就几乎没有复盘价值优先裁剪。已完成工具调用的详细输入输出。比如一次搜索结果已经用完了总结出了结论那么原始搜索结果可以被替代。早期轮次中的计划描述。计划一旦被执行过它的详细版本就不需要再三出现只要保留“计划A已完成”这个状态即可。阶段性状态记录。如果任务分多个阶段执行前一阶段的完整过程对当前阶段往往只剩参考价值。最近一轮到两轮的原始内容。这部分最贴近当前决策只要空间允许就尽量保留。这份顺序背后的原则是时效性越低、可替代性越强的内容裁剪优先级越高。跟当前决策直接相关的工具结果、从用户需求里拆出来的验收标准、中间确认过的硬性约束这些属于“不可再生资源”一旦裁掉就再也找不回来优先级必须放到最后。3.3 摘要压缩时机、预算与内容保真当裁剪已经无法满足空间需求时就该做摘要压缩了。摘要的核心挑战不是“把长文本变短”的这一步而是“压缩之后信息还能不能准确复原”。模型做摘要时经常会丢掉一些当时觉得不重要、但后续决策恰恰需要的关键细节比如一个具体数值、一个被否定的方案或一句用户强调过的偏好。我给摘要压缩定了几条硬性规则执行一段时间后效果稳定了不少。第一固定触发时机而非常态执行。我一般设置两个触发点一是当前上下文总量超过窗口上限的70%二是单轮工具交互记录超过3000 token且短期内不再需要原始内容。满足任一条件就对最早的部分做一次批量压缩而不是每一轮都做。频繁做摘要的成本极高既增加一次额外的模型调用还可能把新信息与旧摘要搅在一起造成混淆。第二给每条摘要打上结构化标签。我要求模型在生成摘要时按固定模板输出时间范围、涉及工具、核心事实、未完成事项、关键数值。这样压缩出来的东西才能被后续逻辑检索和使用而不是一段语义含糊的散文。第三摘要本身也要“长大”。如果任务持续几个小时早期步骤的摘要又会越积越厚等到它本身也超过阈值就需要做二次摘要——把旧摘要进一步提炼。这时候我会给摘要目标增加一条规则不保留过程只保留结论和状态确保二级压缩后信息仍然是原子化的、可索引的。3.4 记忆分区把上下文变成Agent可查询的数据库这一层是我个人认为管理策略里上限最高的部分不是单纯在窗口里腾挪空间而是主动重建信息存储结构。前面几层把上下文当成“一块工作区”来维护而记忆分区是在工作区之外加了一个“外置硬盘”。我现在习惯把Agent的记忆拆成三块分区工作上下文区只存放当前任务正在使用的数据和推理链保持精简与活跃。任务档案区存放当前任务的完整目标、验收标准、已经确认的关键决策。这个区的内容体积不大但优先级很高每一轮都会注入。长期知识区跨任务沉淀的领域知识、历史项目经验、常用代码模式平时不占窗口空间只在需要时通过检索召回并临时注入。这个设计参考了人类做事的模式你不会把几个月前学过的所有知识都背在脑子里才开始干活而是在需要用到某个细节时去查资料、翻笔记。Agent也一样让模型把所有东西都“记住”既不经济也不可靠。把长期知识交给向量检索把任务状态交给结构化缓存把窗口留给当前的动作决策——每一层各司其职上下文就不会成为任务时长的硬约束。4. 一个可直接复用的上下文管理器实现理论讲再多不如贴一份能跑的代码。下面这个ContextManager是我在一个内部Agent项目里沉淀下来的简化版本去掉了业务耦合之后核心逻辑大概160行左右。它能做的事情包括维护消息列表、估算Token开销、监听阈值、在触发条件满足时对旧内容执行摘要压缩。4.1 数据结构与初始化Agent上下文管理器——分层预算动态摘要压缩 from dataclasses import dataclass, field from typing import List, Dict, Optional import time import tiktoken dataclass class ContextMessage: 一条带元数据的消息 role: str # system / user / assistant / tool content: str category: str history # system / plan / evidence / history token_count: int 0 timestamp: float field(default_factorytime.time) compressible: bool True # 是否允许被摘要压缩 class ContextWindowManager: def __init__(self, token_limit: int 12000, compress_threshold: float 0.7, encoder_name: str gpt-4o): self.token_limit token_limit self.compress_threshold compress_threshold self.messages: List[ContextMessage] [] self.encoder tiktoken.encoding_for_model(encoder_name) self.total_tokens 0 # 摘要压缩回调外部可注入具体的摘要实现 self.summarizer None这个结构里的关键点在于每条消息都带着category和compressible标记。category用来区分这条消息属于目标声明、路径证据还是普通会话历史compressible决定它能否被摘要替换。为什么这么设计因为不同的内容在窗口里的生命周期策略不同系统目标无论如何不能被裁掉而工具日志一旦确认处理完就标记为可压缩。4.2 核心操作追加、计算与自动压缩def _count_tokens(self, text: str) - int: 估算文本token数 return len(self.encoder.encode(text)) def add_message(self, role: str, content: str, category: str history, compressible: bool True) - None: 向管理器追加一条消息同时更新token总量 token_count self._count_tokens(content) msg ContextMessage( rolerole, contentcontent, categorycategory, token_counttoken_count, compressiblecompressible ) self.messages.append(msg) self.total_tokens token_count def _current_usage_ratio(self) - float: 当前窗口使用率预留空间的计算要算上最近两轮输出预估 # 这里额外加上800个token作为模型本轮输出的预估值 estimated_output 800 return (self.total_tokens estimated_output) / self.token_limit def build_context(self) - List[Dict[str, str]]: 输出给模型的context消息列表 context [] for msg in self.messages: context.append({ role: msg.role, content: msg.content }) return context追加消息时实时统计token数这是后面一切管理逻辑的记账基础。build_context把内存数据结构转成模型标准的messages格式。 _current_usage_ratio里的“预估输出空间”是我吃过亏之后加上的——以前我只统计已存在的内容结果经常在模型生成长答案时窗口溢出后半段直接被静默截断。加上这个预估值之后窗口“假满”的概率低了很多。真正的核心逻辑在自动压缩这一步def step(self) - Optional[str]: 每一轮结束后调用判断是否需要压缩返回压缩动作描述 if self._current_usage_ratio() self.compress_threshold: return None compressible [m for m in self.messages if m.compressible] if len(compressible) 2: return None # 可压缩内容太少时强行压缩会伤害上下文 # 找出最早的可压缩消息作为压缩批次 batch [] batch_tokens 0 max_batch_tokens int(self.token_limit * 0.25) # 单次压缩最多释放25%窗口 for msg in compressible: if batch_tokens msg.token_count max_batch_tokens: break batch.append(msg) batch_tokens msg.token_count if not batch: return None # 调用外部摘要器 if self.summarizer is None: raise RuntimeError(需要先注入summarizer实现) original_texts [f[{m.role}] {m.content} for m in batch] summary self.summarizer(\n.join(original_texts)) summary_tokens self._count_tokens(summary) # 从消息列表中移除批次并插入摘要 for msg in batch: self.messages.remove(msg) self.total_tokens - msg.token_count summary_msg ContextMessage( rolesystem, contentf[上下文摘要]\n{summary}, categorysummary, token_countsummary_tokens, compressibleTrue # 摘要本身在后续也可以被二次压缩 ) self.messages.insert(0, summary_msg) self.total_tokens summary_tokens return f压缩完成释放 {batch_tokens - summary_tokens} tokens4.3 关键参数的选择逻辑这里的几个参数是我反复调试后定下来的解释一下为什么这么选单次压缩释放上限设为窗口的25%是一个保守但安全的数值。如果一次摘要吞掉的内容太多生成的摘要粒度太粗关键细节更容易丢失。分多次小步压缩、每次只处理最早的部分虽然引入了更多次摘要调用但保真度明显更好。用摘要调用成本换取关键信息不丢这笔账怎么算都划算。触发阈值设在70%留出30%的空间给后续模型输出和新内容。很多公开的Agent项目把这个阈值调到85%甚至90%表面看“用得更满”但实际运行时经常出现一种尴尬压完没多久又触发压缩模型隔几轮就被打断一次去处理摘要请求任务流畅性大打折扣。70%是我在“空间利用率”和“运行稳定性”之间找到的比较舒服的平衡点。这个summarizer你必须自己注入实现——因为它和你的任务类型强相关。简单通用任务直接用LLM调用即可但如果你的Agent跑的是代码分析这类高精度场景我建议摘要函数里额外传一条提示词“保留函数名、接口签名、文件路径、关键技术决策不保留原文中的情绪化表述和无关示例。”任务定制化的摘要策略比任何通用摘要配方都有效。5. 常见问题排查为什么摘要后模型反而变笨了策略落地之后新的问题会浮上来。我把自己踩过的坑和排查经验整理成一张速查表应该能帮你省掉不少调试时间。症状排查方向解决方案摘要压缩后Agent开始重复问同一件事摘要里丢了“已完成事项”状态摘要模板必须包含Completed / Pending两项列表压缩频率过高任务频繁被打断阈值设得太低或系统提示词太长将触发阈值调到0.75-0.8压缩单批体积调大一点模型违反早期定下的规则规则被淹没在长摘要之后核心规则放system prompt开头关键约束不要塞进摘要区上下文还剩30%但模型输出开始乱内部有接近窗口极限的挤压感检查是否有超大消息几万token被一次性写入工具调用日志被摘要压缩后数值出现幻觉摘要器未能保留精确数字摘要提示词里明确要求数值一律原样保留不得改写摘要后的系统提示区出现低质内容压缩批次混入了不可归类内容所有需要长期保留的消息必须设置compressibleFalse5.1 摘要导致的“信息偏食”与二次召回我不止一次遇到这种情况任务跑了一个小时中间压过三轮摘要结果模型在总结阶段漏掉了用户最早需求里的一个重要限制条件。排查之后发现那个“重要限制”确实被写进了摘要——但摘要里它是简短的一句“要求结果基于Python 3.10以上版本”后面还跟着几十条其它事项。当模型在长时间任务末尾读取这段高度浓缩的摘要时它在注意力分布上对这句话的权重远不如任务刚下发时那么高。这个问题的本质是摘要保留了信息但没有保留信息的重要性优先级。所以我后来在摘要模板里增加了一个Priority字段凡涉及“不能做什么”“必须满足什么”的规则摘要时必须标记为High并汇总在摘要开头。这个方法实测下来很有用模型在长任务末端的规则遵守率回升了一大截。5.2 被工具结果淹没的“事实漂移”还有一类隐蔽问题在工具调用特别密集的任务里Agent对同一个事实可能会得到多份互相矛盾的观测结果。比如先搜到某个接口在某版本已被废弃后来又搜到旧的调用案例还在广泛使用。两条信息都留在上下文里之后模型会根据新看到的内容偏向错误结论。这就是上下文中的“事实漂移”。针对这个问题我的做法是在Agent的关键决策点上强制做一次“事实核对”把已经确认的事实单独提炼出来清理与之冲突的旧消息而不是放任矛盾证据共存在工作区里。简单来说你需要在Agent的执行流程里加一个轻量级的“去冲突”动作——当新旧工具结果冲突时保存新结论并标记旧结论已被取代。这个细节在短期任务里几乎用不上但那种跑很久的调研型Agent一定会碰到。5.3 预算表要根据模型规格动态调整最后一类问题出在模型选择上。我在不同的Agent任务里切换过多个模型。不同模型上下文规格不同同一个管理器在128K窗口和32K窗口上跑出来的行为差异巨大。后来我把窗口大小抽象成了参数并在启动时根据模型规格做一次预算权重初始化窗口越大历史区和工具日志区可分配的比例就越高窗口越小越要把空间留给系统提示词和当前证据。真正把Agent放到长时间真实任务里实测之后我才意识到上下文窗口的紧张感永远不会消失因为实际问题总是比预估的更复杂。管理策略的目标不是“让窗口永远不爆”而是“让窗口即使面临紧张也不会牺牲任务的核心目标”。说到底Agent跑得远不远、跑得稳不稳真正比拼的就是它对自身资源的调度艺术。上面给的预算表、裁剪顺序和摘要规则是我跑坏了无数个任务之后沉淀下来的实用经验希望能让你少走一些我用token和时间踩出来的弯路。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。