你给 Agent 提供了两个工具:updateUser 和 deactivateUser。一张工单写着“关闭此账户”。Agent 调用了 deactivateUser。而上周,一张几乎相同的工单却让它调用了 updateUser 并将 status 设为 "closed",你的 API 接受了这一请求,但这在下游系统中的含义却有着微妙的不同。
系统并没有崩溃。模型只是在两个看似可行的选项之间进行选择,而这两个选项的描述并没有告诉它哪一个更适用。工具选择失效是一种常见的问题,人们往往会将其归咎于模型,但实际上应该在数据模型中解决,因为数据模型是模型唯一可以依赖的依据。
本指南将介绍模型在选择工具时实际读取的内容、如何编写具有区分性的名称和描述、parameter 设计如何影响错误率,以及如何测试工具选择以防止措辞修改在无形中破坏其功能。一旦你的工具像我们《将 OpenAPI 规范转换为 Agent 工具》指南中那样从规范中生成,那么核心问题就变成了该在规范中写入什么内容。
如果你的工具来自你的 API 定义,那么描述就存在于 Apifox 中,因此优化描述可以同时改善文档和工具。
AI Coding 交流群
如果你也在用 AI 写代码,或者正在研究 Cursor、Claude Code 这些工具,欢迎加入以下交流群。群里平时会聊一些 AI 编程的实际用法、开发工作流,还有各种新工具和新玩法。
模型看到了什么
在进行选择的那一刻,模型能够获取的信息包括对话上下文、系统提示词(system prompt)以及工具定义列表。每个工具定义都包含名称、描述和 parameter 数据模型。它没有你的接口文档,没有你的代码注释,也没有“updateUser 是遗留接口”这种内部常识。
这意味着所有的消除歧义信息都必须写进定义本身。OpenAI 函数调用指南和 Anthropic 工具使用文档都指出了这一点:在整个定义中,描述是最关键的文本,它应该足够详细,而不是过于简短。
工具选择错误主要有四种形式,每种都有不同的解决方法。
当两个定义重叠时,模型会选择相似的工具。解决方法是修改描述,明确说明何时不使用该工具。当没有任何描述与任务语言匹配时,模型不会选择任何工具,而是凭记忆回答。解决方法是使用用户常用的词汇。当 parameter 存在歧义时,模型会选择正确的工具但传入错误的参数值。解决方法是明确类型、枚举和单位。当执行顺序很重要但未明确说明时,模型在链式调用工具时表现很差。解决方法是在描述中声明前置条件。
根据工具的作用来命名
名称传递的信息比其长度所暗示的要多得多,因为模型首先读取的就是名称。
在整个工具集中使用统一的 动词名词(verbNoun)风格:例如 createOrder、refundOrder、getOrderStatus。一致性与具体的命名选择同样重要,因为如果一个工具集中混杂了 order_create、getOrder 和 refund,就会让每个名称的阅读理解成本都稍稍增加。
具体指明操作的对象。search 是一个糟糕的工具名称,而 searchCustomersByEmail 则是一个好名称,它同时告诉了模型要搜索什么以及如何搜索。
避免使用内部术语。如果你的 API 将客户称为“entity”,将订阅称为“instrument”,那么模型就无法将它们与包含“customer”和“plan”的工单联系起来。请根据任务的语言来命名工具,而不是根据数据模型的语言。
切勿跨上下文复用名称。如果两个不同命名空间下的工具都命名为 list,一旦它们出现在同一个列表中,就会立即产生歧义。
编写具有区分度的描述
一份有用的描述需要回答四个问题:它做什么、它会改变什么、何时使用它,以及何时不该使用它。
以下是一组区分度较弱的例子:
{ "name": "updateUser", "description": "Updates a user." }
{ "name": "deactivateUser", "description": "Deactivates a user." }
而这是一组真正能有效区分的例子:
{
"name": "updateUser",
"description": "Updates profile fields on an active user, such as name, email, or timezone. Use for corrections and profile edits requested by the user. Does NOT change account status. To disable an account, use deactivateUser instead. Do not use to close or cancel an account."
}
{
"name": "deactivateUser",
"description": "Disables a user account, revoking all sessions and blocking sign-in. Reversible with reactivateUser. Use when a customer asks to close, cancel, pause, or suspend their account. Does NOT delete data. For permanent deletion use deleteUser, which cannot be undone."
}
这里主要运用了四种技巧。
指明关联的同级工具。 “改用 deactivateUser”直接在模型进行对比的瞬间消除了歧义。
融入用户的词汇。 描述中包含 “close”(关闭)、“cancel”(取消)、“pause”(暂停)和 “suspend”(挂起)等词,是因为这些词经常出现在工单中。这是你能做的回报率最高的优化,而且几乎零成本。
说明它不做什么。 否定性的表述比肯定性的表述更具区分度,因为两个相邻工具的肯定性声明往往看起来很相似。
标记可逆性。 当你告诉模型存在风险时,它才会去权衡风险。这与我们关于 AI agent 防护栏(guardrails)文章中提到的执行模式相得益彰,而防护栏才是真正实现保护的地方。
字数多一点也没关系。为了防止一次错误地调用破坏性的接口,写上一百个词的描述是十分划算的。
设计 parameter,增加错误传参的难度
一旦选对了工具,接下来容易出错的地方就是传参了。
JSON Schema 提供了你所需的大部分约束,建议大致浏览一下 JSON Schema validation vocabulary,以了解你的工具调用 API 支持哪些关键字。
只要集合是封闭的,就使用 enum(枚举)。 一个被定义为 string 类型的 status parameter 会诱导模型凭空捏造数据。而如果将其定义为 enum,就能限制模型仅能使用你的 API 所接受的数值。
"status": {
"type": "string",
"enum": ["pending", "paid", "refunded", "cancelled"],
"description": "Order status. 'cancelled' means never fulfilled; 'refunded' means fulfilled then reversed."
}
在名称中包含单位。 amount 具有歧义,模型在猜测其代表美元还是分时会前后不一致。而 amount_cents 则永远不会产生歧义。同理,timeout_seconds、distance_meters 和 duration_ms 也是如此。
为日期格式提供示例。 "description": "Start date in ISO 8601 format, for example 2026-08-26"(例如 2026-08-26 的 ISO 8601 格式的开始日期)生成格式正确日期的概率要比仅提供 “start date” 高得多。
保持必填列表的真实性。 将所有内容都标记为可选(optional)会将失败推迟到运行时;而将 API 默认能合理解析的内容标记为必填(required),则会导致模型胡乱生成值。这两种情况都很常见,并且都会以校验错误的形式出现,我们在关于 agent 的 API 错误设计的文章中对此有所介绍。
扁平结构优于嵌套结构。 模型在填充 {"customer": {"address": {"postal_code": "..."}}} 时容易犯结构性错误,而在处理 customer_postal_code 时则不会。因此,建议在工具边界处进行扁平化处理,并在执行器中重新组装。
拆分重载的工具。 如果一个工具的 mode parameter 会改变其他所有字段的含义,那么它实际上应该被拆分为两个工具。拆分它可以提高选择准确性并简化这两种数据模型。
声明前置条件和顺序
当模型不知道执行顺序时,多步骤的任务就会失败。请在依赖工具的描述中明确指出:
{
"name": "captureCharge",
"description": "Captures a previously authorized charge. Requires an authorization_id from authorizeCharge. Call authorizeCharge first if you do not already have one. Cannot capture more than the authorized amount."
}
仅需两行,就能在模型阅读描述的环节解决顺序问题。这适用于所有此类操作:先创建再更新、先上传再处理、先授权再捕获。如果依赖步骤的描述中没有指明其前置步骤,模型很可能会直接跳过它。若该序列跨越多个 agent 而非多次调用,则适用我们关于在子 agent 之间传递上下文的文章中介绍的移交规则。
像测试其他行为一样测试选择
描述也是代码,它们也会发生退化。有人为了符合风格指南而缩短了描述,可能下周二 agent 就会开始选错接口。
构建一个小型选择测试套件。准备 20 到 50 个 prompt,每个都关联你所期望的工具。运行它们,记录模型选择的工具,并仅对名称进行断言。虽然每次运行的参数可能会有所不同,但选择的工具不应该改变。这就是我们测试非确定性 agent 指南中所介绍方法的实际应用形式。
在测试集中加入最容易出错的用例:
- 工具集中最相似的两个工具,并配有应该分别路由到这两个工具的 prompt。
- 使用客户常用词汇而非 API 词汇的 prompt。
- 一个不应该匹配任何工具的 prompt,此时正确的行为是进行询问,而不是强行调用。
- 具有破坏性的工具,因为一旦选错会付出真实的代价。
将每个 prompt 运行多次。一个五次中只成功四次的工具在生产环境中无异于抛硬币,其描述需要重新打磨。
将运行指向 mock,从而确保测试在选择工具时永远不会触及真实数据。我们关于“在 mock 类似环境而非生产环境中运行 Agent”的博文介绍了相关设置,而 Apifox 可以基于生成工具的相同定义来提供这些 mock,从而保持数据模型和行为的一致性。

