当智能体的"手"不听使唤
如果说幻觉是智能体"脑子"的问题,那工具调用失败就是"手脚"的问题。智能体不仅要思考,还要行动——调用 API、查询数据库、操作文件系统。而但凡涉及外部系统交互,失败的维度比纯文本幻觉要多得多。
Arize AI 2026 年对生产环境智能体的故障分析指出:工具相关失败占 Agent 异常的 40% 以上,其中最危险的不是"调用失败"本身,而是失败后的错误处理——智能体对错误的理解和处理能力,往往比工具调用本身更不可靠。
三大失败模式
模式一:静默错误——最危险的失败
工具返回了错误信息,但智能体没有正确解读,把错误响应当做正常结果继续推理。
典型场景
// 智能体调用天气 API
{"tool": "get_weather", "arguments": {"city": "北京", "date": "2026-07-01"}}
// API 返回错误
{"error": "city_not_found", "message": "City name must be in English: Beijing"}
// 智能体把 json 当成了天气数据继续推理
"根据返回的数据,北京7月1日的湿度和 error 字段显示..."
这比调用直接报错更危险——至少报错时智能体知道出错了,还能重试。静默错误时,智能体浑然不知,把垃圾数据当成金子继续加工。
为什么会发生?
- 错误格式不统一:不同 API 的错误响应结构不同(HTTP 状态码、JSON error 字段、HTML 错误页……),智能体难以用统一逻辑识别
- Prompt 缺少错误处理指令:很多人在 system prompt 里只写了"使用 XX 工具查询",没写"如果工具返回错误,你应该……"
- 模型倾向"继续完成任务":遇到异常数据时,模型的训练倾向是给用户一个答案,而不是停下来讨论 API 报错
模式二:参数构造错误——最常见的失败
智能体理解了要调什么工具,但构造参数时出错。
常见错误类型
- 格式错误:日期传
2026/07/01而 API 要2026-07-01 - 类型错误:传字符串
"5"而 API 要整数5 - 枚举错误:传
"medium"而有效值只有["low", "mid", "high"] - 遗漏必填参数:忘了传
currency字段 - 语义错误:传了合法格式但语义不对,比如把
page_size=100传给了限制最大 50 的分页接口
根因分析
模型对 API 的理解来自 prompt 中的工具描述(Function Schema)。如果描述不够精确——比如某个参数的合法值只写了 string 没有枚举——模型就只能"猜"。
更微妙的问题是参数间约束:参数 A 的值影响参数 B 的合法范围。比如 country=US 时 state 是两字母缩写,country=CN 时 state 是省份全名。这种上下文相关的约束,Function Schema 很难完整表达。
模式三:无限循环重试——最消耗资源的失败
工具调用失败 → 智能体重试 → 参数稍有不同但仍然错误 → 再次失败 → 再重试……
典型循环
尝试 1: get_weather(city="北京") → error: use English name
尝试 2: get_weather(city="Beijng") → error: invalid city name (拼写错误)
尝试 3: get_weather(city="Beijing ") → error: invalid city name (多了空格)
尝试 4: get_weather(city="Peking") → error: invalid city name (旧称)
尝试 5: get_weather(city="BJ") → error: invalid city name (缩写)
...
每次重试都是一个完整 LLM 推理周期——消耗 token、时间和钱。更糟的是,有些智能体框架默认无限重试,直到 token 预算耗尽。
为什么会陷入循环?
- 错误反馈太模糊:“invalid city name” 没说正确的应该是什么
- 缺乏退出策略:智能体没有被教导"尝试 3 次后应该放弃或求助"
- 模型的"执着"倾向:一旦开始尝试,模型倾向于继续而非承认失败
工程对策
对策一:强类型 Schema 约束
工具定义尽可能精确,减少模型"猜"的空间:
# 宽松定义——容易出错
{"city": {"type": "string", "description": "城市名称"}}
# 精确定义——大幅减少错误
{
"city": {
"type": "string",
"enum": ["Beijing", "Shanghai", "Guangzhou", "Shenzhen", ...],
"description": "城市英文名,仅支持以下列表中的值"
},
"date": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "日期,格式必须为 YYYY-MM-DD"
}
}
越严格越好——这不仅约束模型,还为后续校验提供了依据。
对策二:错误处理 Prompt 模板
在 System Prompt 中明确错误处理流程:
当你收到工具的错误响应时:
1. 识别错误类型(参数错误 / 服务不可用 / 权限不足)
2. 如果是参数错误:修正参数后重试一次
3. 如果重试仍然失败:向用户说明错误,不要继续尝试
4. 如果是服务不可用:告知用户并建议稍后重试
5. 绝对不要把错误响应当做正常数据继续推理
6. 同一个工具最多调用 3 次
这几条规则可以消除大部分静默错误和无限循环问题。
对策三:超时和重试熔断
MAX_RETRIES = 3
RETRY_BACKOFF = [1, 3, 5] # 递增等待秒数
for attempt in range(MAX_RETRIES):
result = call_tool(tool_name, params)
if not is_error(result):
return result
if attempt < MAX_RETRIES - 1:
time.sleep(RETRY_BACKOFF[attempt])
# 基于错误信息修正参数
params = fix_params_from_error(result, params)
else:
raise ToolExhaustedError(f"{tool_name} 失败 {MAX_RETRIES} 次")
核心原则:有限重试 + 递增退避 + 强制熔断。
对策四:错误信息结构化
让工具的错误响应足够具体,包含修复指导:
// 差的错误信息
{"error": "invalid_parameter"}
// 好的错误信息
{
"error": "invalid_parameter",
"parameter": "city",
"reason": "City name must be in English",
"hint": "Try: Beijing, Shanghai, Guangzhou",
"received": "北京"
}
这样的错误信息让智能体不仅能"知道错了"还能"知道怎么改"。
对策五:工具调用后置校验
在工具返回后增加一层自动校验:
def validate_tool_output(tool_name, output):
# 类型校验
if tool_name == "get_weather" and output.get("temperature"):
temp = output["temperature"]
if not (-60 <= temp <= 60):
return f"温度 {temp}°C 超出物理可能范围,数据可能有误"
# 错误响应检测
if "error" in output or "Error" in str(output):
return f"工具返回了错误: {output}"
return None # 通过校验
更深层的设计问题
工具设计的"防呆"原则
很多工具调用失败不是因为智能体笨,而是因为工具设计不友好。好的工具 API 设计应该遵循:
- 最小惊讶原则:默认行为符合直觉
- 容错输入:
"Beijing "和"Beijing"应该等价 - 丰富错误信息:错误响应应该包含足够信息让调用方自我修正
- 幂等设计:同一调用执行多次结果一致,避免重复执行的副作用
“不调用"也是一种能力
人类在工作时不是每个问题都要动手——很多时候"判断不需要操作"本身就是正确决策。智能体也需要这种能力:
- 信息不足时先查询,不要盲目操作
- 操作不可逆时先确认
- 连续失败时及时上报,不要硬撑
这需要在 Agent 框架层面设计"思考-判断-行动"的三段式流程,而非简单的"思考-行动"两段式。
小结
工具调用失败不像幻觉那样"高端”——它更多是工程问题而非智能问题。但正因为如此,它也更容易被系统性解决:
- 强 Schema 压缩参数构造错误
- 错误处理 Prompt 减少静默错误
- 熔断机制 防止无限循环
- 结构化错误信息 加速自我修正
- 后置校验 最后一道防线
做好这些工程规范,工具调用的可靠性可以从 60% 提升到 95% 以上。剩下的问题是——当智能体执行的是一个跨越 10 步、20 步的长链路任务时,如何保证它不从中间断掉?下一篇我们聊:可靠性与中途崩溃。