Vercel: 我们删掉了 Agent 80% 的工具,它反而变好了
Vercel 内部 Text-to-SQL Agent d0 的重构故事:从 17 个专用工具精简到 2 个,成功率从 80% 提升到 100%,速度快了 3.5 倍。
Vercel 的 Text-to-SQL Agent 重构故事
本文译自 We removed 80% of our agent's tools,作者:Andrew Qu(Vercel)
它变好了。
我们花了数月时间,为内部 Text-to-SQL Agent d0 构建了一套精密的架构——专用工具、大量 prompt 工程、精心的上下文管理。它能用……勉强能用。但系统很脆,速度很慢,需要持续维护。
于是我们尝试了一种不同的方式。我们删掉了大部分代码,把 Agent 精简到只剩一个工具:执行任意 bash 命令。我们称之为"文件系统 Agent"。Claude 直接访问文件,用 grep、cat、ls 自己摸清楚。
Agent 变简单了,同时也变好了。成功率从 80% 提升到 100%。步骤更少,token 更少,响应更快。靠的是做得更少。
如果说 v0 是我们用于构建 UI 的 AI,那么 d0 就是我们用于理解数据的 AI。

d0 让任何人都能通过 Slack 提问来做出数据驱动的决策
d0 将自然语言问题转化为针对分析基础设施的 SQL 查询,让团队中的任何人都能在不写代码、不等待数据团队的情况下获得答案。
d0 运转顺畅时,它让数据访问在整个公司民主化。它一旦出错,人们就会失去信任,重新回到 Slack 上找数据分析师。我们需要 d0 既快、又准、又可靠。
我们在替模型思考
回过头看,我们当时解决的那些问题,模型本可以自己处理。我们假设它会在复杂 schema 中迷失、做出错误的 JOIN、或者产生幻觉表名。所以我们修建了护栏。我们预先过滤上下文、限制选项、用验证逻辑包裹每次交互。我们在替模型思考:
- 构建了多个专用工具(schema 查询、查询验证、错误恢复等)
- 加入大量 prompt 工程来约束推理
- 精心管理上下文,避免"淹没"模型
- 手写检索逻辑,以浮现"相关"schema 信息和维度属性
每个边缘情况意味着又一个补丁,每次模型更新意味着重新校准约束。我们花在维护脚手架上的时间,比花在改进 Agent 上的时间还要多。
import { ToolLoopAgent } from 'ai';
import { GetEntityJoins, LoadCatalog,
/*...*/ } from '@/lib/tools'
const agent = new ToolLoopAgent({
model: "anthropic/claude-opus-4.5",
instructions: "",
tools: {
GetEntityJoins, LoadCatalog,
RecallContext, LoadEntityDetails,
SearchCatalog, ClarifyIntent,
SearchSchema, GenerateAnalysisPlan,
FinalizeQueryPlan, FinalizeNoData,
JoinPathFinder, SyntaxValidator,
FinalizeBuild, ExecuteSQL,
FormatResults, VisualizeData,
ExplainResults
},
});我们意识到,我们在逆势而为。我们限制了模型的推理能力。把它本可以自己阅读的信息提前摘要。构建工具来保护它免受它其实能处理的复杂性冲击。
文件系统 Agent
于是我们停下来了。假设是这样:如果我们直接把 Cube DSL 文件给 Claude,让它自己搞定,会怎样?如果 bash 就是你所需要的全部呢?模型越来越聪明,上下文窗口越来越大——也许最好的 Agent 架构,几乎就是没有架构。
新技术栈:
- 模型: 通过 AI SDK 使用 Claude Opus 4.5
- 执行: 使用 Vercel Sandbox 进行上下文探索
- 路由: 使用 Vercel Gateway 处理请求和可观测性
- 服务器: 使用 Vercel Slack Bolt 的 Next.js API 路由
- 数据层: 由 YAML、Markdown 和 JSON 文件组成的 Cube 语义层目录
文件系统 Agent 现在像一个人类数据分析师一样浏览语义层。它读取文件,用 grep 寻找规律,建立心智模型,然后用 grep、cat、find、ls 这些标准 Unix 工具编写 SQL。
这之所以有效,是因为语义层本身已经是很好的文档。这些文件包含维度定义、度量计算和 JOIN 关系。我们构建工具是为了摘要那些本已清晰易读的内容。Claude 只需要直接读取的权限。
import { Sandbox } from "@vercel/sandbox";
import { files } from './semantic-catalog'
import { tool, ToolLoopAgent } from "ai";
import { ExecuteSQL } from "@/lib/tools";
const sandbox = await Sandbox.create();
await sandbox.writeFiles(files);
const executeCommandTool = (sandbox: Sandbox) => {
return tool({
/* ... */
execute: async ({ command }) => {
const result = await sandbox.exec(command);
return { /* */ };
}
})
}
const agent = new ToolLoopAgent({
model: "anthropic/claude-opus-4.5",
instructions: "",
tools: {
ExecuteCommand: executeCommandTool(sandbox),
ExecuteSQL,
},
})基准测试结果
我们用 5 个有代表性的查询,对旧架构和新文件系统方案进行了基准测试。
| 指标 | 复杂方案(旧) | 文件系统(新) | 变化 |
|---|---|---|---|
| 平均执行时间 | 274.8 秒 | 77.4 秒 | 快 3.5 倍 |
| 成功率 | 4/5(80%) | 5/5(100%) | +20% |
| 平均 token 用量 | 约 102k | 约 61k | 减少 37% |
| 平均步骤数 | 约 12 步 | 约 7 步 | 减少 42% |
文件系统 Agent 赢得了每一项对比。旧架构最差的情况耗时 724 秒、100 步、145,463 个 token,最终还失败了。文件系统 Agent 用 141 秒、19 步、67,483 个 token 完成了同一个查询,而且成功了。
定性上的转变同样重要。Agent 能捕捉到我们从未预料到的边缘情况,并以我们能理解的方式解释其推理过程。
三点启示
不要逆势而为。 文件系统是一个极其强大的抽象。Grep 已经有 50 年历史,依然完美地完成我们需要的工作。我们当时在为 Unix 已经解决的问题构建自定义工具。
我们约束推理,是因为我们不信任模型去推理。 面对 Opus 4.5,这种约束变成了负担。当我们停止替模型做选择,模型反而做出了更好的选择。
这之所以奏效,是因为我们的语义层本身已经是好文档。 YAML 文件结构良好、命名一致、包含清晰的定义。如果你的数据层是一堆历史遗留命名和没有文档的 JOIN,把原始文件访问权给 Claude 并不能拯救你——只会让你更快地得到错误的查询结果。
减法即加法
最好的 Agent 可能就是工具最少的那个。每一个工具,都是你替模型做的一个选择。有时候,模型自己做选择会更好。
诱惑总是存在的:试图覆盖每一种可能性。抵制它。从最简单的架构开始:模型 + 文件系统 + 目标。只有在你证明了复杂性是必要的时候,才去添加它。
但简单的架构本身还不够。模型需要好的上下文来工作。投资于文档、清晰的命名和结构良好的数据。那个基础,比聪明的工具更重要。
模型的进步速度,快过你的工具所能跟上的速度。为你六个月后将拥有的模型而构建,而不是为你今天手里的模型。