我们把 EAMX 的工具面从几十个扩到 247 个之后,Agent 的表现不是变好,而是肉眼可见地变差了:该查列表的去查详情、参数填错、反复试错,偶尔干脆不用工具自己编答案。本文复盘这个反直觉现象——为什么工具越多 Agent 越笨,以及我们最后怎么把它治好的。
1. 现象:工具越全,Agent 越不听话
EAMX 是一套 AI-Native 的设备资产管理系统。按「一切皆插件」的架构重构完,我们把 11 个业务域的动作全部收敛进一份统一的动作契约 actionRegistry,然后一次性投影为 MCP 工具。当时的心情是:工具面越完整,Agent 越强大。
现实很快打脸。同一个问题「查一下 3 号机的情况」,在不同轮次里 Agent 的走法完全不同:
- 有时候调
eamx:equipment:list带一堆筛选条件,返回 200 条然后开始在上下文里"读"; - 有时候先调
eamx:equipment:list,再调eamx:equipment:get,再调eamx:equipment:history,来回四五轮; - 还有时候,它直接不调工具,根据上文猜了一个设备状态就回答了。
最让人警觉的不是它慢,而是它自信地做错。工具就在那里、描述也写得清清楚楚,它就是挑不对。
💡 先给结论
工具数量是一个需要主动管理的预算,不是一个可以无限增长的 KPI。我们最后的解法不是"优化提示词让模型更聪明",而是把工具可见性变成权限问题——让 Agent 只看见当前身份有权限、当前场景需要的那几个工具。同一个 Agent、同一套提示词,可见工具从 247 降到 72,问题基本消失。
2. 为什么:工具预算是笔看不见的上下文税
2.1 每个工具都在"收租"
一个 MCP 工具进入模型上下文的不只是名字。它至少要带上三样东西:
name—— 工具标识description—— 什么时候用、用来干什么inputSchema—— 每个参数的名称、类型、含义、是否必填
参数一多,inputSchema 就会迅速膨胀。以我们的一张业务单据动作为例,创建一张报废单的工具,光参数就有设备编码、报废原因、处置方式、审批人、附件等十几个字段。工具的成本不在调用时产生,而在每一轮对话里都要付一次——哪怕这一轮根本用不到它。
247 个工具全部注册进去,结果是:每一轮推理,上下文里都塞着几万 token 的工具定义。这些 token 不只是花钱,它们会挤掉本该留给业务数据、用户历史、以及模型思考空间的位置。
2.2 选择空间变大,选错率上升
这是最反直觉的一点。工具的"可选项"从 20 个变成 247 个,模型要在一个巨大的候选集里做匹配。相关性稍弱但"看起来也行"的工具大量出现,模型的注意力被大量近似选项稀释。
更麻烦的是连锁错误:第一个工具选错,返回的数据形态就不对,模型基于错误数据继续推断,后面每一步都在错误的轨道上。上一节那些"来回四五轮"的现象,本质上就是错误的自我修正尝试。
2.3 描述撞车:模型分不清"提交"和"更新"
我们最典型的一组撞车是报废单的四个动作:
eamx:change-order:bf-save-draft 保存报废单草稿(不生效不写回)
eamx:change-order:bf-submit 提交报废单草稿生效(写回设备卡 + 履历)
eamx:change-order:bf-update 更新报废单草稿(仅 draft 可编辑)
eamx:change-order:bf-delete 删除报废单(仅草稿可删,软删留痕)
对写这段代码的人来说,这四个的边界一清二楚。但对模型来说,「保存草稿」「更新草稿」「提交生效」在语义上高度重叠——它经常先 save-draft 再 update,或者跳过草稿直接 submit 一个根本不存在的单据。工具名里的 bf 是我们内部的单据类型缩写(报废),模型还要额外学一遍这套缩写。
换句话说:我们按"程序员的分类法"暴露工具,却期望模型按"语义"去理解它。
3. 我们踩过的三个坑
3.1 坑一:按"程序员的分类法"命名和描述
我们的工具名是 eamx:域:动作,这个结构对开发者友好、可读性强、和代码里的动作契约一一对应。但它隐含了一套只有内部人才懂的词汇表:bf(报废)、db(调拨)、yz(移装)、xg(台账信息修改)。
我们后来做的第一件事不是改名字(那会破坏契约一致性),而是把 description 从"给人看的注释"改写成"给模型看的决策依据"——明确写出「什么时候用」「什么时候不要用」「和哪个工具容易混」。比如 bf-save-draft 的描述里加了「准备一张报废单但先不生效;要让报废真正生效请用 bf-submit」。
3.2 坑二:读操作和写操作混在一张清单里
最开始我们把 247 个工具不做区分地全部暴露。后果是:Agent 在"查一下设备台账"这种纯读任务里,也看得见 bf-submit、db-delete 这类写动作。
模型不会主动使坏,但它会"顺手"——发现单据状态不对,就顺手改一下;发现草稿没提交,就顺手提交。对一个设备资产系统来说,这种"主动帮忙"是灾难性的:台账变动必须经单据、单据必须经人确认,这是业务底线,不能交给模型的热心。
3.3 坑三:全量注册,让工具定义挤掉业务上下文
这是最直接可量化的一坑。全量注册后,一次普通的"这周有几台设备该保养"的问答,上下文里工具定义的占比高得离谱,而真正的业务数据(保养计划、设备清单)反而被压缩。
模型不是没有能力回答,是它的注意力预算被工具目录吃掉了。
📌 一个值得记住的判据
当你发现 Agent 在简单问题上"想太多"或者"绕远路",先别急着改提示词。先去看它的工具清单有多长——很多时候问题不在模型,在你给了它一张太长的菜单。
4. 解法:把"工具可见性"当成权限问题
我们最终的判断是:不要指望模型在几百个工具里学会挑对的,而要让它根本看不见那些不该看见的。
这不是提示词工程,而是权限工程。
4.1 核心手段:权限注解驱动 tools/list 过滤
EAMX 里每个动作在定义时就带上了 CASL 权限注解。同一个注解同时驱动三件事:
| 通道 | 这份注解做什么 |
|---|---|
| REST 接口 | 决定这个路由是否放行 |
| 内嵌 AI 助手 | 决定这个工具是否进入模型上下文 |
MCP tools/list | 决定这个工具是否出现在返回清单里 |
关键在于第三行是真正的过滤,不是"标记为不可用"。无权限的工具不会出现在 tools/list 的返回里,模型压根不知道它存在。
真实数据是这样的:
- 管理员身份的 API Key:247 个工具 / 35 个业务域,其中 82 个是只读工具;
- 只读角色的 API Key:只看得见 72 个,写操作一个都没有。
换句话说,权限收紧直接等于上下文瘦身。我们不需要为"只读用户"单独维护一套精简工具集,权限一变,工具面自动收窄。
4.2 给工具标注"行为边界"
除了可见性,我们还给每个动作打了行为标注,让模型知道自己在碰什么:
- 只读标记 —— 这个动作不会改变任何数据;
- 破坏性标记 —— 这个动作会造成不可逆变更;
- 幂等标记 —— 重复调用是否安全。
4.3 高危动作强制确认 + 先预演后执行
光靠标注还不够,我们把约束落到了执行管道的代码里:
- 强制确认:标注为高危的动作,调用时没带
confirm: true一律拒绝执行,并返回明确提示让调用方先去预演。这意味着 Agent 无法单方面完成破坏性操作,必须由人确认。 - dryRun 预演:写操作可以先传
dryRun: true空跑一遍——校验参数、返回将要发生什么,但不落库。Agent 可以在真正动手前把结果给人看。
这两条把"要不要做"这个决定权交还给了人,而把"怎么做"留给 Agent。这个分工是 AI 能进生产环境的前提。
4.4 进一步:工具数要跟着"场景"走,而不是跟着"系统"走
权限过滤解决了"这个身份能看见什么",但还有一层:同一个身份在不同场景下需要的工具也不同。用户在设备台账页面上的诉求,和他在工单页面上的诉求不是一回事。
我们的方向是让工具面跟着场景走——在哪个页面、哪个业务流程里,就只暴露那一小块工具,离开即注销。这样即使是一个权限很高的管理员,单次推理里面对的候选集也始终是几十个而不是几百个。
💡 一个容易忽略的推论
工具数是系统属性,可见工具数是会话属性。前者可以很大(我们就是 247 个),后者必须小(我们把它压到 72 以内,还会继续压)。把这两个数分开看,很多设计纠结会立刻解开。
5. 这些不是 PPT,你可以当场验证
技术文章最容易变成自说自话。所以这篇里出现的每个数字,都留了验证路径:
| 说法 | 你怎么验证 |
|---|---|
| 系统里有 247 个动作 / 35 个业务域 / 82 个只读工具 | 打开 MCP 工具清单,那是从线上服务实时导出的 |
| 只读角色只看得见 72 个工具 | 用 公开只读演示端点连上你自己的 AI,让它数一数 tools/list 返回了几条 |
| 无权限的工具对 Agent 完全不可见 | 用只读 Key 让 AI「删除某台设备」——它会告诉你找不到这个工具,因为这个动作压根没进它的清单 |
我们特意没有在这篇文章里放任何"优化后效率提升 X%"的数字。原因很简单:没有公开可核验口径的性能数字,本质上和编造没区别。工具清单是活的、端点是你自己连的,这些你都能当场对账。
6. 五条可以带走的结论
- 先做权限过滤,再做提示词优化。如果你正在为"Agent 选错工具"调提示词,先看一眼工具清单长度——很可能你该做的是权限而不是文案。
- 工具数不是 KPI,可见工具数才是。系统里有多少工具不重要,重要的是单次推理里模型面对多少个。我们的两个数字是 247 和 72,还在继续拉大差距。
description是接口的一部分,要像写 API 文档一样写。写出"什么时候别用"和"和谁容易混",比多写十行"这个工具很强大"有用得多。- 给模型留退路。预演(dryRun)和强制确认(confirm)让 Agent 可以"先说不动手"。把决定权留给人,Agent 才敢用。
- 用真实会话日志调优,不要拍脑袋。我们发现的每一个坑,来源都是某一次具体的、可复现的失败调用。
7. 最后
「工具越多 Agent 越笨」这件事,本质上是把一个权限与注意力管理问题误当成了能力问题。MCP 这类协议让暴露工具变得非常容易——几行注解就能把一个动作变成工具。正因为容易,才更需要克制。
我们现在的做法是:动作契约可以继续长大(这是系统的能力),但 Agent 的可见工具面必须始终受控(这是会话的纪律)。247 个动作是我们对业务的覆盖,72 个可见工具是我们对模型的克制——这两个数字不矛盾,它们是一件事的两面。
🔎 想自己试一下?
我们在 在线试用页放了一个免注册的只读演示端点:复制一段配置到 WorkBuddy / Claude Desktop / Cursor,你的 AI 就能连上正在运行的 EAMX 服务,当场数一数它看得见几个工具、能查到几台设备。也可以直接用 curl 走完握手、列工具、读数据三步。