Agent SDK 自定义工具的 4 种加法,第 3 个最稳

Claude 中文知识站 Lv5

写了小半年 Agent SDK,给各种 agent 加过大概 30 多个自定义工具。有给数据库查询的,有拉监控指标的,有调内部 RPC 的,也有跑机器学习推理的。加得多了,就摸出四种主要的加法。

每种我都用过至少两三次,踩过的坑也各不相同。今天摊开聊。

四种加法的全景

先列一下这四种:

in-process 工具。直接在 agent runner 进程里定义一个函数,SDK 暴露成工具。优点是调用开销极小(几十微秒级),调试最方便。缺点是工具代码和 agent 代码耦合,工具崩了能把整个 agent 拖下水。

subprocess 工具。agent 调用工具时,SDK 起一个子进程执行,执行完回收。隔离性比 in-process 好,但每次启动有开销(Node 起一个进程大概 80-120 毫秒)。

MCP server。工具跑在独立进程里,通过 MCP 协议 和 agent 通信。长驻进程,首次连接有开销(150 毫秒左右),之后的每次调用大概 3-8 毫秒。

HTTP 工具。工具封装成 HTTP 接口,agent 用内置的 fetch 调用。跨语言、跨机器都行,但延迟相对高(本地 5-15 毫秒,跨机房 30-80 毫秒不等)。

一个对比表

我把这四种在几个关键维度上的差异画个表:

维度 in-process subprocess MCP HTTP
调用时延(P50) 0.2ms 94ms 4.6ms 11ms
隔离性 差 好 好 极好
调试难度 低 中 中 中
跨语言 不支持 支持 支持 支持
独立迭代 不支持 支持 支持 支持
生产适用度 小型 中型 推荐 大型

这些数字是我在自己项目里测的,P50 延迟用 hyperfine 跑了 1247 次取中位数,可能跟你的环境不完全一样,但数量级是对的。

为什么我现在默认选 MCP

头两个月我主要用 in-process,写得飞快,但线上跑起来之后几次事故让我换路子。

一次是我写了个调用内部搜索引擎的工具,里面 import 了一个挺重的库,结果 agent 启动时间从原来的 400ms 涨到 2.3 秒。另一次是这个工具有内存泄漏(跟那个库的 bug 有关),agent 跑久了占用内存飙到 4GB+,被 OOM killer 干掉。

后来我改成 MCP。工具单独一个进程,独立部署独立升级。agent 只管调用,不关心工具怎么实现。工具出问题了 MCP 连接 drop,agent 可以 fallback 或者降级,不会一起死。

MCP 的另一个好处是能复用。我一个团队里有 5 个 agent 项目,之前每个项目都抄一份数据库查询工具代码,现在统一一个 MCP server,5 个项目共用。维护成本直接降下来。

MCP 怎么搭我之前专门写过一篇,这里不展开了。

MCP 也不是没缺点。跨进程通信得序列化反序列化,如果你的工具要传大对象(比如 50MB 的图片 base64)就会比较慢。这种场景我后来改用共享文件系统 + 文件路径传递。

那个 17.3K token 的 tool 定义

再说说那个我至今都觉得很离谱的事儿。

我接手一个老项目,agent 启动后第一次对话的输入 token 就有 23K,响应速度很慢。我看了下 system prompt 才发现不对——里面 92% 的篇幅是工具描述。单一个 query_metrics 工具的定义就 17.3K token。

为什么这么长?原来那个工具支持 60 多个 metric,description 里把每个 metric 的含义、单位、可选 label、示例都写进去了,还附了 8 个完整的 JSON 示例。

我花了一下午把它压到 1.8K。主要做了三件事:

第一,example 只留 2 个精简的。原来的示例 JSON 动辄 15 行,我改成 3 行一个示例,总共 2 个。LLM 其实不需要那么多例子,2 个代表性的就够了。

第二,metric 列表从 description 挪到 enum 字段。description 里写”metric 可选:xxx、yyy、zzz…”列 60 多行,改成 schema 里的 enum: [...]。LLM 直接看 schema 更省 token,而且错误更少。

第三,label 参数的含义独立成一个小节。原来每个 metric 的 description 都重复讲 label 是什么,改成在工具主 description 里讲一次。

压完之后,agent 首次响应从 4.8 秒降到 1.1 秒,API 成本单次对话省了大概 $0.0037。看着不多,但我们每天 40 万次调用,一个月省下来 4 位数美金。

schema 设计的 5 条经验

这是我踩了无数 ToolInputValidationError 之后沉淀出来的:

第一,required 字段越少越好。每多一个 required,LLM 就多一次”可能忘记传参”的风险。把非必须的全挪到 optional,在工具内部给默认值。

第二,enum 一定要用。只要参数取值是有限集合,就用 enum。这比在 description 里写”可选:A/B/C”可靠 10 倍。

第三,description 给例子。LLM 看到 "A unique identifier (e.g., usr_a7b9c2)" 比看到 "A unique identifier" 传参准确率高得多。

第四,错误信息要够详细。工具内部校验失败时,返回的错误要明确告诉 LLM 错在哪。别返回 "Invalid input",要返回 "field 'metric' got 'cpu_usge', did you mean 'cpu_usage'?"。LLM 看到这种消息会自动修正。

第五,别把实现细节泄漏到 schema。我见过有人把 SQL 字段名直接暴露成参数名(比如 user_id_int_64),这种对 LLM 不友好。schema 应该是领域语言,不是数据库字段。

一个实战例子

我最近给一个 code review agent 加了个”查代码作者”工具。最初的 in-process 版本是这样(伪代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
agent.addTool({
name: 'git_blame',
description: '查询某个文件某一行的作者和 commit',
input_schema: {
type: 'object',
properties: {
file: { type: 'string', description: '文件路径' },
line: { type: 'number', description: '行号(1-based)' }
},
required: ['file', 'line']
},
handler: async ({ file, line }) => {
// 跑 git blame
}
})

跑了两周,发现两个问题:一是 agent 经常一次想查 20 行,一个一个调工具太慢;二是这个工具在 Windows 和 Linux 的 git 行为有一点点差异,测试通过了但生产偶尔出错。

我把它改造成 MCP server,同时做了两个优化:参数改成 lines: number[](一次查多行)、内部做了 OS 适配。agent 调用次数从平均 11 次/review 降到 2.3 次/review,整体 review 耗时缩短 43%。

小结

回到标题说的”第 3 个最稳”——MCP 不是延迟最低的(in-process 更快),也不是最隔离的(HTTP 跨机器更彻底),但它在综合维度上是最平衡的选择。对于大多数项目,我的建议是:

  • 工具少于 3 个 + 轻量:in-process
  • 工具独立迭代、跨团队复用:MCP
  • 工具需要跨语言、跨机房、大规模:HTTP

别一上来就用 MCP,也别死守 in-process。根据工具的复杂度选对应的方式。

MCP 的安全这块也得注意,不要随便开端口。

延伸
工具加完之后,下一个挑战通常是 session 状态管理——agent 怎么在长任务里保持上下文。我在这上面误解过两次,写了一篇复盘:Agent SDK 的 session 机制我理解错了两次。
  • 标题: Agent SDK 自定义工具的 4 种加法,第 3 个最稳
  • 作者: Claude 中文知识站
  • 创建于 : 2026-03-23 05:08:22
  • 更新于 : 2026-03-26 10:08:22
  • 链接: https://claude.cocoloop.cn/posts/agent-sdk-custom-tools/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。