Skip to main content

工具有多少才合适

接工具的门槛被协议降下来之后,问题反转了:以前是"接不上",现在是"接太多"。一个 Agent 挂了三四十个工具,看似能力齐全,实际表现是选错工具、参数瞎填、或者明明有对应工具却自己去编一个答案出来。

这篇讲工具集规模为什么会影响质量、怎么判断已经超了、以及怎么在不砍功能的前提下把选择面收窄。

一次全给全部 20 个定义一股脑进上下文在 20 个里挑一个按需加载只放摘要目录按问题检索只加载命中项在 3 到 5 个里挑工具越多,「选哪个」本身就越消耗注意力;按需加载把「目录」和「定义」拆成两层。
图:一次性给出全部定义,与先检索再加载命中项

1. 为什么数量会影响质量​

模型看到的是工具的名称加描述加参数模式,这些全部占上下文。二十个工具、每个带一段说明和一个 JSON Schema,轻松吃掉几千 token。这部分是每次请求都要付的固定成本,而且它挤占的是本可以放业务上下文的空间(见《上下文是有限预算》)。

比占位更麻烦的是三件事:

相似工具互相干扰。 searchOrder 和 queryOrderHistory 和 listOrders,描述都提到"订单",模型没有可靠依据区分它们。人看得懂的差别,模型不一定看得懂——因为这个差别通常写在文档里,没写进描述。

选择本身要消耗注意力。 候选越多,"该调哪个"这个决策越难。这不是模型能力问题,是任何在 N 个近似选项里做选择的系统都会遇到的问题。

工具描述本身就是指令。 描述文本进入上下文后模型会照着它行动,这一点在《MCP 的工具、资源和提示模板》里展开过。工具一多,描述之间的边界就模糊,串味的风险也跟着涨。

2. 三个信号说明已经超了​

不要靠感觉,看这三个指标:

选错工具。 模型调了一个语义相近但不是这个场景该用的工具。统计方式是给每个任务标注"应该调哪些工具",然后比对实际调用。

参数瞎填。 工具选对了,参数填得不对——必填项漏了、枚举值编了一个、类型不对。这通常不是模型不会,是参数模式写得不清楚。

有工具不用。 任务明显需要某个工具,模型却直接编了个答案。这是最隐蔽的一个,因为从最终结果看不出来,只有对着轨迹看才能发现。

这三个指标都很容易从调用日志里算出来,而且都是确定性的——工具名对不对、参数在不在 schema 里、该调的调没调,都能用代码判定,不需要模型来打分。这一点在《Agent 怎么评测》里还会用到。

3. 渐进式披露:把目录和定义拆开​

解决办法不是砍功能,是分层。

第一层:摘要目录。 只放工具名加一句话摘要,几十个工具也只有几百 token。这一层回答"有哪些能力"。

第二层:按需检索。 拿到用户问题后,用语义检索(和检索文档是同一套机制)在目录里找出最相关的几个。

第三层:加载命中项的完整定义。 只把这 3 到 5 个工具的完整描述和参数模式放进上下文。

协议侧已经往这个方向走了:2026-07-28 版把能力查询单独抽成 server/discover,列表类结果带缓存提示(ttlMs 与 cacheScope),客户端可以缓存工具目录而不必每次重新拉;工具列表还要求确定性排序(规范措辞是 SHOULD,多数客户端也这么做),目的之一就是让提示缓存能稳定命中。更进一步的"渐进式发现"目前还在路线图上,尚未进规范——别把路线图当已实现的能力来设计。

自己实现时有个便宜的做法:先用规则分域(客服域、订单域、内容域),再在域内做语义检索。纯语义检索在小目录上容易把明显不相关的东西捞进来,加一层粗分域能省掉很多误召回。

4. 工具描述该怎么写​

描述是模型唯一的判断依据,写法直接决定选择准确率。三条经验:

第一句说清"什么时候用"。 不要描述它是什么,描述它在什么情况下该被调用。"按订单号查询单个订单的当前状态,只读,不修改任何数据"比"订单查询工具"有用得多。

显式区分相似工具。 如果存在两个相近的工具,在描述里直接写"与 X 的区别是……"。别指望模型自己从参数差异里推断。

负面约束要写出来。 "只查最近 90 天的订单,更早的查不到"这类限制不写出来,模型会照常调用然后拿到空结果,接着开始编。

顺带一句:描述一改,前缀哈希就变了,提示缓存会全部失效。所以工具描述改一次,等于给所有请求重新预热一次缓存。频繁改描述在生产上是有实际成本的,别把它当成可以随手调的配置。

5. 什么时候该合并,什么时候该拆​

该合并的:一组工具做同一件事的不同变体(createOrder / createOrderWithDiscount / createBulkOrder)。合成一个工具,用 mode 参数区分。变体多的时候,分开注册只会增加混淆。

该拆的:一个工具里塞了两种语义完全不同的操作(upsertUser 既能创建又能更新)。模型分不清"创建"和"更新"的边界,就会在该确认的时候不确认——而这两个动作的副作用完全不同。

判断依据:合并看参数,拆分看副作用。 参数结构相近就合并;副作用性质不同(读/写、可逆/不可逆)就必须拆开。

6. 可见性要按身份过滤​

工具集不是全员一致的。同一个 Agent 在不同身份下应当看到不同的工具子集——这一点在《Agent 不能借用用户令牌》里是从授权角度讲的,从工具集角度讲是同一件事:连上服务器不等于这个身份可以调用全部工具。

过滤要在服务端按身份做,不能只靠前端隐藏按钮,也不能只靠描述里写"你没有权限"。模型看到的工具就是它认为可调的,看不见的才不会调。

7. 该留多少:给一个可操作的判断​

没有普适的数字,因为取决于工具之间的相似度。但有一条可用的经验线:

  • 个位数且彼此语义距离大(查天气、发邮件、查订单):全部常驻,不用分层
  • 十几个且分属几个明确的域:上分层,目录常驻,定义按需
  • 几十个以上:必须分层,而且要按域粗分后再检索;同时认真考虑是不是该拆成多个 Agent

超过某个点之后,继续加工具是负收益。 新工具带来的能力增量,会被既有工具的选择噪声吃光。这时候正确的做法不是优化描述,是拆 Agent——让每个 Agent 只面对自己那一小片工具集,用一个路由层决定该交给谁。分工怎么划见《多智能体怎么分工》。

8. 一个容易忽略的副作用​

工具集规模还会影响人机确认的设计。工具少的时候,"每个写操作都问一次"是可行的;工具多了以后,用户会被确认弹窗淹没,最后变成无脑点允许,护栏形同虚设。

所以工具集变大的同时,确认策略要从"逐个确认"变成"分级确认":只读免确认,白名单内的写操作免确认,白名单外的高危操作才弹窗。这套分级怎么设计见《护栏要做在基础设施层》。

工具是能力,也是负担。加工具的时候同时问一句"它让选择变难了多少",比只问"它能做什么"更接近正确答案。