news 2026/10/15 7:11:16

自建Coding Agent省Token 58.7%:从黑盒到白盒的优化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自建Coding Agent省Token 58.7%:从黑盒到白盒的优化实践

1. 为什么我要自己写一个 Coding Agent

1.1 用现成 Coding Agent 时,最让我难受的不是代码质量,而是 Token 消耗

先说结论:我花了一段时间自己搭了一个 Coding Agent,拿同一组编码任务和 Claude Code 跑了一遍对比。结果是,它只用了相当于对照组约四成的 Token,就完成了同样的活儿。标题里的 58.7% 就是这么来的。

很多人看到这种数字,第一反应是“你的 Agent 是不是代码能力更强”。其实不是。我的 Agent 在模型层面没有任何优势,用的就是同一个模型接口。真正的差异在于:我知道每一步把什么数据送进模型,而 Claude Code 对我来说是一个黑盒。

举一个实际例子。我让它写一个“游戏存档自动备份脚本”。任务本身不算难,但它过程中会反复列出同一批目录、反复读取同一批文件、反复把同一段代码片段打印出来,然后再修改。这些重复操作在最终代码里完全看不到,却在 Token 账单上留下的痕迹非常明显。我事后统计才发现,它把大量 Token 花在了“确认环境状态”上——明明这些状态之前已经确认过了。

这不是某一家产品独有的问题。只要 Agent 没有显式的 Token 预算意识,它就倾向于把上下文做得越来越完整,而不是越来越精简。完整意味着安全,模型可以随时从上下文中找到它需要的任何信息,但代价是每次请求都要携带大量可能用不到的内容。我要做的,就是从架构层面替模型做这个取舍。

1.2 省 Token 解决的远不止钱的问题

如果你觉得 Token 优化只是为了省钱,那就把它想小了。Token 消耗量对 Agent 的影响有三个层面,每个都直接决定好不好用。

第一是延迟。模型处理输入的时间大致和输入长度成正比。同样的任务,一次请求只带 5 千 Token 和带 5 万 Token,模型思考的时间完全不是一个量级。早期的自定义 Agent 跑长任务时,经常是等几秒才看到下一步输出,后来把上下文压缩之后,响应速度明显提升。第二是稳定性。上下文越长,模型越容易“迷路”,它会因为在历史记录里看到一个不相关的配置项,转头去处理那个配置。上下文里塞满无关内容时,Agent 的每一步都像在原地打转。第三才是成本。API 按 Token 计费,省下一半 Token 就等于省下一半调用费用。

这三者叠加起来,我的理解是:Token 预算本质上是在强制 Agent 做减法。真正高效的 Coding Agent,不是“思考得更多”,而是“只思考必要的事情”。

1.3 自建 Agent 适合谁,不建议谁盲目上手

在做这件事之前,建议先对号入座。如果你平时只需要改一个小函数、补一个测试用例,那现成工具已经很顺手,自建 Agent 的维护成本反而超过收益。但如果你经常处理多文件改动、批量重构、跨模块联调,或者你本身就喜欢把每个环节捏在手里,那就值得投入。

自建 Agent 的核心价值不是“比市场产品更聪明”,而是“可观测、可控制、可优化”。现成工具对你是一个黑盒,你只能接受它的 Token 消耗水位;自建方案里,每一条上下文、每一次工具调用、每一轮模型请求都摆在台面上,哪里浪费了,当场就能发现。

我后面的所有步骤,都是基于一个最小可运行方案来写的。整体架构并不复杂,不涉及训练模型,只需要能调用大模型接口,以及一个能执行本地命令的运行环境。接下来,我从整体设计开始拆解。

2. 整体设计思路:把一个“会聊天”的模型变成“会干活”的 Agent

2.1 最小可运行架构三件套:调度循环、工具注册中心、记忆管理器

我把自己的 Agent 简化成三个核心组件:调度循环、工具注册中心、记忆管理器。

