给 Agent 装了 46 个工具,我算了一笔 token 账
原创 · 约 19 分钟阅读 · 阅读 --

给 Agent 装了 46 个工具,我算了一笔 token 账

作者: 字与码


古董级程序员,大厂出来后一直在创业公司,现在仍然在一线写 AI 相关的代码。这些年踩过的坑、对工具链的判断和偶尔的反思,更完整的版本会写在微信公众号「字与码」,这个专栏的后续几篇也会在那里同步。要是这篇账单对你有用,顺手关注一下就行。

过去两个月我一直在推荐别人装那套确定性工具:一条命令,46 个工具,JSON 校验能给你精确到行列的偏移,编码检测会说”这两个汉字我真的分不出来”,线程 dump 会自己把死锁环画出来。

推荐得很顺手,直到我朋友问了一句:这些工具本身要花多少 token?

这个问题有点扎人。工具 schema 是请求的一部分,装在 profile 里就跟着每一次请求发出去——你用不用都要付。我之前的回答基本靠感觉,所以这周把它测了一遍。这篇是第一篇,先把账算清楚;后面再讲这些工具具体长什么样、怎么验收。

先承认”模型自己也能做”

我不想把这篇写成”没有插件就不行”。实测里有一个反例,正好说明问题。

有一组任务是算 hello world 的 sha256。不装插件,只给模型一个 PowerShell,它的做法是自己写 pwsh:

# 不给插件时,模型自己现写的命令(它写了两次才收工)
$bytes = [System.Text.Encoding]::UTF8.GetBytes("hello world")
$sha = [System.Security.Cryptography.SHA256]::Create()
([BitConverter]::ToString($sha.ComputeHash($bytes)) -replace '-','').ToLower()

结果是 b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9,是对的。

所以”模型算不准哈希”这个前提,在有 shell 的环境里根本不成立。它写得出、也算得对。真正能拉开差距的是别的东西,而这件事必须先把账摊开才看得清。

固定税:装了不用也要付

先量最大的一块

同一套 headless base、同一个工作目录、同一条 LLM 路由,两次干净启动,只看首次请求的 inputTokens:

配置工具名数量首次请求 inputTokens
干净 headless base7511,931
base + 四个插件12119,608

多出来的 46 个工具名,对应 +7,677 tokens/请求。摊到 46 个工具上是平均约 167 tokens 一个,不过这个平均只能当量级看:四个插件各自的 schema 长短不一样,单装 dsh-deterministic-tools(29 个工具)实测大约是 +3.4k,不是 29 × 167。占满配请求 19,608 的 39%。

39% 这个数字是”要不要全装”这件事上唯一的硬指标。我原来以为工具描述只是几十个 token 的开销,实际量出来才发现:这不是零头,这是接近四成的固定开销。

这笔开销的形状

顺便说清楚这个成本的形状。它不随任务大小变化。 你让 Agent 干一件三秒钟的小活,和让它跑半小时的重活,这 7,677 tokens 一模一样地跟着走。所以它更像一笔”每请求税”,而不是”每次调用费”。

四组对照:把”自己算”和”用工具”放同一张表上

固定税只是账单的一半。另一半要看跑起来之后 token 怎么流动。

这里有个容易被忽略的方向问题。调用一次工具其实是两步:模型把数据作为参数生成(这是输出 token),工具结果作为输入 token 回来,然后模型再说结论。而”自己算”通常是一步,或者像上面那样写成 pwsh 变成两到三步。

输出 token 一般比输入贵四倍左右,而且输入还能命中缓存——所以用工具相当于把一部分输出换成了输入,同时减少了步骤,也就减少了上下文重读。

同一个 base、同一条 LLM 路由,B 组和 D 组只多挂 dsh-deterministic-tools(29 个工具)。两个任务:A/B 校验 {"a":1,} 并给出精确行列偏移,C/D 算 hello world 的 sha256。

组配置步骤inputoutputcache read原始总量耗时模型选的路
A无插件312,5691,32326,49613,89213.0 s自己写了 pwsh 两次
B装 29 个工具215,98431417,53616,2986.1 sjson_validate
C无插件37,49558431,3608,0797.9 s自己写了 pwsh 两次
D装 29 个工具215,3127918,17615,3915.4 shash_text

三件事一眼能看出来。

输出少了一个数量级。 1,323 → 314,584 → 79。模型不用再把整段 JSON、整条哈希命令重新吐一遍,结论直接来自结构化回执。

步骤少一步,快一倍左右。 13.0 s → 6.1 s,7.9 s → 5.4 s。少一步不只是省时间,也少一次上下文重读(cache read 从 26,496 / 31,360 降到 17,536 / 18,176)。

但原始 token 总量反而更高。 A→B 涨了 17%,C→D 涨了 90%。多出来的就是那 29 个工具的固定税。这两组任务都太小了,省下来的输出根本盖不住固定税。

所以”装插件省 token”这个说法,对一次性小任务是错的。

什么情况下真的划算

四组数据只能说明两个具体任务。把工具当成一类东西看,划算与否取决于数据的形状,而不是工具本身好不好:

场景判断
一次性、几十字节的活更贵。固定税加参数往返,省下来的输出盖不住
同一会话里反复用划算。固定税是每请求成本,用得越多摊得越薄
聚合/压缩类(dir_usage、file_duplicates)明显省。内容根本不进上下文,只有汇总进来
结果远小于输入(text_diff、json_diff)省输出。模型不必把两份文档各复述一遍
shell 一行搞不定、要几十行脚本的活省 token 也省时间,更省”写错重来”那一整轮
模型算不准的(逐字节编码判断、8.3 名、MAX_PATH)只能靠工具

