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

Claude 中文知识站 Lv4

写了小半年 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-04-18 14:06:00
  • 更新于 : 2026-04-19 10:33:00
  • 链接: https://claude.cocoloop.cn/posts/agent-sdk-custom-tools/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论