大家好,我是蓝戒。本篇我们来聊聊:“智能体工具降本增效”。
不知道大家最近在用 Cursor、Claude Code、Codex 或者自研 Coding Agent 的时候,有没有遇到过下面这种“血压飙升”的瞬间:
你只是让 Agent 修一个小 Bug,它在后台跑了一次测试,或者看了一眼代码变动。
结果一打开控制台账单,刚才那次交互竟然直接吞掉了好几万 Token!
更离谱的是,随着多轮对话深入,Agent 响应越来越迟钝,甚至开始“睁眼说瞎话”:把明明通过的用例看成报错,或者漏掉最致命的核心异常,对着无关代码反复折腾。
很多人第一反应是“大模型变笨了”或者“上下文窗口不够大”。
但权威技术媒体 The New Stack 实践专栏
近期的一篇深度分析直接戳破了真相:大模型完全是被喂进去的“垃圾数据”给撑傻的!
在很多开发者编写的自定义 MCP 工具或脚本中,命令行原始的 stdout 和 stderr 被全盘生吞活剥地塞给模型。
通过引入高信噪比的工具输出过滤层(Better Tool Output),不仅能把单次交互的 Token 消耗暴砍 50% 到 70%,还能让 Agent 任务完成速度直接翻倍!
今天蓝戒就带大家把这个“隐形账单刺客”连根拔起,手把手教你编写一套极简高效的工具过滤层。
账单刺客真凶:你的 Agent 为什么总在“读垃圾”?
我们可以打个更贴切的比方:
大模型就像一位按分钟计费的顶级外部技术顾问。
线上系统报了异常,你请顾问帮忙紧急排查。结果你不仅没给核心报错信息,反而把整个系统过去几个月的常规心跳打卡、包下载进度条、监控告警底噪,整整上万页无害日志打包甩在顾问脸上,让他“自己从头翻线索”。
结果可想而知:咨询费按字计费贵得离谱,顾问被淹没在茫茫多的垃圾信息里翻得头晕眼花,反而极容易漏掉真正致命的一行空指针异常。
现实中很多开发者写工具时,正是这样对待 Agent 的:
- 终端噪音轰炸:执行一条测试命令,工具把数百个依赖包的下载进度、环境路径、已通过用例的废话全塞了进去。
- Git Diff 暴力全文 Dump:明明只改了某个函数的两行逻辑,工具却把整个文件因格式化带来的 1500 行空白变动原样吐给模型。
- “大海捞针”导致注意力涣散:大模型存在不可避免的“中间迷失(Lost in the Middle)”效应。核心报错信息被淹没在两千行垃圾日志里,模型极易产生幻觉。
高信噪比工具输出的 3 大黄金原则
要让 Agent 跑得飞快还不乱花钱,核心原则只有一个:把噪音就地消化在工具过滤层,只把高密度的价值信息喂给大脑。
原则一:结构优先(紧凑 JSON 替代长篇文本)
不要让大模型去猜你的控制台排版。工具返回前,应当提炼出结构清晰的键值对。标明执行状态(status)、受影响模块与摘要,让模型第一眼就能建立结构化认知。
原则二:错误聚焦(提炼关键 5 行,过滤 99% 构建噪音)
在绝大多数自动化编译或测试执行中,99% 的日志都是“一切正常”。工具层应当执行“报忧不报喜”的过滤机制:
- 全绿时:直接返回
{"status": "passed", "tests": 52}; - 报错时:正则捕获真正的
AssertionError,只保留异常抛出的文件行号与前后 3-5 行关键代码,剔除外部库的深层递归堆栈。
原则三:增量差异(提取 AST 语义节点,拒绝整份文件)
审视代码变动时,避免直接暴露纯文本 Unified Diff。可以优先提供函数级的语义变动摘要(例如:“在 auth.ts 中为 verifyToken 新增了过期校验”),只有当 Agent 明确需要局部实现时,再按需切片返回。
代码实战:把 10,000 Token 压缩至 300 Token 的过滤层
以日常开发中最高频的两个场景——测试运行与 Git 变动检查 为例,看看经过过滤层改造后的惊人对比:
场景一:单元测试输出过滤(以 Jest/Pytest 为例)
TypeScript
import { exec } from "node:child_process";
import { promisify } from "node:util";
const execAsync = promisify(exec);
// 改造前:直接返回原始终端日志(动辄消耗 8,000 - 15,000 Token)
// return stdout + "\n" + stderr;
// 改造后:高信噪比过滤层(压缩至 200 - 300 Token)
export async function runTestWithFilter(command: string) {
try {
const { stdout } = await execAsync(command);
// 全部通过时,绝不传递无用日志
return JSON.stringify({
status: "success",
message: "All test cases passed."
});
} catch (error: any) {
const rawOutput = (error.stdout || "") + "\n" + (error.stderr || "");
// 正则提取失败的核心断言与文件行号
const failureRegex = /(FAIL\s+(.*?)\n)([\s\S]*?)(?=✕|\n\s*at|$)/g;
const errors: any[] = [];
let match;
while ((match = failureRegex.exec(rawOutput)) !== null) {
const [, , filePath, errorBody] = match;
// 仅提取关键错误信息,剔除庞大的 node_modules 调用栈
const cleanMessage = errorBody
.split("\n")
.filter(line => !line.includes("node_modules") && line.trim().length > 0)
.slice(0, 6) // 只保留关键的前 6 行堆栈与断言对比
.join("\n");
errors.push({ file: filePath.trim(), summary: cleanMessage });
}
return JSON.stringify({
status: "failed",
total_failures: errors.length,
failures: errors
});
}
}
场景二:Git Diff 语义提炼过滤
不要直接执行 git diff,改用带有增量语义过滤的封装:
TypeScript
export async function getCleanGitDiff() {
// 1. 先通过 name-status 获取文件维度的轻量级状态
const { stdout: statusOut } = await execAsync("git status --short");
// 2. 忽略纯空格与换行变动,聚焦真实变更
const { stdout: diffOut } = await execAsync("git diff -U2 --ignore-all-space");
// 3. 如果改动过大(超 100 行),自动退化为文件级语义摘要
if (diffOut.split("\n").length > 100) {
return JSON.stringify({
status: "large_diff",
summary: statusOut.trim(),
hint: "Changes are large. Please inspect specific files individually."
});
}
return diffOut;
}
实测收益:速度翻倍,单次 API 成本直降 60%
在真实工程项目的多轮 Debug 实测中,工具过滤层带来的收益极其可观:
- Token 消耗砍半以上:原本动辄需要消耗 20-30 万 Token 的排错流程,过滤后直接压到了 8-10 万 Token,成本直降超 60%。
- 响应延迟缩短 2 倍:模型无需再为成千上万行的无关文字做自注意力计算,首字响应与推理思考变得丝滑顺畅。
- 改错一次命中率大幅提升:干净的上下文消除了噪音干扰,Agent 能够快速锁定问题根因,几乎不再出现“修一个 Bug 引入三个新 Bug”的尴尬场面。
思考:Agent 时代,Tool 工程才是真正的内功
前两年大家用大模型,注意力都在怎么写 Prompt、怎么做 Few-shot 示例。
但当技术演进到让智能体自己调用工具操控代码和系统时,工具接口的信噪比,直接决定了智能体的智商上限。
与其花大价钱升级更贵的模型,或者在每次任务失败后苦苦调优提示词,不如回过头来给你的自定义工具补上一层几十行的数据过滤逻辑。把无序的机器垃圾信息拦截在外面,让模型专注做最有价值的逻辑推理,这才是真正的降本增效之道。
文章评论