Agent 调用工具经常失败或选错工具,怎么排查和优化?
工具调用(Tool Calling)是 Agent 最容易出问题的一环:模型要读懂工具描述、选对工具、填对参数、看懂返回结果,任何一步偏了,整条链路就断。 排查的关键不是反复改提示词,而是把一次调用拆成断点,逐个定位在哪一环断了。
1. 结论:先分类症状,再对症下药
| 症状 | 典型表现 | 高概率根因 | 优先修复 |
|---|---|---|---|
| 不调用 | 直接凭记忆回答 | 提示没要求用工具、工具不在候选集 | 加"必须核实"约束、检查工具注册 |
| 选错工具 | 该查订单却去查物流 | 描述重叠、命名含糊 | 重写描述、加反例、收敛工具数 |
| 参数错 | 类型不符、缺必填 | schema 太松、字段名歧义 | 严格 schema + 枚举 + 默认值 |
| 编造工具名 | 调用了不存在的方法 | 工具清单太长、示例过时 | 运行时校验 + 明确报错清单 |
| 忽略返回 | 拿到数据仍说"查不到" | 返回值结构复杂、关键信息埋太深 | 结构化裁剪、显式摘要字段 |
| 重复调用 | 同一参数连调三次 | 无去重、结果不满足预期 | 相同调用短路、结果显式回显 |
2. 机制:把链路切成四个断点
① 描述可读性 ② 选型 ③ 填参 ④ 用结果
工具文档 ──▶ 模型挑工具 ──▶ 模型生成参数 ──▶ 运行时执行 ──▶ 模型读返回
│ │ │ │ │
└─ 名称/描述 └─ 候选集过大 └─ schema 松 └─ 报错不友好 └─ 返回太长
排查顺序固定为:看 trace 里模型原始输出 → 校验参数 → 看工具真实返回 → 看下一轮模型是否采纳。四步里哪一步和预期不符,问题就在那。
3. 代码:让错误自己说话
多数"调用失败"其实是错误信息对模型不可用。工具抛一个 NullPointerException,模型只能瞎猜;返回一句可操作的原因,它下一次往往就能改对。
from pydantic import BaseModel, Field, ValidationError
from typing import Literal
class QueryOrderArgs(BaseModel):
order_id: str = Field(pattern=r"^[A-Z]\d{4}$", description="订单号,形如 A1029")
detail: Literal["summary", "full"] = Field(default="summary", description="返回详细程度")
def query_order(raw_args: dict) -> dict:
try:
args = QueryOrderArgs(**raw_args) # ③ 严格校验,错误可回传
except ValidationError as exc:
return {"ok": False, "error": "invalid_arguments",
"detail": exc.errors()[0]["msg"], # 例如 "String should match pattern..."
"hint": "order_id 必须以大写字母开头,后接 4 位数字",
"retryable": True}
row = db.get_order(args.order_id)
if row is None:
return {"ok": False, "error": "not_found",
"detail": f"订单 {args.order_id} 不存在",
"hint": "先用 search_order 按手机号后四位查订单号",
"retryable": False}
return {"ok": True, "data": {"status": row.status, "amount": row.amount,
"paid_at": row.paid_at.isoformat()}}
工具描述同样是"接口文档",写法有讲究:
{
"name": "query_order",
"description": "按订单号查询订单状态与金额。仅当用户提供了订单号时使用;只有手机号时请先用 search_order。",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "pattern": "^[A-Z]\\d{4}$", "description": "订单号,例如 A1029"}
},
"required": ["order_id"],
"additionalProperties": false
}
}
description 里写清"什么时候用、什么时候不用",比堆十个示例更能降低误选。
4. 优化清单
| 优化项 | 做法 | 预期收益 |
|---|---|---|
| 收敛工具数 | 单次暴露 ≤ 10 个,其余按意图分组动态挂载 | 选型准确率显著上升 |
| 命名规范化 | 动词_对象:query_order、refund_order | 减少同义混淆 |
| 强制结构化 | 关闭自由文本参数,全部 JSON Schema | 参数错误率下降 |
| 返回裁剪 | 工具侧只返关键字段,原始数据给 id | 减少上下文污染 |
| 幂等与去重 | 相同 (工具, 参数) 短期缓存 | 减少重复副作用 |
| 失败可读 | 统一 {ok, error, hint, retryable} | 自愈率提升 |
| 并行调用 | 无依赖的多个查询一次发起 | 降低延迟 |
5. 常见追问
问:工具太多怎么办?
三层处理:按会话意图只挂载相关工具组;给工具加命名空间(crm.query_order);提供一个 list_tools 元工具让模型按需发现。别指望模型在 50 个工具里精准挑一个。
问:怎么量化"选错工具"? 建一个工具选择评测集(问题 → 期望工具 + 期望参数),跑准确率与参数正确率两个指标,每次改描述或加工具都回归一次。低于 95% 就先别加新工具。
问:写操作怎么保证安全? 幂等键 + 二次确认 + 白名单。高风险动作(退款、删库)走人工审批节点,Agent 只负责准备参数和说明理由。
问:模型并行调用多个工具,结果顺序乱了怎么办?
按 tool_call_id 回填,不要依赖数组顺序;工具内部无状态,避免依赖调用先后。
问:观测上要记什么? 每次调用记:工具名、参数、耗时、返回码、返回大小、是否被下一轮采纳。这五项能覆盖绝大多数线上问题定位。
小结
工具调用不稳定的根因,八成在"描述含糊、schema 太松、报错无用"这三件事上,而不是模型不够强。排查按"模型原始输出 → 参数校验 → 工具返回 → 下一轮采纳"四个断点走,优化按"收敛工具数、严格 schema、结构化错误、裁剪返回"四条做,再把工具选择准确率纳入回归,稳定性就能稳步提升。