第一行和第二行其实是一回事的两面:固定税是摊销成本,摊销的前提是你真的重复用。 一个只在会话里用一次的 JSON 格式化,装 46 个工具纯属浪费;一个每天要在几十个目录上做统计的 Agent,这 7,677 tokens 买回来的东西完全值。

一个必须说清的短板

还有一个短板得说清楚,因为它直接决定什么时候不该用这些工具:当前所有工具都吃内联参数。也就是说,一份 2 MiB 的 thread dump 必须先被模型重新吐一遍当参数传进去——这一步在 token 上就非常不划算。给文本类工具加一个有预算的 path 入参(只读、超限拒绝)是 v0.2 该做的事;在那之前,大工件要么先切片,要么交给 dir_usage 这种自己读盘的工具。

不想全装:把 29 个工具裁到 5 个

上面两张表其实给出了一个很实用的结论:固定税按工具数量线性增长,那你只注册要用的就行。

46 个全装约 7.7k tokens/请求;只装 dsh-deterministic-tools 的 29 个,实测约 +4.0k。而按 id 覆盖,把它裁到 5 个:

# ~/.dsh/profiles/<profile>/cordis.patch.yml
# 注意这一层的位置:这个文件头写着"applied after every bundle layer"。
# 用 `dsh --patch <file>` 那种 CLI 覆盖层改同一个 id 会报
# patch: entry "deterministic-tools" not found —— 它在 bundle 层之前应用。
- id: deterministic-tools
  config:
    only: [json_format, json_validate, json_query, text_diff, csv_to_json]
    toolPrefix: zi

同一条任务(校验 {"a":1,})跑三组真实回合,看首次请求的 prompt 总量:

配置进请求的工具名首次请求 prompt
干净 headless7512,838
only 裁到 5 个8013,529(+0.7k)
装全部 29 个10416,799(+4.0k)

裁掉那 24 个 schema 省下约 3.3k,剩下的 5 个工具只占约 0.7k。进请求的工具名也在会话日志里核过:从 104 个变成 80 个,模型那次真的改用了 zi__json_validate(加了前缀,一眼能看出是哪个配置层在生效)。

这个 only 裁剪是整件事里性价比最高的一个动作。裁剪之前,你付的是”工具箱的价格”;裁剪之后,你付的是”你真正会用的那几个工具的价格”。中间那 3k 多 tokens 每个请求都在白白流走。

那这笔钱到底买了什么

算到这里,结论有点反直觉:如果你只看 token,大部分场景下插件是亏的。

一次性小活更贵,这是实测;聚合压缩类才明显省;真正让它划算的前提是你会重复用。那为什么还装?

因为 214 个测试盯着的固定实现买到的不是省钱,是三件事:

一次就对。 模型自己写 pwsh 那段能算对 sha256,但它每次都要重新写命令、处理引号、担心编码和控制台代码页。一次写错就是多一整轮——而一整轮的成本,往往比这个请求的固定税高得多。

可复现。 同样输入永远同样输出,bytesIn/bytesOut 都一致。没有时钟、没有随机、没有环境变量读取。所以结论可以进 issue、进 CI、进复盘文档,别人拿着同样的输入能算出同样的结果。

可审计。 每个工具返回的是 { result, meta } 回执,不是一段自由文本。超出预算的结构化输入会抛 INPUT_TOO_LARGE,并明确说”没有任何截断或部分处理”;解析不了的行进 unknown_lines 并让 confidence 掉下来;jwt_inspect 直接声明自己不验签,netstat_parse 把”PID 是否还活着”放进 limitations[] 而不是猜一个答案。这些都是模型心算给不了的东西。

所以我会这样描述这笔交易:确定性工具是有保费的保险,不是打折券。 保费按请求收,一次性的小活你亏一点;真出事的时候——线上报错原文要变成可核对的结论、审计要追问”这个数是哪来的”——你买的就是”当时是对的,而且能证明它是对的”。

至于怎么把这些承诺做成硬契约(为什么超预算要拒绝而不是截断、为什么”我不知道”必须是一个字段),是这个专栏第二篇的事。

怎么自己复跑

这篇文章里的每一个数字都是本机跑出来的,不是估算。想核对的话:

# 1) 四组对照与固定税:脚本会打印 steps / in / out / cache / sum / 耗时 / 模型选的路
pwsh -NoProfile -File D:\dsh\research\token-economics.ps1

# 2) 裁剪那三组:干净 base / only 5 个 / 全部 29 个,比较首次请求 prompt 总量
pwsh -NoProfile -File D:\dsh\research\measure-base-tokens.ps1
pwsh -NoProfile -File D:\dsh\research\measure-only-trim.ps1

# 3) 核对工具名是否真的进了请求(不是"装了就算",要看会话日志)
node D:\dsh\research\verify-installed-tools.mjs <session.v4.jsonl.zstd>

# 4) 不看仓库也能看工具清单与实测输出
npm view dsh-deterministic-tools readme
npm view @ax2zicode/dsh-dev-toolbox readme

第 4 条要注意包名:合装包在 npm 上是 @ax2zicode/dsh-dev-toolbox(无 scope 的名字会被 npm 以”与已有包过于相似”拒掉),GitHub 仓库仍是 ax2/dsh-dev-toolbox。装的时候别和单独安装的四个插件混用,会重复注册同名工具。

如果你想先试再决定,最快的路径是临时 profile:

dsh plugin --profile web add @ax2zicode/dsh-dev-toolbox
# 装完重启应用:bundle 列表是启动时读的

装完第一件事,建议就去这个 profile 的 cordis.patch.yml 里按需写一遍 only。先把那 39% 砍到你真正需要的规模,再谈值不值。

打开原图 ↗