上一篇文章里我们聊了上下文工程的基础边界和常见误区,这次把焦点收缩到Agent场景里最刚需的一环:上下文压缩。为什么单独拎出来写一篇?因为我在实际调试一个数据采集Agent的时候,真真切切被"上下文爆掉"这件事坑过——任务跑到一半,模型开始遗忘用户最初设定的分类规则,把该归入A类的条目硬塞进了B类。排查了半天发现不是规则写得有问题,而是早期指令被后来堆积的工具返回结果挤出了有效注意力范围。这个问题和普通多轮对话里的"聊太长了"完全不是一回事。Agent的上下文里塞的不只是对话,还有工具定义、调用参数、接口返回、中间推理,这些内容增长速度快、单块体积大、信息密度差异悬殊。单纯把窗口拉大或者简单截断,都不能解决根本问题。这篇就围绕上下文压缩方法展开,讲清楚我试过的几条路线、各自的实现细节,以及上线之后踩过的坑。1. Agent上下文为什么比普通对话更容易"撑爆"1.1 上下文里到底装了什么先梳理一个Agent每轮运行下来,上下文里会堆积哪些东西。和普通Chat对话相比,Agent上下文最大的特点是"工作过程"成为主体,而不只是"对话轮次":系统提示词:角色设定、任务规则、工具协议描述,这部分是固定不变的,但往往要占1000到2000 token;用户需求和约束:任务目标、限制条件、输出格式要求;Agent的中间推理:如果用了思维链或者思考过程,每一步的推理文本都会被保存;工具调用记录:调了哪个工具、传了什么参数;工具返回结果:API响应、数据库查询结果、页面抓取文本,这一块是最容易失控的;历史摘要:如果做过压缩,摘要本身也会持续累积。普通对话每轮增长几百token已经算多了,而Agent一轮工具调用可能就涨几千token。一个稍微复杂点的任务调五到八个工具,上下文从1K涨到8K到10K tokens是非常正常的事情。更麻烦的是,工具返回结果往往是块状的大文本——一次搜索返回十条摘要轻松上千token,一个数据库查询接口吐出20条完整记录更是直接吃掉一片窗口。举个例子方便理解。某个多步骤分析任务,初始上下文大概是1800 token(系统提示加工具定义加单条用户需求)。第一个工具调用查完API,涨到3500;第二个工具获取明细,到了6800;第三个工具做关联查询,直接干到11200。三次工具调用,上下文翻了六倍。如果任务有二十步,任何模型窗口都扛不住,压缩不是可选项,是必选项。1.2 上下文撑爆之后的三类典型症状把上下文撑到接近极限,你会观察到三类典型问题,这三类问题我在不同项目里都遇到过:第一是规则遗忘。最早写进系统提示和用户需求里的约束,在长上下文里被"淹没"。模型不是故意的,而是注意力分布天然倾向于最近的内容,开头的长段指令在后期几乎不参与决策。第二是中间内容迷失。论文里讲的"lost in the middle"现象在Agent场景被放大得很明显。模型对开头的指令和结尾的最新内容记得比较牢,中间夹着的一堆工具结果反而被弱化。结果就是:最新的工具结果能被用上,早期的关键中间结论却经常被忽略,导致同一个数据被重复处理。第三是成本与延迟失控。输入越长,按token计费的成本越高,首字响应也越慢。很多团队等到这一步才想起来要压缩,但结构性问题已经体现在账单和用户体感上了。有不少人第一反应是"换一个上下文窗口更大的模型"。这个选择可以缓解问题,但没有解决本质。在一个足够复杂的任务里,不管窗口多大,只要任务足够长,上下文总会被填满。而且窗口越大,模型处理和检索长文本的代价越高,很多长任务场景里甚至不可接受。所以压缩方法本身,才是Agent工程里真正值得投入的设计环节。2. 五条压缩路线的全景对比与选型逻辑在我自己折腾过的方案里,上下文压缩大致可以分成五条技术路线。它们不是互斥的,实际项目里经常组合使用,但你必须先清楚每一条的原理和边界,才能做出合理搭配。2.1 截断与滑动窗口:最低成本,但只适合短任务最简单的办法,保留固定数量的最近token,前面的直接丢弃。实现就是维护一个队列,超过长度就弹出最老的内容。适用场景非常明确:任务本身没有长期依赖,比如单轮问答、短链路的工具调用,早期内容用完即弃。优点是零成本、零延迟,实现也就十几行代码。缺点是信息丢失是硬性的,一旦任务中途需要依赖早期信息——比如用户最初的偏好、前面步骤得到的关键结论——截断就是事实丢失。我曾经在一个多步骤信息核对任务里用过纯滑动窗口,结果就是后期模型反复在没有依据的情况下瞎猜,输出质量完全不可控。所以现在我的原则是:只有在明确知道这个任务不需要跨步骤引用早期信息时,才敢用纯截断。2.2 摘要式压缩:语义保持为主,适合中长期对话用LLM对旧上下文做归纳,生成一段压缩摘要,替代原文继续参与后续推理。摘要保留核心事实、决策和待办事项,这是目前实践中用得最多的一种。优点是语义保持相对好,不依赖外部检索设施,单靠Prompt就能实现。缺点是每次压缩都有token和时间成本,而且摘要必然存在信息损失,模型还可能产生幻觉——把原文没有的内容"补充"进摘要里。这三条决定了摘要式压缩不能无脑频繁使用,也不能对用户约束类信息做自由改写。第3节我会专门展开怎么写好压缩Prompt。2.3 结构化压缩:把自由文本变成高密度字段把一大段自由文本抽取成结构化字段:实体、属性、关系、状态、时间线。工具返回结果经过结构化之后,token占用往往能降到原来的五分之一甚至十分之一,而且信息精度比摘要更高。结构化压缩的适用场景很清晰:工具返回数据量大、字段规整、后续决策只依赖其中部分字段。比如电商数据查询返回20个字段,实际任务只需要其中6个,那就可以把另外14个过滤掉,甚至把6个字段压缩成定界符拼接的紧凑行。缺点也很明显:抽取能力依赖模型质量,复杂语义(比如模糊推理过程)很难转成规整字段,强行结构化会丢失微妙的上下文。这一块我在第4节展开。2.4 检索式压缩:不主动丢信息,需要时再取回检索式方案的本质是不压缩历史本身,而是把历史内容向量化存入外部存储,每次对话按相关性检索出需要的片段,再注入当前上下文。它解决了摘要和结构化的"信息不可逆损失"问题——原始内容一直都在,只是不在主上下文里。缺点是需要额外的基础设施(向量存储、嵌入模型、检索链路),而且召回质量是最大的风险点。检索出来的片段如果相关性不够,模型基于错误信息推理,结果比没有检索更差。所以在Agent任务里,检索式压缩很少单独使用,更多是作为摘要的补充,第5节会聊具体做法。2.5 混合压缩:工程上最稳妥的默认选择把前面几条路线组合起来,通常的形态是:摘要打底 + 关键原文保留 + 按需检索兜底。用户明确指令永远不压缩,最近几轮完整保留,更早的内容用摘要加原始片段混合存放,信息不足时触发检索找回细节。为什么混合形态最稳?因为单一摘要会丢失细节,单一检索在召回失败时没有兜底,两者互补之后容错率大幅提升。代价是系统复杂度上来了,需要同时维护两套存储和两套流程。这个取舍我认为是值得的,尤其是长时间运行的生产Agent。下面这张表是我自己常用的选型参考,具体到项目里可以拿它做初步判断:方法实现成本信息损失依赖检索设施适用场景滑动窗口截断低高否短任务、单轮问答摘要式压缩中中否多轮对话、中等长度任务结构化压缩中高中否工具返回数据量大、字段规整检索式压缩高低是长任务、需要频繁回溯任意历史混合压缩高低可选复杂Agent任务、生产环境3. 摘要式压缩的核心:把Prompt从"总结"改造成"信息保全"摘要式压缩是最容易上手也最容易做坏的一种方法。我见过太多实现,压缩Prompt写得跟"帮我总结一下这段对话"差不多,结果就是token省了不少,任务执行质量也跟着崩了。问题出在对压缩任务的理解上——压缩Prompt不是在让模型"总结",而是在让模型"保全关键信息"。3.1 压缩Prompt必须逼模型干四件事一份真正可用的摘要压缩Prompt,至少要强制模型完成四类信息的提取:事实提取:所有明确的事实性信息,包括实体名、数字、时间点、状态变化,用简洁短语逐个列出;决策记录:所有已经做出的决策和选择,防止后续重复决策或者遗忘决策前提;约束保持:用户明确表达过的要求、规则、偏好、禁止项,尽量逐字保留;待办追踪:正在进行的任务、尚未完成的事项、下一步需要推进的步骤。为什么这四件事缺一不可?因为我踩过坑。早期版本的压缩Prompt只写了"保留重要信息",模型每轮确实能省很多token,但几轮迭代压缩之后,用户最初说的"报告里只放我指定的三个指标",会被逐步缩成"用户需要一些指标",最后变成"按用户要求输出报告"。约束在压缩链路里被一层层稀释,这就是摘要丢失的典型路径。3.2 一份可以直接改来用的压缩模板下面这个模板是我在多个项目里迭代过的版本,结构上没有什么花哨的地方,但每一条都有它的用意:你现在需要把一段旧对话历史压缩成摘要,这段摘要之后会作为Agent系统上下文的一部分继续使用。 请严格按以下结构输出: 1. 关键事实:列出所有实体、数字、时间、状态变化,每项用简短短语。 2. 决策记录:记录所有已经做出的选择及其理由。 3. 用户约束:逐字引用用户提出的规则、偏好、禁止项。 4. 待办事项:列出所有未完成的任务和后续步骤。 5. 一句话概括:用不超过50个字概括当前任务进展。 要求: - 只写原文中确实存在的信息,禁止补充任何原文没有的内容。 - 用户约束必须以原文表述为准,不得改写、不得省略。 - 某一部分没有内容时写"无"。 - 只输出以上结构,不要额外解释。这个模板的关键不是第5条那一句话概括,而是"用户约束逐字引用"这一条。它把最容易在压缩中丢失的信息单独划成了强制保留区,模型没有自由度去改写用户的原话。3.3 多轮迭代压缩:摘要之上再做摘要的节奏任务特别长时,摘要本身也会累积。你不可能无限追加摘要,所以需要在摘要之上再做摘要。这里要处理好分层和节奏。我习惯把上下文分成三层管理:最近会话原文层,保留最近N轮完整对话和工具记录;中期摘要层,把更早的内容合并成一份摘要;长期核心层,放最关键的用户约束和整体任务目标,这部分永不压缩。只有当中期摘要本身超过一定长度、导致上下文总长度再次逼近阈值时,才对摘要再做一次摘要。触发压缩的时机,我推荐"水位线"策略而不是"每N轮压缩一次"。具体做法是:设定一个压缩阈值,比如当前上下文总量的70%,一旦超过就把最早的一半压缩成摘要,最近的对话保持原文。为什么按水位而不是按轮次?因为工具返回大小差异极大,按轮次压缩经常出现该压的时候没压、不该压的时候白白消耗了摘要成本。水位线策略天然适配任务的实际消耗节奏。还有一个实操细节:压缩时需要把原始内容完整喂给模型做归纳,万一压缩对象本身超过模型输入上限,就要分批抽取关键信息再汇总,或者对原始内容先做一轮结构化瘦身再进摘要。总之,摘要的质量上限取决于喂进去的材料质量,这一步不能省。4. 结构化压缩:给工具调用记录"瘦身"的实战做法摘要式压缩解决的是"对话历史怎么归纳"的问题,但Agent上下文里最大的token黑洞往往不是对话,而是工具返回结果。这一节专门讲怎么给工具输出瘦身。4.1 工具返回结果为什么会成为token黑洞我观察过一个典型的失败案例:一个商品信息查询Agent,数据库接口返回20条完整记录,每条记录15个字段,其中8个字段和当前任务完全无关。搜索引擎返回了10条摘要,每条都有完整的标题、链接、发布时间、内容片段。代码执行工具返回了完整堆栈和全部中间输出。这些原始返回直接拼进上下文,一个工具调用就可能吃掉5000到8000 token,有效信息密度却低得可怜。而且这个问题会累积——每次工具调用都这么干,几个循环下来窗口就满了。很多人忽略了一个事实:工具返回结果的处理逻辑,完全是你能控制的,它比对话历史的压缩要简单得多,收益也确定得多。4.2 三层瘦身策略:从调用前到拼装前我对工具输出的处理分三层:第一层,调用前约束。在工具定义和调用参数里就限制返回量:设置limit、指定返回字段、要求服务端做聚合计算。这一层压缩成本最低,效果最直接,但也是被忽略最多的。很多开发者在定义工具时图省事,直接把全量查询接口暴露给Agent,返回多少就吃多少。正确的做法是在工具层做裁剪,让Agent只能拿到它可能需要的最小集合。第二层,调用后裁剪。如果工具返回值已经进来了,再在拼进上下文前用代码规则过滤一遍。最简单的是字段白名单:只保留任务可能用到的字段,其余全部丢弃。稍微复杂一点的场景,可以用一个小模型做关键字段抽取,但这会增加一层延迟和成本,能用规则解决的就别用模型。第三层,格式压缩。把多行JSON或层级结构转成紧凑的单行定界符格式,去掉所有不必要的语法字符和空行。比如一个商品记录,原始JSON长这样:{ "product_id": "P10001", "product_name": "某型号降噪耳机", "price": 1299, "original_price": 1699, "discount_ratio": 0.76, "stock_status": "in_stock", "review_count": 1287, "avg_rating": 4.6, "category": "audio", "specs": { "color": "black", "weight": "254g", "battery": "30h" } }压缩之后可以变成这样:P10001|某型号降噪耳机|1299|原价1699|折扣0.76|现货|4.6分/1287评|黑色|续航30h字段名信息没了,但后续决策需要的数据全部还在。token消耗从200多token降到40左右,压缩比超过80%。4.3 用"决策导向"决定保留哪些字段裁剪掉哪些字段,我有一个核心判断标准:只保留可能影响后续决策的字段,去掉只是展示用的字段。上一条记录里的category,如果任务不需要按类别筛选就可以去掉;specs里的颜色和重量,如果任务不涉及规格比较也可以去掉。判断标准不是"字段有没有用",而是"字段是否可能影响任务接下来的任何一步"。另外我强烈建议把任务的进度状态单独维护一份执行日志,放在上下文的高优先级位置。比如"当前任务进行到第3步:已获取A平台数据、已合并B平台数据、待核验C平台数据"。这条进展信息放在上下文顶部系统提示附近,模型会持续注意到它;放在历史里,很容易被成堆的工具结果淹没。这是我自己在多次调试中验证过的:同样一份执行状态,位置不同,任务成功率能差出好几个百分点。5. 检索式压缩与分层记忆:让信息按需回到上下文结构化压缩和摘要式压缩,本质都是主动丢信息换取token空间。但有些任务场景必须保留随时回溯任意早期信息的能力,比如一个持续数小时、跨多个数据源的数据收集Agent,用户可能随时追问"两个小时前你提到的那条异常记录是什么"。这种时候摘要已经覆盖不了细节,需要检索式方案介入。5.1 工作记忆与长期记忆的分离设计我把Agent的记忆从逻辑上分成两层。工作记忆是当前窗口内正在处理的内容,包括系统提示、最近几轮对话、当前任务的上下文摘要,它是有长度限制的、可被压缩的。长期记忆是所有历史原始内容,向量化后存放在外部存储,不占主上下文,需要时通过检索取回相关片段。这两层的边界要划分清楚:工作记忆负责"当下决策",长期记忆负责"万一需要"。凡是压缩进摘要、但摘要又无法完整承载的细节,都应该落进长期记忆。这样设计的好处是,工作记忆可以维持在一个很小的规模,模型始终在"看得清"的状态下工作,而长期记忆保证了信息不丢,只是从"始终在场"变成了"随叫随到"。5.2 检索式压缩的搭建步骤与关键参数给Agent加检索能力,我建议按下面这个顺序搭,每一步都有坑:内容切块。把每次工具返回、每轮对话按语义切块。块太小,召回结果碎片化,模型拼不出完整上下文;块太大,召回精度下降,一次注入带着一堆无关内容。我常用512到1024字符作为粒度,具体还要看内容密度,密度高的取上限,密度低的取下限。向量化存储。把每块转成向量,连同元信息(来源、时间、当时的任务阶段)一起存入向量存储。元信息很重要,后续过滤和校验都要靠它。检索触发。不是每轮都检索,那样成本和延迟都受不了。我习惯设置触发条件:当前上下文中出现了需要求证的关键词,或者用户问了一个"之前提到过"的问题,才执行检索。触发逻辑写在系统提示里,让模型自己决定何时需要补充信息。结果注入。检索结果按相关性排序后,在系统提示之后注入,作为当前轮次的参考上下文。注入时限制数量,我一般最多取回top3到top5,避免参考上下文本身膨胀。这一步最大的坑在召回质量。如果检索回来的是不相关的片段,模型会一本正经地基于错误信息做推理,结果比不检索更糟。所以在注入之前我会加一道相关性校验——让模型先判断检索结果和当前问题是否相关,不相关的直接丢弃。这道校验会多花一次调用,但能挡住大量错误推理。5.3 摘要兜底加检索补充的混合方案生产环境里最好用的组合,我总结下来是这套:摘要式压缩保任务主线:决策、约束、待办,这些是Agent每一步行动的依据,不能丢。向量存储保原始细节:所有历史原文都在,支持任意时刻回溯。触发式检索补细节:当摘要信息不足以支撑当前决策时,自动检索原始片段回填。兜底策略要明确:检索不到时,直接告诉模型"该细节不在当前可用信息中",而不是让它自由发挥编一个出来。这套混合设计的核心思想是让摘要和检索各管一段:摘要负责当下决策,检索负责历史备份。代价是系统复杂度上来了,要维护两套存储和两套流程,但从任务的稳定性和可排查性来看,这个复杂度换来的收益非常明显。6. 压缩策略上线之后踩过的三个坑与调优建议压缩方案从能跑到跑稳,中间还隔着不少细节。这里聊聊我上线后反复调过的三个问题,也是大概率你会碰到的。6.1 压缩水位调太高或太低都不行压缩触发阈值设到90%再动手,风险很大,因为压缩本身要耗时,摘要生成还有失败概率,得预留出压缩期间的token余量。我建议触发阈值设在60%到70%,压缩完成后目标水位控制在40%到50%,给后续工具调用留下足够空间。算一笔账更直观:假设上下文窗口是32K token,70%触发压缩就是22.4K,压到40%就是12.8K,单次压缩能释放约9.6K token的空间。如果一次工具调用平均吃掉1.5K到2K token,那压缩间隔大概能支撑5到6次调用。如果单次工具调用吃3K到5K token,两个循环就要压一次,这时候就要考虑提高结构化压缩的比例,减少进上下文的原始数据量。6.2 摘要偏差滚雪球:最隐蔽的质量杀手摘要压缩最大的风险是偏差积累。第一次压缩丢掉了一点细节,第二次压缩在缺失信息的基础上又做了归纳,就可能产生错误推断,第三次就会把错误推断当成事实写进摘要。这就是压缩的"滚雪球效应"。对抗滚雪球,我目前用三个办法。第一,原始内容压缩后不要立刻删除,至少在缓存里保留几轮,出现可疑行为时可以回溯对照。第二,摘要生成后做一致性校验——把摘要里的关键实体和原文的关键实体自动比对,发现不一致就重试或回退。第三,对不可压缩信息做显式标记:用户约束、数字、专有名词、时间点,在摘要模板里有固定区块,模型只能原文保留,不能自由改写。这三条同时用,能把摘要漂移的概率压到很低的水平。6.3 压缩质量的评估框架:别只看省了多少token压缩好不好,不是单看token节省率就行的。我自己的评估框架包含四个维度,每次改压缩策略都会跑:信息覆盖率:压缩后的摘要能否回答关于原始内容的事实性问题。做法是拿压缩前后各跑一组同样的事实性问题,对比两组准确率,目标是压缩后不显著下降。约束保持率:压缩后用户指定的规则是否仍然生效。构造一组"违反约束"的测试样例丢给Agent,看它能不能识别并拒绝。任务完成率:在真实任务上对比压缩前后的成功率,这是最直接的业务指标,也是最终唯一有说服力的指标。成本降幅:记录压缩前后的token消耗和调用耗时,用来平衡质量与成本。这四个维度不必每次全跑。我自己的习惯是:压缩策略初次上线和每次有大改的时候用固定测试集全量跑一遍;日常小改动只跑任务完成率和信息覆盖率两个维度,够用了。再分享一个我踩过几次坑之后的体会。上下文压缩不要等到上线出问题再补,架构设计阶段就要把压缩方案纳入。做Agent任务,最怕的不是模型能力不够,而是上下文越跑越乱,所有信息都在场但模型没有优先级概念。压缩方法本质上就是在帮模型建立"记忆的优先级":什么必须精确保留、什么可以模糊、什么可以随时找回。这三类信息的划分做好了,Agent的长任务执行会稳定很多,后期排查问题也会轻松得多——因为你知道哪些信息是"不在场"的,也知道去哪里把它找回来。
上一篇文章里我们聊了上下文工程的基础边界和常见误区,这次把焦点收缩到Agent场景里最刚需的一环:上下文压缩。为什么单独拎出来写一篇?因为我在实际调试一个数据采集Agent的时候,真真切切被"上下文爆掉"这件事坑过——任务跑到一半,模型开始遗忘用户最初设定的分类规则,把该归入A类的条目硬塞进了B类。排查了半天发现不是规则写得有问题,而是早期指令被后来堆积的工具返回结果挤出了有效注意力范围。这个问题和普通多轮对话里的"聊太长了"完全不是一回事。Agent的上下文里塞的不只是对话,还有工具定义、调用参数、接口返回、中间推理,这些内容增长速度快、单块体积大、信息密度差异悬殊。单纯把窗口拉大或者简单截断,都不能解决根本问题。这篇就围绕上下文压缩方法展开,讲清楚我试过的几条路线、各自的实现细节,以及上线之后踩过的坑。1. Agent上下文为什么比普通对话更容易"撑爆"1.1 上下文里到底装了什么先梳理一个Agent每轮运行下来,上下文里会堆积哪些东西。和普通Chat对话相比,Agent上下文最大的特点是"工作过程"成为主体,而不只是"对话轮次":系统提示词:角色设定、任务规则、工具协议描述,这部分是固定不变的,但往往要占1000到2000 token;用户需求和约束:任务目标、限制条件、输出格式要求;Agent的中间推理:如果用了思维链或者思考过程,每一步的推理文本都会被保存;工具调用记录:调了哪个工具、传了什么参数;工具返回结果:API响应、数据库查询结果、页面抓取文本,这一块是最容易失控的;历史摘要:如果做过压缩,摘要本身也会持续累积。普通对话每轮增长几百token已经算多了,而Agent一轮工具调用可能就涨几千token。一个稍微复杂点的任务调五到八个工具,上下文从1K涨到8K到10K tokens是非常正常的事情。更麻烦的是,工具返回结果往往是块状的大文本——一次搜索返回十条摘要轻松上千token,一个数据库查询接口吐出20条完整记录更是直接吃掉一片窗口。举个例子方便理解。某个多步骤分析任务,初始上下文大概是1800 token(系统提示加工具定义加单条用户需求)。第一个工具调用查完API,涨到3500;第二个工具获取明细,到了6800;第三个工具做关联查询,直接干到11200。三次工具调用,上下文翻了六倍。如果任务有二十步,任何模型窗口都扛不住,压缩不是可选项,是必选项。1.2 上下文撑爆之后的三类典型症状把上下文撑到接近极限,你会观察到三类典型问题,这三类问题我在不同项目里都遇到过:第一是规则遗忘。最早写进系统提示和用户需求里的约束,在长上下文里被"淹没"。模型不是故意的,而是注意力分布天然倾向于最近的内容,开头的长段指令在后期几乎不参与决策。第二是中间内容迷失。论文里讲的"lost in the middle"现象在Agent场景被放大得很明显。模型对开头的指令和结尾的最新内容记得比较牢,中间夹着的一堆工具结果反而被弱化。结果就是:最新的工具结果能被用上,早期的关键中间结论却经常被忽略,导致同一个数据被重复处理。第三是成本与延迟失控。输入越长,按token计费的成本越高,首字响应也越慢。很多团队等到这一步才想起来要压缩,但结构性问题已经体现在账单和用户体感上了。有不少人第一反应是"换一个上下文窗口更大的模型"。这个选择可以缓解问题,但没有解决本质。在一个足够复杂的任务里,不管窗口多大,只要任务足够长,上下文总会被填满。而且窗口越大,模型处理和检索长文本的代价越高,很多长任务场景里甚至不可接受。所以压缩方法本身,才是Agent工程里真正值得投入的设计环节。2. 五条压缩路线的全景对比与选型逻辑在我自己折腾过的方案里,上下文压缩大致可以分成五条技术路线。它们不是互斥的,实际项目里经常组合使用,但你必须先清楚每一条的原理和边界,才能做出合理搭配。2.1 截断与滑动窗口:最低成本,但只适合短任务最简单的办法,保留固定数量的最近token,前面的直接丢弃。实现就是维护一个队列,超过长度就弹出最老的内容。适用场景非常明确:任务本身没有长期依赖,比如单轮问答、短链路的工具调用,早期内容用完即弃。优点是零成本、零延迟,实现也就十几行代码。缺点是信息丢失是硬性的,一旦任务中途需要依赖早期信息——比如用户最初的偏好、前面步骤得到的关键结论——截断就是事实丢失。我曾经在一个多步骤信息核对任务里用过纯滑动窗口,结果就是后期模型反复在没有依据的情况下瞎猜,输出质量完全不可控。所以现在我的原则是:只有在明确知道这个任务不需要跨步骤引用早期信息时,才敢用纯截断。2.2 摘要式压缩:语义保持为主,适合中长期对话用LLM对旧上下文做归纳,生成一段压缩摘要,替代原文继续参与后续推理。摘要保留核心事实、决策和待办事项,这是目前实践中用得最多的一种。优点是语义保持相对好,不依赖外部检索设施,单靠Prompt就能实现。缺点是每次压缩都有token和时间成本,而且摘要必然存在信息损失,模型还可能产生幻觉——把原文没有的内容"补充"进摘要里。这三条决定了摘要式压缩不能无脑频繁使用,也不能对用户约束类信息做自由改写。第3节我会专门展开怎么写好压缩Prompt。2.3 结构化压缩:把自由文本变成高密度字段把一大段自由文本抽取成结构化字段:实体、属性、关系、状态、时间线。工具返回结果经过结构化之后,token占用往往能降到原来的五分之一甚至十分之一,而且信息精度比摘要更高。结构化压缩的适用场景很清晰:工具返回数据量大、字段规整、后续决策只依赖其中部分字段。比如电商数据查询返回20个字段,实际任务只需要其中6个,那就可以把另外14个过滤掉,甚至把6个字段压缩成定界符拼接的紧凑行。缺点也很明显:抽取能力依赖模型质量,复杂语义(比如模糊推理过程)很难转成规整字段,强行结构化会丢失微妙的上下文。这一块我在第4节展开。2.4 检索式压缩:不主动丢信息,需要时再取回检索式方案的本质是不压缩历史本身,而是把历史内容向量化存入外部存储,每次对话按相关性检索出需要的片段,再注入当前上下文。它解决了摘要和结构化的"信息不可逆损失"问题——原始内容一直都在,只是不在主上下文里。缺点是需要额外的基础设施(向量存储、嵌入模型、检索链路),而且召回质量是最大的风险点。检索出来的片段如果相关性不够,模型基于错误信息推理,结果比没有检索更差。所以在Agent任务里,检索式压缩很少单独使用,更多是作为摘要的补充,第5节会聊具体做法。2.5 混合压缩:工程上最稳妥的默认选择把前面几条路线组合起来,通常的形态是:摘要打底 + 关键原文保留 + 按需检索兜底。用户明确指令永远不压缩,最近几轮完整保留,更早的内容用摘要加原始片段混合存放,信息不足时触发检索找回细节。为什么混合形态最稳?因为单一摘要会丢失细节,单一检索在召回失败时没有兜底,两者互补之后容错率大幅提升。代价是系统复杂度上来了,需要同时维护两套存储和两套流程。这个取舍我认为是值得的,尤其是长时间运行的生产Agent。下面这张表是我自己常用的选型参考,具体到项目里可以拿它做初步判断:方法实现成本信息损失依赖检索设施适用场景滑动窗口截断低高否短任务、单轮问答摘要式压缩中中否多轮对话、中等长度任务结构化压缩中高中否工具返回数据量大、字段规整检索式压缩高低是长任务、需要频繁回溯任意历史混合压缩高低可选复杂Agent任务、生产环境3. 摘要式压缩的核心:把Prompt从"总结"改造成"信息保全"摘要式压缩是最容易上手也最容易做坏的一种方法。我见过太多实现,压缩Prompt写得跟"帮我总结一下这段对话"差不多,结果就是token省了不少,任务执行质量也跟着崩了。问题出在对压缩任务的理解上——压缩Prompt不是在让模型"总结",而是在让模型"保全关键信息"。3.1 压缩Prompt必须逼模型干四件事一份真正可用的摘要压缩Prompt,至少要强制模型完成四类信息的提取:事实提取:所有明确的事实性信息,包括实体名、数字、时间点、状态变化,用简洁短语逐个列出;决策记录:所有已经做出的决策和选择,防止后续重复决策或者遗忘决策前提;约束保持:用户明确表达过的要求、规则、偏好、禁止项,尽量逐字保留;待办追踪:正在进行的任务、尚未完成的事项、下一步需要推进的步骤。为什么这四件事缺一不可?因为我踩过坑。早期版本的压缩Prompt只写了"保留重要信息",模型每轮确实能省很多token,但几轮迭代压缩之后,用户最初说的"报告里只放我指定的三个指标",会被逐步缩成"用户需要一些指标",最后变成"按用户要求输出报告"。约束在压缩链路里被一层层稀释,这就是摘要丢失的典型路径。3.2 一份可以直接改来用的压缩模板下面这个模板是我在多个项目里迭代过的版本,结构上没有什么花哨的地方,但每一条都有它的用意:你现在需要把一段旧对话历史压缩成摘要,这段摘要之后会作为Agent系统上下文的一部分继续使用。 请严格按以下结构输出: 1. 关键事实:列出所有实体、数字、时间、状态变化,每项用简短短语。 2. 决策记录:记录所有已经做出的选择及其理由。 3. 用户约束:逐字引用用户提出的规则、偏好、禁止项。 4. 待办事项:列出所有未完成的任务和后续步骤。 5. 一句话概括:用不超过50个字概括当前任务进展。 要求: - 只写原文中确实存在的信息,禁止补充任何原文没有的内容。 - 用户约束必须以原文表述为准,不得改写、不得省略。 - 某一部分没有内容时写"无"。 - 只输出以上结构,不要额外解释。这个模板的关键不是第5条那一句话概括,而是"用户约束逐字引用"这一条。它把最容易在压缩中丢失的信息单独划成了强制保留区,模型没有自由度去改写用户的原话。3.3 多轮迭代压缩:摘要之上再做摘要的节奏任务特别长时,摘要本身也会累积。你不可能无限追加摘要,所以需要在摘要之上再做摘要。这里要处理好分层和节奏。我习惯把上下文分成三层管理:最近会话原文层,保留最近N轮完整对话和工具记录;中期摘要层,把更早的内容合并成一份摘要;长期核心层,放最关键的用户约束和整体任务目标,这部分永不压缩。只有当中期摘要本身超过一定长度、导致上下文总长度再次逼近阈值时,才对摘要再做一次摘要。触发压缩的时机,我推荐"水位线"策略而不是"每N轮压缩一次"。具体做法是:设定一个压缩阈值,比如当前上下文总量的70%,一旦超过就把最早的一半压缩成摘要,最近的对话保持原文。为什么按水位而不是按轮次?因为工具返回大小差异极大,按轮次压缩经常出现该压的时候没压、不该压的时候白白消耗了摘要成本。水位线策略天然适配任务的实际消耗节奏。还有一个实操细节:压缩时需要把原始内容完整喂给模型做归纳,万一压缩对象本身超过模型输入上限,就要分批抽取关键信息再汇总,或者对原始内容先做一轮结构化瘦身再进摘要。总之,摘要的质量上限取决于喂进去的材料质量,这一步不能省。4. 结构化压缩:给工具调用记录"瘦身"的实战做法摘要式压缩解决的是"对话历史怎么归纳"的问题,但Agent上下文里最大的token黑洞往往不是对话,而是工具返回结果。这一节专门讲怎么给工具输出瘦身。4.1 工具返回结果为什么会成为token黑洞我观察过一个典型的失败案例:一个商品信息查询Agent,数据库接口返回20条完整记录,每条记录15个字段,其中8个字段和当前任务完全无关。搜索引擎返回了10条摘要,每条都有完整的标题、链接、发布时间、内容片段。代码执行工具返回了完整堆栈和全部中间输出。这些原始返回直接拼进上下文,一个工具调用就可能吃掉5000到8000 token,有效信息密度却低得可怜。而且这个问题会累积——每次工具调用都这么干,几个循环下来窗口就满了。很多人忽略了一个事实:工具返回结果的处理逻辑,完全是你能控制的,它比对话历史的压缩要简单得多,收益也确定得多。4.2 三层瘦身策略:从调用前到拼装前我对工具输出的处理分三层:第一层,调用前约束。在工具定义和调用参数里就限制返回量:设置limit、指定返回字段、要求服务端做聚合计算。这一层压缩成本最低,效果最直接,但也是被忽略最多的。很多开发者在定义工具时图省事,直接把全量查询接口暴露给Agent,返回多少就吃多少。正确的做法是在工具层做裁剪,让Agent只能拿到它可能需要的最小集合。第二层,调用后裁剪。如果工具返回值已经进来了,再在拼进上下文前用代码规则过滤一遍。最简单的是字段白名单:只保留任务可能用到的字段,其余全部丢弃。稍微复杂一点的场景,可以用一个小模型做关键字段抽取,但这会增加一层延迟和成本,能用规则解决的就别用模型。第三层,格式压缩。把多行JSON或层级结构转成紧凑的单行定界符格式,去掉所有不必要的语法字符和空行。比如一个商品记录,原始JSON长这样:{ "product_id": "P10001", "product_name": "某型号降噪耳机", "price": 1299, "original_price": 1699, "discount_ratio": 0.76, "stock_status": "in_stock", "review_count": 1287, "avg_rating": 4.6, "category": "audio", "specs": { "color": "black", "weight": "254g", "battery": "30h" } }压缩之后可以变成这样:P10001|某型号降噪耳机|1299|原价1699|折扣0.76|现货|4.6分/1287评|黑色|续航30h字段名信息没了,但后续决策需要的数据全部还在。token消耗从200多token降到40左右,压缩比超过80%。4.3 用"决策导向"决定保留哪些字段裁剪掉哪些字段,我有一个核心判断标准:只保留可能影响后续决策的字段,去掉只是展示用的字段。上一条记录里的category,如果任务不需要按类别筛选就可以去掉;specs里的颜色和重量,如果任务不涉及规格比较也可以去掉。判断标准不是"字段有没有用",而是"字段是否可能影响任务接下来的任何一步"。另外我强烈建议把任务的进度状态单独维护一份执行日志,放在上下文的高优先级位置。比如"当前任务进行到第3步:已获取A平台数据、已合并B平台数据、待核验C平台数据"。这条进展信息放在上下文顶部系统提示附近,模型会持续注意到它;放在历史里,很容易被成堆的工具结果淹没。这是我自己在多次调试中验证过的:同样一份执行状态,位置不同,任务成功率能差出好几个百分点。5. 检索式压缩与分层记忆:让信息按需回到上下文结构化压缩和摘要式压缩,本质都是主动丢信息换取token空间。但有些任务场景必须保留随时回溯任意早期信息的能力,比如一个持续数小时、跨多个数据源的数据收集Agent,用户可能随时追问"两个小时前你提到的那条异常记录是什么"。这种时候摘要已经覆盖不了细节,需要检索式方案介入。5.1 工作记忆与长期记忆的分离设计我把Agent的记忆从逻辑上分成两层。工作记忆是当前窗口内正在处理的内容,包括系统提示、最近几轮对话、当前任务的上下文摘要,它是有长度限制的、可被压缩的。长期记忆是所有历史原始内容,向量化后存放在外部存储,不占主上下文,需要时通过检索取回相关片段。这两层的边界要划分清楚:工作记忆负责"当下决策",长期记忆负责"万一需要"。凡是压缩进摘要、但摘要又无法完整承载的细节,都应该落进长期记忆。这样设计的好处是,工作记忆可以维持在一个很小的规模,模型始终在"看得清"的状态下工作,而长期记忆保证了信息不丢,只是从"始终在场"变成了"随叫随到"。5.2 检索式压缩的搭建步骤与关键参数给Agent加检索能力,我建议按下面这个顺序搭,每一步都有坑:内容切块。把每次工具返回、每轮对话按语义切块。块太小,召回结果碎片化,模型拼不出完整上下文;块太大,召回精度下降,一次注入带着一堆无关内容。我常用512到1024字符作为粒度,具体还要看内容密度,密度高的取上限,密度低的取下限。向量化存储。把每块转成向量,连同元信息(来源、时间、当时的任务阶段)一起存入向量存储。元信息很重要,后续过滤和校验都要靠它。检索触发。不是每轮都检索,那样成本和延迟都受不了。我习惯设置触发条件:当前上下文中出现了需要求证的关键词,或者用户问了一个"之前提到过"的问题,才执行检索。触发逻辑写在系统提示里,让模型自己决定何时需要补充信息。结果注入。检索结果按相关性排序后,在系统提示之后注入,作为当前轮次的参考上下文。注入时限制数量,我一般最多取回top3到top5,避免参考上下文本身膨胀。这一步最大的坑在召回质量。如果检索回来的是不相关的片段,模型会一本正经地基于错误信息做推理,结果比不检索更糟。所以在注入之前我会加一道相关性校验——让模型先判断检索结果和当前问题是否相关,不相关的直接丢弃。这道校验会多花一次调用,但能挡住大量错误推理。5.3 摘要兜底加检索补充的混合方案生产环境里最好用的组合,我总结下来是这套:摘要式压缩保任务主线:决策、约束、待办,这些是Agent每一步行动的依据,不能丢。向量存储保原始细节:所有历史原文都在,支持任意时刻回溯。触发式检索补细节:当摘要信息不足以支撑当前决策时,自动检索原始片段回填。兜底策略要明确:检索不到时,直接告诉模型"该细节不在当前可用信息中",而不是让它自由发挥编一个出来。这套混合设计的核心思想是让摘要和检索各管一段:摘要负责当下决策,检索负责历史备份。代价是系统复杂度上来了,要维护两套存储和两套流程,但从任务的稳定性和可排查性来看,这个复杂度换来的收益非常明显。6. 压缩策略上线之后踩过的三个坑与调优建议压缩方案从能跑到跑稳,中间还隔着不少细节。这里聊聊我上线后反复调过的三个问题,也是大概率你会碰到的。6.1 压缩水位调太高或太低都不行压缩触发阈值设到90%再动手,风险很大,因为压缩本身要耗时,摘要生成还有失败概率,得预留出压缩期间的token余量。我建议触发阈值设在60%到70%,压缩完成后目标水位控制在40%到50%,给后续工具调用留下足够空间。算一笔账更直观:假设上下文窗口是32K token,70%触发压缩就是22.4K,压到40%就是12.8K,单次压缩能释放约9.6K token的空间。如果一次工具调用平均吃掉1.5K到2K token,那压缩间隔大概能支撑5到6次调用。如果单次工具调用吃3K到5K token,两个循环就要压一次,这时候就要考虑提高结构化压缩的比例,减少进上下文的原始数据量。6.2 摘要偏差滚雪球:最隐蔽的质量杀手摘要压缩最大的风险是偏差积累。第一次压缩丢掉了一点细节,第二次压缩在缺失信息的基础上又做了归纳,就可能产生错误推断,第三次就会把错误推断当成事实写进摘要。这就是压缩的"滚雪球效应"。对抗滚雪球,我目前用三个办法。第一,原始内容压缩后不要立刻删除,至少在缓存里保留几轮,出现可疑行为时可以回溯对照。第二,摘要生成后做一致性校验——把摘要里的关键实体和原文的关键实体自动比对,发现不一致就重试或回退。第三,对不可压缩信息做显式标记:用户约束、数字、专有名词、时间点,在摘要模板里有固定区块,模型只能原文保留,不能自由改写。这三条同时用,能把摘要漂移的概率压到很低的水平。6.3 压缩质量的评估框架:别只看省了多少token压缩好不好,不是单看token节省率就行的。我自己的评估框架包含四个维度,每次改压缩策略都会跑:信息覆盖率:压缩后的摘要能否回答关于原始内容的事实性问题。做法是拿压缩前后各跑一组同样的事实性问题,对比两组准确率,目标是压缩后不显著下降。约束保持率:压缩后用户指定的规则是否仍然生效。构造一组"违反约束"的测试样例丢给Agent,看它能不能识别并拒绝。任务完成率:在真实任务上对比压缩前后的成功率,这是最直接的业务指标,也是最终唯一有说服力的指标。成本降幅:记录压缩前后的token消耗和调用耗时,用来平衡质量与成本。这四个维度不必每次全跑。我自己的习惯是:压缩策略初次上线和每次有大改的时候用固定测试集全量跑一遍;日常小改动只跑任务完成率和信息覆盖率两个维度,够用了。再分享一个我踩过几次坑之后的体会。上下文压缩不要等到上线出问题再补,架构设计阶段就要把压缩方案纳入。做Agent任务,最怕的不是模型能力不够,而是上下文越跑越乱,所有信息都在场但模型没有优先级概念。压缩方法本质上就是在帮模型建立"记忆的优先级":什么必须精确保留、什么可以模糊、什么可以随时找回。这三类信息的划分做好了,Agent的长任务执行会稳定很多,后期排查问题也会轻松得多——因为你知道哪些信息是"不在场"的,也知道去哪里把它找回来。