news 2026/10/14 11:17:50

Agent成本失控?SparseRead稀疏读取把Token消耗降低92%的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent成本失控?SparseRead稀疏读取把Token消耗降低92%的工程实践

我最早意识到 Agent 的成本问题,不是在看模型 API 账单的时候,而是在一次线上事故排查里。那次一个客服工单 Agent 在处理一条用户申诉时,居然连续调用了四次查询工具,来回读了三遍同一份订单日志,最后还把结论答错了。我点开调用记录一看,单次会话消耗了接近 12 万 token,而真正有用的信息满打满算不超过 8000。那一刻我确定了一件事:如果 Agent 的上下文管理不从根本上改掉"什么都读"的思路,成本根本压不住,效果也救不回来。

后来我们团队做了个叫 SparseRead 的稀疏读取方案,围绕"只读该读的"这个原则,把上下文里的无关内容过滤掉,再按需重组输入给模型。整个过程踩了不少坑,一开始甚至做出了"负优化"——token 是减下来了,准确率也跟着跳水,算上重试反而更贵。直到把机制彻底想清楚,才在三个典型场景里做到最高 92% 的 token 削减,同时任务成功率基本持平,部分场景甚至略有提升。

这篇把完整思路整理出来,包括我们一开始为什么做得那么蠢、SparseRead 的机制到底是什么、实测数据如何,以及落地的几个关键细节。

1. Agent 成本失控的根源:先看清 token 都花在了哪里

想解决成本问题,第一步不是找压缩工具,而是搞清楚 token 到底是怎么烧掉的。很多人只盯着单次调用的价格,从来不看一个完整会话的累积消耗,这是最大的误区。

1.1 一个普通任务是怎样烧掉几万 token 的

我举个我们实际见过的例子。假设一个文档问答 Agent 接到这样一个问题:"根据当前项目的接口文档,帮我确认一下用户模块的鉴权接口在什么情况下会返回 401,并给出对应的处理建议。"

这个任务听起来很轻,对吧?但看它的执行过程:

  • 第一轮,系统提示词占 2500 token,用户问题占 300 token,Agent 决定调用"搜索接口文档"工具。此时上下文还小,模型输出了一段思考过程,又花了 200 token。
  • 第二轮,工具返回了搜索到的文档片段,足足 3500 token。这些内容会完整拼接进上下文。现在模型要重新阅读全部内容,输入已经变成 6500 token。
  • 第三轮,Agent 发现搜索片段不够具体,又调用了"获取接口详情"工具,这次直接返回了接口的完整定义、参数表、错误码枚举,又是 5200 token。上下文来到了 12000 token。
  • 第四轮,Agent 终于开始回答,但因为它觉得还需要参考另一个历史工单,又查了一次。最终这一轮会话下来,累计消耗接近 3 万 token。

你以为这就是全部?还不是。Agent 回答完后,用户往往还会追问,而每一轮追问都会把之前的全部对话历史重新读一遍。如果对话持续到第八轮、第十轮,单会话突破 10 万 token 非常常见。

我在做这个统计之前,一直以为 Agent 烧钱是烧在"输出"上,毕竟输出单价比输入贵。但实际上,输入侧因为上下文不断膨胀导致的重复读取,才是成本失控的最大头。

1.2 隐性放大效应:上下文越长,出错和浪费越多

上下文膨胀不只是增加账单数字,它还会产生两个很难量化的隐性成本。

第一是模型注意力被稀释。上下文里塞了太多无关的历史记录、工具返回片段、系统说明之后,模型反而更容易忽略真正关键的信息。我们在测试里发现,上下文超过 2 万 token 后,模型引用错误参数的次数明显上升。一旦调用了错误的工具或参数,就得重新请求、重新推理,这又会产生一批全新的 token 消耗。

第二是延迟增加。在同样的模型规格下,输入 token 上万和输入 token 数万,首字响应时间差距可能达到 2 到 3 倍。用户等得不耐烦,就会刷新重发,又变成额外请求。

