Agent SDK 自定义工具的 4 种加法,第 3 个最稳
写了小半年 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 | agent.addTool({ |
跑了两周,发现两个问题:一是 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 进行许可。