以相同方式出错的三类集合
CRUD 集合。 某个 API 暴露了 getUser、listUsers、searchUsers 和 queryUsers 接口,它们都是从多年累积演进的接口中生成的。对于模型来说,这是同一个概念的四个不同名称。解决方法并不是为这四个接口都写出更好的描述,而是只向 Agent 暴露其中一个,并把其余的从工具列表中排除。精选的集合每次都完胜完整的集合。
管理类集合。 读取类工具与破坏性工具并排放在一起,语气毫无区别:getInvoice、voidInvoice、deleteInvoice。没有任何文字迹象表明其中两个操作会断送职业生涯。应该在描述中添加后果说明,将其标记为需要审批,并在执行器中强制执行规则,而不是仅寄希望于文字描述。这种分层防御的方法在我们关于“如何阻止 Agent 摧毁你的 API”的博文中有详细介绍。
历史遗留集合。 两个接口做同样的工作,其中一个已被弃用。接口定义/规范中仍然列出了这两个接口,因此生成器会同时输出它们,而 Agent 大约有一半的时间会选择旧的那个。要么从生成的工具中剔除已弃用的操作,要么在其描述的开头加上“Deprecated. Use createOrderV2 instead.”(已弃用。请改用 createOrderV2)。如果把这句话放在开头,模型会遵守它;如果把它埋在末尾,模型则会忽略它。
描述是共享的配置
一旦你接受了“工具描述决定行为”这一观点,接下来的问题就是谁拥有这些描述。在大多数团队中,答案往往是偶然的:谁最早设置了 Agent,描述就在谁本地机器的某个文件里。
相反,应该将工具集视为共享制品,像评审其他接口一样对其进行评审。围绕 Agent 工作构建的平台通常会直接对此进行建模。一个 HiFox Agent 是一个包含指令(instructions)、Runtime、Skills 和仓库的已保存配置,在 Space(空间)中共享它可以让一个人的工作配置被整个团队复用。这里的价值不在于存储本身,而在于描述的修改变成了一个可评审的编辑操作,并且会影响所有人,而不是一个悄无声息的本地微调,导致某个开发人员的 Agent 表现与其他人不同。

