尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
从12万到1万Token:Agent上下文稀疏化读取优化实战
说实话我最近在给项目的客服工单处理Agent做成本收敛最终实现了单任务Token消耗从12万降到不到1万整体降低92%。这个结果不是因为换了更便宜的模型也不是简单粗暴地把上下文裁短——恰恰相反我最早尝试的“截断方案”不仅没有省钱反而把任务成功率干穿了一大截纯纯的负优化。SparseRead这个名字是我内部给一套“稀疏化上下文读取”组件起的代号核心思路是把Agent每一轮看到的Prompt从“全量搬运历史”改成“按需检索关键片段”。这篇文章就把这个优化从头到尾复盘一遍包括踩过的坑、调参记录、上线后遇到的各种问题以及那些常规技术文档不会写但实际很致命的细节。如果你正在跑多轮Agent调用、处理长上下文任务或者被大模型账单吓到过这篇文章应该能给你一些可以直接抄作业的思路。1. 项目背景与问题定义1.1 上下文里到底塞了什么东西先说我接手的项目长什么样一套客服工单处理Agent负责接用户消息、查订单、查售后规则、调用内部系统改状态、最后回话。业务逻辑不算复杂但有一个特点——每一轮任务都几乎要引用前面所有的对话。我拉了一下当前线上调用的完整请求体发现单任务跑到结束平均要给模型灌124k Tokens。我当时以为这些内容都是必要的直到我把一次完整的请求体导出来做了逐行标记才发现里面真正和最终回答相关的信息量少得可怜。具体拆开来看这些Token主要花在四个地方历史消息全文用户说了什么、Agent上一轮回答了什么全部原样带上。多轮之后这部分会越滚越大。工具返回的原始结果查一次订单返回的JSON里可能包含几十个字段但Agent最终只需要其中的订单状态、收货地址和联系电话。知识库片段每次调用都把整套售后规则前缀拖进来而实际上当前用户可能只是想修改收货地址。提示词模板系统指令、输出格式说明、工具定义这些还算合理但也架不住每轮都原样重复。我把某一次真实任务的Token消耗做了个比例拆解历史消息占37%工具输出占29%知识库片段占23%提示词模板占11%。也就是说模型每处理100个Token大约有89个是在“复习”之前已经处理过或根本用不到的内容。这个浪费是系统性的。因为Agent的调用不是一轮就结束而是十几轮交叉进行每一轮都要把前面的所有上下文重新发一遍给模型。于是消耗不是线性增长而是近似平方级增长。第1轮可能只有2k Token第15轮就可能膨胀到30k整体成本大头全在尾巴上。1.2 成本为什么会越滚越夸张当时看到账单曲线我的第一反应是查哪里出现了重复计费。后来发现结论更扎心——大模型服务商并没有重复收我钱是我自己每轮都在把同样的信息重复塞给模型等于主动重复付费。举个例子Agent第3轮查询订单拿到一个66字段的JSON结果模型成功提取出订单号“A10086”然后第4轮要改地址于是我又把那个66字段的JSON原封不动带上了只是为了确保模型“还记得订单号”。第5轮要确认退款再带一次。一个订单能连续陪伴模型五轮每次都是全量登场。更要命的是知识库片段。当时为了让Agent稳定遵守售后规则我把一套共计40k Token的规则手册压缩成“核心摘要高频条款”还是习惯性地每轮都注入。结果就是哪怕用户只问一句“能不能今天发货”模型也要先读完40k的规则才能回答稳定性是有了成本也上去了。所以问题的本质不是某一个环节浪费而是整个上下文管理缺少“时效”和“相关度”的概念。我们把持久存储、短期记忆、即时输入全混在一个扁平的Context里让模型自己挑重点。1.3 第一个直觉方案反而成了负优化既然上下文太大最自然的想法就是截断。我当时做了个简单粗暴的版本设置一个最大上下文长度4k超出部分直接从头部丢。结果很惨。上线灰度了一天任务成功率从原来的94%掉到71%。模型开始“失忆”用户第2轮重新问问题Agent已经不记得前面给过的收货地址处理退款时因为找不到前几轮的订单ID开始编造订单号更离谱的是有一次Agent连续给用户发了三条内容完全重复的解决方案因为它已经“忘了”自己回复过什么。这个时候我才意识到一个问题上下文对Agent来说不是“历史记录”它更像是短期工作记忆。模型本身就是无状态的我给它多少上下文它就只能在这段上下文里工作。粗暴截断等于不分青红皂白地把一个助手脑子里的记忆挖掉一块它不犯错才奇怪。这个负优化阶段让我明白了一件事省Token不能靠“丢信息”要靠“让模型总能低成本地拿到它真正需要的信息”。这才是SparseRead要解决的核心问题。2. SparseRead的整体设计思路2.1 把上下文当数据库而不是当流水账想通了上面那段设计方向就定了不能把上下文看作一份从头到尾的日志文件每次调用都整份发送而要把它看作一个数据库Agent每次“提问”也就是Round开始前时只查询并读取当前这一步所需要的记录。我把这个思路落实成SparseRead的两层结构持久层完整保存全部会话数据包括每一条用户消息、每一条Agent回复、每一次工具调用的原始输出、每一次知识库命中结果。这部分不进Prompt只进存储。工作集每一轮真正发送给模型的内容由SparseRead根据当前意图从持久层里“查出”并拼装成一份紧凑的上下文。两者的关系相当于你不会每一次开会都抱着过去三年的会议纪要进会议室而是只带“上次会议结论”“本次议题相关附件”“待办清单”这三样。持久层是档案室工作集是这次会议桌上的文件。这个转变不只是省Token它还改变了Agent的稳定性模型。过去是“把所有可能用到的信息都堆上去让模型自己挑”现在是“信息按需到达模型只需把注意力放在当前任务上”。前一种方式在上下文足够短时还勉强能跑一旦跨入百万Token级别的复杂任务模型几乎必然被无关信息干扰出现幻觉回答或者关键信息漏读。2.2 稀疏索引和按需读取SparseRead的关键组件是一个伴随会话构建的稀疏索引。每收到一条新消息、产生一次工具调用系统就会抽取它的元信息写入索引而不是把原文写入索引。索引里存哪些内容我最终定下来的是六类字段消息类型用户消息、助手回复、工具结果、系统事件。来源标识工具名称、知识库文档ID、会话内序号。时间戳精确到分钟用于做时间衰减权重。核心实体订单号、用户ID、地址、金额等业务关键字段用正则词典抽取。内容摘要调用一个轻量小模型生成20-50字的摘要并缓存下来。状态标记如“已完成”“待确认”“失败重试”用于处理状态类查询。到了每一轮要拼Prompt时SparseRead做三件事解析当前最新用户消息和当前待调用的工具名生成查询条件。在稀疏索引里做“关键词匹配 向量相似度 时间衰减”的综合排序取前15条候选记录。根据预算比如本轮上限6000 Token裁剪候选并把这个预算逐条分配进去。这套结构其实很像RAG但有个关键区别RAG面对的是外部知识库关注“哪些文档和问题相关”SparseRead面对的是Agent自身的会话历史和工具输出关注“哪一条消息、哪一次输出对当前动作最重要”。两者可以叠加使用RAG解决“不知道”SparseRead解决“记不清”。2.3 为什么要保留一个工作集而不是全量检索一开始我怀疑过既然都做索引了干脆每次只发检索出来的三五条信息就够了为什么还要保留一个“工作集”因为Agent的执行是有连续性的。比如用户在第五轮说“那不用改了还是用刚才的地址吧”——这里的“刚才的地址”如果只按关键词检索很可能命中的不是地址本身而是一条无关对话。模型在没有足够近期上下文的情况下会分不清“刚才”到底指哪一轮。所以我最终给SparseRead设计了一个很小但不可省略的“近期窗口”总是保留最近5条原始消息目的是让模型至少知道当前对话进行到了哪一步。在这个窗口之上再用稀疏索引去补充更早但相关的信息。这相当于人脑的“工作记忆区”和“长期记忆区”分离短期窗口保证连续性长期索引保证知识可达。2.4 为什么敢叫“稀疏”和全量的本质差异名字里的“Sparse”源自整个过程的信息处理方式全量是Dense每条信息无差别占地位Sparse则是每条信息只有“被需要”时才进入上下文权重天然不同。从开销模型上可以简单算一笔账。原来N轮任务每轮都带上前面的所有内容总Token消耗大概是O(N^2)。SparseRead把每一轮的上下文规模限制在一个预算B内总Token消耗就变成了O(N×B)而且由于信息是精准的模型往往还能更快收敛、减少重复工具调用。这也是为什么后面能做到92%的Token下降不是靠“每轮少一点”而是直接把复杂度等级降了下来。3. 三块最核心的实现细节3.1 索引构建抽取、摘要、状态标记索引构建是SparseRead的第一道工序。如果索引里的字段是脏的后面的检索决策全是空中楼阁。先说过滤。工具返回的JSON内容通常包含大量无用字段我在接到工具调用结果时先根据每个工具预定义的Schema做字段裁剪。比如查订单接口返回66个字段但Schema里只保留订单号、订单状态、收货人、联系电话、商品清单、支付金额这几个核心字段其余一律不写入原始存储。这一步就能砍掉工具输出里70%的冗余。然后是摘要。不是所有上下文都需要全文进入索引。我对历史消息和工具结果做的处理是正文继续持久化到磁盘索引里只放摘要。摘要由一个小模型完成每条约消耗30-60 Token。这个开销相比于原始正文动辄几百Token是值得的。但摘要有一个坑如果摘要生成得太“抽象”后面的检索就会丢细节。比如工具结果里明明有个退款状态“审核中”摘要却写成“用户查询退款进度”那后续Agent想拿这个状态做判断时检索满意度就会下降。解决办法是给摘要加模板约束。我对工具结果类消息强制套用以下格式[工具名] 目标对象订单号/用户ID 执行状态 核心字段提取实测下来这种结构化摘要的检索命中率明显高于自由文本摘要。因为它确保了关键业务字段一定出现在摘要里查询时也更容易被关键词直接命中。3.2 读取决策怎么决定“该带什么进Prompt”这是SparseRead最花时间的部分。我最初实现了一个很朴素的读取器把用户最新消息里的关键词抽出来去索引里做关键词匹配然后按分数取top 15。但效果不太行问题出在两个地方一是关键词匹配对同义改写无能为力二是没有考虑时效性导致经常拿到很早以前的聊天记录而看不到最近一次修改后的地址。后来改成了“三路打分取加权和”的机制关键词命中分数衡量查询条件和索引实体、原文的文本重合度。向量相似度分数用一个384维的轻量Embedding模型对所有候选做语义召回。时间衰减系数按消息时间计算权重例如6小时前权重0.824小时前权重0.3对“最近状态类”查询时间权重会更高。最终每条候选的分值是三者加权相加。权重不是从一开始就定死的而是用历史日志离线跑了几十次对比后调出来的对于客服工单场景关键词0.35向量0.4时间衰减0.25最稳。再提“预算分配”。Prompt不能无限膨胀我设定了每轮Token预算开始给的6000。实际操作顺序是先把最近的5条原始消息放入工作集约占2000 Token再用预算扣除工作集后剩下的空间按分数从高到低填充候选记录如果候选记录太多优先级低的摘要只保留标题和一句话摘要不再展开正文。这就像一个整理会议桌的过程先放必须的文件近期窗口再把其他相关文件按优先级一张张放上去桌子放不下时只放在手边能看到标题。3.3 强制保留机制防止“关键的最后一公里”被挤掉纯靠分数排序会有一个盲区某些字段虽然文本上不算高相关但对业务操作是强制必需的。最典型的就是订单号、用户ID、退款状态这类“操作锚点”。我把它们做成一个force-include白名单。无论检索打分结果如何只要当前会话曾经出现过这些字段且字段值有更新就会以独立条目强制进入Prompt。这样处理退款和改地址时模型一定知道操作对象是谁。白名单会造成一定额外的Token消耗但我实测大概只占整体节约量的百分之几完全可以忽略。宁可多带这三个字段也不能出现“模型一本正经地编造订单号”这种事故。3.4 二次读取路由让模型自己承认“我缺信息”即使做了以上所有机制仍然存在一些检索漏配的场景。早期方案的做法是“漏了就漏了模型自己看着办”后果是模型会在缺乏证据时强行回答产生大量幻觉。后来我加了一个“二次读取路由”Prompt模板里固定加了一段说明告诉模型“如果当前提供的信息不足以支撑回答你可以在回复中输出一个特殊标识RETRIEVE_MORE系统会拦截这一轮输出进行额外的稀疏检索并补充上下文后重试”。这段说明在技术上是微不足道的改动却把任务成功率从88%拉回91%以上。它本质上是把“检索失败”显式暴露出来而不是隐藏到模型的幻觉里。当然为了防止模型滥用这个能力我额外限制了同一个任务最多触发二次读取3次。4. 从负优化到最高减少92%的调参过程4.1 先建立指标别急着看效果优化开始前我先定了一套评估指标没有指标就没有判断标准。最终沿用下来的有四个单任务平均Token消耗成本指标。任务成功率指Agent能在给定轮次内完成工单处理并生成靠答案由人工抽审规则校验。平均工具调用次数间接反映模型是否因为丢失上下文而反复查询。首轮检索命中率SparseRead读取的关键条目是否被真实需要。评估方式是离线回放把生产环境的历史任务日志完整存下来模拟Agent重跑对每一版方案打分。离线回放的好处是不会影响线上用户。4.2 三个版本的演进数据这个优化过程不是一步到位的中间经历了三个大的版本我把数据整理成一张表版本平均单任务Token任务成功率平均工具调用次数Token较基线下降基线全量上下文124,80094.2%5.8—v1硬截断4k42,10071.3%7.9-66.3%负优化v2朴素检索召回68,40088.6%6.1-45.2%v3SparseRead完整版9,60091.5%4.2-92.3%v1看起来Token少了一大半但成功率掉了23个百分点这就是负优化钱“省”出了表面数字业务上却变成了灾难。v2加入了按需检索效果恢复了一些但因为没做预算控制和强制字段冗余仍然很多。真正达到理想状态的是v3把预算控制、近期窗口、白名单、二次读取全部加上后Token降到十分之一以下成功率只比基线掉了2.7个百分点工具调用次数还因为信息精准而变少了。4.3 关键参数的选择逻辑SparseRead里可调的旋钮不少我说几个影响最大的。先说TopK。之前用的15条候选看起来是经验值实际是跑出来的候选太少会漏关键信息候选太多预算很快就满了后面的优先级排序失去意义。在客服场景数据流里15条是一个收敛点超过15条后成功率基本不再提升反而是Token成本继续增加。然后是预算值。起步设定6000 Token是因为测算发现一次工单任务的必要执行信息大约在4000-6000 Token之间低于4000就很容易触发二次读取。这个预算不是铁律不同场景需要分别测。还有时间衰减的半衰期。我用的是6小时意思是6小时前的信息权重降到一半。客服场景用户可能隔一天再问“上次那个地址”6小时显然太短后来改成24小时并区分查询类型状态查询用短半衰期实体查询用长半衰期。这一点也算是在调参过程中踩出来的细节。4.4 负优化阶段做错了什么复盘v1硬截断的问题核心不是“截断”这个概念错了而是“无差别截断”错了。我做的是按位置从头丢弃但Agent需要的信息分布在整段对话的各个位置最早的用户需求、中间的工具返回、最后的状态变更。v2的朴素检索也有问题它把“检索结果”和“原始上下文”割裂了检索命中的条目被当作最新信息传进去但模型没有足够的共现上下文不知道哪些信息已经被更新过。比如索引里同时存在两个地址一个来自第2轮一个来自第8轮朴素检索容易两个都命中模型就迷茫了。这些负优化经历让我形成一个判断标准任何一种上下文优化都必须保证“信息在需要时可用”而非“通过降低信息量来降低成本”。如果你的优化方案让模型不得不在缺信息的状态下做决定那就不要做。5. 上线后的问题排查与避坑实录5.1 一个必须检查的深夜事故SparseRead上线后并不平静。有一次凌晨监控告警系统连续喊了十几条工单处理失败我打开日志看到典型症状Agent在处理退款时报出“订单状态未知无法执行退款”。按道理订单状态是白名单字段不应该丢。排查后发现原因很丢人当晚项目发了一个新版本重构工具接口时把订单状态字段改名成了statusNew但白名单配置里还写的是orderStatus。Schema一改字段没抽取出来白名单自然也就没生效。这个事故给我的教训是任何涉及字段名、接口Schema变动的发布都必须同步检查SparseRead的抽取模板和白名单配置。现在我把Schema抽取配置和白名单放进了同一个配置中心并在每次发版时做自动校验。5.2 高频问题速查表把上线三个月遇到的问题整理成一张速查表方便碰到类似情况的人快速定位症状可能原因解决办法Agent操作订单时找不到ID关键标识字段未抽取进索引确认白名单配置检查工具Schema映射用户问“刚才那个”时答非所问指代消解依赖的近期窗口被裁掉把近期窗口从3条增加到5条时间相关任务命中旧信息时间衰减权重设置不当区分状态查询和实体查询分别设定半衰期模型输出重复内容预算过小或二次读取被禁用适当提升预算检查二次读取触发统计Token下降但成功率波动大摘要丢失了状态字段摘要模板强制加入状态和核心字段5.3 三个印象深刻的坑第一个坑是“唯一ID被摘要吞掉”。早期的摘要生成自由度太高把订单号这类字符串自动归类为“数字”导致检索时无法精确命中。后来我直接让摘要模板强制保留白名单字段原文不再让模型做泛化。第二个坑是“最新状态被旧摘要覆盖”。用户先咨询退货后来又改主意不退了结果检索到的摘要还停留在“用户想退货”的旧版本。解决方式是在持久层的每条记录里维护一个状态版本号最新状态进入摘要的时间晚于旧状态时旧状态自动降权。第三个坑是“模型完全依赖近期窗口”。初期近期窗口保留了10条原始消息模型面对窗口里的冗余历史开始不信任索引提供的信息频繁要求再次补充。把近期窗口从10降到5后模型的行为才回归正轨。这提醒我保留的原始上下文越少模型的注意力才会越集中到检索结果上。5.4 灰度与兜底策略SparseRead这类组件做灰度最怕的不是效果不稳定而是出了问题后没有退路。我在设计里加了一个开关当SparseRead的命中率连续低于阈值或者任务失败率超过设定值自动切回全量上下文模式并发送告警。这个兜底开关让产品在极端情况下不会彻底瘫痪。灰度策略分三步第一批放5%流量跑24小时观察工具调用次数和人工抽审结果确认无异常后放到50%再跑48小时后放全量。每一步都同步看离线回放指标和线上指标两者不一致时线上为准因为离线回放无法完全模拟用户输入的新变化。6. 后续扩展方向与个人体会6.1 还能继续优化的空间SparseRead当前版本已经跑得比较稳但我觉得还有几个方向可以继续做。一是工具输出Schema自动生成。现在每个工具的字段裁剪还是手工配置如果工具有几十个接口维护成本不低。理想状态是从接口文档自动推断输出模板核心字段自动进入白名单。二是摘要模型的轻量化。目前引入的小模型摘要大约占额外3-5%的Token消耗如果把摘要生成合并到工具输出阶段复用信息抽取结果应该还能省一笔。三是和缓存策略配合。一些高频知识库片段、固定规则和工具定义可以使用精确缓存命中来避免重新计费。SparseRead已经让重复信息从检索结果中消失了再往上加缓存属于锦上添花。6.2 我的真实感受回头看从负优化到最高减少92% Token最大的收获不是这个数字而是我重新理解了“上下文”在Agent系统里的位置。它不是一个可以随意删减的“输入参数”而是模型推理时唯一能依赖的外部记忆。剪掉40%的无用Token固然好但如果你无法保证那40%里没有关键信息就不要剪。SparseRead能做到现在这样主要原因是它把“删减”变成了“选择和重放”——模型的信息来源从被动接收变成了主动读取。还有一点做Token优化必须先把日志和离线回放体系建好。没有回放你根本无法判断一次改动究竟是优化还是负优化。我亲眼见过许多人拿着“Token下降了一半”的A/B结果沾沾自喜完全忽略同期任务成功率可能已经崩了这正是最容易掉进去的陷阱。数据先行指标带队兜底开关保命——这十二个字是这次优化全程最重要的经验。
RELATED

