跳到主要内容
文章
Agent4 分钟阅读

Tool Calling 设计:什么时候该给模型一个工具,什么时候不该

工具不是越多越好。这篇给出一个可以照着用的判断清单,以及四种常见的错误工具设计。

一句话结论

如果一个判断能用代码写出来,就不要交给模型——包括「该调用哪个工具」这件事。

本文目录 · 8

每次有人问我「怎么让 Agent 更准」,我的第一反应都是先看工具列表

大部分准确率问题不在模型,在于给了它不该给的自主权

一个判断清单#

新增一个工具之前,我会依次问四个问题:

Q1. 这个动作有确定性答案吗?

有 → 不要做成工具,写成代码,在模型之外执行。

「报价是否低于底价」有确定性答案。让模型判断它,就是在引入随机错误。 正确做法是模型提出报价,代码校验,不通过就重来。

Q2. 模型能从这个动作的返回值里学到东西吗?

不能 → 不要做成工具。

典型反例:log_analytics_event。这个动作对模型完成当前任务没有任何帮助, 只会浪费一次往返和一段上下文。埋点应该在代码层做。

Q3. 模型的输入能被可靠地构造出来吗?

不能 → 说明工具粒度不对。

反例:query_database(sql: string)。 模型写 SQL 的准确率在复杂 schema 上很低,而且失败时的报错对它没有指导意义。

正例:find_orders(customerId, status, dateRange)。 参数都是模型能从上下文里可靠提取的枚举值。

Q4. 这个动作需要人工确认吗?

需要 → 做成工具,但在工具返回值里明确标记,并中断流程。

发邮件、改价格、删数据这类不可逆动作,工具应该在执行前返回 一个「需要确认」的状态,由外层代码接管。

四种常见的错误工具设计#

错误一:把「判断」做成工具#

ts
// ❌ 让模型决定要不要拦下这笔报价
server.tool("should_block_quote", "判断报价是否应该被拦截", ...);
 
// ✅ 判断在代码里,模型只负责组织语言
if (quote.unitPrice < pricing.floor(quote.sku)) {
  return { blocked: true, reason: "低于底价" };
}

把判断交给模型,等于把确定性逻辑变成概率逻辑。 没有任何 prompt 技巧能修复这个。

错误二:一个工具做太多事#

ts
// ❌ 参数组合爆炸,模型经常漏填
server.tool("manage_order", {
  action: z.enum(["create", "update", "cancel", "query"]),
  orderId: z.string().optional(),
  items: z.array(...).optional(),
  reason: z.string().optional(),
  // ... 十几个可选参数
}, ...);

模型看到 action 参数时,需要先判断动作类型,再判断哪些参数该填。 两步判断叠加,错误率是乘法的。

ts
// ✅ 拆成三个语义单一的工具
server.tool("create_order", { items, customerId }, ...);
server.tool("cancel_order", { orderId, reason }, ...);
server.tool("find_orders", { customerId, status }, ...);

工具名本身就是判断结果。模型看到 cancel_order 就知道这是取消订单, 不需要额外的 action 参数。

错误三:返回值是给人看的#

ts
// ❌ 模型要自己解析这段文本
return "订单 A123 当前状态为待发货,预计 11 月 3 日发出,共 3 件商品。";
 
// ✅ 结构化,字段名自带语义
return {
  orderId: "A123",
  status: "pending_shipment",
  estimatedShipDate: "2026-11-03",
  itemCount: 3,
};

文本返回值的问题不是「模型看不懂」,而是它需要多花一次推理去解析, 而且解析结果不稳定。结构化返回让模型可以直接引用字段。

如果确实需要人类可读的版本,两个都返回:

ts
return {
  structured: { orderId: "A123", status: "pending_shipment", /* ... */ },
  humanReadable: "订单 A123 当前状态为待发货,预计 11 月 3 日发出。",
};

错误四:失败时返回「操作失败」#

ts
// ❌ 模型不知道下一步该做什么
return { error: "操作失败" };
 
// ✅ 失败原因要能指导下一步
return {
  error: "insufficient_inventory",
  message: "SKU AB-1200 可用库存 120,需求 500,缺口 380",
  suggestion: "可建议客户分批发货,或改用 AB-1200B(库存充足)",
  retryable: false,
};

工具返回的错误是给模型看的上下文。一句「操作失败」等于把模型逼到瞎猜。

好的错误返回应该回答:发生了什么、为什么、下一步可以做什么。

工具数量的实际拐点#

我在真实项目里测到的数据(同一个模型、同一批任务):

工具数量选择准确率备注
3–5~98%基本无错
6–10~93%偶发误选
11–15~84%开始明显下降
16+~71%需要路由层

拐点在 12 左右。超过这个数,加工具之前先合并。

合并的方法:找出经常被连续调用的工具,把它们合成一个语义完整的动作。 我在数字员工项目里用这个方法把 9 个工具压到 5 个,准确率从 88% 回到 96%。

一个反直觉的做法:主动减少模型的选择#

最有效的一次优化,不是给模型加工具,而是把一部分工具从模型的可选列表里拿掉

具体做法是按流程阶段动态加载:

ts
// 第一阶段:只读工具,模型的任务是理解需求
const stage1Tools = ["get_inventory", "get_pricing", "find_orders"];
 
// 第二阶段:模型已经确定了方案,只给执行工具
const stage2Tools = ["draft_quote", "schedule_followup"];
 
// 第三阶段:不可逆动作,需要人工确认,模型不直接调用
// (由外层代码在人工确认后执行)

模型在每个阶段只看到 2–3 个工具,选择准确率回到 98% 以上。

代价是流程变长了一点。但准确率比流程长度重要得多—— 一次错误调用的代价,远大于多一次模型往返。

Agent 设计的本质不是「让模型做更多」, 而是「精确地划定模型做什么」。


这套原则在 AI Digital Employee 里完整落地了, 工具层的实现细节见 MCP 实战