☰
从1340万token到150万:LLM验收流程的预算控制实战复盘
2026/10/11 4:45:50 网站建设 项目流程

说实话,看到账单的时候,我第一反应是怀疑计量出错了。92次请求,1340万token,就为了验收一个“带工具调用的智能问答Demo”——这数字比我预想的高了整整两个数量级。

事情本身不复杂:一个需要验证模型能否在几个固定业务场景里正确调用工具、返回合法参数的验收任务。原计划30分钟跑完20次请求,顶多几十万token收工。结果从下午一直折腾到晚上,92次请求、1340万token就这么烧没了。中途我一直在怀疑模型是不是反应迟钝,但逐条翻请求日志后发现,单次响应其实1到3秒就返回了。真正慢的、真正贵的,是我的验收流程:25次提示词调试、14次工具调用修正、15次多轮重放、12次回归复测、8次参数对比、8次重试验证、5次解析修补、4次保险性复测。

这篇文章就是把这笔账彻底拆开,复盘每一个数字是怎么来的,token到底烧在哪里,以及我后来建立的预算控制流程。如果你也在做LLM应用的验收、测试或调试,这篇文章应该能帮你省下不少真金白银。

1. 92次请求怎么来的:账本比我想象的难看

先解释标题里那个等式:91 = 25 + 14 + 15 + 12 + 8 + 8 + 5 + 4。这里的92次,是91次正式验收请求加1次环境连通性测试。整个复盘下来,每一组数字背后都对应一种典型的浪费模式,而且几乎没有哪一组是真正“必要”的。

1.1 25+14+15:三次“局部失控”的调试循环

先看最前面的25次。这25次请求全部花在提示词调试上。我当时在定义系统提示词,期望模型按照指定格式输出结构化结果。问题在于,我每次改一小段措辞就重新跑一次完整请求,而测试输入样本又没有固定——上一轮用一个订单样本,下一轮换了个库存样本,再下一轮又换了个物流样本。输出一旦不符合预期,我也分不清是提示词的问题还是样本本身的问题。来回折腾了25次,真正有效的信息增量大概只值5次请求。这是我踩过最深的坑:调试提示词时频繁更换输入样本,等于同时改变两个变量,结论永远无法收敛。

接着是14次工具调用调试。这个项目里模型需要调用一个订单查询工具,工具的参数包含订单状态枚举值。模型几次返回的枚举值不在我定义的范围内,我以为是提示词描述不够清楚,就反复修改工具函数的功能描述,加了一堆“请严格按照枚举值返回”之类的强调。直到第14次请求时我才发现,真正的原因是我在工具参数schema里写错了其中一个枚举值的拼写。模型没错,错的是定义。这14次里至少有10次是完全没必要的——如果第一次失败时就去看原始返回体而不是凭感觉改描述,这个数字可能只需要3次。

最后是15次多轮场景联调。在真实业务链路里跑对话,模型需要结合历史消息回答后续问题。问题出在第4轮左右:一次工具返回结果解析失败,我的代码直接做了“整段会话重新提交”,也就是把前面3轮的用户消息、助手消息、工具消息全部重新发给模型。你以为你只重试了一次,实际上token消耗是前面好几轮的总和。整个联调阶段15次请求里,有6次是这样的完整会话重放,单次请求token从1万一路涨到10万以上。这一环节的浪费是几何级数式的。

1.2 12+8+8+5+4:看起来有理由,其实都在重复付费

后面的数字看起来各有“正当理由”,拆开看全是重复劳动。

12次回归复测。中途我改了3版提示词,每一版改完都把前面调试过的场景全部重跑了一遍。一共3个场景,3版提示词,正好9次全量回归,只有3次真正基于增量变更的定向测试是必要的。问题出在我没有记录每个场景受哪些提示词影响,只要改了prompt就不分青红皂白全部重测。这就像修了个卫生间漏水,却把整个屋子重新装修了一遍。

8次参数敏感性对比。我分别测了temperature 0、0.2、0.7、1.0,和top_p 0.8、0.9、1.0。听上去很科学对不对?实际上我在测temperature时没有固定提示词版本——上一版提示词跑到一半被我改了几个字,下一轮测试用的已经是新提示词了。参数对比的结果完全不可信,于是只能重新改回去再跑一遍。控制变量这四个字,在紧张的项目进度里特别容易被忘掉。

8次重试与并发测试。当时想验证服务不可用时代码的重试逻辑是否正常,于是人为设置了几次超时。我的实现方式是在整段会话级别重试:超时后把整个对话历史重新提交。这跟多轮联调里踩的坑一模一样——重试不是重发单条请求,而是重放整段会话。5次请求就这么被“合理”地放大成了接近10次的token量。

