一个有 18 个工具的 Agent 老是调错工具:明明要"查订单详情",它调了"查订单列表";明明要"改状态",它调了"查状态"。日志里能看到它连续三次调用同一个错了的工具,每次都得到"参数不符",然后又换参数重试——因为它以为"是参数问题"。
工具选择错误是 Agent 最常见的失败模式,而它有三类成因:描述不清、工具重叠、以及失败反馈不可行动。这个"只对第一类有用"是判断的关键:如果"加提示"两次都没改善,说明问题不在"它不知道",而在"它没法区分"或"它得不到反馈"。
三者修法完全不同,而"加提示"只对第一类有用。
1. 三类"选错"
| 类型 | 表现 |
|---|---|
| 该用的没找到 | 选了"看起来像"的另一个 |
| 该用的被排除 | 前置条件不满足(如缺参数) |
| 用对了但用错方式 | 参数/顺序错 |
第一项是"描述问题":**两个工具的描述里都有"查订单"**第一项的"机制"是"语义相似度高于差异":在它的视角里,"查订单详情"与"查订单列表"共享大量词,而"详情 vs 列表"这个差异只占一小部分。
,而它们实在的区别(列表 vs 详情)没被写清。
第三项是"接口设计"问题:参数名相似但语义不同(status 与 state),于是它"按字面填"。
2. 描述该怎么写才"可区分"
| 要素 | 作用 |
|---|---|
| 一句话"做什么" | 决定何时调用 |
| "什么时候不该用" | 排除相近工具 |
| 必需的输入 | 决定能否调用 |
| 返回什么(含失败) | 决定下一步 |
第二项的"原理"是"排除比包含更省判断":它不必"比较两个工具的正面描述",只需"看到否决条件就排除"——于是判断成本下降。
第二项最被忽略:"不要用它来查列表"这类"负向说明"往往比"正向说明"更能区分——因为正向他能从名字猜到,负向只有你说。
// 两个易混工具的"可区分"写法
get_order_detail: 查"单个订单"的完整详情(含商品、金额、状态)。
不要用它查多个订单——那用 list_orders。
必需:order_id(字符串,形如 O-123)。
list_orders: 按条件查"订单列表"(分页返回摘要,不含商品明细)。
不要用它取单个订单详情——那用 get_order_detail。
必需:user_id 或 status 至少一个。
// 关键:每个都写了"不要用它做什么"
3. "工具重叠"的三种程度
| 程度 | 表现 |
|---|---|
| 完全重复 | 两个工具做同一件事 |
| 部分重叠 | 覆盖范围交叉 |
| 粒度不同 | 一个做一步,一个做整链 |
第三项最容易被"加工具"制造出来:**既有"下单"(含扣库存),又有"扣库存"与"建订单"**第三项的"成因"通常是"渐进添加":先有"下单",后来为"补偿场景"加了"扣库存",再后来为"报表"加了"建订单"——于是三个都存在,而"整链"与"单步"没有标注。
——于是"下单"时它可能选"分两步走",结果漏了一步。
修法不是"删一个",而是"在描述里写清适用层级"("整链" vs "单步")。
4. 失败反馈为什么必须"可行动"
| 返回 | Agent 的行为 |
|---|---|
"error": "invalid param" |
换参数重试 |
"error": "field=order_id, expected O-xxx" |
改格式重试 |
"error": "该订单不存在,可先用 list_orders 确认" |
换工具 |
第二项(有字段与期望格式)能救一半:它知道"哪个字段错了、应该长什么样",于是"改格式"而不是"换工具"——而这两者的成功率差很多。
第一项会让它"猜",而"猜"的表现就是"连续三次同一个工具"——因为它不知道错在"工具选错"还是"参数写错"。
第三项是"最好的反馈":它直接指向"下一步该做什么"。第三项的"额外"是"它顺便教了工具选择":一句"可先用 list_orders 确认 ID"让 Agent 学到"这两件事的先后关系",于是在下一轮更可能选对。
// 可行动的错误返回(四个字段)
{"ok": false,
"code": "NOT_FOUND",
"field": "order_id",
"hint": "该订单不存在;可先用 list_orders 确认 ID",
"retryable": false}
"选错率"可以量化,否则"改描述"只能靠感觉:
// 三个可算的数(按任务类型分)
1 选对率 = 第一次就选对工具的次数 / 任务总数
2 重试率 = 同一任务里"重复调用同一工具"的次数 / 工具调用总数
3 切换率 = 换了工具后成功的比例(低 → 换工具也没用)
// 判据:选对率 < 80% → 描述/分层有问题;重试率高 → 反馈不行动
5. 让"选对"变简单的三个手段
| 手段 | 效果 |
|---|---|
| 减少工具数(分层暴露) | 最有效 |
| 工具名带"动作 + 对象" | 提高可区分 |
| 提示里给"任务 → 工具"的示例 | 补上下文 |
第一项的"代价"要说清:分层意味着"多一轮交互"(先要工具,再调用),而它换来的是"选择准确率"——所以只在"选错代价高"时值得。
第一项是"结构"解法:18 个工具全暴露,选择空间就大。分层(先给"订单相关"5 个,需要时再给其它)能把"选错概率"降一个量级。
第二项很便宜:get_order_detail 比 order_query 清楚得多第二项的"命名规则"可以固定为"动词_对象_限定":get_order_detail、list_orders_by_status——而"限定"部分正是最容易缺的。
——因为后者无法区分"查一个"还是"查一堆"。
// 分层暴露(按任务阶段给工具子集)
stage_collect: [list_orders, get_order_detail, get_user]
stage_mutate: [update_order_status, cancel_order]
stage_notify: [send_message]
// 收益:每阶段只看到 2–3 个工具,选择空间小
"工具清单"应当做成一张"可区分矩阵",一眼看出重叠:
// 工具矩阵(行=工具,列=关键维度)
工具 对象 数量 是否变更 粒度
get_order_detail 订单 单个 否 整条
list_orders 订单 多个 否 摘要
update_order_status订单 单个 是 单字段
cancel_order 订单 单个 是 整链(含退款)
// 冲突:update 与 cancel 都能"改状态" → 描述需写"cancel 才做退款"
6. 排错:五种"工具选错"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 反复调同一个错工具 | 错误反馈无信息 | 加 hint/field |
| 两个工具混用 | 描述不可区分 | 加"不要用它做什么" |
| 选"分步工具"漏步骤 | 粒度重叠 | 描述标"整链/单步" |
| 换了模型就好了 | 兼容性差异 | 不改描述,标注模型差异 |
| 只在长任务里错 | 上下文稀释 | 缩短上下文/分层 |
第四项的"排查方式"是"同一描述、两个模型各跑 20 次任务":差异显著 → 模型差异;都错 → 描述问题。这个实验半小时能做完。
第四项很重要:"换模型就好"说明是"模型差异"而不是"描述问题"——此时改描述没用,要么换模型,要么给出更强的结构化约束。
最后一个"验收"方式:用任务集回归,而不是看单次对话。
// 工具选择回归集(10 条,覆盖三类易混)
"查订单 O-123 的详情" → 期望 get_order_detail
"看看我最近 5 个订单" → 期望 list_orders
"把这单取消掉并退款" → 期望 cancel_order(不是 update_status)
// 断言:第一次调用的工具名 + 参数键集合;每周跑一次,看选对率
7. 边界:什么情况不必优化工具选择
| 场景 | 为什么 | 做法 |
|---|---|---|
| 工具很少(≤ 3) | 选择空间小 | 不优化 |
| 任务单一(每次同一流程) | 几乎不需选择 | 写死流程 |
| 有人工确认每一步 | 错了会被拦 | 不优化 |
| 失败代价低(可重试) | 试错便宜 | 允许试错 |
判断口径一句话:"工具数 × 任务类型数"是否小到"它能记住"。最后一条与"演进"有关的边界:工具数增长会让"选错率"非线性上升。所以"加工具"应当与"分层 + 描述复核"一起做——只加不治,很快会到"怎么改描述都没用"的状态。
小(3 个工具 1 种任务),不必优化;大(18 个工具 10 种任务),必须靠"描述 + 分层 + 可行动反馈"来控制。
8. 自测
- 三类"选错"分别对应什么成因?为什么"加提示"只对第一类有用?
- 描述里"什么时候不该用"为什么比"做什么"更能区分?
- "粒度不同"的重叠是怎么被制造出来的?修法该写什么?
- 为什么
"invalid param"会让 Agent 反复重试同一个工具? - "分层暴露"为什么能降低选错概率?它牺牲了什么?
- 判断"是否需要优化工具选择"的那个问题是什么?