摘要:给 Agent 接搜索、数据库、脚本执行一类工具时,大家容易先写「调用成功返回什么」。线上真正耗时间的,是超时、权限失败、参数不合法、部分成功时 Agent 会怎么说、会不会死循环重试。本文把失败契约写成可落地的检查清单,并区分工具层、适配层、策略层和用户预期层。适合正在给 Agent / MCP / function calling 加工具的开发者。

说明:不同产品的工具协议细节会变,本文讲工程约定,不绑定某一家 SDK 版本。


1. 结论先行

  1. 先定义失败,再定义成功。 成功路径好写,失败路径决定系统稳不稳。
  2. 失败要可分类:可重试、不可重试、需人介入,别都当成再试一次。
  3. 把错误变成结构化结果:错误码、是否可重试、给模型看的短说明、给日志看的细节。
  4. 重试必须有上限和退避;否则工具抖动会被放大成费用和延迟雪崩。
  5. 工具能调通 ≠ Agent 会用对;还要约束何时允许调用。

2. 为什么成功 demo 扛不住真实对话

本地演示通常是:

  • 工具总是在线
  • 参数总是合法
  • 返回总是完整 JSON

用户场景里更常见:

  • 第三方 API 偶发 429/5xx
  • 模型捏造了不存在的字段
  • 工具执行成功了一半(写了库但通知失败)
  • 用户以为已经帮我提交了,实际只是查了状态

如果只实现 happy path,Agent 会在失败时胡编、空转或重复调用。

请添加图片描述

图1. 这些不是边角料,而是工具接口的一部分。

一个真实感很强的例子:客服 Agent 调用「创建退款单」。上游超时后,若返回值只有一句自然语言“好像失败了”,模型可能再调一次,造成重复退款;若返回 error_code=TIMEOUT, retryable=false, side_effect=unknown,策略层就可以要求人工确认,而不是自动重试。


3. 一份最小失败契约

每个工具建议至少写清:

字段作用
error_code稳定枚举,便于策略分支
retryabletrue/false,禁止模型自由发挥
user_message可直接对用户说的短句
log_detail仅日志,含请求 id,不含密钥
timeout_ms超时阈值
max_retries适配层重试上限
side_effectnone / possible / done,标明是否可能已产生副作用

示例(示意):

{
  "ok": false,
  "error_code": "UPSTREAM_TIMEOUT",
  "retryable": true,
  "side_effect": "unknown",
  "user_message": "上游服务超时,请稍后重试或转人工",
  "request_id": "req_xxx"
}

模型侧提示词里应写明:遇到 retryable=false 不要盲着重试;side_effect 不是 none 时,禁止在未确认前再次执行写操作。


4. 失败通常落在哪一层

请添加图片描述

图2. 先定位层,再改提示词或重装框架。

典型问题优先动作
工具本身服务挂、权限、配额修凭证、熔断、降级
适配/Schema参数类型、必填、枚举收紧 schema,加校验
Agent 策略该不该调、何时调改策略与权限,不先怪工具
用户预期以为已提交产品文案与确认步骤

很多 Agent 很蠢的问题,其实是适配层允许了过宽的参数,或策略层允许在高风险操作上自动重试。

4.1 参数校验为什么要放在适配层

不要指望模型永远生成合法参数。适配层应:

  • 校验必填与范围
  • 拒绝未知字段(或明确忽略策略)
  • 把校验失败映射成 VALIDATION_ERROR, retryable=false

这样模型会被引导修正参数,而不是把脏请求打到上游。


5. 部分成功:最容易被忽略的坑

例如:工单已创建,但发邮件失败。若只返回一个布尔值,Agent 无法解释状态。

更稳妥:

  • 返回明确状态机:created / notified / failed_notify
  • 提供补偿接口或人工工单链接
  • 禁止在不确定时假装全部完成

对用户可以说:退款单已创建(单号 xxx),通知短信发送失败,已转人工补发。这句话比含糊的失败有用得多。


6. 重试策略的底线

错误类型建议
网络抖动、429、502有限次退避重试
参数错误、权限拒绝不重试,改输入或停
超时且可能有副作用不自动重试写操作
未知错误记日志,降级或转人工

退避要带抖动,避免多会话同时打爆上游。总重试预算应写入契约,而不是留给模型临场发挥。


7. 适合马上做的,和不适合一上来做的

适合马上做:

  • 为每个工具补齐错误码与 retryable
  • 给超时和重试写死上限
  • 高风险操作(转账、删数据、发公告)强制确认
  • 给每个写工具标明 side_effect

不适合一上来做:

  • 没失败契约就接一堆工具
  • 用再问一遍模型代替结构化错误
  • 让 Agent 在不可重试错误上循环调用
  • 把生产写权限和只读查询混在同一工具里且不分层级

7.1 快速能做的改造清单

若你已有三个以上工具,不必重写框架,按这个顺序补:

  1. 给每个工具返回值统一包一层:ok + error_code + retryable + user_message
  2. 写操作补 side_effect;读操作固定为 none
  3. 适配层加超时;超时默认 retryable=trueside_effect=unknown(除非你能证明请求未达上游)
  4. 策略提示词加三条硬规则:不可重试不重试;未知副作用不重做写操作;连续失败转人工
  5. 准备四个故障用例:超时、401、参数缺失、上游 500,全部要跑过

做完这五步,成功率未必立刻上升,但失败时系统会变得可解释,排障时间通常会明显下降。

和 MCP / function calling 的关系

协议解决的是怎么暴露工具;失败契约解决的是工具坏了以后系统怎么表现。两者叠在一起才接近可运营。


8. 常见误区

误区更好的做法
失败就返回一段自然语言同时给机器可读字段
所有错误都重试三次retryable 分支
工具越多 Agent 越强工具越多,失败面越大
只测成功样例用故障注入测超时与权限
信任模型自己总结错误契约字段为准

9. 术语速查

术语含义
失败契约对失败形态、字段、重试策略的事先约定
可重试错误临时故障,允许有限次重试
适配层把外部 API 转成 Agent 可用的工具接口
熔断连续失败后短时间停止调用上游
副作用调用可能已改变外部状态

9.1 评审或上线前的验收问题

可以拿去问自己或问供应商:

  1. 工具超时后,会不会自动再打一枪写接口?
  2. 权限失败时,用户看到的是什么,日志里能不能定位到请求 id?
  3. 部分成功有没有状态机,还是一个布尔值糊弄过去?
  4. 连续失败是否会熔断,熔断恢复条件是什么?
  5. 高风险操作有没有强制确认,确认超时怎么处理?

五问答不清,就还没到「可以放心把工具交给 Agent」的程度。连通性 demo 只能证明网络通,不能证明失败可运营。


10. 小结

给 Agent 加工具,工程上半场是连通,下半场是失败怎么被理解、被限制、被恢复。先写失败契约,成功路径反而好写,也更接近可上线。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