5次输出解析修正。模型返回的JSON带了markdown代码块包裹,我的解析函数只处理纯JSON,于是解析失败。我的第一反应是改解析函数,前后改了5个版本,加了一堆正则、容错逻辑。第5次失败的时候我才打开原始响应看了一眼:原来模型从来没返回过裸JSON,一直是```json包裹的格式。如果一开始就查看原始响应,只需要改一行strip代码,剩下4次请求完全可以省掉。这个教训我后面还会反复提到。

最后的4次“保险性复测”最可惜。验收标准没有写下来,测试到了尾声总觉得“再跑一遍确认一下比较稳”。实际上这4次请求没有带来任何新信息,纯粹是我自己在缓解焦虑。没有验收标准,就永远不知道什么时候该停。

2. 1340万token烧在哪:不是每次请求都那么贵

92次请求,1340万token,平均每次14.6万token。但如果按这个平均数去理解,就完全理解错了——这组数据的特征是“平均掩盖了真相”。

2.1 平均每次14.6万token,但这不是真相

早期调试阶段的单次请求其实只有1万到2万token。越往后,单个请求越重。等到多轮联调和完整会话重放阶段,单次请求已经能到30万到40万token。真正吃掉大盘子的并不是“很多次廉价的请求”,而是少量几次体量巨大的重放式请求。项目里最贵的一次完整会话重放,接近50万token——大概相当于早期调试阶段的25次请求。

这个现象在长会话、工具调用场景里特别典型:第N轮请求的输入token,包含前面N-1轮的所有内容,包括模型输出、工具返回结果。一旦养成“重放整个会话”的习惯,token消耗会是线性增长的几倍。

2.2 输入token的重复税:上下文在滚雪球

1340万token里,粗算下来输入token约1150万,占86%;输出token约190万,占14%。很多人只盯着输出token单价高,实际上在调试场景中,输入token才是隐形的大头。

我算了这样一笔账:92次请求里,每次都会重复发送系统提示词、工具函数定义和few-shot示例,这部分固定开销大约4000 token,乘92次是36.8万,占总量的比例其实很小。真正可怕的是上下文历史累积。平均每次请求携带的输入约12.5万token,其中真正变化的只有“当前这一轮的用户输入”和“上一轮的新增内容”,其余的全是重复发送的历史记录。对话越长,“重复税”越高。也就是说,在同一个会话里你做的每一次无效重试,都要为之前的所有轮次重复买单。

2.3 输出token的浪费:无效输出在“二次计费”

190万输出token同样有不少水份。调试阶段至少有一半请求的输出是不符合预期的,这些无效输出不仅本身花费了输出token,还会以“历史消息”的形式被塞进下一次请求的输入里,继续产生输入token费用。换句话说,一条垃圾输出至少要付两次钱:一次是生成它,一次是把它当作上下文重新发给模型。

项目里最典型的例子就是那5次解析修正。模型反复输出带有markdown代码块包裹的JSON,解析失败的响应全部进入了会话历史。后续每次重新请求,都会把之前解析失败的历史消息原样发给模型。这5次失败的“遗产”,在后面好几轮请求里持续增加输入token成本。

2.4 算一笔账:这点功能值不值这些token

按照折算价格来算,1340万token大约对应人民币七八百元(输入输出单价不同,具体取决于渠道和模型)。看起来似乎“也就几百块”,但对比之下,这个项目实际承担的业务价值可能连几十块都不到。更扎心的是,这还没有算上人工时间成本——6小时的人力投入,才是这92次请求里最贵的东西。token烧了可以充值,时间烧了没法充值。

3. 慢的不是AI,是验收流程的四个失控点

按理说,一个简单的验收任务变成92次请求,一定有系统性原因。我把整个流程拆了一遍,总结出四个失控点。这四个问题比token浪费本身更值得重视。

3.1 验收标准没有写下来:全凭感觉判定通过/不通过

整个验收过程中,我没有写过一份“验收标准卡”。什么是“合格”?工具调用是否必须100%返回合法枚举值?未知类型的输入该不该拒绝?输出格式的容错边界在哪里?这些问题在测试开始前全是模糊的。

没有标准,就会陷入“差不多就行”和“再试一次”之间反复摇摆。输出看起来接近预期,但不敢确认,于是再跑一次;某一轮用例通过了,但担心不稳定,于是又跑一次。那4次“保险性复测”就是这么来的。如果一开始定义好通过/不通过的具体条件,比如“工具函数必须返回合法JSON,且枚举值100%命中定义”,每一轮请求是去是留就非常清楚。

3.2 prompt没有版本控制:改坏了只能全量重跑

我改了3版提示词,但没有给它们做版本管理。所有改动直接写在代码字符串里,上一版长什么样只能靠记忆。等到想回退的时候,已经找不到原来的版本了,只能凭印象“重新写一个接近的”。

这里的问题有两层:第一,prompt是业务流程的一部分,它和代码一样需要版本管理;第二,没有版本号就没法做A/B对比——你不知道这次输出的变化是因为你的修改,还是因为模型采样本身的随机性。后来我把prompt全部抽离成独立的版本文件,每次测试都带版本ID,这个问题才彻底解决。

3.3 全量回归代替定向回归:把工程问题当成玄学

提示词一改,我就把前面所有场景全部重测一遍。用来用去都是全量回归,一方面耗时耗token,另一方面还掩盖了真正的问题——你不知道某个场景为什么失败,因为失败可能是任意一个变量引起的,既可能是prompt,也可能是工具定义,还可能是样本选择。

正确做法是先做影响面分析。每次改动前问自己:这个改动影响的输入范围是什么?只和特定工具相关的改动,就不需要重跑其他工具的测试用例。把“改动点”到“受影响场景”之间的映射关系列出来,才能让回归测试有边界。我在后续项目里用一张简单的矩阵表格维护这种映射,效率提升了不止一倍。

3.4 没有预算熔断:直到账单出来才知道疼

整个验收过程没有任何预算上限。既没设token阈值,也没设金额阈值。更致命的是,没有在代码层面加“request guard”——每次调用前估算token,达到阈值自动熔断。所以当我意识到“不对劲”的时候,已经烧掉了1340万token。

这是我认为最值得分享的经验:任何AI应用的验收流程都应该在开始之前定好预算红线。一旦达到红线,立刻终止测试,先写复盘报告,说明每笔token花在哪、下一次怎么优化,再决定是否继续。没有熔断机制,就永远不知道“止损点”在哪里。

4. 复盘后我建立了这套token预算流程

经历了这次惨痛复盘,我把验收流程重新设计了一遍。这套流程现在已经固化下来,每次做AI相关验收都会直接套用。以下是我实际用下来的可执行方案。

4.1 先把验收标准卡写死:可量化、可判定

测试开始前,先写清楚每一个测试场景的通过标准。标准必须是可量化的,不能出现“效果不错”“语义相似”这类主观表述。格式建议用表格,列清楚五件事:场景描述、输入样本、预期输出格式、必填字段、边界容错。

以我这个项目的“订单状态查询”工具为例:

验收项具体要求
场景用户查询订单状态,模型应调用订单查询工具
输入样本固定5条用户指令,包含正常、模糊、缺参数3种类型
输出格式返回合法JSON,不包含markdown代码块包裹
必填字段order_id、status、timestamp 三个字段必须存在
边界容错缺参数时模型应主动追问,而不是编造参数值

有了这张卡,每一轮请求都可以用一个明确的“通过/不通过”来判定。跑完一轮,对照卡片打勾或者打叉,不再有任何模糊地带。这份标准卡本身是静态的,但它能把测试过程中“要不要再跑一次”的决策成本降到几乎为零。

4.2 给测试分档:冒烟、定向、回归、压力各配预算

不要把所有测试请求混在一起谈预算。我把测试分成四档,每一档单独配token预算:

  • 冒烟测试:1到2次请求,只覆盖主路径,目的是验证“链路通不通”,不追求细节。
  • 定向测试:围绕改动点设计2到5次请求,只覆盖和改动相关的用例,不做全量验证。
  • 回归测试:只在提示词版本或工具定义发生变更后执行,用例选择依据“影响面矩阵”,而不是全部场景。
  • 压力/参数测试:有独立的预算池,绝不和验收预算混用。

预算分配上,我通常按比例切分:冒烟5%,定向30%,回归45%,参数对比20%。同时给每一档加1.2倍冗余系数。也就是说,如果我预估定向测试需要10万token,实际预算就是12万,超了就得停下来解释原因。把预算显式分配之后,每一类请求都有了“额度感”,不再是敞开口子随便花。

4.3 用快照和离线复用削减一半请求

这是我认为整体优化里性价比最高的一步。以前调试解析器、调UI展示、验证逻辑分支,全都要重新调模型。现在改成:模型原始响应全部落盘保存为JSON快照,后续所有不涉及模型本身的调试,直接读取快照数据作为输入。

解析器改了,跑的是快照,不消耗token;前端展示改了,跑的还是快照,不消耗token;只有真正需要改变模型行为——比如改prompt、改工具定义——才发新请求。这一步直接削减了我的测试场景中将近一半的模型调用。注意快照里必须保留完整的响应元数据,包括token用量、响应耗时、参数配置,方便后续复现和对比。

4.4 加一个request guard熔断层

在代码层写一个拦截器,每次准备发请求前估算token用量。如果本次累计token将超过阈值,直接拒绝发起请求,返回错误信息提示“预算不足,请检查测试计划”。阈值设置成硬编码配置项,不允许在测试过程中临时修改。

因为LLM每次请求的token消耗事前无法精确预估,我用一个相对保守的估算公式:预估token = 字符数 ÷ 2(中文)或字符数 ÷ 4(英文),再乘以1.5的保险系数。实测下来这个公式大部分时候能把误差控制在3倍以内,作为熔断依据足够用了。熔断器可以做得再细一点:按档位设置,比如达到阈值80%时打印警告,达到100%时强制熔断。

4.5 控制变量:没有对比就没有结论

关于参数对比和A/B测试,我给自己定了一条死规矩:任何一组对比测试,一次只能改一个变量。测temperature,prompt版本、输入样本、工具定义必须全部锁死;测提示词版本,temperature和样本也必须全部锁死。判断结果差异的原因时,只允许归因于你主动改变的那个变量,其余全部视作干扰因素,不许下结论。

另一个配套习惯是:测试用的输入样本用一组带ID的固定数据。直接把样例子集放到配置文件里,跑测试前加载同一个配置。这样无论跑多少次,模型的输入侧都是一致的,方差只来自模型本身。没有固定样本的对比测试,结果基本不可信,跑得越多浪费越多。

5. 改完以后的实际效果

按照上面的流程重新做了一次同规模验收,效果非常直观:请求次数从92次降到25次,token消耗从1340万降到450万。后面再配合快照复用和熔断机制,整体稳定在12次请求、150万token以内。同一套验收目标,成本降到原来的十分之一左右。

这组数字的差异,并不在于我变得更“懂AI”了,而在于我变“懂流程”了。模型的行为始终是概率性的,你没法控制单次生成的质量,但你可以控制测试的设计、请求的必要性和预算的边界。

5.1 同规模验收:从92次变成25次,从1340万变成450万

新的流程下25次请求的分布大致是:冒烟测试2次、定向测试8次、回归测试9次、参数对比6次。而且每一个数字背后都有记录:为什么需要这次请求、预期验证什么、结果是什么。比起之前“跑完就忘”,现在每一轮测试都可回溯、可复盘。如果后续还想继续压成本,可以再通过扩大快照复用范围、使用缓存策略等方式继续降低,但我觉得450万token以内的水平对于这个体量的项目来说,已经是一个健康值——它保留了必要的安全余量,又没有为了省钱牺牲验证质量。

5.2 踩过的坑与能直接用的建议

最后分享几个具体的建议,都是这次经历换来的:

  • 调试时永远先看原始响应。无论是解析失败、字段缺失还是格式错误,第一步永远是打开完整的返回内容看一遍,而不是凭报错信息猜测原因。我烧掉的5次解析修正请求,根因只是没有查看原始响应。
  • prompt也要建版本目录。把所有提示词版本抽成独立文件,文件名标注时间戳和版本说明,测试时把版本ID写入日志和快照。回退时直接切换到上一个版本文件,而不是靠记忆“重新写”。
  • 在代码里埋一个token计量函数。测试结束时打印每次请求的输入token、输出token、累计消耗,把它写进测试报告。有了计量才有管理,没有计量的时候,你根本不知道自己浪费了多少。
  • 重试机制永远放在“单条消息”级别,而不是“整个会话”级别。整段会话重放的玩法,一次就能烧掉前面所有轮次的总和。重试之前先想一想:这个请求本身失败了,还是前面的上下文有问题?如果是模型偶尔抽风,重发单条就够了;如果是上下文污染,重发多少条都一样错。

我自己的体会是,这类问题更像是流程管理上的“预算制度问题”。当请求次数和token消耗开始失控时,不要急着怀疑AI能力有问题,先回头看一眼验收流程的每一个环节是否都能给出明确答案:为什么发这次请求?为什么需要重试?这个用例为什么算通过?如果三个问题里有任何一个答不上来,说明流程里存在一个可以被优化的浪费点。把这几个问题逼自己回答一遍,省下来的token,可能比调十轮提示词都多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询