留意用户使用的词汇
最常见的鸿沟在于词汇。你的 API 使用的是 subscription,而你的客户说的是 plan、membership 和 billing。你的 API 使用的是 deactivate,而他们说的是 cancel、close 和 turn off。
收集真实的语言。从支持工单、搜索日志或失败的 Agent 运行记录中提取高频词汇,然后将它们融入到本应匹配的工具描述中。这只需花费一个小时,但对提升选择准确率的效果通常比任何数据模型调整都更为显著。
也要关注失败的情况。当 Agent 没有选择任何工具并根据自身知识进行回答时,这属于词汇缺失,而不是推理失败。任务语言与工具文本从未重合,因此该工具是不可见的。
工具集清单
- 名称遵循统一的
verbNoun命名规范,并指代特定的对象。 - 每个描述都说明了会发生什么改变、何时使用以及何时不该使用。
- 功能重叠的工具要显式地相互提及。
- 描述中应包含用户实际使用的词汇,而不仅仅是内部术语。
- 破坏性和不可逆的操作要在描述中明确说明。
- 为每个闭集声明枚举。
- 单位和格式应包含在 parameter 名称或描述中,并附带示例。
- 必填项列表与 API 实际强制执行的要求一致。
- 有依赖关系的工具要注明其前置条件。
- 选择测试套件在 CI 中针对 mock 运行。
模型是在对你编写的文本进行模式匹配。当它选择错误时,首先应该检查文本,而这通常也是你唯一需要修改的地方。如果你希望在一个项目中管理描述、mock 和测试,请下载 Apifox。
常见问题解答
工具描述应该多长? 足够消除歧义即可,通常为两到五句话。描述确实会占用上下文空间,因此可以精简那些无歧义工具的描述,把空间留给功能相近的工具。
我应该在描述中放入示例吗? 对于格式和单位,是的,示例可以避免一整类错误。但应避免放入冗长的使用示例,因为它们会消耗上下文,且很少能改变选择结果。
是拥有许多功能单一的工具更好,还是少数灵活的工具更好? 在一定程度上,功能单一的工具更好。每个工具因为只做一件事,其选择的可靠性更高。但超过几十个之后,工具列表本身就会成为问题,你需要进行过滤或检索,正如我们在关于从 OpenAPI 生成 Agent 工具的文章中所讨论的那样。
我可以直接在 system prompt 中解决选择问题吗? 部分可以,对于解决一两个已知的混淆,这是一个合理的权宜之计。但它无法规模化扩展,因为提示词是所有工具共享的,而描述是随需要它的工具一起移动的。
如果模型一直凭空捏造 parameter 值怎么办? 限制类型,添加枚举,并在描述中说明该值必须来自之前的调用,而不是凭空构建。如果问题仍然存在,请在包装器中进行校验,并返回包含允许值的错误信息。
这些规则也适用于 MCP 服务端吗? 是的。MCP 服务端以相同的形式暴露名称、描述和数据模型,因此适用相同的措辞规则。我们关于什么是 MCP 的解释文章介绍了该协议本身。
开发必备:API 全流程管理神器 Apifox
介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。
如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用。

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案。
获取专属报价与部署方案
详细的私有化部署系统架构与安全白皮书
针对您公司规模的专属报价单
免费的 1v1 专属产品演示 (Demo) 机会