调度循环是心脏。它做的事情很机械:接收目标,生成计划,调用工具,读取工具结果,判断是否完成,继续或输出总结。每转一圈,就是一次模型 API 调用。Token 消耗就发生在这个循环里,所以循环的设计目标只有一个,用最少的轮次完成任务。

记忆管理器负责“什么东西可以不用发给模型”。它不是把完整对话历史原样塞进上下文,而是维护一个当前焦点窗口和一个历史摘要。当前窗口通常只保留最近一两轮的完整状态,更早的信息被压缩成一小段摘要文本。这个组件的存在,直接决定了上下文会不会线性膨胀。

工具注册中心则决定模型能干哪些活。它是一组带名称、描述和参数结构的函数集合。模型不直接执行命令,它只表达“我想调用某个工具”,再由调度器去执行,执行结果返回给模型。这个机制本身不复杂,复杂的是工具粒度的设计,后面我会专门展开。

2.2 调度循环是怎么控制 Token 流向的

为了说清楚调度循环对 Token 的影响,我贴一段最简骨架。实际代码肯定更复杂,但核心逻辑就是这样一个循环:

state = { "goal": "编写游戏存档自动备份脚本", "history": [], "done": False, "usage_total": 0 } while not state["done"]: messages = build_messages(state) # 只选择必要上下文 response = model_client.chat(messages) # 模型接口调用 usage = response.usage # 每次调用都可以拿到 Token 统计 state["usage_total"] += usage.total_tokens if response.has_tool_call(): result = tool_registry.execute(response.tool_call) state["history"].append(result) else: state["done"] = True

这个循环里最核心的函数是build_messages。它决定了本次请求到底带多少内容。实现时,它只从状态里挑选当前焦点和历史摘要,而不是把state["history"]全部展开。只改这一个函数,Token 消耗就能出现巨大差别。

我在实战中还发现,循环里最容易烧 Token 的操作,是模型反复读取同一个文件。模型没有短期记忆,每轮调用结束后,它的上下文就是下一轮调用时的初始状态。如果它发现自己需要某个文件内容,而上下文里没有,它就会重新调用读取工具,把整个文件再读一遍。解决这个问题不能靠提示词,最有效的办法是在工具层加缓存:同一个文件如果已经在上下文里出现过,读取工具返回一个“文件未变化”的短标识,而不是完整内容。

2.3 为什么选择 Claude Code 作为对比基线

文章标题写到了 Claude Code,这里说明一下选择它作为参照的考虑。

第一,它是目前比较典型的主流 Coding Agent,开箱即用,工具链完整,作为“通用基线”非常合适。第二,我本来就有部分工作依赖它,搭建对照环境几乎没有额外成本。第三,它不代表“最差的 Token 表现”,恰恰相反,它思考得很全面,质量也稳定。问题就在于,这种全面是需要“付出代价”的。

拿它做参照,本质是在比较“黑盒通用策略”和“白盒定向优化策略”。Claude Code 不知道自己会面对什么任务,所以它必须做大量探索性操作来保证正确性;我的 Agent 每一步都按既定流程走,不需要反复试探。这两者的差距,正好就是 Token 优化的空间。

3. Token 成本大幅下降的四个关键优化

3.1 上下文分层:别再让 Agent 把全部历史一锅端给模型

大多数 Agent 的上下文增长是有规律的。会话刚开始时可能只有几千 Token,几轮之后,因为积累了工具输出、模型回复和系统消息,很快会超过几万。如果每次都把所有历史原样发给模型,Token 消耗会随着轮次接近线性甚至超线性增长。

我的做法是把上下文分成三层。第一层是系统提示和任务目标,固定在每次请求中出现,但只保留最重要的约束,细节放到具体步骤里再展开。第二层是当前焦点,也就是最近一次工具调用的结果、当前待处理的代码片段、最近的模型回复,这一层必须完整发送。第三层是历史摘要,更早的对话不再原样保存,而是由记忆管理器压缩成紧凑的文本摘要。

