工具调用失败不是异常,而是常态:LLM Agent 函数调用的容错设计
很多团队的第一版 Agent 都是这样写出来的:模型输出一个函数调用,后端解析参数、执行工具、把结果塞回对话,然后再让模型接着说。Demo 阶段一切顺利,一旦接上真实业务,成功率会断崖式下跌——工具报 500、参数缺字段、模型幻觉出不存在的接口、用户中途改口。这时候工程师的本能反应是换更强的模型,但实际经验是:容错设计带来的稳定性提升,往往比换模型大得多。
一、先把"失败"分类,再谈处理
工具调用失败至少有四类,处理方式完全不同:
- 参数级失败:模型生成的参数不符合 schema,比如缺必填字段、日期格式不对、枚举值幻觉。这类失败占比最高,也最容易兜底。
- 执行级失败:参数没问题,但工具执行报错——下游超时、权限不足、数据不存在。
- 结果级失败:调用成功、返回 200,但结果语义上是错的,比如查错了用户、聚合口径不对。这类最隐蔽,也最危险。
- 意图级失败:模型压根选错了工具,或者应该在没信息时直接反问用户,却硬编了一个调用。
区分这四类的意义在于:参数级失败可以在发送前拦截,执行级失败适合重试或降级,结果级失败需要校验层,意图级失败则要靠提示词与流程设计。混在一起处理,就会出现"所有失败都无脑重试三次"的反模式。
二、参数校验前置:把拦截放在调用之前
最划算的一道防线是发送前校验。拿到模型的函数调用后,先跑一遍 JSON Schema 校验,缺字段就返回结构化错误让模型补,而不是让工具在执行时才炸。实践中推荐把校验错误做成"给模型看的报文":
{"error": "invalid_params", "field": "start_date",
"reason": "expected ISO 8601 date, got 'next Monday'",
"hint": "请把相对日期换算为 YYYY-MM-DD 后重新调用"}
注意 hint 字段——它不是给人看的,是给模型看的。一个明确的修复提示,能把第二轮调用的成功率从五成拉到九成以上。这比在系统提示词里写一万字"请仔细检查参数格式"有效得多,因为错误发生时的上下文里,模型的注意力是最集中的。
日期与时间是一个重灾区。相对表述("明天"、"上周三")必须由调用方注入当前时间并显式换算,不要指望模型自己算准。把"当前时间"作为系统参数注入每次请求,是一个几乎没有成本但收益极高的做法。
三、执行失败:重试要带退避,降级要有出口
执行级失败的重试策略没有想象中简单。幂等工具(查询类)可以直接重试;非幂等操作(下单、支付、发消息)绝不能盲目重试,必须携带幂等键,或者在工具侧保证去重。一个常见的工程疏漏是:HTTP 层的超时重试和业务层的重试叠加,一次用户请求实际打到了下游七八次。
降级路径同样需要提前设计。查询类工具失败时,与其让模型编一个答案,不如显式告诉它"该数据暂不可用,请基于已知信息回答并说明局限性"。Agent 的可信度很大程度取决于它承认无知的能力,而这个能力是被降级文案塑造出来的。
四、结果校验:用轻量断言代替盲目信任
返回 200 不代表结果正确。对关键工具,值得写一层轻量断言:返回的用户 ID 是否与请求一致、列表是否按要求的字段排序、金额单位是分还是元。断言失败就走"参数级失败"的同一套回传机制,让模型看到结构化的错误。
另一个实用技巧是让模型自述调用意图:在发起调用前,要求模型用一句话说明"我准备调用 X 来达成 Y"。这句话会留在对话里,事后排查时有奇效——你能一眼看出模型当时的意图和实际结果之间的偏差。
五、可观测性:没有轨迹就没有改进
容错设计的最后一环是把每次调用链路记下来:模型输出的原始参数、校验结果、工具耗时、返回摘要、是否触发了重试。上线后你会发现,失败高度集中在少数几个工具和参数组合上,修复它们就覆盖了绝大部分损失。轨迹数据比任何离线评测都更贴近真实分布,是改进提示词和工具设计的第一手依据。
结语
函数调用的可靠性不来自模型本身,而来自围绕模型的工程护栏:前置校验拦截低级错误,结构化错误反馈驱动自我修复,幂等与退避控制重试风险,轻量断言守住结果正确性,全量轨迹支撑持续改进。把这五层做完,同一个模型的工具调用成功率翻倍是常态——而且这些护栏在换模型、换业务时都可以复用。对于正在把 Agent 推向生产的团队,这比追逐最新模型是更高确定性的投入。
- 点赞
- 收藏
- 关注作者
评论(0)