所以,当我们说"降低 Agent 成本"时,绝不仅仅是省输入 token。它实际上是在做四件事:减少输入体积、缩短输出长度、提升任务成功率、降低重试率。任何只盯着其中一项的方案,最终都会在另外几项上栽跟头。

2. 朴素优化为什么做成"负优化"

我们团队第一次做成本优化,思路非常简单粗暴:既然输入太多,那就少喂一些。当时试了三种方法,结果全部翻车,而且翻车的方式很有意思——每种都是典型的"负优化"。

2.1 三种常见的省 token 尝试:截断、检索、摘要

第一种是滑窗截断。维护一个固定长度的上下文窗口,比如 4000 token,超出部分直接丢掉。实现很简单,效果也很惊人——token 直接降到原来的五分之一。但紧接着就出现问题了:模型经常丢失关键前置信息,遇到需要跨窗口回溯的问题时,回答质量崩塌式下降。用户问"我刚才让你记的那个单号是多少",模型完全答不上来,因为那个信息早就被窗口挤出去了。

第二种是关键词检索裁剪。根据当前用户问题提取关键词,在历史记录里做匹配,只保留命中片段。这个思路像 RAG,但问题在于 Agent 的对话不是单向的,用户的问题往往含有指代和省略,比如"那这个呢""再用刚才那个方法试一次"。关键词一失效,裁剪出来的上下文根本不够模型完成推理。

第三种是让模型自己做摘要。每轮结束后,把历史浓缩成一段摘要存入长时记忆,下次调用只带摘要。这个方案的致命伤是,摘要本身就是有损压缩,模型在摘要时并不知道未来会问什么。等到需要某个细节时,它已经被摘要抹掉了。

2.2 负优化的本质:任务成功率下降导致的总成本反升

什么叫"负优化"?我给它一个量化定义:

实际总成本 = 单次调用成本 × 调用次数 × (1 + 失败重试率) + 下游人工修复成本

如果减少 token 之后,失败重试率从 5% 涨到 40%,那么调用次数大幅上升,省下来的 token 会被重试产生的冗余请求吃得干干净净。更可怕的是,有些 Agent 的失败不是自我纠错能解决的——比如错误地更新了某个字段,需要人工介入修复,这部分成本根本无法用 token 数字衡量。

我们当时做了个对照实验:用全量上下文的 Agent 跑客服工单分类,准确率是 86%,单会话平均 4.2 万 token。用滑窗截断到 5000 token 后再跑,准确率掉到 61%,但单会话 token 降到 1.8 万。表面看,成本省了 57%,但准确率掉了 25 个百分点。考虑到失败后需要转人工处理的成本,整体开销反而上升了 30% 以上。

便宜但不好用的方案,本质上不是优化,是慢性中毒。这让我意识到,省 token 必须有一个大前提:不能以牺牲任务成功率为代价。做不到这一点的一切裁剪手段都应该直接否掉。

3. SparseRead 的稀疏读取机制:把"读全文"变成"选择性精读"

想通了负优化的坑之后,我们把方向彻底换掉了。不再追求"所有上下文都压短",而是追求"只把对当前任务有用的部分挑出来喂给模型"。这就是 SparseRead 的由来。

3.1 核心思想:像人读书一样,先看目录再跳读

人可以快速读完一本几百页的技术手册,并不是因为我们逐字阅读了每一页,而是因为我们先翻目录,确定章节位置,再跳到相关章节精读,读完还会根据书页之间的交叉引用适当扩读。SparseRead 做的就是这个事。

它把 Agent 的上下文来源拆成三块:系统提示词、历史对话、工具调用返回。系统提示词相对固定,我们处理得保守——基本全量保留,因为这里面的指令一旦缺失,模型的整体行为就会走样。主要做减法的是后两块。

