资讯详情

Agent上下文压缩实战:从Token成本到质量衰减的取舍策略

📅 2026/10/14 22:32:01 | 华诺云谱 👁 阅读
Agent上下文压缩实战:从Token成本到质量衰减的取舍策略
作为一个常年跟Agent上下文打交道的人我一直觉得“上下文压缩”这词被严重低估了。市面上聊它的人不少但大多数停留在“让模型总结一下历史对话”这种最粗浅的用法。真正上了生产环境你会发现压缩不是“把字变少”而是在跟信息衰减赛跑。这篇文章我不打算讲教科书定义就聊聊我在真实Agent项目里反复折腾上下文压缩的几种做法、取舍逻辑以及那些只有实际跑过才会踩到的坑。1. 为什么长上下文不等于高智商先看清压缩要解决的真实问题1.1 大窗口背后的“成本墙”和“延迟墙”先算一笔账。很多Agent应用跑着跑着就变慢、变贵大家第一反应是“换更大的模型”很少有人意识到问题出在上下文本身。假设你的Agent每轮任务会固定携带一些系统指令和工具定义这部分输入token假设是S然后每一轮对话新增的内容是A。如果采用最笨的全量拼凑策略那么第N轮调用时输入token数大约是 S (N-1) × A。到了第10轮输入量可能已经是第1轮的五六倍到了第20轮直接破万甚至几万。输入token一旦上去最直观的影响就是钱API计费按输入token计费输入量翻倍单次调用成本跟着翻倍底层模型做prefill预填充的耗时随输入token增长响应首字时间明显变长更麻烦的是上下文里塞了太多无关紧要的历史信息后模型注意力被稀释回答质量反而不升反降。这不是凭空担忧。我在一个模拟客服Agent项目里实测过前5轮对话时响应又快又准到第12轮时同样的任务回答耗时涨了三倍而且开始丢掉用户订单号这类关键信息。后来复盘罪魁祸首就是上下文里堆了太多中间过程的“废话”。所以上下文压缩要解决的第一个问题是“钱”和“速度”不是“能不能装下”。1.2 质量衰减模型真的能“看完”长窗口吗很多模型的上下文窗口已经从几万token扩展到几十万甚至上百万但“能装下”和“能用好”是两回事。我在实践里的体感是当输入超过某个量级后模型对中间位置的记忆明显弱于开头和结尾行为表现就像读了一本大部头之后只记得开头和结局中间的细节全模糊了。有人把这个现象叫做“迷失在中间”Lost in the Middle。虽然各家模型在不断优化长文本能力但实际上你不可能赌模型每次都能精准定位到某个被淹没在长上下文里的关键数字。上下文压缩的核心价值就是主动把“模型不需要看到的噪音”去掉让它只需要关注高价值信息这样注意力自然就更集中了。压缩不是为了省钱更是为了质量。1.3 三种压缩层级Token级、语义级、信息抽取级在动手之前先理清概念。市面上的上下文压缩方法五花八门但底层逻辑其实分三个层级层级处理对象典型方法特点字符/Token级原始文本去掉换行、缩写、固定模板裁剪无损或近无损节省量有限语义重写级对话/文本含义用LLM生成摘要、提炼结论压缩比高但可能丢失细节信息抽取级关键事实抽取实体、状态表、向量化检索按需取回动态恢复细节这三个层级不是互斥的生产环境通常会组合使用。我见过最稳的方案是先用语义重写把冗长对话压成“记忆骨架”再配合信息抽取把关键状态变成结构化数据最后在真正需要细节时用检索按需取回。下面几章我就把每一条路的具体做法、提示词设计和适用边界都拆开讲。2. 语义重写型压缩让模型自己“说重点”的具体做法2.1 摘要式压缩的原理与提示词设计语义重写型压缩说白了就是再调用一次LLM把一段长对话改写成一段精简摘要。听起来简单但要做好有三个关键点第一压缩提示词里必须明确“保留什么、丢弃什么、按什么格式输出”第二必须限制输出长度否则摘要比原文还长就没意义了第三要区分“对话事实”和“行为指令”这两个一旦混在一起后续Agent执行任务时行为容易失控。我实际验证过的一套压缩提示词长这样可以直接抄你是上下文的精炼器。下面将给你一段Agent与用户的多轮对话记录。 请提炼并输出一个结构化摘要要求如下 1. 只保留与任务目标直接相关的事实信息包括用户的明确需求、关键参数、已确认的决策、尚未完成的事项、用户的偏好或禁忌。 2. 丢弃寒暄、重复、纠错过程中的中间态、与目标无关的闲聊。 3. 输出格式为JSON字段包括user_goal用户的核心目标、confirmed_facts已确认的事实列表、pending_tasks待办事项、preferences用户偏好、tone当前对话语气。 4. 全文字数控制在300个中文字以内不要解释直接输出JSON。这里面的“偏好字段”特别容易被忽略。有一次我在压缩时没保留用户“请用简短语气回复”的偏好结果压缩后的Agent回复风格一下子变了用户感觉很突兀。后来我把偏好单独建了一个字段问题就解决了。2.2 增量压缩不要每次全量重来一个常见的低级错误是每次要压缩时把从第一轮至今的所有对话丢给模型再压一遍。这在对话轮次多的时候会带来两个问题一是费用极高反复为同样已经被压缩过内容重复付费二是摘要会“二次污染”模型在压缩一段已经包含摘要的文本时很容易把摘要里的措辞当作事实输出造成信息漂移。更好的做法是滚动增量压缩。维护一个“长期记忆长期记忆段”每次新对话产生后只把“最近一轮新增内容”与“上轮末尾保留的近期摘要”一起压缩合入长期记忆。我举个例子伪代码类似这样long_term_memory [] # 已经压缩完毕的长期记忆 recent_buffer # 最近几轮的原始对话暂存区 def on_new_turn(new_text): # 缓冲区累积到指定长度时触发增量压缩 recent_buffer new_text if token_count(recent_buffer) 800: global long_term_memory long_term_memory compress(long_term_memory recent_buffer) recent_buffer # 压完清空缓冲区 return build_prompt(long_term_memory, recent_buffer)这样每一轮输入给主模型的上文就是“长期记忆摘要 最近缓冲区的原文”而不是一路堆到天荒地老。长期记忆可以限制在几百token之内缓冲区几百token整体上文保持在一个稳定范围成本和质量都更可控。核心思想是压缩要分频次进行而不是一次性大扫除。2.3 结构化摘要让压缩结果可被程序消费纯文本摘要有一个天然缺陷下游程序很难直接判断“用户偏好”到底存不存在。如果直接把摘要塞回上下文主模型可能把摘要里的过渡语也当成事实。所以我现在做语义重写型压缩时输出格式基本都强制要求JSON化。JSON化的好处至少有四个后续程序可以直接用JSON字段来控制prompt模板比如有preferences字段就拼进系统提示没有就跳过压缩结果可以被二次压缩字段名本身就是一种“锚点”防止摘要串台方便对不同字段设置不同的更新策略比如confirmed_facts每次压缩时追加preferences则覆盖更新做日志和审计时能精确追溯某条信息是哪一轮确认的。当然JSON化有一个代价它要求压缩时保留的字段维度足够通用。如果任务太特殊字段会膨胀最后变成“压了个寂寞”。我在项目里常用的做法是维护一个核心字段集合再加上optional字段遇到新的信息类型就动态扩展。3. 关键信息保持型压缩抽取、检索与结构化存储3.1 从“让它写摘要”到“让状态表自己说话”语义重写型压缩有一个天花板它把事实和表达方式打包在一个摘要里保真度取决于模型的理解能力和摘要的篇幅。对于强任务导向的Agent来说很多信息本质上不是“文本”而是“状态”——订单号、用户地址、金额、审批状态、待处理步骤。这些用摘要表述属于杀鸡用牛刀。我在几个业务流程类Agent里更倾向用“状态表抽取”的方式做压缩让模型每轮结束后只抽取出特定字段并写入一个结构化的状态记录整个会话的状态由这张“表”兜底。比如一个售后处理Agent状态表就是当前用户张三 关联订单OD-2024-XXXX 用户诉求退货退款 已确认事实订单已签收3天、商品存在外观瑕疵 待办事项上传凭证照片、等待仓库质检结果 当前阶段等待用户上传照片每次对话结束时更新这张表传给主模型的“历史”就可以从几千字压缩成这不到一百字的记录。这比任何摘要都高效因为框架本身就决定了哪些信息值得被记住。用生活化类比摘要是“重述一段故事”状态表是“保管一张档案卡”。故事留着总会出现偏差但档案卡更新规则清晰永远指向最新状态。3.2 检索增强的懒压缩需要时再取回第三种思路比较取巧叫懒压缩——平时不压缩而是把原始对话按块切分、建立索引等到真正需要某段细节时再快速取回。这其实就是RAG的思路用在“记忆”上而不是用在“文档库”上。具体实现也不复杂把每一轮对话封装成一个带序号、时间戳和关键词标签的chunk放进内存里的检索器。普通场景用BM25就能取得不错的效果如果文本语义关联很强就配一个向量检索。当主模型需要了解某个细节时我们先通过检索找到最相关的top3个chunk作为“临时回忆”拼接到当前上下文里。这个方案最大的优势是几乎不损失信息所有原文都还在只是暂时不进入主模型的视线。但它也有前提——Agent必须能判断“我需要哪段记忆”这个判断本身可能需要一次推理。实践中的做法是在主模型回答之前先安排一个轻量级的“检索决策”比如硬编码规则检测到当前用户提问涉及订单号/人名/日期时触发检索。这种规则式触发在多数场景足够了。3.3 底层视角KV Cache与缓存命中压缩的“物理基础”前面聊的都是应用层压缩。但在真实部署时还有一个底层因素不可忽略就是KV Cache。大模型在生成回复时需要把输入的token逐层加工成Key和Value缓存这个缓存越大prefill阶段越慢而提示词压缩做得越好KV Cache越小首字延迟越低。此外很多API服务商会对“相同的输入前缀”做缓存命中缓存命中的部分收费更低、速度更快。这给了上下文工程一个启发把固定不变的内容系统指令、固定工具定义放在提示词最前面把变化频繁的上下文放在后面。这样即使后面的上下文不断变化前缀部分还是能命中缓存。我踩过的一个低级错误是把动态时间戳插在系统指令和工具定义之间导致整个前缀失效缓存一次没命中成本凭空高了一截。后来把所有动态内容全部挪到提示词尾部同时用分隔符明确划分“静态指令区”和“动态上下文区”这个问题才解决。单看这不算“压缩”但它决定了你的压缩结果能否真正降本增效。4. 上下文压缩的权衡指标与选择策略什么时候该狠压、什么时候该保守4.1 三种压缩方法的横向对比聊完了三种主流路线先放在一张表里对比一下对比维度语义重写型压缩状态表提取检索增强懒压缩信息密度高很高中按需取回保真度中依赖模型理解高字段明确高保留原文实现复杂度低中高压缩成本每次压缩需调用一次模型每轮需一次轻量抽取切块索引无需LLM压缩适用场景闲聊型、通用助手强流程/强任务型Agent需要频繁回查细节的场景主要风险细节丢失、摘要串台字段设计不合理则僵化检索效果差则遗忘这张表是我在项目里反复调整后的一个比较准的画像。但注意实际系统通常不是单选而是把其中两个组合。比如我先用状态表管“硬事实”再用语义摘要管“软偏好”最后拿检索兜底“偶发但重要的细节”。4.2 压缩比例与质量损耗两个参数决定一切在真正落地时我个人觉得最需要盯住的参数只有两个压缩比例和压缩频率。压缩比例指的是压缩前后token数的比值。太激进比如压到原来的1/20往往会把关键细节一起压没太保守比如只压到原来的1/2又没省下多少钱。我常用的经验值如下对话型任务控制在 1/5 到 1/8压缩后摘要保持300-500 token比较合适强事实型任务状态表压缩后通常只有几十到一两百token信息密度极高文档处理型任务如果压缩的是长文档而不是对话可以压到1/10但必须有“原文可检索”的兜底。压缩频率则需要考虑两个因素一个是成本压得越频繁调用LLM越多另一个是信息新鲜度压得太久不压缓冲区可能膨胀到失控。我在生产里常用的策略是“双阈值触发”当缓冲区超过预设token阈值比如1500时立即压缩同时每次对话轮次达到3-5轮时强制压缩一轮不管缓冲区大小。两条规则取先到者效果很稳。4.3 什么时候不压缩不压也是一种策略这句话听起来像废话但其实现场很多工程师就是没忍住手。我在复盘好几次线上事故后发现有几类场景真的不适合压缩对话轮次少、上下文短总共才三五轮加起来不到2000 token压了反而增加一次额外调用延迟和成本都变高。高精度任务比如对原文有强依赖的合同条款解读、数值核对、代码片段修改。这里任何细节丢失都是不可接受的应该优先检索原文而非让摘要顶替。单轮长输入但结构清晰比如用户一次性给了一篇JSON配置压缩它毫无意义该做的是解析和校验。Agent本身已经很强有明确指令、少量历史、边界清晰任务上下文根本构成不了负担不必硬压。一句话总结我的取舍基准上下文压缩是在“不合理的长上下文”和“可接受的丢失风险”之间找平衡如果长上下文本身不是问题就别创造压缩问题。5. 实测经验压缩比例、质量损耗与踩坑记录5.1 一段真实对话的压缩案例从3000 token到400 token拿我在一个模拟售前咨询Agent里实际跑过的一段对话举例。这个Agent的任务是让用户描述企业需求然后输出方案建议。对话大概进行了10轮累计原始上下文约3000 token其中包含大量用户来回斟酌的表述、Agent试探性的追问和几次需求修正。我用语义重写型压缩后输出摘要约400 token信息覆盖情况如下用户的核心业务场景从电商换到线下门店保留预算范围保留期望上线时间保留已否决的方案A及原因保留这个很关键否则第二轮方案会再次推被否决的方案用户当时的犹豫语气丢弃寒暄和“嗯嗯”“我再想想”丢弃压完之后主模型上下文从3000 token降到大约1200 token摘要400 token 系统指令600 token 当前轮对话200 token单轮调用成本降了60%左右响应速度明显提升。关键是后续连续追问了三个需求细节Agent的回答都基于压缩摘要保持了逻辑一致没有出现记忆混乱。5.2 我在Agent任务里踩过的三个坑坑一行为指令被当成对话内容压掉了。有一版压缩提示词里没有“偏好”字段结果用户之前交代过“回复要简短不要罗列一堆选项”这个行为偏好被当作无关信息删掉了。压缩后Agent开始长篇大论。教训是压缩前先盘点所有影响模型行为的信息类型行为指令的优先级高于对话事实。坑二摘要串台。这个特别容易发生。当一个会话里有多个子话题时整段压缩会把“话题A的结论”错误映射到“话题B”。我后来做了两处修正压缩时要求按子话题分别输出段落每个段落打上主题标签压缩结果里禁止跨主题合并描述。现在再没出现过串台。坑三反复嵌套压缩导致信息递减。长期记忆已经是一份摘要滚动压缩时又把“长期记忆 新增内容”压缩一遍相当于对摘要再做摘要几次之后原始细节丢得差不多了。后来我加了“压缩停止条件”长期记忆本身不参与二次压缩只在新增内容触发时把新增部分压入长期记忆。简单说就是压一次就入库不要反复翻炒。还有一个不算坑但很容易忽略的点压缩后的输出里如果包含很多“布尔断言”比如订单状态“已发货”后续Agent若没有时间概念可能会把这个状态当成永久事实。我在状态表里专门加了update_time字段每次被检索到时能看到“这个事实是哪一轮确认的”避免推理链条跑偏。5.3 压缩结果加载恢复上下文的顺序比压缩本身更重要最后聊一个细节压缩完怎么把上下文“拼回去”。这步做不好前面压缩得再好也会翻车。我目前验证下来比较稳的上下文结构是四段式1. 系统指令区固定角色设定、任务规则、输出格式 2. 长期记忆区语义摘要 状态表 3. 检索片段区最近一次检索取回的关键原文块可为空 4. 当前对话区最近几轮原始对话这个顺序有几个讲究最前面放固定指令是为了利用缓存命中的前缀特性长期记忆作为“历史事实背景”放第二层检索片段一定要用醒目标记比如search_result标签包起来并注明“这些是检索到的原始记录仅作参考”当前对话放在最后因为主模型对末尾信息的注意力最强最新的用户诉求必须离它最近。我见过一个项目把压缩摘要放在最前面系统指令放中间用户新诉求反而被埋在中后部结果模型对“当前任务”的感知变弱老是把用户的需求放到历史诉求的框架里去执行。整个上下文工程的最后一公里就是让你想让它做的事恰好出现在模型最容易注意到的位置。我个人的体会是上下文压缩不是一道“让模型变聪明”的魔法它更像一套信息衰减策略。你要先想清楚哪些信息衰减了无所谓哪些信息必须永久保留然后再去选压缩方法。选错了方法省下的钱会在质量事故里成倍还回去选对了Agent会明显更稳、更快、更可控。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