举个具体的例子。完成“扫描目录”这步之后,历史摘要可能就是一句:“已扫描存档目录,发现 3 个存档,路径已记录”。模型下一步只需要基于这个摘要继续处理,不需要再看一眼完整的目录树。用这套模型,一段很长的会话在每次请求时携带的 Token 量都能保持平稳,而不会越滚越大。

3.2 工具粒度:粗粒度业务工具,细粒度内部实现

工具粒度是 Token 消耗的第二大变量。我把它比作“让人去便利店买东西”。如果只告诉这个人“去便利店买牛奶”,他跑一趟就完事。如果告诉他“出门右转,走三条街,进门,找冰柜,开柜门,拿牛奶,关柜门,去收银台结账”,那每一步都是一个决策点,任何一步理解错了都可能从头再来。

所以我在工具设计上做了一个反直觉的选择:工具写得越“大”,Token 消耗越低。比如备份任务,我不让模型去调“扫描目录”“读取文件列表”“创建目录”“复制文件”“校验副本”这些细粒度工具,而是直接给它一个backup_game_saves工具,内部自己完成路径探查、复制、校验、轮转。模型只需要决定“备份哪个游戏,用哪种模式”,剩下的它统统不关心。

细粒度工具和粗粒度工具的区别,我用一个对比表来说明:

维度细粒度工具粗粒度工具
模型决策次数多,每一步都要判断少,只判断业务意图
上下文往返次数多,每轮都要重新发送上下文少,一次调用完成业务动作
中间状态复杂度高,容易在下一步丢失信息低,工具内部封装状态
典型 Token 水位高低

粗粒度工具还有一个好处:模型不容易在中间状态上反复确认。细粒度链路里,模型经常打完第一步就忘了第二步的前提,于是回头重新扫描一遍,这在 Token 账单上非常刺眼。

3.3 结构化输出约束:把模型的自由发挥空间关进笼子

模型默认的回复方式是自然语言,它会解释、会补充、甚至会道歉。这些内容在编程场景里基本都是 Token 浪费。我给自己 Agent 定了一条硬规则:计划输出必须用 JSON,状态更新必须用 JSON,最终报告必须用固定格式。在输出正式内容之前,不允许出现“好的,我来处理”这类过渡话术。

为了让这条硬规则可执行,我把计划管理也变成了一个工具调用。模型先调用submit_plan提交一份结构化计划,执行过程中每完成一步就更新一次状态 JSON,直到所有步骤完成。这样虽然看起来约束变多了,但模型反而更省力,因为它不用考虑“怎么用自然语言解释自己的行为”。

一个计划 JSON 的实例长这样:

{ "goal": "编写游戏存档自动备份脚本", "steps": [ {"step": "detect_save_dirs", "params": {"game": "example_game"}}, {"step": "analyze_save_structure", "params": {"path": ""}}, {"step": "backup_game_saves", "params": {"game": "example_game", "mode": "incremental"}}, {"step": "verify_and_report", "params": {"path": ""}} ] }

模型只需要按这个结构填内容,不需要发挥文采。这能让它的输出 Token 大幅下降,同时也让后续解析逻辑变得非常简单,不会因为模型自由发挥而频繁出错。

3.4 少样本提示瘦身:把例子当参数,不当教材

给 Agent 提供示例时,很容易陷入一个误区:示例写成了教学材料——开头解释背景,中间展示完整代码,结尾再来一句总结。这些内容模型真正需要的只是“数据格式”和“边界行为”,其余都是死重。

我后来把所有示例压缩成两类。第一类是“最短完整示例”,只保留结构上必须的字段。第二类是“反例提示”,直接告诉模型哪些输出会被拒绝,比如“NOT_ALLOWED:不要输出非 JSON 格式的计划”。反例通常用一个很短的标记表达,不需要长篇大论。

这轮优化让我的系统提示从接近四万字符降到了不到五千字符。字符数降下来之后,每一轮请求的固定开销都同步下降。前面做的上下文分层、工具粒度优化,在这个基础上才发挥出组合效果。