相关推荐

鸿蒙开发转大模型:端侧小模型和云端大模型怎么分工

鸿蒙开发转大模型:端侧小模型和云端大模型怎么分工

版权与内容来源声明 本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部…

📅 2026/10/14 11:16:48
Java DAO层测试优化:连接池复用与事务回滚实践

Java DAO层测试优化:连接池复用与事务回滚实践

1. 项目概述 在Java企业级应用开发中,DAO(Data Access Object)层作为数据持久化的核心组件,其稳定性和可靠性直接影响整个系统的质量。然而在实际开发中,DAO层测试往往面临连接泄漏、事务污染、测试耦合度高等典型问题。本文将分享一套经过生…

📅 2026/10/14 11:11:45
高阶迭代最小二乘波前重构:有限差分模型与IRLS鲁棒求解

高阶迭代最小二乘波前重构:有限差分模型与IRLS鲁棒求解

简介:针对哈特曼波前传感器数据的高精度波前重构需求,这份资源提供了一套基于有限差分法与高阶迭代最小二乘积分的MATLAB实现方案。算法通过有限差分估算波前局部曲率,再以最小二乘积分迭代优化模型参数,适合光学系统设计、大气湍…

📅 2026/10/14 11:11:45
MORE NEWS

更多资讯

📰

