ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

1340万token烧出的教训:大模型批量代码修改的验收流程重构

1340万token烧出的教训:大模型批量代码修改的验收流程重构 1340 万 token、92 次请求忙活了大半个月最后真正有效的输出连 9% 都不到。这个账我从账单里拉出来的时候第一反应不是AI 不够聪明而是被自己的流程蠢到了。25141512885491这 8 个数不是随机序列是我把 1340 万 token 的浪费按原因拆出来的占比单位是百分比——也就是说超过九成的 token 都烧在了返工、等待、格式不对和重复请求上。这篇东西不是来吐槽大模型 API 有多贵的而是要复盘一个更扎心的事实慢的不是 AI是我的验收流程。我会把这 92 次请求的完整过程、账单拆解、病根分析和改造后的流程全部摊开。适合正在用大模型做批量代码修改、文档生成、代码审查的工程师看也适合所有被 token 账单吓到、却还没想清楚钱到底花在哪的人。1. 事件回放92 次请求是怎么一点一点烧穿 1340 万 token 的1.1 任务背景一个看起来很简单的接口层迁移先说说这次任务本身。我手上的老项目有一批接口调用代码分散在大约 40 个文件里调用方式不统一、错误处理全靠 copy-paste、类型定义基本靠猜。新规范要求统一走一套 HTTP 客户端封装标准化错误码、补全类型声明、去掉所有硬编码 URL。这是个典型的模式统一工作——每段代码结构差不多但细节差异又特别多特别适合让 AI 帮忙批量改。我当时的设想很简单把新规范文档和 40 个文件一次性丢给 AI让它尽量都改了。听起来没什么问题对吧结果就是从这个决定开始我走上了 92 次请求的烧钱之路。这 92 次里有 20 多次是同一批文件反复重生成有 10 多次是请求参数或权限出问题重试剩下的一大部分是对话越滚越长之后每次发送都在重传整个历史消息。1.2 账单复盘1340 万 token 里九成是浪费我把账单按请求原因做了归类得到了标题里那串数字。25% 是全量上下文反复加载——每次请求都把 40 多个文件和历史对话原封不动地发给模型这是最大的一笔开销14% 是模型一次性输出大段代码后被我整段推翻重写15% 是验收标准模糊导致的再生成一版试试12% 是输出格式不符合预期返回了一堆散文而不是可解析的结构8% 是网络和鉴权异常导致的重试8% 是多轮对话历史累积产生的额外消耗5% 是中间结果生成后我没及时看上下文一变全都过期作废剩下 4% 是各种试错 prompt 和测试请求。为了更直观我做了个详细的拆账表浪费来源占比典型场景全量上下文反复加载25%每轮请求都重发全部文件内容与历史消息输出被推翻重写14%AI 返回了完整重构代码人工评审后全盘否决验收标准模糊15%感觉不太对“再换一种写法试试”格式不符合预期12%没约束输出结构解析失败无法直接落地异常重试8%401、443、限流导致的失败后重新发送历史消息累积8%同一会话内对话轮数越多单次请求越贵中间结果过期5%生成结果放了一天才审上下文早已变化其他杂项4%试错、重复提交、误操作每次请求贵的离谱还有一个技术原因token 是双向算钱的。输入要钱、输出要钱而输入端的成本完全由上下文体积决定。一次携带 40 个文件的请求输入随随便便几万 token如果是输出也长的重构任务一次请求吃掉十几万 token 非常正常。92 次累积下来1340 万就是这么烧出来的。1.3 一个隐藏放大器对话轮次越久历史越贵说到这必须提一个很多人忽略的隐性消耗——对话历史累积。同一个会话里聊了 20 轮之后每一轮新请求都会把前面的对话全部再发送一遍。你感觉是在接着聊实际上每次都在为之前所有的输出买单。到后期我开一次新请求光历史消息就占了 2 到 3 万 token而这些历史里大部分内容是已经被推翻的旧方案。如果早点把长会话拆成短会话 必要的上下文摘要光这一项就能省下至少 8% 的开销。这个经验我在后面第 5 章会细说。2. 归因分析AI 的响应速度完全正常卡住的是我的返工循环2.1 时间流水账AI 只占整体耗时不到一成既然账单这么吓人我第一反应是怀疑 AI 本身太慢或者能力不行。但翻出请求日志之后我发现一个事实92 次请求的 AI 端平均响应时间大约 40 秒到 90 秒所有请求的模型生成时间加在一起大约 5 小时。而整个项目我实际花了 11 天每天 4 到 6 小时在对着输出做人工评审、改 prompt、重新发起请求。换句话说模型在想和写上的时间连总工作时间的 10% 都不到。真正的时间黑洞是什么是我在看 AI 给的 diff读完 40 个文件的改动之后发现方向不对然后翻回去重新改描述、重新生成、重新看。一个返工周期短则半小时长则大半天。这种循环重复了十几轮每轮都在烧时间也在烧 token——因为每一轮都是一次新的全量请求。2.2 AI 好慢是一种错觉为什么我一开始会觉得是 AI 慢因为人在等待两次响应之间的空窗期会把等待时间的主观感受放大。你发出一堆文件AI 转了 90 秒返回结果你读了两分钟感觉不对改了一段 prompt 再发出去又等 90 秒。这个节奏看起来确实是AI 一次要跑好久。但你如果把人类评审的时间拉出来统计90 秒在 30 分钟面前根本不值一提。还有一个容易被忽视的因素等待会打断心流。我经常在等 AI 生成的时候切去做别的返回之后又要重新梳理上下文这段重新进入状态的时间同样是隐性成本。AI 本身是个吞吐量极高的生产者真正的瓶颈在我这边我没有一套能快速判断这份输出行不行的流程导致每一次评审都变成一次小型项目。2.3 返工循环的三个断点这 92 次请求背后其实就三个断点。第一个是验收标准没有量化——我只有一个模糊的差不多改好的直觉没有可执行、可检查的定义AI 每次生成的版本对我来说都差点意思。第二个是反馈没有结构化——我给的反馈经常是这个不对重新来AI 根本不知道该往哪个方向调整于是只能换一种风格再试试错了再换撞大运式迭代。第三个是修改 prompt 靠感觉——我没有记录哪一次修改带来了什么影响同一个坑反复踩。这三个断点共同作用形成了一个死循环生成、人工看、觉得不行、说再改改、重新生成。循环每走一轮token 烧掉一批时间也烧掉一段。问题的核心不是 AI 不够快而是我的验收流程设计得太粗糙把判断是否合格这件事变成了一场漫长的暗箱操作。3. 病根拆解我的验收流程到底犯了哪四个错3.1 病根一拿感觉当验收标准最致命的问题是我从来没有给 AI 一份可执行的验收清单。我说把这个模块处理一下AI 改完我一看感觉还是不太对然后让 AI 再改。问题是AI 收到的反馈是你做得不对但它根本不知道哪里不对、按什么标准算对。这种感觉式验收的本质是把模型当成了读心术器我觉得它应该懂但它其实只能猜。后来我补了一个很简单的东西——定义完成DoD。具体到这次项目我列出了 8 条硬性要求不改变对外接口语义、错误码统一映射到新标准、所有 URL 从配置中心读取、新增代码必须通过编译和 lint、每个文件必须带类型注解、不允许出现 console.log 调试输出、改动后的核心流程必须跑通现有单测、输出必须附上变更清单。当我把这 8 条写进提示词之后效果立竿见影AI 不再瞎猜每一次输出都有明确的锚点可以自检。3.2 病根二任务颗粒度彻底失控第二个错误是让一次请求吞下整个子系统。40 个文件、几百处调用全部塞到一个上下文里模型输出的时候既要照顾这个文件又要兼顾那个文件任何一个文件的特殊情况都可能影响其他部分的输出质量。更麻烦的是如果中间有一个文件的处理方式选错了后续所有文件都会跟着错——因为模型会模仿自己前面的输出风格。正确的做法是按文件组拆分每 3 到 5 个相关文件作为一批每批单独发请求批与批之间用质量门禁断开。拆分之后单次请求的上下文体积大幅下降模型可以聚焦在局部一致性上输出质量明显稳定。从 25 万 token 的大请求降到 4 万左右的批次请求就是从这一步开始的。3.3 病根三输出没有格式约束和自检环节我用 AI 做代码改动时最怕的不是它写错而是它返回一大段自然语言夹杂代码的解释我得靠肉眼从散文里抠代码。项目初期 AI 经常返回类似我建议将 fetchUser 改为以下形式同时需要注意 error handling 和 timeout 配置这种内容然后代码片段、说明文字、旧代码残留混在一起我需要手动整理才能落地。后来我把输出格式写死成 JSON 结构要求 AI 必须返回变更文件列表、每个文件的具体改动部分用代码块包裹、自检结果对照 DoD 逐条打勾、风险说明。这样我写了个简单的解析脚本每次请求返回后自动解析不合格的直接自动打回不再浪费我人工阅读的时间。这一个改动把格式不符合预期的 12% 浪费几乎降到了零。3.4 病根四没有中间检查点一路黑箱到底第三个问题其实是个更深层的流程缺陷我没有设置中间检查点。最初我是一次性让 AI 改完全部 40 个文件然后统一 review。这意味着如果前面某个文件里有个错误决策后面 30 多个文件都会在同一个错误基础上继续生成等到最后发现时所有依赖它的输出全部作废。正确的思路是一段一验。每完成 3 个文件我就立即运行编译和 lint跑一遍相关单测确认没有破坏现有功能后才继续下一批。这些检查脚本运行时间通常只有几十秒但它们像流水线上的探针每次都能在问题扩散之前把它拦下来。没有检查点的流程就像写了一整夜代码却没有编译过一样等到最后才发现语法错误扎堆——成本已经收不回来了。4. 重构后的验收流程把判断标准前置、量化、自动化token 省下六成4.1 第一步给每个任务写完成定义然后再打开 AI 对话框这次经验教会我的第一件事是写 prompt 之前先写验收清单。我把之前第 3 章那 8 条硬性要求做成了一个可复用的模板任何类似任务都往里面套。模板长这样功能行为是否与迁移前一致由现有测试用例兜底是否遵守目标规范错误码、命名、配置来源是否通过编译器和 lint 检查是否移除调试代码和死代码是否提供变更文件列表是否说明潜在风险点把这份清单直接写进系统提示词里并且明确告诉 AI无论你生成什么最后必须逐条自检并给出结论。AI 是概率模型你给它越明确的约束它的输出分布就越集中在合格区域。没有这份清单之前AI 的输出方差大得惊人有了之后第一次生成就通过验收的比例从大约 10% 涨到了 60% 以上。4.2 第二步把任务拆成可独立验收的小批次任务拆分不是粗暴地一次少改点而是要保证每一批都有独立的验收价值。我把 40 个文件按依赖关系分组第一批改纯工具类第二批改不互相调用的独立模块第三批改有相互依赖的模块最后再统一改入口和配置。每组 3 到 5 个文件每组单独发一次请求每次请求只带这组文件 新规范摘要 完成定义。这样做的收益非常直观。拆之前一个请求 20 万 token 起步输出动不动一整片评审压力巨大拆之后单次请求 4 万 token输出聚焦每个文件逐一审查也轻松得多。而且如果某一组的输出不合格只需要废弃这一组重新生成不会牵连到其他已经通过验收的批次。从 25 万到 4 万的请求瘦身是 token 账单下降的最大功臣。4.3 第三步用脚本做第一道门禁不再靠肉眼评审人工评审最大的问题是慢而且不稳定。我后来写了一个不到 50 行的验收脚本每次请求完成后自动跑三件事解析 AI 返回的 JSON、把代码改动写入工作区、运行编译和 lint。如果编译失败脚本直接打印错误摘要并标记该批次为未通过连人工 review 都不用看直接带着错误上下文重新生成。这套机械门禁解决了一个关键问题把我觉得行变成了脚本说行。人会说谎、会疲惫、会看漏但 compile error 不会。通过门禁之后再进入人工评审阶段人只需要关注逻辑层面的是否合理语法和规范问题全部交给脚本。这个改动让我的评审时间平均下降了 70%更重要的是它消灭了低水平反复——因为 AI 每次带错误重新生成的时候我会把编译错误一并回传给它让它有据可改而不是瞎猜。4.4 第四步prompt 里绑定输出结构和自检清单这一步是被 12% 的格式浪费逼出来的。我在每个请求里都加了这样一段约束请严格以 JSON 格式输出结构如下 { changed_files: [文件路径按实际改动列出], changes: [{file: 文件路径, diff_or_code: 完整代码块}], self_check: [{criterion: 完成定义中的条目, pass: true/false, note: 说明}], risks: [潜在风险说明] } 不要输出任何 JSON 以外的内容。不要使用 Markdown 包裹 JSON。配合解析脚本返回的 JSON 可以直接进入门禁流程。自检清单是这里最微妙的设计让 AI 对照完成定义逐条自检等于在生成阶段就内置了一个校验回路很多明显不合规的输出在源头就被挡住了。即使它自检结果不准确也不影响我把它的输出放进脚本里跑一遍再下结论。4.5 第五步给每轮任务设置 token 预算和熔断机制最后才是成本控制本身。我给每个批次定了 token 预算比如目标输出量 3000 token允许偏差 50%超过就直接熔断停止生成然后压缩上下文重来。更直接的一个手段是一旦某批次连续 2 次验收不合格立即停止在同一会话里继续改开新会话、精简上下文、重写描述再开始。因为连续失败往往意味着上下文里积累了太多错误方向和无效信息继续追加只会让问题越来越乱。预算熔断结合会话重建让单批次的消耗从平均 15 万 token 降到了 5 万左右。92 次请求剩余部分如果按新流程跑大概 20 多次就能完成同样的事总量压缩一半起步。这就是为什么我说 token 不是省出来的是流程省出来的。5. 过程中的连环坑403、443、限流与 token 失效批量请求有多脆5.1 批量请求的异常现场一次失败的 8% 账单92 次请求里有大约 8% 是因为异常重试烧掉的。批量调用 API 时单线程串行发请求是最稳但最慢的一旦并发提上去各种异常就来了。我遇到过的几种401/403token 过期或者 API key 权限不足。有一次我换了网络环境请求直接返回 403排查了半天发现是权限白名单的问题。443 连接超时批量请求并发一多连接池容易打满表现为连接长时间不释放最终 443。429 限流并发太高触发了速率限制返回 429重试几次都一样。408 请求超时单次请求 payload 太大导致处理时间超过服务端超时阈值。这些都是非常基础的 API 调用问题但放在 batch 场景下会被放大。应对的办法也不复杂并发控制在 3 到 5 条设置统一的超时时间加上带退避的指数重试。注意指数退避不是线性重试——等 1 秒、2 秒、4 秒、8 秒这样递增而不是每隔 2 秒重试一次否则限流只会越来越严重。5.2 重试策略里最容易被忽略的隐性消耗重试逻辑写起来简单但有一个坑特别容易烧 token重试时是否要重发全部上下文。大多数 SDK 的逻辑是每次请求都是一次独立的 HTTP 调用你构造请求的时候把消息数组传一遍。如果失败后重新构造请求你没有存住原来的消息数组而是又从某个上游重新拉一遍文件内容拼进去——那么这次重试的成本几乎是原请求的 100%。更聪明的做法是失败时先判断失败类型。如果是 429/443 这类瞬时错误消息数组不变只是重新发送理论上只消耗一次请求的费用如果是 401/403 这类鉴权错误重试也没意义先去修 token 和权限。我当时没有区分失败类型所有失败一律重发完整上下文这就是为什么异常重试能占到 8% 的浪费。5.3 对话历史的断点续传别让一个会话写到天荒地老前面提过多轮对话的历史累积这里展开说。我在同一会话里最多记录过 40 轮对话到后面每次请求光历史消息就有 3 万多 token而真正有用的只有最近两轮。这个问题的解法是会话分段每完成一批独立模块的验收就把当前会话结束开一个新会话新会话里只放规范摘要 已完成的文件列表 下一批的文件内容。刚开始我担心开新会话会让 AI 丢掉上下文后来发现这种担心是多余的。真正关键的上下文是规范要求和已完成部分的约定这些可以压缩成几百 token 的描述而不是把整个对话历史原封不动携带。会话一开新单次请求成本立刻掉下来而且模型反而更专注新任务因为不再被前面的废话干扰。5.4 token 失效和刷新批量任务前先做一次预热我吃过一个很尴尬的亏任务跑了一个小时突然发现 token 早就过期了中间所有请求等于白烧。批量任务开始之前先发一个 1 token 的探测请求确认鉴权有效再进入正式循环。这个预热请求的成本可以忽略不计但能避免大批量请求在失效 token 上空转。我自己后来是通过axios发起请求在封装层统一拦截 401 响应自动刷新 token 并重放请求。当时如果早一点把这个拦截逻辑写好后面的 8% 异常浪费至少能砍掉一半。6. 如果重新来一遍我会这样控制预算和节奏给同路人三条建议6.1 新旧流程对照token 和时间都不在一个量级这次项目旧流程总共用了 11 天92 次请求1340 万 token。按新流程重新推演同样的 40 个文件我预计 20 次请求左右可以完成token 总量大概能压到 500 万以内时间可以压缩到 3 天。差距不是 AI 变强了而是流程变严了。我做了一张简短的对照表环节旧流程新流程任务拆分一次性全量按依赖分批3~5 文件/批验收标准模糊感觉明确 DoD写入 prompt检查方式人工 review 全部代码脚本门禁 人工逻辑审查输出格式自由文本强制 JSON 自检清单失败处理同会话反复改连续失败即重建会话预算控制无每批 token 预算 熔断6.2 给同路人的三条建议第一把设计验收流程的时间放到写 prompt 之前。我吃过最大的亏就是把大半时间花在让 AI 再改一版上却不愿意花二十分钟把验收标准写清楚。验收清单写得越早后面的迭代越少。第二机械检查永远比肉眼可靠。能用编译、lint、单测、脚本验证的东西绝不要依赖人工阅读。人适合判断方向对不对不适合在几千行代码里找 format 错误。第三控制上下文体积就是控制成本。全量上下文加载是我账单里最大的一项浪费25%。任何能减少上下文体积的手段——开新会话、做摘要、分批处理——都值得优先做。这次之后我接所有 AI 任务的第一件事永远是问自己我怎么判断输出是合格的如果这个问题没有答案下一个 1340 万 token 的账单已经在路上了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进