开头先聊一个最近在重构内部 coding agent 时遇到的真实问题。同样是 36 次工具调用逻辑和任务目标完全没有变化但调整完上下文管理和工具描述之后总 token 消耗直接降了一半。这让我意识到很多团队在评估 coding agent 时只关心模型本身的推理能力却严重低估了 token 消耗结构对成本和效率的影响。这篇文章就基于这次优化实践把“如何用更少的 token 完成同样的工具调用”这件事系统拆解一下。文章会覆盖 coding agent 的 token 消耗原理、工具调用的上下文章节构成、导致高消耗的典型设计以及一套可以直接落地的优化策略和配置示例。适合正在搭建或重构 AI 编程助手的开发者、关注大模型调用成本的技术负责人以及想弄明白“为什么 coding agent 用得越多越贵”的读者。1. 理解 coding agent 与工具调用的 token 消耗模型1.1 什么是 coding agentcoding agent 指的是能自主完成编码任务的智能体程序。它不只是“聊天窗口里帮你写代码”的问答工具而是能感知仓库上下文、调用外部工具、修改文件、运行命令、读取反馈并根据反馈决定下一步动作的自动化系统。一个典型的 coding agent 工作流是这样的接收用户自然语言任务。读取相关源码文件、配置文件或文档。生成编辑计划或操作序列。调用工具执行操作比如修改文件、执行 shell 命令、运行测试。观察工具返回结果。根据结果决定继续修改、补充测试还是结束任务。这个流程在 Agent 架构中被称为 ReAct 模式即“推理 - 行动 - 观察”循环。每一次循环都会涉及一组工具调用而每一次工具调用背后模型都需要在上下文中重新处理一遍大量历史信息。1.2 token 的计量方式与成本含义在 LLM 场景里token 是模型处理文本的基本单位。一个英文单词通常对应 1 到 2 个 token一段中文大约是一个字对应 1 到 2 个 token。API 计费时会把“输入 token”和“输出 token”分开计算。关键概念是 TPM即 Tokens Per Minute。它表示在一分钟内输入 token 和输出 token 的总和。例如一个模型的 TPM 上限是 100k意味着每分钟最多处理 10 万 token 的输入输出总量。这也解释了为什么 coding agent 在长任务中经常会触发限流——不是模型不够聪明而是上下文太长一分钟内根本处理不完。Coding agent 的 token 消耗结构跟普通对话有本质区别普通聊天上下文短输入少输出也不多消耗集中在几轮问答。Coding agent上下文会随着工具调用不断膨胀每一轮都需要把“系统提示词 历史对话 工具描述 工具返回结果 文件内容”全部重新发送给模型。结果就是即使最终的代码改动不大整个任务过程消耗的 token 也可能非常惊人。我们这次优化的目标就是在保持工具调用次数不变的前提下压缩每一轮发送给模型的上下文体积。1.3 为什么工具调用是 token 消耗的重灾区工具调用也叫 Function Calling 或 Tool Use在 coding agent 中的形态通常是读取文件read_file编辑文件edit_file执行命令execute_command搜索代码search_code列出目录list_directory运行测试run_tests每调用一次工具模型都需要经历一次“生成工具调用参数 接收工具返回结果”的完整回合。这两个过程都会产生 token输出 token模型生成工具名称、参数 JSON、说明文字。输入 token工具执行结果被填充回对话历史在下一次模型推理时作为输入重新发送。这里有个很容易被忽略的细节工具返回的结果文本会一直保留在上下文里。也就是说第一次读取文件产生的输出在后续 35 次工具调用中每次都会被当成输入重新计算一次。这就导致了一个 paradox——工具调用越多上下文越膨胀后续每次请求的输入 token 越高总体消耗呈指数级上升。1.4 优化目标不要减少工具调用而是压缩上下文很多团队在遇到 token 消耗过高时第一反应是减少工具调用次数例如让 agent 少读文件、少执行命令。但这样做往往会牺牲任务完成质量。更合理的思路是保持工具调用次数不变减少每次调用时上下文中重复、冗余、低信息密度内容的数量。这就是本文标题“Half the tokens for the same 36 tool calls”的核心含义——同样是 36 次工具调用通过优化上下文结构让总 token 消耗大幅下降。2. 为什么 coding agent 的 token 消耗会“越来越大”2.1 记忆累积机制是消耗膨胀的根源Coding agent 的每次推理都需要基于完整的对话历史。假设初始系统提示词为 2000 token第一次读取文件返回 3000 token此时第二次调用工具的输入就已经涨到 5000 token。到第 10 次工具调用时如果每次工具返回平均 2000 token历史累积可能已经达到 20k token。第 20 次调用时会膨胀到 40k。第 36 次调用时仅历史输入就可能超过 70k token。这就解释了为什么 Claude 的 58k tokens 会在 coding agent 场景里被快速消耗掉——它对应的不是一两次对话而是几十次工具调用的上下文累积。2.2 “重复输入”是最大的浪费在 coding agent 里重复输入的表现形式非常常见同一份文件内容被多次读取且每次都完整放进上下文。工具返回的冗长日志没有截断完整保留。系统提示词里的大段说明文字在每次请求中都重复发送。历史对话中的中间推理过程、草稿代码、失败尝试被原样保留。多个工具的描述文本过长压缩了真正用于推理的空间。这些重复内容本身并不会让模型变得“更聪明”只是白白占用上下文窗口并推高成本。2.3 输出 token 往往被忽略注意 TPM 的计算方式输入 token 输出 token。在很多 coding agent 框架中模型为了生成一个工具调用会先输出一大段“思考过程”或“解释文本”然后才输出结构化的工具调用参数。这部分输出 token 同样会计费而且如果 agent 被设计成“话多”的偏好输出 token 会非常可观。我们发现部分模型在每次工具调用前都会输出 500 到 1500 token 的推理说明36 次工具调用下来光是输出 token 就多了 18k 到 54k。这提醒我们优化不仅要压缩输入侧的上下文也要控制输出侧的“废话”。2.4 任务复杂度与 token 消耗的关系不同的编程任务token 消耗差异非常显著。下面是实际任务类型的总体量级对比任务类型典型工具调用次数消耗量级说明修改一个函数签名3 - 5 次10k - 30k token上下文短修改点少修复一个单元测试8 - 15 次40k - 80k token需要读取源码、测试文件、运行结果实现一个新功能模块15 - 25 次80k - 150k token涉及多文件读取和多次编辑跨模块重构30 - 60 次150k - 500k token上下文膨胀严重最消耗 token所以当你听到某个 coding agent 任务消耗了几十万 token先不要惊讶很可能它背后就是几十次工具调用的上下文累积。3. 优化策略如何在 36 次工具调用内省出一半 token下面进入核心实操部分。我们的目标很简单同一任务同样的 36 次工具调用把总 token 消耗降下来一半。下面是我在实践中验证有效的六类策略。3.1 策略一压缩系统提示词System Prompt系统提示词是所有请求都会携带的固定开销。很多 coding agent 框架会把大量的规则、编码规范、模型行为说明都塞进系统提示词导致每次调用都背着 3000 到 5000 token 的“固定包袱”。优化方法把系统提示词中“静态、不随任务变化”的内容抽出只在需要时通过动态注入方式添加。删除重复的编码规范描述用精简的指令代替长篇规则。将代码风格偏好、语言规范等内容收敛到单独的配置文件而不是每次都写进提示词。示例对比优化前你是一个专业的编程助手。请严格遵守以下编码规范 1. 变量命名必须使用驼峰风格函数名使用小驼峰类名使用大驼峰。 2. 缩进必须使用两个空格不能使用 Tab。 3. 所有公共方法必须添加 Javadoc 注释。 4. 禁止使用 System.out.println 打印调试信息。 5. 文件末尾必须保留一个换行符。 ... 此处省略另外 30 条规则优化后你是一个编码助手。遵循项目 .coding-rules 文件中的规范。如果规则文件有 2000 token直接写成“遵循规范文件”只用 50 个 token一把省下 1950 token而且系统每次请求都能少背 1950 token 的重量。36 次工具调用下来省下的量非常可观。3.2 策略二精简工具描述Tool Descriptions工具描述是模型决定“何时调用哪个工具”的依据。很多框架会把工具描述写得很长很详细但模型真正需要的其实是“工具名称、功能边界、参数结构、返回结果的关键信息”。工具描述占用的空间也是每次调用都会重复计算的。如果一个工具的描述有 400 token10 个工具就是 4000 token36 次调用光工具描述就要浪费 144k token。优化方法把工具描述控制在 100 token 以内。用简洁的动词短语描述功能不使用完整句子。把参数说明从描述中移到 JSON Schema 里。示例对比优化前{ name: execute_command, description: 在终端中执行指定的 shell 命令。该命令会被放置到一个临时目录中执行支持设置环境变量支持超时设置执行完成后返回标准输出和标准错误。如果命令执行时间过长可能会被强制终止。执行结果将被捕获并返回给模型以便模型判断命令是否成功。 }优化后{ name: execute_command, description: 执行 shell 命令返回 stdout/stderr。 }从约 80 个 token 压缩到约 10 个 token节省了 87.5%。3.3 策略三控制工具返回结果的大小工具执行后返回的结果是上下文膨胀的最主要来源。我们来做一个简单的估算假设每次工具返回平均 2000 token。36 次工具调用如果结果全部保留历史中工具返回的累积贡献就是 72k token。再加上每次重复重发这些历史整体消耗会放大数倍。优化方法对工具返回结果设置截断阈值比如最多保留 1500 token超出部分用提示代替。从工具返回中提取关键信息只返回“摘要 关键行”。文件读取工具应支持指定行范围而不是每次读取整个文件。示例截断长输出def truncate_result(text, max_chars1500): if len(text) max_chars: return text head text[:max_chars // 2] tail text[-max_chars // 4:] return f{head}\n...[已截断总计 {len(text)} 字符]...\n{tail}这样既能保留开头和结尾的关键信息又避免大段日志填满上下文。3.4 策略四动态清理与压缩历史消息并不是所有历史消息都对当前决策有同等价值。一个长期运行的 coding agent 里历史消息中可能包含早期失败的尝试和错误日志。已经完成并验证过的代码修改记录。重复的执行结果。与当前目标无关的中间解释。优化方法定期对历史消息做压缩把早期对话摘要成简短的状态描述。对工具返回结果做去重如果同一个命令的返回没有变化后续请求中可以用“结果同上”代替完整内容。对已完成操作的历史进行归档只保留关键结论。例如如果第 5 次工具调用时执行过ls -la第 15 次又执行了一次且输出相同那第 16 次请求时就不应该再携带完整结果只需要提示“目录内容未变化见第 5 次调用结果”。3.5 策略五减少中间推理输出的 token如前文所述模型在生成工具调用前可能会输出一大段“思考过程”。这些内容对调试可能有帮助但对最终结果不是每次都必要。优化方法在提示词中明确要求“直接输出工具调用不要输出解释文字”。对于成熟的 coding agent 框架可以通过配置关闭思考链输出。将模型推理模式从“详细思考”切换为“简洁模式”。示例当你决定调用工具时直接输出 JSON 格式的工具调用不要输出任何解释性文字。这个简单指令可以省掉相当可观的输出 token。3.6 策略六按任务拆分上下文窗口如果你的 coding agent 需要处理的是一个很大的代码仓库而你让模型每次都携带“整个仓库文件列表 多个核心文件内容”token 消耗肯定会爆炸。优化方法把一次大任务拆成多个子任务每个子任务独立启动上下文。通过代码索引如代码搜索工具按需拉取文件内容而不是一次性加载全部内容。只在必要时把文件内容放入上下文用完即释放。比如实现一个新功能不需要把整个项目的 50 个文件全部读一遍。模型应该先搜索相关代码再按需读取最相关的 5 个文件。这样能极大减少上下文体积。4. 实战案例36 次工具调用的 token 优化对比下面用一个近似真实场景的案例来演示优化效果。为了便于理解我们把任务简化为“修复一个测试失败并完成代码重构”整个过程一共产生了 36 次工具调用。4.1 优化前的配置与消耗优化前我们使用了一套比较“重”的配置系统提示词内容4200 token。工具描述总计6800 token12 个工具。每次工具返回结果不截断。历史消息全部保留。模型在工具调用前输出较长推理文本。模拟统计如下项目数值工具调用次数36输入 token 总量186,500输出 token 总量54,200总 token240,7004.2 优化后的配置与消耗我们按照第三部分的策略做了以下调整系统提示词压缩到 950 token。工具描述压缩到 1200 token。工具返回结果按 1500 token 截断。历史消息做摘要压缩已完成的子任务信息归档。提示词要求模型直接输出工具调用去除解释性文字。优化后统计项目数值工具调用次数36输入 token 总量82,300输出 token 总量26,400总 token108,700总 token 从 240,700 降到 108,700节省约 55%。工具调用次数完全没变任务完成质量也保持一致。4.3 核心配置示例下面给出一个优化后的 coding agent 工具描述配置示例用 JSON 格式说明{ tools: [ { name: read_file, description: 读取文件指定行范围, parameters: { path: string, start_line: number, end_line: number } }, { name: edit_file, description: 替换文件指定区间内容, parameters: { path: string, start_line: number, end_line: number, new_content: string } }, { name: execute_command, description: 执行 shell 命令返回截断输出, parameters: { command: string, timeout_ms: number } } ] }这里的关键点每个描述都控制在 10 个词以内。参数信息放在 JSON Schema 中不占用自然语言描述。工具返回结果会在框架层统一截断。4.4 上下文管理伪代码下面是实现历史消息压缩的核心伪代码展示了如何在发送给模型前做处理def prepare_messages(raw_history, max_context_tokens32000): # 1. 保留系统提示词已压缩 messages [{role: system, content: SYSTEM_PROMPT_COMPRESSED}] # 2. 对历史消息按时间顺序遍历 remaining_budget max_context_tokens compressed_messages [] for msg in reversed(raw_history): content msg[content] token_count estimate_tokens(content) # 3. 如果当前消息超过剩余预算则压缩 if token_count remaining_budget: compressed truncate_and_summarize(content, remaining_budget) compressed_messages.insert(0, { role: msg[role], content: compressed }) break else: compressed_messages.insert(0, msg) remaining_budget - token_count # 4. 如果仍然超预算对早期消息做摘要 while estimate_tokens(messages) estimate_tokens(compressed_messages) max_context_tokens: oldest compressed_messages.pop(0) summary summarize(oldest[content]) compressed_messages.insert(0, { role: system, content: f[历史摘要] {summary} }) messages.extend(compressed_messages) return messages这段代码的核心思想是首先确保最新的、最相关的上下文得到保留早期信息以摘要形式存在避免大段原始日志阻塞上下文窗口。4.5 运行结果验证优化后需要验证两点模型仍然能正确完成 36 次工具调用。任务最终结果与优化前一致。建议在验证时加入这两个检查点检查每次工具调用的参数是否正确。检查最终代码变更是否与优化前一致。如果发现某个环节因为上下文被压缩导致模型丢失关键信息可以针对性调整截断阈值或摘要策略。5. 常见问题与排查思路在实施 token 优化时你可能会遇到下面这些问题。问题现象常见原因解决思路模型忘记早期读取的文件内容历史压缩太激进摘要丢失关键细节提高摘要保留比例重要文件内容不压缩工具调用参数生成错误工具描述过短模型无法判断参数含义在 JSON Schema 中补充必填字段说明任务结果与优化前不一致截断工具返回结果导致关键报错信息丢失对错误日志采取保留尾部策略而不是只留头部模型重复调用同一个工具工具返回摘要无法提供足够信息在摘要中包含文件路径、行号、关键上下文优化后总 token 反而增加摘要生成过程本身消耗了大量输出 token使用轻量级摘要模型或简单截断避免逐条摘要部分工具结果对当前目标不再重要没有按任务阶段动态清理历史在完成子任务后主动清理历史消息5.1 如何定位 token 消耗的大头如果优化前你不知道 token 消耗在哪个环节建议先做一次 Token 审计方法如下在 agent 运行过程中记录每次请求的输入 token 数和输出 token 数。统计系统提示词、历史消息、工具描述、工具返回结果分别占比。找出消耗最重的 TOP 3 个环节。示例日志{ request_id: req_001, tool_call_index: 12, input_tokens: 24500, output_tokens: 320, breakdown: { system_prompt: 950, tool_descriptions: 1200, history_without_tool_results: 5800, tool_results: 16550 } }从这份日志可以看出来工具返回结果占了输入 token 的大头下一步就应该针对工具结果做压缩。5.2 为什么压缩之后任务质量下降有时候压缩上下文确实会带来任务质量下降。根本原因是模型失去了做出正确决策所需的某些关键信息。解决方法不是放弃压缩而是做“结构化保留”用“文件路径 修改时间 摘要”代替完整文件内容。用“上一步结论 当前待办”代替完整操作日志。在摘要中显式标记关键变量名、关键行号和错误信息。比如原来读取的是一个 3000 token 的 Java 文件压缩后可以变成文件 src/main/java/com/example/OrderService.java 已于第 8 次调用被读取。 关键方法createOrder(OrderRequest) 第 102-145 行。 与当前任务相关的字段orderStatus、totalAmount。这样既保留了决策所需的核心信息又去掉了大段无关代码。6. 最佳实践与工程建议6.1 建立 Token 消耗审计机制不要等到账单出来才发现消耗过高。建议在 coding agent 中内置 token 计量日志记录每次请求的输入输出分部。这样不仅能帮你定位问题还能为后续优化提供数据支持。6.2 上下文分级管理把上下文分成三个等级长期保留系统提示词、核心任务目标、关键文件路径。中期保留最近 5 到 10 轮工具调用记录、文件修改结果。短期保留命令执行输出、临时调试信息。每一轮请求前自动把短期内容压缩或删除保留中长期的稳定信息。6.3 工具设计遵循“最小返回”原则工具返回的内容越少模型需要处理的 token 就越少。设计工具时建议文件读取支持行范围参数默认只读前 200 行。命令执行默认截断输出只有显式指定才返回完整日志。搜索工具只返回文件路径和匹配行不返回完整文件内容。6.4 选择合适的模型与上下文窗口不同模型的 token 计费方式不同有些模型擅长长上下文但对长文本收费更高有些模型上下文较短但单位成本低。选择模型时除了关注推理能力还要看两点上下文窗口大小是否匹配你的工具调用量。输入输出 token 的单价比例。如果任务经常需要 30 次以上的工具调用建议优先选上下文窗口较大的模型同时做好上下文压缩避免 TPM 限流。Tpm 限流是按“每分钟输入输出”计算的上下文越长限流风险越高。6.5 慎用“无限上下文”设计有些框架允许上下文无限增长直到接近模型窗口上限。这种设计在简单对话中可行但在 coding agent 中会带来两个问题成本快速上升。模型在大量历史信息中更难聚焦当前任务反而降低效果。合理做法是给上下文设置软上限到达上限后触发压缩或摘要流程而不是放任不管。6.6 测试时同时关注“两次结果一致性”优化 token 后建议跑一遍回归测试确认同样的任务在优化前后得到一致结果。特别是文件修改内容是否一致。测试通过状态是否一致。工具调用数量是否一致。这样才能确保优化没有以牺牲任务质量为代价。7. 总结与下一步实践建议这次优化实践最有价值的一点是用事实证明了一个容易被忽视的结论coding agent 的 token 消耗并不完全等于模型能力的体现。改进系统提示词、工具描述、结果截断和上下文管理方式可以在保持同样工具调用次数的情况下实现接近 50% 的 token 节省。如果你正在搭建自己的 coding agent建议按下面的顺序推进优化先做 Token 审计找出消耗大头。压缩系统提示词和工具描述。对工具返回结果做截断或摘要。实现历史消息动态清理。对模型输出做“简洁模式”约束。回归验证任务质量。如果只是调用现成的 coding agent API也可以从配置层面入手例如调整上下文窗口参数、设置结果截断、关闭思考链输出。很多官方框架已经开始提供这些选项只是默认未必开启。下一步可以考虑的方向包括按任务类型建立不同的工具调用模板、针对不同模型调整压缩策略、将 token 优化与缓存机制结合。这些都建立在今天这篇文章的基础上——先弄清 token 消耗的结构再对症下药。