MSeg论文翻译全流程实战:语义分割复合数据集技术解析

MSeg这个项目我前后折腾了整整两周,从最开始的读完就忘,到后来恨不得把每句话都拆开揉碎,期间踩了不少坑,也攒了不少经验。这篇论文翻译不是简简单单拿个翻译软件跑一遍就完事,它涉及大量视觉领域的专业术语、数据集构…

📰

分布式电源与电动汽车充电站协同规划优化方法

1. 项目背景与核心挑战 在新型电力系统加速建设的当下,分布式电源(DG)与电动汽车充电站(EVCS)的协同规划已成为行业热点。传统配置方法往往将两者割裂考虑,忽略了充电负荷特有的时空灵活性。我们团队在参与某园区微电网设计时发现,当DG渗透率…

📰

分布式电源与充电站协同规划方法及Matlab实现

1. 项目背景与核心价值 在新型电力系统建设背景下,分布式电源(DG)与电动汽车充电站(EVCS)的协同规划已成为行业热点课题。传统规划方法往往将两者割裂考虑,导致配电网资源利用率低下、投资浪费等问题。我们团队开发的这套联合配置方法,首次将…

📰

SpringBoot+Vue停车场系统毕设Zip全解析:搭建、核心实现与避坑指南

简介:面向Java毕业设计的停车场管理系统完整项目包,基于SpringBootVue前后端分离架构,适合需要完成毕业设计或课程设计的计算机专业学生,可覆盖从编码实现、数据库设计到论文撰写、本地部署的全流程。项目围绕停车位信息、用户管理…

📰

微信小程序加载外部3D模型:Three.js适配与glTF实战避坑指南

简介:面向微信小程序开发者的3D模型加载示例工程,核心解决小程序环境中引入three.js并加载外部OBJ等三维模型的问题。工程包含从环境搭建、场景初始化、渲染器绑定、模型加载到动画循环、交互监听与性能优化的完整演示代码,开发人员可直接对照…

📰

Node.js CLI工具架构设计与OpenClaw启动流程解析

1. 项目背景与核心价值 OpenClaw作为一款基于Node.js的命令行工具,其启动与配置系统的设计直接决定了工具的稳定性和扩展性。这次我们要深入分析的是CLI启动链路的核心实现,这对于理解现代Node.js命令行工具的开发范式具有典型意义。 在实际开发中&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