4. 一个实测任务:让 Agent 写“游戏存档自动备份脚本”

4.1 为什么选这个任务,以及验收标准怎么定

我在做基准测试时,特意选了一个大家都懂的任务:编写游戏存档自动备份脚本。它算得上一道比较典型的“多文件、多步骤编码任务”,和大部分实际开发需求具备类似的结构。

游戏存档的痛点不用多解释:存档往往藏在又深又偏的目录里,重装系统、误删文件、硬盘故障都可能让几十个小时的进度一夜清零。给存档加一道自动备份防线,是既能体现 Agent 能力、又很容易验证成果的任务。

任务需求我写成几条明确的标准:

  • 自动扫描指定游戏对应的存档目录,覆盖常见平台默认路径
  • 支持全量备份和增量备份两种模式
  • 备份后在目标目录生成带时间戳的版本目录,并保留最近 N 份
  • 备份完成后校验源文件与备份文件的数量和大小,生成 Markdown 格式报告
  • 提供命令行参数,允许手动指定游戏名和目标目录

这些标准最终都变成了自动化验收脚本里的检查点,保证两套 Agent 的产出是在同一把尺子下衡量的。

4.2 我的 Agent 怎么拆解这个任务

我的 Agent 拿到需求后,并没有立刻开始写代码,而是先提交了一个结构化计划。这个计划决定了整个执行路线,后续所有行为都按计划推进,不再像普通对话式 Agent 那样走一步看一步。

实际执行中,它会先扫描存档目录,再分析目录结构,然后调用备份工具完成备份,最后校验并生成报告。整个流程的核心是,它在两个关键节点之间不会产生无关的工具调用,也不会反复读取已经确认过的信息。

对比之下,Claude Code 也能完成这些步骤,但它更像一个“边做边探索”的人。它会列出目录看看到底有什么,会读几个文件确认格式,会在修改代码之后重新读一遍目录确认结果。每一步单独看都合理,合在一起就产生了大量重复的 Token。

4.3 两组 Agent 的实际 Token 消耗对比:58.7% 到底差在哪

在同一台机器、同一个模型接口、同一份任务描述的条件下,我连续跑了三轮测试,各取中位数。结果如下:

指标自定义 AgentClaude Code
提示词 Token(中位数)3191488650
补全 Token(中位数)1924235292
总 Token(中位数)51156123942
是否通过验收是是

用总 Token 计算节省比例:(123942 - 51156) / 123942 ≈ 0.587,也就是 58.7%。

差距主要出现在提示词 Token 上。查找原因时发现,Claude Code 会把大量文件内容、目录树、历史中间结果原样保留在上下文里,下次请求时完整发送;而我的 Agent 只发送当前焦点和历史摘要,所以在提示词这一侧直接省掉了一大半。

4.4 输出质量验证:省 Token 不等于偷工减料

省 Token 的前提是输出质量不能缩水。我对两套 Agent 生成的结果做了同样的自动化验收:

  • 准备模拟存档目录,执行备份脚本
  • 检查备份目录是否生成时间戳文件夹
  • 核对源文件与备份文件的数量、大小是否一致
  • 验证最近 N 份的轮转逻辑
  • 检查报告是否包含开始时间、耗时、文件数等字段

最终两套 Agent 都通过了全部检查项。区别只在于过程中的 Token 消耗和耗时。这说明 Token 优化并不是靠减少工作量换来的,而是靠减少重复、减少试探、减少冗余上下文换来的。这条路走通之后,我意识到一个更重要的结论:省 Token 的能力,本质上反映了 Agent 对自身行为的控制力。

5. 基准对比实验怎么设计才有参考价值

5.1 变量控制三原则

不少 Token 对比实验的数据经不起推敲,问题往往出在会话状态。比如先在 A 工具里跑了一轮,再把包含上下文的历史带到 B 工具里继续跑,那对比结果就没有意义。

