
用大半年Claude Code和Codex这类AI编程代理之后我最深的感受不是它写代码有多快而是Token烧得有多快。很多人以为Token消耗的大头是对话和代码生成其实真正吃量的反而是平时不太注意的“工具输出”。跑一次测试、翻一次日志、列一次文件成千上万个Token就没了而且这些Token会留在上下文里后面每一轮对话都得重新“背着”它们。这篇文章就围绕“优化工具输出减少AI编程代理的Token使用”这个话题把我踩过的坑、用过的方案、验证过的参数一次性讲清楚。如果你也在为额度焦虑或者想提高编程代理的使用效率这篇内容应该能帮你省下不少成本。1. 工具输出吃Token的真相为什么你的额度总是不够用1.1 Token都被悄悄烧在了哪里先看三类最常见的Token黑洞它们几乎存在于每个AI编程代理的日常会话里。第一类是命令执行的返回结果。比如你在项目里跑一个grep -r TODO src/看起来只是一行命令但输出可能带上几十个文件的匹配内容每个匹配行又带着绝对路径、缩进和上下文。如果一个文件被匹配到5行20个文件就是100行输出轻松几千Token。第二类是日志和报错堆栈。写代码过程中让代理帮忙排查测试失败一个完整的Java或Python堆栈可能有三四十层每层一行类名加行号。在吞吐量大的时候再配几条Error日志一次工具返回三四千Token很正常。更麻烦的是这些内容语义密度很低真正有用就一句话“NullPointerException at line 42”剩下全是噪音。第三类是文件列表和目录扫描。代理要理解项目结构时经常执行find . -type f或直接看ls -R遇到node_modules、dist、.next、build这类目录返回的就是几百上千个路径。路径名之间几乎没有语义信息但Token量是实打实的。有人会问“返回多就多呗反正只算一次。”这是一个很大的误区。工具输出一旦进入上下文之后每一次模型推理都要把整段上下文重新读一遍。也就是说这一条5千Token的工具输出如果后续你还要让代理继续改代码、继续调用工具它会在每一轮里至少再被计算一次。如果你一次会话有20轮那么这条输出的实际成本可能接近2万Token。这才是“工具输出导致Token爆掉”的核心机制。1.2 一次工具调用的Token账本我用一个具体场景算一笔账让代理“检查一下登录接口为什么超时”假设代理决定先读配置文件再看路由文件最后跑一次带详细日志的测试。第一步读取配置文件命令约150 Token返回200行配置约2000 Token随后模型总结约300 Token共约2450 Token。第二步读取路由文件命令约120 Token返回150行代码约1800 Token模型总结约250 Token共约2170 Token。第三步跑测试并抓日志命令约200 Token返回300行日志约3500 Token模型分析约500 Token共约4200 Token。三个步骤加起来就花了近9000 Token而真正有价值的中间结论可能只有“超时发生在发送HTTP请求之后”。如果开发者在设计工具输出时做一点控制比如限制配置文件只返回变更行、日志只返回最近50行里带关键字的行这三步的Token用量能压到2500左右节省超过70%。从成本角度算一下按市面上常见的编码类模型定价粗估不同服务商每百万Token价格从几美元到几十美元不等。按相对便宜的价位、3美元/百万Token算一次会话因为工具输出浪费8000 Token约合人民币不到两毛钱看着不多。但一个重度用户一天有20个会话一个月就白白烧掉一两百块的额度和大量的时间窗口。如果是团队采购、多人同时使用这就是一笔不小的开销。1.3 影响范围不只是钱的问题Token成本高只是表层工具输出过长还会带来几个更难察觉的问题。响应变慢。模型处理超长上下文的时间随Token数近似线性增长你让代理多看了5000行日志它每一步思考都要慢两秒。本来30秒能完成的任务拖到一两分钟实际体验非常糟糕。准确性下降。上下文越长模型越容易在无关信息中迷失产生幻觉或漏掉关键约束。有同行反馈过测试日志太多的时候Claude Code偶尔会“虚构”一个不存在的报错原因依据就是日志里某些边缘内容。工具输出越精简结论就越聚焦。超出输出上限。不少模型有32k、64k或128k的输出Token上限一旦上下文过长再叠加一次大段的工具返回模型会直接中断出现“claudes response exceeded the 32000 output token maximum”这类报错。这个不是配置问题而是你把模型“喂太饱”了。所以优化工具输出不是扣扣搜搜省几毛钱而是在提升整个工作流的稳定性、速度和可维护性。2. 工具输出瘦身的核心设计思路2.1 最小化原则够用就行别给模型带饭做AI编程代理的工具输出第一原则是“最小够用”不是“完整全面”。你在写一个工具时先问自己模型拿到这个结果之后要做什么如果只是判断文件是否存在就返回exists: true/false加上文件大小不用把整个文件内容塞给它。如果只是判断接口是否注册成功就返回状态码和耗时不用把HTTP响应全文拿回来。实操中最见效的一个改动所有执行类工具默认关闭完整输出只在显式要求时才开启全量返回。比如命令工具规定默认返回最后50行超过部分用“已截断共342行如需完整输出请指定--full”来代替。模型很聪明它看到截断标记一般会主动决定是否需要补全。还有一个容易忽略的点错误信息也要裁剪。很多时候工具执行失败stderr里的原始错误长达上百行其实就第一行“Permission denied”有用。给工具统一加一个错误处理逻辑只返回错误类型和核心信息比如ERROR[3] Permission denied (path: /root/config.yaml)其他堆栈细节作为可选参数再读取。2.2 结构化优先让模型少做阅读理解模型处理结构化的输出远比处理自由文本高效token消耗也更低。同样是返回10个搜索结果下面两种格式的Token差很多# 低效格式 文件 src/utils/auth.js 的第42行有一个函数 validateToken这个函数接收一个参数 token返回一个布尔值用于校验token是否过期。文件中还有其他内容包括错误处理和其他工具函数。{file:src/utils/auth.js,line:42,symbol:validateToken,type:function,args:[token],return:boolean,summary:校验token是否过期}第一种自然语言描述容易让模型反复重读才能提取关键字段第二种JSON格式模型一眼就能定位。实际中只要工具能改造成JSON行(JSON Lines)或紧凑的表格形式输出整体效果都会有明显提升。但要注意结构化不代表无脑堆字段。一个检索文件内容的工具返回结果时带上file_path、line_start、line_end、matched_line就够了没必要把last_modified、owner、permissions这些与当前任务无关的元数据全部带上。2.3 分层摘要先给结论再按需下钻这一个策略是我自己最推荐的也是投入产出比最高的一招把工具输出设计成分层结构。第一层返回摘要控制在500 Token以内只告诉模型“有哪些候选、各自的关键信息是什么”。第二层返回局部详情比如某个文件的前100行、某个日志区间。第三层才是完整内容是模型在明确判断“不看完无法继续”时才会去拉的。以日志分析为例第一层可以只返回各个级别日志的数量统计和出现异常的位置ERROR 12次, WARN 35次, INFO 240次 ERROR常见位置: - src/auth/jwt.ts:78 (5次) - src/services/order.ts:210 (3次)第二层再返回某个具体位置的详细错误信息。第一层最多300 Token第二层500 Token第三层才放完整日志。比起一口气把3000行日志全塞给模型这种设计至少能节约80%的Token。我自己在自定义工具时一般会留一个开关detailsummary|local|full。默认是summary模型如果觉得不够可以带detaillocal再调一次。既保留了工具的灵活性又控制了默认成本。3. 实操高频工具的具体优化方案3.1 Shell执行类命令、日志、测试输出Shell类工具是Token消耗的重灾区因为它太灵活任何输出都可能被直接返回。我的做法是把常用命令封装成固定工具而不是让模型自己裸跑任意shell命令。输出截断给每个命令结果设置硬性上限。比如Pipeline里自动追加| head -n 100日志文件读取一律走tail -n 50超过就标记截断。这样即使模型写了cat server.log最终返回的也只是一部分而不是整个文件。一个我在Claude Code的hooks配置里实践过的方式给工具执行加一层包装脚本#!/bin/bash output_file$(mktemp) timeout 30s $ $output_file 21 exit_code$? line_count$(wc -l $output_file) if [ $line_count -gt 100 ]; then echo --- 输出过长共 ${line_count} 行仅显示前 100 行 --- head -n 100 $output_file else cat $output_file fi exit $exit_code从系统提示词层面也可以约束要求编程代理优先使用带过滤的查询命令而不是直接读取整个文件。例如对日志文件只做关键词过滤和尾部截断。日志级别过滤排查问题时明确告诉代理生产环境先看ERROR和WARN不要一上来就拉INFO和DEBUG。在实际工具设计上可以为日志文件专门提供--level参数封装在配置里。测试输出跑测试时建议使用--fail-fast在第一个失败点停下同时用JUnit/XML格式或精简的-q模式而不是完整输出。失败时再单独收集失败用例的堆栈不要所有用例全部打印。3.2 文件读写类按需读取拒绝整读文件读写是另一个大头。很多代理默认会把整个文件读进上下文一个500行的组件文件就有近5000 Token项目中五六个文件一读上下文就满了。优化方向很明确提供支持行号区间的读取工具。让模型先通过grep或符号索引定位目标函数再按行号片段读取。设计上参考{tool:read_file_lines,file:src/api/user.ts,start:120,end:160,truncate:true}中间没有输出的行用...占位避免每行都重复路径前缀。尤其是TypeScript/Java这类带长签名的语言一个方法动辄占40行按需读取能省一半。文件排除配置直接在编程代理的配置文件里把不重要的目录排除掉。比如.gitignore思路的扩展单独设置一个扫描黑名单node_modules、dist、build、.next、coverage、vendor、__pycache__、*.lock。目录树和全局搜索里直接跳过这些目录避免模型被海量无意义路径淹没。自动跳过二进制文件读取工具默认检测文件类型图片、压缩包、SQLite数据库这些一律不读只返回binary file, skipped。这一点对防止“模型突然开始分析一张图片”的情况非常有效。3.3 搜索与检索类限制数量精简字段搜索工具是调优的重点。语义检索和代码搜索工具只要稍微改几个参数就能从“一次返回100条”变成“一次返回5条高相关结果”。默认limit设置小一点。全局代码搜索默认10条以内需要更多再让模型自己加参数。过滤条件前置。指定文件类型比如只搜*.ts或*.py排除测试目录减少无关命中。结果去重。多个分支文件里常见的同名符号按文件路径聚合成一条。字段裁剪。检索结果默认只返回文件路径、行号和一行摘要完整内容留给后续读取工具。我建议给搜索工具增加top_k参数默认值3到5。大部分编程任务模型其实只需要看最相关的三五个位置就能解答问题给100条反而会增加它判断的负担。还有一个好用的设计检索结果里加入score字段。当多个结果都匹配时模型可以优先看score最高的部分不需要全部扫描。实测下来这个字段能明显减少模型在多结果之间犹豫的次数。3.4 自定义MCP工具把截断逻辑写进协议如果你在用MCPModel Context Protocol或者自有Agent框架工具输出优化更是从源头就要做的事情。一个常见误区是“工具数量尽量多每个功能一个工具”。实际上每个工具的描述文本也占用系统提示词工具一多还没开始干活几千Token就没了。我把同类型的操作用参数区分合并比如文件类工具就保留read_file和edit_file两个搜索类工具统一成一个search_code描述写的准确且精简整体Token开销瞬间降下来。工具返回的内容需要支持“软截断”和“硬截断”。硬截断是超过某个阈值直接切断软截断是还在运行时就持续发送中间摘要直到拿到完整结果再合并成最终返回。硬截断适合日志和文件内容软截断适合流式输出。下面是我在自定义MCP工具中的一份精简返回逻辑def summarize_output(output: str, limit: int 100) - str: lines output.splitlines() if len(lines) limit: return output head lines[:limit] tail_count len(lines) - limit return \n.join(head) f\n... 已省略 {tail_count} 行可用 detailfull 获取完整输出这段逻辑看着简单但在一个实际项目中帮我把工具输出从平均2500 Token压到了平均600 Token。省略提示本身是关键模型看到提示后如果确实需要完整内容会自己决定额外调用一次不会影响任务准确性。4. 常见问题与排查技巧实录4.1 不要把“认证Token报错”和“模型Token消耗”搞混网上关于Token话题的讨论很杂最近搜“token”的热词里有一大半其实是登录认证问题比如token exchange failed: token endpoint returned status 403、sign-in could not be completed、your access token could not be refreshed。这些跟本文讨论的上下文Token完全是两回事。如果遇到这类报错排查思路应该是本地凭证是否过期、当前登录状态是否失效、用户身份提供方IdP配置是否正确、时区或时间是否偏差导致JWT校验失败。有一回我本地电脑系统时间差了三分钟JWT直接校验不过折腾了半个小时。先把时间同步对、再重新登录一次绝大多数会话类报错都能解决。编程代理里出现的“access token could not be refreshed”一般是因为登出后重新登录时旧凭证还在本地或凭证存储文件损坏。处理方式是彻底登出、删除本地凭证缓存目录、重新登录。不要把时间和精力浪费在调整工具输出上那解决不了认证问题。4.2 “输出Token上限被截断”的处理思路不少人在用长上下文模型时会遇到“claudes response exceeded the 32000 output token maximum”或“已达到输出Token上限回答被截断”然后点击“继续”让模型往下接。这个做法只能救急不能根治。出现这类报错的触发条件通常是上下文太长模型需要在极长上下文中生成完整回复或者单次工具返回的内容太大。按“继续”确实可以补全但每继续一次都要重新计算前面的全部上下文成本反而更高。更推荐的做法是先把当前任务拆小一次让模型只做一个完整的小模块而不是让它一口气写800行代码。重点在于控制上下文可以把已完成的部分保存到临时文件里然后开一个新会话把临时文件路径作为上下文传入清空旧的历史累积。“输出Token上限”不是配置项而是模型架构的硬约束。面对它只能做减法。4.3 关于“便宜Token”“3亿Token”这类说法的个人判断热词里还出现了一些类似“zcode 3亿token”“便宜token”“免费token”的说法。我的观点是应该理性看待。Token单价便宜不一定代表总成本低。很可能便宜的是输入Token但输出Token和工具调用额外计费或者限制并发、限制模型版本。更关键的是第三方“Token中转站”类服务要谨慎使用。一旦你的代码、业务数据、私有仓库内容经过别人的平台安全边界就是最大的问题。对我而言与其纠结单价不如先优化自己的用法把整体消耗降下来用量降低之后再考虑哪家方案更划算。如果真的要用第三方Token服务至少要确认数据是否加密、是否记录日志、模型输出是否被用于训练、服务商是否有明确的隐私协议。这些比单纯的Token单价重要得多。4.4 一个很实用的会话管理习惯工具输出优化完了还有一个配套动作定期换新会话。编程代理的一个缺点是上下文会随着对话轮次不断膨胀早期工具输出的历史内容即使已经被“遗忘”在业务逻辑里仍然会被重新计算。所以我在实际操作中会养成两个习惯。一是每完成一个可验证的功能点比如测试通过、代码编译通过就把关键信息写进项目根目录的规范文件比如Claude Code支持的CLAUDE.md或通用的AGENTS.md记录当前项目结构、模块边界、命令用法。然后果断开新会话。新会话只需要读取这一份摘要历史垃圾全部丢掉Token用量能大幅下降而且回答质量反而更好。二是大型重构任务强制拆分成多个阶段一个阶段一个会话。第一阶段只分析和设计第二阶段只实现某个具体模块第三阶段统一跑测试。这样每个会话的上下文都相对干净工具输出的累积效应也不会太严重。最后再说一点个人体会我自己的项目在做了上述优化之后同一个任务的Token消耗明显下降。原来的长会话前后对比下来单轮工具调用的Token量下降了大概四成会话总成本能省将近一半。代价是前期需要花时间设计工具、写截断逻辑、做排除配置这部分投入是值得的。优化AI编程代理的Token使用核心不是“省”而是“让模型的注意力集中在真正重要的信息上”。工具输出越短模型越能快速找到关键问题上下文越干净回答越稳定。这套方法无论你用的是Claude Code、Codex还是其他编程代理都适用。如果你也一直在为Token“跑得飞快”而头疼建议先从工具输出下手这是性价比最高的一步。