ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI编程代理Token消耗优化:从工具输出瘦身开始

AI编程代理Token消耗优化:从工具输出瘦身开始 如果你在用 Claude Code、Codex 这类 AI 编程代理可能也有过同样的困惑明明一个任务没聊几句Token 用量却像水龙头一样哗哗流。我最初把账算在模型“话痨”头上后来把每个会话的消耗拆开看才发现真正占大头的东西往往不是模型的回复文本而是那些我们根本没心思看的工具输出——代理执行命令、读文件、查日志时带回的一大堆原始内容。我花了大概两周时间做“输出瘦身”在同一个任务上对比了调整前后的 Token 消耗结果相当明显。这篇文章就把我不吃灰的经验整理出来核心只讲一件事在不削弱代理能力、不让它变笨的前提下怎么通过优化工具输出把 Token 花在刀刃上。要是你正在为 API 按量付费或者订阅套餐里的额度总是不够用这篇对你尤其有用。就算你用的是不限量套餐优化工具输出也能减少长任务里的上下文膨胀和失败重试做完一个任务明显更顺。1. Token 消耗的秘密工具输出会变成下一轮“输入”理解这个问题的关键是先搞清楚 AI 编程代理的一次工具调用到底怎么计费。当代理决定运行一个命令时它先是生成一段工具调用参数比如一条 bash 命令或者 read_file 的文件路径这部分算模型的输出 Token。命令执行完终端结果、日志内容、文件片段会被封装成工具结果放入下一轮请求的上下文。下面这个才是最要命的这些工具结果在下一轮请求里是以“输入 Token”的形式被模型重新读取的。也就是说一次工具调用产生的费用是两层工具调用本身有一点输出 Token随后真正昂贵的是工具返回结果进入上下文后被当作输入 Token 重新计费。如果这轮调用之后代理还要思考、还要再调用别的工具那这份输出会一直待在上下文里每一轮推理都要被模型重新遍历一遍。很多代理产品做了 prompt caching 之类的优化重复的上下文再次计费时会打折。但请注意缓存降低的是“单价”它没有减少内容在上下文窗口里占据的空间。一个长会话里如果早期塞进了一份几千行的日志它不会“自己消失”它会在上下文窗口里一直占着位置直到被压缩机制或者手动清理。我们还要考虑“复利效应”。代理执行任务时经常是读文件 → 思考 → 跑命令 → 读报错 → 再思考 → 再读文件。每多一个环节上一轮的输出就会跟着进入下一轮。一次大输出可能只是多花几个 Token 的事但十几次工具调用下来这份大输出就会反复被读十几次。我用一个比较夸张但真实的例子说明某次被派去修一个登录接口的报错代理第一步直接cat log.txt把一份约 1500 行的日志全量塞进上下文这大概就已经用掉了几千 Token接着它又用 read_file 把 auth.js 整份 400 行源码读出来又是一笔。这两步发生的瞬间这个会话的输入 Token 已经比后面几十行模型回复高出一个数量级。更遗憾的是那 1500 行日志里真正有用的可能只有 3 行 error 堆栈。这才是 Token 用量失控的真相不是模型话多而是代理被允许以比较低效的方式使用工具把大量无关内容拖进了上下文。所以优化工具输出的第一原则不是少调用工具而是让每次工具返回的体积更小、更相关。接下来我拆开讲具体操作。2. 三类最烧 Token 的工具调用看文件、跑命令、全量搜索先建立一个直观的“开销画像”方便你排查自己的会话到底烧在哪。2.1 第一类整文件读取尤其是大文件AI 编程代理都有一个内置的 read_file 或者类似工具允许模型直接读文件。这个工具天然安静、没有命令输出污染看起来人畜无害。可问题是如果代理默认一次读整个文件而你项目的源码文件动辄几百行、配置文件也有好几百行那一次工具调用传输的内容就相当大。更隐蔽的是有些代理工具支持一次读多个文件模型可能“顺手”把相关文件都读一遍。比如让它查一下用户模块的代码它可能把 controller、service、repository 三个文件全读进来。三个文件各四五百行瞬间就是上万个 Token。我最开始也犯过这个毛病跟代理说“帮我看看登录模块怎么回事”它就从 login.go 开始一路把整个目录文件读了个遍其实问题只是某个参数解析逻辑导致的空指针。后来我学乖了在任务描述里主动把范围缩小到函数级并且明确告诉它不要一次读完整文件先读文件头部或者直接定位到可疑函数附近。2.2 第二类日志、测试输出、构建结果整段暴露如果说读文件还能勉强算“必要开销”那日志和测试输出整段甩给代理才是很多会话账单爆炸的元凶。代理排查问题时常规思路是先运行测试或者看应用日志。一次npm test的输出可能会包含几十个测试用例的执行信息docker logs拉出来的可能是过去几个小时的所有请求记录make build的编译输出也经常有几百行。这些输出会原样进入上下文而且里面夹杂着大量成功用例、正常访问日志、编译警告等噪音信息。我见过最夸张的一次代理为了确认某个服务是否启动成功跑了一条docker logs -f api_service然后因为命令没有退出超时被系统强制中断。而中断之前带回来的输出有一百多行绝大多数是健康检查请求的访问日志。真正跟问题有关的错误只藏在倒数几行里。2.3 第三类全目录搜索、全仓差量等“大而全”命令第三类比较隐蔽但实际烧得也很厉害grep -r全目录搜索git diff全仓库改动find /类递归查找tree递归列目录。这些命令在功能上确实很强但返回体积很容易失控。尤其是git diff。代理让你改完代码后往往习惯性执行一次不带路径的git diff看全部改动。如果项目有大量文件被改动或者有自动生成的 lock 文件、vendored 依赖被误改输出可以到几千行。这时上下文里装满的更多是你根本不想让模型看到的 content而不是你需要它确认的 diff 摘要。另外很多代理分不清“列出文件名”和“输出文件内容”的差别。它要了解项目结构时用一条find . -type f -name *.ts | head -n 100就够了但有的代理会直接去动态枚举目录一层层把结构全列出来。目录项本身不大但是文件数量极多时累积起来也不容小觑。要快速定位一个会话里哪些工具调用最浪费我建议你开着用量明细/调试面板跑一个任务观察 token 数骤增的那几步。大概率就是这几个场景读取大文件、整段日志、全量 diff 或者递归搜索。知道了这些高耗点下面就可以针对性地做规则和命令层面的改造。3. 从规则下手把“输出纪律”写进代理的项目指南你可能想不到我自己做的第一项优化没有改任何命令而是在项目根目录的 AGENTS.md或者我用 Claude Code 时习惯维护的 CLAUDE.md里写了一段“输出纪律”。这段文件会被代理自动读取并作为上下文的一部分效果相当于在每次任务开始前就耳提面命地叮嘱它控制输出、别读大文件。3.1 给代理立下可见的文件读取规矩我把下面这段内容放进了项目指南里## Token 与输出规则 - 查看文件前先估算大小普通源码文件超过 200 行时不要整体 read_file优先用 sed 读取可疑区间。 - 需要理解项目结构时用 find/ls 获取文件名列表不要递归打印目录树内容。 - 排查日志时先用 grep 过滤关键词再 head/tail 限定行数禁止 cat 整份日志文件。 - 运行测试或构建命令前优先检查工具自身是否支持 --silent、--quiet、--short、--reporter compact 这类参数。 - git diff 默认只查看当前改动文件范围或 --stat不要对全仓库做无差别 diff 输出。 - 命令若有超时风险前置 timeout 30避免工具卡死导致大量无意义输出。加完这部分内容后我能明确感受到代理读文件的方式变了。它会自己先执行wc -l file看文件行数或者用sed -n去读可疑函数的区域。当然项目指南本身也会占一点 Token但因为内容精炼这点开销和它省下来的相比微不足道。如果你的项目里已经有 CLAUDE.md 或 AGENTS.md别写太长控制在几十行内不然规则本身的 Token 开销会抵消收益。写的时候要具体不要用“请高效使用工具”这种模糊描述要给代理可执行的行为指令。3.2 在每条指令里加上“输出边界”除了写全局规则我也养成了一个习惯给代理布置任务时顺手在自然语言里加上输出边界例如“先 grep ERROR不要读整个日志。”“读取 UserService.java 第 100 到 160 行。”“跑测试时用 --reporter dot或者只输出失败用例。”“curl 返回的结果只保留 status 和 message 字段。”这本质上是在约束每次工具调用的返回范围。在 Agent 模式下模型对自然语言指令的执行力很强你要求它“不要 cat 只要局部”它 80% 的情况下会照做。3.3 不要在系统性提示里塞一堆无关工具规则层面还有一件事常被忽略系统提示里的工具列表越长模型做工具选择时的“思考上下文”开销越大执行时输出的工具调用参数也越复杂。如果你给代理装了一大堆 MCP 工具但多数时候用不上建议做个减法。我把自己常用的 MCP 服务从十几个精简到六个以后不只是工具选择准确率提高了整体 Token 也有肉眼可见的下降。4. 改造工具命令从“全量输出”到“精准切面”规则是软约束真正在命令层面把它落地才能形成硬效果。我自己把常用的命令做了一套“输出小体积改造”下面是我实测最顺手的一组组合你可以直接抄。4.1 读文件不对全量下手以前代理读文件习惯用工具自带的 read_file或者直接cat。现在的做法是# 先看文件行数避免盲读 wc -l src/service/UserService.java # 只读头部注释或结构 head -n 80 src/service/UserService.java # 只读第 100 到 160 行 sed -n 100,160p src/service/UserService.java # 用 grep 直接定位关键字所在行再精确读 grep -n --colornever loginUser src/service/UserService.java控制住命令输出量之后你会发现代理理解代码的速度反而变快了。因为不再有几百行无关声明和工具函数干扰它的注意力。有一个小挣扎需要提一下保留行号会多花一点 Token但对定位问题很有帮助。我通常让代理默认不加行号等真正需要精确引用时再手动要求。4.2 看日志和测试输出先过滤、再采样、最后落盘如果你只是想知道“错误出在哪”完全没有理由让模型看全部日志。下面是我改过的套路# 只统计报错类型不输出具体信息 grep -c ERROR logs/app.log # 匹配到关键词的行限制 20 条 grep -n -m 20 ERROR\|Exception logs/app.log # 查看日志最后 80 行适合进程刚退出时的短日志 tail -n 80 logs/app.log # 完整日志写到临时文件需要时再分段查看 cargo test /tmp/test_full.log 21; tail -n 40 /tmp/test_full.log这里面最推荐的是先把完整输出重定向到文件再让代理有目的地grep或tail。这样既不会丢失任何关键信息又不会让全量输出一次性冲进上下文。代理需要更多上下文时还可以再跑一条sed -n 20,30p /tmp/test_full.log按需读取。你可能会想问为什么不直接让命令输出完整内容靠代理自己“理解”因为代理理解的是上下文里存在的内容面对几千行日志时模型可能会被中间大量干扰信息带偏。信息精准之后给出的判断反而更可靠。4.3 查 git 历史与差异时控制范围看代码历史上我最容易忽视的大坑是git diff。现在规则很简单# 只看改动统计确认到底动了哪些文件 git diff --stat # 只看具体文件的 diff并限制行数 git diff -- src/UserController.ts | head -n 120 # 只看未跟踪文件清单 git status --short如果你希望代理在完成修改后自查可以在安排任务时就要求它“用 git diff --stat 总结变更再抽查具体文件”。这样它不会把整个仓库的变更历史全拖进上下文。4.4 调用外部 API用 jq 裁剪返回字段代理经常需要调本地服务接口来验证行为。如果服务接口返回一个大 JSON整段响应被丢进上下文是很亏的。我以前让代理直接curl localhost:8080/api/users返回的列表可能有几百个用户对象。现在每个 curl 都顺手加一个管道# 只看 HTTP 状态码 curl -sS -o /dev/null -w %{http_code}\n http://localhost:8080/api/users # 用 jq 提取指定字段 curl -sS http://localhost:8080/api/users | jq .data[0:5] | map({id, name, email}) # 只统计数量 curl -sS http://localhost:8080/api/users | jq .data | length这样工具输出会小很多而且模型能立刻抓到要点不需要自己在大 JSON 里翻找。对代理而言结构化的小数据块比一长串未加工的响应更容易推理。4.5 优先用静默参数、避免 ANSI 转义和不必要的交互命令还有几个很容易忽略的细节。构建工具加静默参数npm run build --silent、gradle -q build、make -s。这些参数能挡住横幅、步骤提示。不要跑tail -f、watch、REPL 这类交互式命令理由显而易见它们要么不退出要么不断产生新输出代理容易卡住还会带回一堆无用内容。如果一定要持续观察进程建议启动后台并把日志落到文件隔几秒用tail读一次。给命令加--no-ansi或--colornever避免终端颜色控制字符污染上下文。ANSI 转义序列占用 Token 不说还会扰乱模型对文本内容的判断。在命令层改造这块我额外想强调一个例外不要为了省 Token 强制所有命令都输出最小化有时候你为了少看几个文件而多跑了好几条命令反而更贵。这里的核心权衡是“用有限几次精准输出代替冗余的全量输出”。如果代理只差看 3 行内容就能继续那就让它精确读那 3 行而不是为了少跑一条命令去读整个文件。5. 工具本身的“话痨”属性MCP 与自定义工具的输出设计上一节处理的是系统工具和命令行的输出还有一个开销很容易被忽略你用 MCP 接入的各种工具返回结果。MCP 工具输出的设计直接决定了代理每调用一次要吞进多少 Token。5.1 工具返回信息要“摘要优先”我见过很多团队自建的内部 MCP 工具返回的是整个数据库表结构、整份配置文件、甚至完整请求日志。这样对接方用起来确实省事但对代理极为不友好——它会原样把这些内容当成上下文用掉。如果你有能力修改自己的工具实现建议把返回内容设计成“摘要优先”结构。例如一个获取用户详情的工具不要返回用户对象的所有字段而是返回以下结构{ status: success, id: 1024, name: alice, summary: active user, last_login: 2025-05-01, fields_of_interest: { subscription_plan: pro, risk_flag: false } }需要完整字段时再加一个工具参数include_fulltrue去取。这样模型在大多数场景下只需要读那个小摘要只有确实需要全量细节时才付出额外的 Token 代价。这个设计思路和普通 API 分页是一样的永远不要让消费者被迫拉回一整个超大响应。5.2 少装工具也给工具写“会省钱”的描述MCP 工具列表最终也会变成系统提示的一部分。工具越多系统提示越长模型每次请求的输入 Token 基数就越高。你可以在用量面板里看一下一个装了 20 个 MCP 工具的系统提示可能光工具描述就吃掉了好几千 Token。这个费用是“过路费”任务还没开始就开始扣了。合理的做法是只保留真正高频的工具其他工具按需临时启用。同时给每个工具写清楚“什么时候用、什么时候不要用”比如在工具描述里加一句- Use this function only when you need user detail; prefer list/search tools for broad queries. - Returns up to 20 records by default. Set limit to get more.工具描述的语义越清楚代理越不容易乱调用或用错参数因误调用产生的无用输出也会少很多。5.3 让搜索结果自带摘要而不是整个原始文档很多 MCP 工具用来做语义搜索或代码搜索。搜索工具如果默认返回匹配命中的整个文档片段消耗会非常大。更合适的做法是在服务端先做摘要处理返回前 N 条命中结果的核心行。代码搜索工具可以返回命中文件、行号、匹配行上下文 2 行文档搜索工具可以返回段落摘要和链接。这样模型通常只需要一次小体积读取就能判断“要不要继续看完整文档”。6. 一次真实任务的对比我到底省了多少 Token方法说了不少下面给你看一组我自己的实测数据。为了让结果有意思些我选了一个中等偏复杂的活在本地项目里排查一个登录接口偶发 500 错误并给出修复建议。6.1 不控制工具输出时的表现第一次我没有做任何输出约束直接把这个任务交给代理。它的操作路径大概是这样的执行cat logs/app.log读取了约 1200 行日志上下文一次增加接近 5000 Token。调 read_file 把 AuthController.java 全部读完300 多行代码又增加约 3000 Token。执行curl localhost:8080/api/login不带任何过滤服务端直接返回了一个超长堆栈约 2000 Token。跑npm test看有没有回归结果输出包含几十个测试用例的明细约 3000 Token。任务做完大概用了 30 次工具调用总 Token 在 7 万到 9 万之间。注意其中很多文件在读取后一直留在上下文里后面每轮推理都在重复消耗这部分上下文。6.2 加入输出纪律后的操作路径我把同样任务交给另一个配置了规则和命令习惯的新会话操作路径明显不一样先grep -n -m 20 ERROR\|Exception logs/app.log只带回 20 行关键错误约 500 Token。再sed -n 40,90p AuthController.java只读可疑方法所在区间约 700 Token。用curl -sS -w %{http_code}\n先确认接口状态码发现是 500 后再用tail -n 50看日志尾部堆栈约 800 Token。跑npm test -- --reporter dot 21 | grep -m 10 -E ✓|✗|fail只保留测试结论约 600 Token。整个任务约 28 次工具调用总 Token 降到了 2 万到 3 万之间几乎只有原来的三分之一。而且因为每步输入更聚焦代理没有走弯路思考质量更高。同样的任务效率差异真的超出了我的预期。6.3 踩过的坑截断过头导致代理反复试错优化过程中我也不是没翻过车。有一次为了让代理输出更少我在全局规则里写“所有命令只保留前 20 行输出”结果代理排查一个构建错误时真正有用的报错信息出现在第 35 行被 head 截断了。模型看到不完整的错误只能反复猜测、反复重试白白消耗了更多 Token。所以这里一定要给个提醒截断的第一原则是保留“错误相关信息”而不是机械地只留前几行。对错误排查来说报错的原因往往在输出头部但堆栈尾部也可能藏着根本线索。最稳的做法是先把完整输出写到文件再用 grep 过滤让代理基于关键词决定查看哪一段。只保留前 20 行这种粗暴策略看起来省了输入实际上可能制造大量重试开销。另一个让我印象很深的问题是二进制输出。代理某次跑了一个会向终端写入二进制内容的小工具终端里出现大量乱码字符这些东西进入上下文后 Token 消耗猛增还让模型误判了输出格式。现在我会在规则里明确要求代理避免把二进制内容直接打印到终端必要时用strings或file命令先提取信息。收尾的一点建议优化工具输出这个事本质上是在教 AI 编程代理更“体面”地使用工具给它一个准确的问题它就能带着放大镜去看而不是把整栋楼都搬过来。整个过程并不需要你成为提示词专家也不需要掌握什么复杂的框架设计它只需要你愿意把平时自己敲命令的经验——少 cat、多 grep、先过滤、再采样、控制范围——原封不动地传递给代理。我踩过几次坑之后现在的最优做法是任务刚开始时先用一句话告诉代理“这个任务的输出请克制先看错误再看文件局部”任务中途如果发现它在读无关文件我会立刻打断并让它改成精确读取。省下来的不仅是 Token更是上下文空间的健康度——长会话末尾代理不会因为早期塞入了太多无关内容而忘记真正重要的信息。如果你正准备从零做这件事我建议从三步开始第一给项目加上一条文件读取规则第二把你最常用的几条命令改成带 head/tail/grep 的精简版第三开着用量统计跑一个真实任务看看哪些步骤仍在浪费。做完这三步你会明显感觉到同样的额度能干更多活了。
返回列表