我遵守三个原则。第一,独立会话,每套 Agent 都在全新会话里开始任务,不携带任何对方信息。第二,任务描述统一,粘贴完全相同的需求文本,不做额外解释。第三,验收交给脚本自动判定,不用人工主观打分。

5.2 Token 统计口径:prompt 与 completion 必须分开

模型接口每次调用返回的用量,一般包含几个关键字段。我在统计时把它们分别记录,而不是只看一个总数。

字段含义分析价值
prompt_tokens发送给模型的提示词 Token反映上下文策略效率
completion_tokens模型生成的补全 Token反映输出约束效果
total_tokens两者之和衡量整体成本

从我的实验看,两套 Agent 的补全 Token 差距相对小,真正的差距几乎全在 prompt_tokens。明确拆开之后,才能定位到“这个 Agent 是在提示词侧浪费还是在补全侧浪费”。如果只统计 total,就不知道下一步该优化哪里。

5.3 这个 58.7% 存在边界条件

我得坦诚,58.7% 不是这个方案在所有场景下的固定收益。它是在“多文件操作、多步骤流程、中等复杂度”的任务里测出来的。如果让两套 Agent 写一个 10 行的小函数,差距可能会小到忽略不计,因为小任务里没有太多历史膨胀的空间,黑盒参照工具的保守策略也不会带来明显代价。

反过来,如果任务更长、文件更多、上下文更容易堆积,Token 优化的空间往往更大。所以这个数字适合当作方向参考,不适合当作对所有任务的承诺。重要的是学会了用同样方法去测自己的实际场景,而不是迷信某个固定百分比。

6. 常见问题与排查技巧实录

6.1 上下文摘要把关键路径参数压丢了怎么办

这条坑我踩得很实。早期让记忆管理器压缩历史对话,结果它把“目标目录是某个具体路径”这种参数压没了。模型后面一直在猜路径,备份目录自然不对,又花了好几轮去排查,白白消耗大量 Token。

后来我改成“双通道记忆”:结构化关键信息单独存成一个状态文件,路径、参数、工具结果这类数据永远不进压缩流程。只有模型之间的非关键对话内容才走摘要。这样改完,再也没有出现“参数被摘要吃掉”的问题。

这套改法让我想明白一个原则:上下文压缩不是所有内容的压缩,关键状态必须独立存储。如果依赖摘要来承载关键状态,那压缩就是在赌模型的运气。

6.2 工具调用失败导致反复重试,特别烧 Token

模型调用工具失败时,最常见的反应是换一个参数再调一次,再失败再换参数。如果工具本身有问题,这种重试会白白消耗大量 Token,而且基本原地踏步。

我做了三层防护。第一层:工具内部做参数校验,明显不合理的参数直接返回错误信息,不给模型含糊空间。第二层:记录同一工具的重试次数,连续三次失败就进入“中止分支”,要求模型改变策略,而不是继续重试。第三层:把错误码写成短消息,比如ERR_NOT_FOUND,让模型秒懂问题在哪,不需要读大段错误日志。

实际使用中我最大的感受是:模型并不会从失败中学习,它只会重试。如果没有“改变策略”的显性指令,它就默认“换个参数再来一次”。省 Token 这件事,很多是靠工程约束,不能靠模型自觉。

6.3 模型思路跑偏了怎么拉回来

模型跑偏的典型表现是开始做和目标无关的操作,比如在多文件项目里突然去读一个无关配置文件。原因不是模型笨,而是上下文中出现了太多可以“分心”的内容。

我从架构上加了两个约束。第一,每轮调度时把当前目标固定在系统提示的固定位置,用分隔线隔开,减少模型遗忘目标的概率。第二,在进入新工具调用之前,要求模型先输出一个“下一步意图”的简短 JSON,比如{"intent": "backup_game_saves", "target": "example_game"}。调度器看到意图和目标不一致,就直接打断,提示模型重新聚焦。

多消耗的那一点 Token,相比它带来的“防止跑偏”收益,是完全可以接受的。

6.4 长任务中断后,所有过程中的 Token 是不是白花了