对于历史对话和工具返回,SparseRead 不再把它们当成一长串字节流,而是先做结构化分割,再判断哪些块和"当前正在执行的这步任务"相关,最终只把相关的块取出来,附加一段非常紧凑的"其余内容摘要"作为兜底,拼装成新的上下文供模型消费。

整套流程可以拆成三步:筛、取、组。

3.2 结构性分割:建立"块"索引,而不是字节截断

第一步,把输入内容按结构切成块。这一步很关键,因为如果用固定字符数硬切,很容易把一段完整的逻辑从中间拦腰截断。比如一个接口定义被切成两半,丢失了参数说明,模型会猜,猜就大概率出错。

我们实现的切分规则很简单,就是利用文档里天然存在的结构标记:

  • Markdown 标题(##、###)作为第一级分块边界
  • 表格、代码块、列表作为独立块
  • 对话按"轮次"切分,每一轮用户消息、Agent 回复、工具返回视为一个整体单元
  • 长文本块如果超过 1500 token 再按段落边界二次切分

每一块会记录自己的类型、标题路径、时间戳(针对对话轮次)、token 数。这样构造出的索引,本质上就是 Agent 上下文的一棵"目录树"。

有一次我们拿一份 80 页的产品需求文档做测试,用这套规则切出了 240 多个块。当模型只需要回答"支付回调失败的补偿逻辑是什么"时,它根本不需要看其他 239 个块里的内容,只需要定位到"支付模块 -> 回调处理 -> 补偿机制"这条路径上的两三个块。

3.3 相关性评分:轻量模型判断"当前这一步需要什么"

有了块索引,下一步是给每个块打分,判断它与当前 Agent 执行步骤的相关程度。这一步我们用的是一个轻量级 scorer,并没有直接动用千亿参数的大模型。

打分主要考虑三个维度:

  • 语义相似度:把当前 Agent 的目标描述(比如"查找订单中金额不一致的记录")和每个块的内容做向量相似度计算。
  • 实体重叠度:提取当前上下文中的关键实体(订单号、用户名、日期、异常码),检查块中有多少命中。实体重叠高,说明这个块可能是关键证据。
  • 位置与时效因子:历史对话中,距离当前轮次较近的内容天然权重更高;工具返回中,最近一次返回的权重高于更早的。

最后把三者加权合并成一个 0 到 1 之间的相关性分数。我们最初使用固定阈值,比如 0.45 分以下一律丢掉。后来调整为动态阈值:根据当前剩余 token 预算上下浮动,预算充足时扩大读入范围,预算紧张时只保留最高分的块。

这里有个细节:打分时用的"需求描述"必须来自 Agent 当前已经产生的思维链,而不是最初的用户问题。因为用户在第三轮可能已经开始处理完全不同的子任务了,用最初的提问去打分,会筛选出完全错误的内容。我们把最近一轮的模型推理内容也作为 scorer 的输入,效果立刻上涨。

3.4 按需组装:保留骨架,附加摘要兜底

筛选出相关块之后,不是简单拼接就行。拼接顺序会影响模型理解。SparseRead 按这样的优先级重组上下文:

  1. 系统提示词(固定指令,全量保留)
  2. 当前步骤的推理摘录(模型最近一次思考的简洁重述)
  3. 命中的高相关块(按类型优先级排列:精确匹配的实体信息 > 工具返回 > 历史对话)
  4. 低相关块的压缩摘要(一段 200 到 400 token 的"其余内容概述")

第 4 点特别重要。很多早期版本一省到底,把低相关块直接丢掉,结果模型遇到"等等,我前面提到过的那个账号还存在吗"这种回溯性问题时,完全无法作答。加上这段压缩摘要后,模型至少知道"有这么一个信息,但不确定细节",它会主动提出需要查看更多内容,而不是凭空编造。

而我们基于这个机制还做了动态展开:如果模型在推理中明确指出"需要查看某个已省略块的具体内容",SparseRead 会把对应的完整块追加注入。这就相当于给模型装了一个"按需翻页"的能力。

3.5 和 RAG 的本质区别:多轮状态 vs 单轮检索

很多人会问:这不就是 RAG 吗?区别其实非常明显。RAG 解决的是知识库检索,它从外部知识源召回内容,通常是一次性的,默认知识源是静态的。SparseRead 处理的是 Agent 自身的会话状态,包括历史工具调用、用户反馈、中间推理,它服务的是整条多轮执行链路。

RAG 可以删掉不相关的段落,但 Agent 如果删掉了历史对话中某个看似无关的约定,就可能违背用户之前提出的偏好。SparseRead 的摘要兜底和按需展开,就是为了应对这种"当前看无关、往后看可能相关"的上下文需求。这已经不是检索问题,而是状态管理问题。

4. 实测数据:从负优化到最高减少 92% token

任何方案如果只在理想 demo 里好看,我是一律不信的。SparseRead 我们前后跑了三组场景,覆盖文档问答、客服工单和代码仓库理解。这里列的是相对完整的数据。

4.1 三个测试场景与基线设置

对照组有三个:基线 A 是完整上下文全量读取;基线 B 是固定的 6000 token 滑窗截断;实验组 C 是 SparseRead。为了保证公平,三组使用完全相同的模型版本和系统提示词,仅替换上下文组装方式。

场景一,某文档问答 Agent:用户围绕一份 120 页的内部技术手册提问,平均会话 6 轮,问题以"某个功能怎么用""某个报错什么原因"为主。

场景二,某客服工单辅助 Agent:用户带着订单号、商品编码来询问售后进度或退款原因,Agent 需要调用订单查询、物流查询等多个工具,平均会话 12 轮。

场景三,某代码仓库问答 Agent:用户针对一个包含 300 多个文件的代码仓库提问,问题包括"这个函数在哪里被调用""这个服务怎么部署",平均会话 4 轮。

4.2 核心结果对比

直接看数据:

场景全量读取消耗滑窗截断消耗SparseRead 消耗token 削减幅度任务成功率变化(对比全量)
文档问答4.8 万/会话2.1 万/会话0.9 万/会话81.3%0.5% 下降
客服工单6.7 万/会话3.3 万/会话1.6 万/会话76.1%0.2% 上升
代码仓库9.1 万/会话3.9 万/会话0.7 万/会话92.3%1.1% 下降

那个 92% 出现在代码仓库场景不是偶然。代码仓库里的参考文件绝大多数和当前任务无关;Agent 处理一个函数调用问题,根本不需要读整个项目的全部源码。滑窗截断在这个场景也省了 57%,但成功率掉得厉害,因为代码里的引用关系常常跨越多个文件,滑窗保头弃尾,偏偏把依赖定义切掉了。

而 SparseRead 在代码场景里之所以能压到 92% 的削减幅度,是因为它还多做了一件事:工具调用返回的代码片段会被先去注释、再去空行、再压缩缩进。这个处理对 token 量影响巨大。同样的语义,去掉格式噪音后可能缩小 30% 体积,而且不损失可读性。

4.3 哪些场景收益没那么大:边际递减要心里有数

也不是所有场景都适合 SparseRead。我们另外测过一个"多文档综合推理"场景,用户问题需要同时引用五个不同文档中分散的若干段落,并且彼此之间有隐含的逻辑推导关系。这种情况下,SparseRead 的 token 削减幅度只有 33%,成功率还下降了 3 个百分点。

原因是打分器很难识别"跨文档的隐式关联"——每个块单独看相关性都不高,但合在一起才是答案。所以我们在这种场景里强制开启一个策略:当问题带着明显的对比、综合、推导类动词时,自动提高已选块的保留数量上限,宁可消耗多一点,也要保证推理链条完整。

从这些结果里我总结出一条经验:SparseRead 的收益边界取决于内容的信息密度分布。信息越稀疏、冗余越高的场景,收益越大;信息高度交织、需要全局视野的场景,收益有限。任何宣称"所有场景都能省 90%"的方案,都不值得信任。

5. 落地 SparseRead 的关键细节与避坑经验

方案在离线测试里数据漂亮,不等于上线就能跑。真正把它接进生产环境,我们踩了一串坑,有些坑甚至反过来说明了 SparseRead 的设计边界。

5.1 阈值参数:相关性门槛怎么定才不伤手

scorer 的阈值是最难调的一个参数。阈值设高了,能省的 token 很多,但容易把关键证据过滤掉;阈值设低了,省不了多少,方案就失去了意义。

我们的调试经验是分场景设初值,不要一把尺子量到底:

  • 客服工单这种"实体强相关"场景,阈值可以放低到 0.35,因为实体匹配的可靠度很高,不怕误召。
  • 开放式文档问答场景,阈值放到 0.5 左右比较稳,语义相似度本身就模糊,太低会把大量无关段落带进来。
  • 代码场景要看是否涉及跨文件引用,涉及的话阈值 0.45,同时提高评分器中"符号引用"维度的权重。

另外,我把动态阈值做成随会话轮次变化的曲线:第一轮冷启动时阈值偏低,因为模型还在试探方向;中间轮次阈值最高,上下文最冗余;接近收尾时阈值再降低,防止最后的关键决策缺少上下文。

5.2 把工具返回也纳入稀疏化:这是见效最快的一步

很多人做上下文优化时只处理历史对话,忽略了工具返回。但根据我们的统计,Agent 上下文膨胀有接近一半来自工具调用返回的原始数据。一个查询订单接口可能返回 30 个字段,而当前任务只需要其中 3 个字段。

所以 SparseRead 集成到工具层之后,我们对每个工具的结果 schema 做了声明式配置:哪些字段是高频使用的核心字段,哪些是详情场景才需要展开的扩展字段。核心字段默认跟随工具返回装入上下文,扩展字段只在模型明确需要时通过二次调用获取。

这个改动让客服场景的 token 消耗又降了接近 30%。事实上,很多"省 token"方案只盯着语言模型的输入窗口,完全忽略了工具结果的瘦身空间,这在我看来是一种视角盲区。

5.3 安全网设计:失败链路自动升级到全量模式

即使是 SparseRead,也会有判断失误的时候。我们的原则是:允许它失误,但绝不允许失误静默引发任务失败。为此设计了三层安全网。

第一层,置信度回退。scorer 对每个块给出的相关性分数同时附带置信度。如果整个上下文中没有任何块的置信度达到预设线,SparseRead 自动切换到全量模式,宁可贵,也要保证信息完整。

第二层,失败感知升级。如果 Agent 执行结果被评估器标记为"任务失败"或"回答自相矛盾",系统会自动重放一次,这次使用完整上下文。我们用这个机制在客服场景里把成功率从 84% 拉回到 90% 以上。

第三层,规则硬保护。某些任务类型被硬编码为"不压缩"——比如涉及金额计算、用户身份校验、法律条款确认的任务。这些场景下出了错就是事故,省 token 的价值远远比不上出错代价。

5.4 和现有 Agent 框架配合:ReAct 循环改造要注意什么

我们最初把 SparseRead 做成了独立的中间层,任何 Agent 框架都能接入,但在和主流的 ReAct 模式配合时,有一个很容易踩的坑:模型在思考阶段产生的"计划"不能只存原文,它本身也是一份需要持续维护的上下文。

ReAct 循环中,模型每一步都要重新读取之前的全部思考过程,这些内容如果被压缩掉,模型可能会失去对整体计划的把控。我们的做法是引入"计划状态卡片":把 Agent 在运行中产生的每一步最终决定(而不是完整思维链)维护成一张简短的卡片,每次重组上下文时放入顶部。它在 token 上的开销极低,却避免了 Agent"走着走着就忘了初衷"的问题。

另外,凡是涉及多工具顺序调用的场景,我们强烈建议给 SparseRead 开一个"上下文追加模式":不覆盖历史选择结果,而是累加本次新增的相关块。否则 Agent 在第五步调用工具时,会丢失第三步调用所依赖的中间结果。

5.5 上线后的持续监控:别把成本优化做成一次性行为

最后说监控。很多团队做完成本优化就以为万事大吉,结果业务方一改提示词或工具结果格式,省 token 的效果立刻失效。

我们上线后固定维护两块监控:一块是每个任务类型的平均 token 消耗趋势,按周观察有没有反弹;另一块是"被过滤块再访问率"——如果某个块类型被过滤后又被模型多次请求补读,说明这块内容其实是高频需求,应该被提升为默认保留块。

同时,我们保留了一个由 500 条典型问题组成的黄金测试集,每次改动(不管是调阈值还是改工具 schema)都必须先在测试集上跑一遍准确率和 token 变化双指标,任何"省了 token 但准确率掉了 2 个百分点"的改动都会被一票否决。

这套机制上线跑了两个月,整体 token 消耗降幅稳定在 65% 到 85% 之间,而且没有出现服务降级类的投诉。现在再回看去年的自己,浪费的每一分 token 都在提醒我:优化成本的技术难点从来不在"减少输入",反而在无损耗地判断哪些输入值得保留。SparseRead 这个名字里的"Sparse",其实强调的不是少,而是准。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 11:17:33

Java类型擦除原理与实战解决方案

1. 类型擦除的本质与设计初衷Java的类型擦除(Type Erasure)是泛型实现的核心机制,也是许多开发者困惑的源头。简单来说,编译器在编译期间会移除所有泛型类型信息,生成的字节码中只保留原始类型(Raw Type&am…

作者头像 李华
网站建设 2026/10/14 11:17:13

制造业供应耗用容差:原理、配置与优化策略

1. 供应耗用容差的概念解析 在制造业生产计划与物料控制领域,供应耗用容差(Supply Consumption Tolerance)是一个关键但常被忽视的参数指标。简单来说,它定义了在实际生产过程中,物料实际消耗量与计划消耗量之间允许的…

作者头像 李华
网站建设 2026/10/14 11:16:47

文华财经多空主力线指标解析与实战应用

1. 指标概述与市场背景 期货市场中的主力资金动向一直是交易者关注的重点。文华财经作为国内主流的期货交易软件,其内置的指标公式功能允许用户自定义分析工具。多空主力线指标就是通过计算大单成交数据来追踪主力资金流向的专业工具。 这个指标的核心价值在于&…

作者头像 李华
网站建设 2026/10/14 11:16:45

图形因果模型:原理、应用与Python实践

1. 图形因果模型概述在数据分析与机器学习领域,图形因果模型(Graphical Causal Models)正逐渐成为理解变量间因果关系的重要工具。这种建模方法通过直观的图形结构展现变量间的因果联系,比传统统计方法更能揭示数据背后的真实机制…

作者头像 李华
网站建设 2026/10/14 11:16:17

HCIA-TCP/IP实战:从以太网帧到三次握手的协议栈深度解析

1. 这不是一张“入场券”,而是一把打开网络世界底层逻辑的钥匙很多人第一次看到“华为认证HCIA-TCP/IP基础知识”这个标题,下意识会把它归类为“又一个要背题库的考试”。我带过几十期网络方向的实训班,几乎每期都有学员在开课前三天发消息问…

作者头像 李华
网站建设 2026/10/14 11:16:16

Unity iOS导出Xcode自动化配置实战:跨版本兼容与CI/CD稳定性方案

1. 为什么Unity导出iOS项目后,Xcode里总要手动改十几处设置?我第一次把Unity项目导出到Xcode时,在会议室被产品盯着等“马上就能真机跑起来”,结果花了47分钟——不是写代码,是在Xcode里点点点:改Bundle ID…

作者头像 李华