如果 Agent 跑了一个小时,突然断网或进程退出,然后从头再跑,那确实白花了。所以我给 Agent 加了检查点机制。每隔几步,任务状态、工具结果、下一步计划会写成一个本地检查点文件,而不只是存在模型上下文里。

中断恢复后,调度器读取检查点,把“已完成动作”和“下一步计划”发送给模型,模型直接从这里继续。有一次跑大批量重命名任务,跑到一半进程意外退出,恢复后它只用了不到正常流程一成的 Token 就完成了剩余工作。

这个机制对长任务尤其重要。长任务最怕的不是“某一轮突然很贵”,而是“跑了很久之后被迫从零开始”。

走完这一圈,我的体会是:做自定义 Coding Agent 最值得的地方,不是那个 58.7% 的数字,而是你第一次能看清楚每一笔 Token 花在了哪里。它让你从一个“被动接受结果”的用户,变成了“主动控制过程”的工程师。

最后分享一个我一直在用的习惯:不要一开始就做花哨架构,先拿一个最小循环跑通,然后把“哪里费 Token”的日志导出来看。日志不会骗人,它会直接指给你看,重复读文件浪费了多少,上下文膨胀浪费了多少,模型跑偏又浪费了多少。我那个 58.7% 其实不是设计出来的,是顺着这些日志一点点抠出来的。如果你也在折腾类似的东西,建议从看日志开始,这一步比任何优化技巧都管用。

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

Spring Boot单元测试实战与最佳实践

1. 为什么Spring Boot单元测试值得投入时间刚接触Spring Boot时,我和很多开发者一样,把大部分精力都花在了业务逻辑实现上。直到线上出现了一个低级空指针异常——这个在单元测试中本应被发现的Bug,让我付出了通宵回滚的代价。从那时起&#…

作者头像 李华
网站建设 2026/10/15 7:09:32

身体扫描冥想:程序员的身心放松技术指南

1. 项目概述:身体扫描冥想是什么?身体扫描冥想(Body Scan Meditation)是一种通过系统性地觉察身体各部位感受来达到放松效果的冥想技术。我第一次接触这种练习是在2013年参加的正念减压工作坊,当时作为长期伏案工作的程…

作者头像 李华
网站建设 2026/10/15 7:09:15

Codex插件生态详解:从MCP到工作流,打造可依赖的AI编程助手

1. 先把"插件"两个字说清楚:Codex扩展的三层结构如果只用一句话评价Codex,我的结论是:它是我见过最值得折腾的AI编程助手,但绝不是开箱即用的那种。很多第一次用Codex的人,只体验到了它的"对话层"…

作者头像 李华
网站建设 2026/10/15 7:09:14

从转导学习到归纳学习:AI范式转变如何影响模型设计与部署

上周一个朋友抱着图模型来找我排查线上指标:训练的时候 F1 有 0.92,一上线遇到新注册的用户节点和新产生的交易边,分数直接掉到 0.74。他问我需不需要继续调参,我翻了一下任务定义,发现他把整张图的节点都当成训练对象…

作者头像 李华
网站建设 2026/10/15 7:09:00

Spring Boot+Vue+MyBatis实战:企业级疗养院管理系统源码剖析

单看这个标题,很多人第一反应是“又一个CRUD管理后台”。确实,Spring Boot Vue MyBatis MySQL 是这几年 Java 全栈项目里最常见的一套组合,但一旦业务场景落到“疗养院”,事情就不完全是 CRUD 那么简单了。疗养院管理系统管理的…

作者头像 李华
网站建设 2026/10/15 7:08:01

SpringBoot+微信小程序实战:美甲店预约管理系统设计与实现

做美甲店管理系统这件事,起因其实挺简单:有位开店的朋友跟我吐槽,预约全靠微信消息一条条对,会员卡余额记在本子上,月底一对账就头疼,遇上节假日更是手忙脚乱。我当时听完就说,这需求不用上太重…

作者头像 李华