针对 AI Agent 的 API 错误设计:可恢复的错误

AI Agent 遇到 API 错误总是无限重试或直接放弃?本文为您提供针对 Agent 的 API 错误设计指南,教您使用结构化格式与引导指令,让 Agent 具备自动修复与恢复任务的能力。

用 Apifox,节省研发团队的每一分钟

针对 AI Agent 的 API 错误设计:可恢复的错误

免费使用 Apifox

相关推荐

最新文章

API

一体化协作平台

API 设计

API 文档

API 调试

自动化测试

API Mock

API Hub

立即体验 Apifox
目录

你的 API 返回了 400 Bad Request,其 body 为 {"error": "invalid input"}。人类开发者打开文档,检查载荷,发现缺失的字段,并在几分钟内将其修复。而 Agent 读到相同的两个词,却无从下手,只能做它唯一能做的事:再次发送相同的请求。然后再试一次。最后它选择放弃,并告诉用户该 API 已损坏。

错误响应是 Agent 最依赖的 API 部分,却往往是团队最后才设计的部分。一个好的错误会告诉调用者出了什么问题、重试是否有帮助以及需要修改什么。Agent 可以针对这三点采取行动。而模糊的错误会将一个本可恢复的问题变成一个失败的任务。

本指南是站在 API 服务端的角度编写的。我们关于 Agent 错误恢复的博文介绍了客户端在重试、退避和熔断时应该做什么。而本文则介绍了你的 API 必须返回什么,才能让客户端的这套逻辑正常运转。

Apifox 在这里至关重要,因为错误响应是大多数 API 中测试最少的部分。你可以在定义规范、进行 mock 以及断言错误响应时,使用与测试正常流程相同的工具。

AI Coding 交流群

如果你也在用 AI 写代码,或者正在研究 Cursor、Claude Code 这些工具,欢迎加入以下交流群。群里平时会聊一些 AI 编程的实际用法、开发工作流,还有各种新工具和新玩法。

错误必须回答的三个问题

Agent 收到的每一个错误响应都应该让它无需猜测就能回答三个问题。

这是我的错还是你的错? 4xx 意味着请求有误,在不作修改的情况下重复发送仍会失败。5xx 意味着服务端出现了问题,相同的请求稍后可能会成功。无法区分这两者的 Agent,要么会在遇到校验错误时无限重试,要么会在遇到短暂波动时直接放弃。

我该重试吗?什么时候重试? 某些 4xx 错误是可以重试的,而有些则不行。429 在等待一段时间后可以重试。409 在重新读取状态后可能可以重试。如果不更改载荷,422 是无法重试的。请明确指出是哪一种情况。

我具体需要修改什么? 这是大多数 API 都会忽略的字段。“校验失败”毫无用处。而“当 countryUS 时,customer.postal_code 字段是必填的”则是 Agent 在下一次尝试时可以直接应用并修复的信息。

将这三点融入到每一个错误中,大多数 Agent 的重试风暴就会消失。

使用结构化的错误格式

不要凭空自创格式。RFC 9457(HTTP API 的问题详情) 已经定义了一种格式,并且得到了很好的支持:

{
  "type": "https://api.example.com/errors/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "The field 'customer.postal_code' is required when 'country' is 'US'.",
  "instance": "/v1/orders",
  "errors": [
    {
      "field": "customer.postal_code",
      "code": "required_conditional",
      "message": "Required when country is US. Provide a 5-digit or 9-digit US postal code.",
      "example": "94107"
    }
  ],
  "retryable": false,
  "next_action": "Add customer.postal_code to the request body and send again."
}

其中有四个部分对 Agent 来说最为关键。

detail 是一个完整的句子,用来命名具体的字段和实际的规则。它不是一个分类,而是指本次请求中具体失败的事项。

errors 数组是机器可读的,每个问题对应一个条目,并且包含一个字段路径,以便智能体(agent)可以将其映射回它所发送的 payload。一次性返回所有的失败信息。如果一次只返回一个,就会把原本一次就能解决的修复变成五次往返交互。

retryable 是一个布尔值,而不是让智能体从状态码中去推断。这是对智能体帮助最大的扩展字段,且只需占用一个字段的成本。

next_action 是纯文本指令。与根据错误码进行推理相比,模型能更可靠地遵循响应 body 中的明确指令,这里的一句话往往能将一个失败的任务转化为成功的任务。

谷歌的 API 错误设计指南 从另一个角度得出了类似的结论,特别是它指出错误详情应该放在结构化列表中,而不是非结构化的文本段落中。

告知何时重试

对于任何临时性的错误,要明确告知重试时间。一个知道需要等待 30 秒的智能体就会等待 30 秒;而不知道的智能体则会随意选择一个时间,而这个时间通常太短了。

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json

{
  "type": "https://api.example.com/errors/rate-limited",
  "title": "Rate limit exceeded",
  "status": 429,
  "detail": "You have used 1000 of 1000 requests in the current minute window.",
  "retryable": true,
  "retry_after_seconds": 30,
  "next_action": "Wait 30 seconds before sending this request again. Do not retry sooner."
}

Retry-After header 接收以秒为单位的延迟时间或 HTTP 日期;对于客户端来说,秒数更容易处理。可以将其作为 header 发送给标准的客户端,并在 body 中为模型再重复一遍。冗余的成本很低,但这样双方都能获取到最适合它们读取的数据。关于速率限制的细节,已在我们的速率限制超限指南以及如果你处于服务端该如何实现 API 速率限制中进行了介绍。

同样的模式也适用于维护期间的 503 错误以及资源被锁定时的 409 错误。任何将“等待”作为正确应对方式的错误,都应该附带一个具体的数值。

绝不泄露内部信息,绝不返回空内容

有两种处于极端对立面的失败模式,它们都会对智能体造成伤害。

第一种是堆栈跟踪(stack trace)。返回内部异常文本会暴露框架版本、文件路径,有时还会暴露查询片段。这在影响智能体之前首先是一个安全问题,我们在关于针对不受信任输入进行 API 测试的博文中所表达的担忧在此同样适用。此外,它还会让上下文窗口充斥着模型无法处理的文本。

第二种是空错误:没有 body 的 500 错误,或者类似 {"error": true} 的响应。智能体无法从中获取任何信息,它唯一能做的就是重试或退出。

折中的方案是提供一个带有关联 ID(correlation ID)的稳定公共错误信息:

{
  "type": "https://api.example.com/errors/internal",
  "title": "Internal error",
  "status": 500,
  "detail": "The order could not be created due to an internal error. No order was created.",
  "retryable": true,
  "retry_after_seconds": 5,
  "request_id": "req_01J8ZK3M2Q",
  "next_action": "Retry once after 5 seconds. If it fails again, stop and report request_id req_01J8ZK3M2Q."
}

“No order was created” 这句话是最有价值的部分。当面对不确定的写入操作时,Agent 必须决定重试是否会带来重复创建的风险,而大多数 Agent 在这方面的决策都很糟糕。直接告诉它们你当前处于什么状态。如果无法做出明确的保证,请使该操作具备幂等性并予以说明,这正是我们在关于 AI Agent 幂等密钥的文章中所介绍的模式。

当人工最终查看记录时,request_id 能够为你提供追溯到日志的线索。将其与我们 API 可观测性指南中的实践相结合,确保该 ID 确实能够关联到具体的内容。

错误应包含在接口定义/规范中

如果你的 OpenAPI 文档中没有包含错误的数据模型,那么对于生成的客户端、mock 和 Agent 工具来说,它就是不存在的。大多数接口定义/规范都详细描述了 200 响应,而对其他所有情况则一笔带过。

responses:
  '201':
    description: Order created
    content:
      application/json:
        schema: { $ref: '#/components/schemas/Order' }
  '422':
    description: >
      Validation failed. Not retryable without changing the request body.
      The errors array names each invalid field.
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/Problem' }
  '429':
    description: >
      Rate limited. Retryable. Wait for retry_after_seconds before sending again.
    content:
      application/problem+json:
        schema: { $ref: '#/components/schemas/Problem' }

这些描述绝非摆设。当你根据接口定义/规范生成 Agent 工具时(如我们的《如何将 OpenAPI 规范转换为 Agent 工具》指南中所述),这些文本就会成为模型理解失败情况的依据。相比于仅写着“Too Many Requests”的描述,写有“可重试,请先等待”的描述可以让 Agent 表现出更好的行为。

测试错误,而不仅仅是成功情况

错误路径往往是测试覆盖率骤降的地方,因为触发它们需要花费不少精力。而 mock 则可以轻松解决这个问题。

在你的 API 项目中定义每个错误响应,然后对它们进行 mock,从而使 Agent 能够根据需要应对各种情况。在 Apifox 中,你可以将失败响应添加到接口定义中,并在不同的 mock 之间进行切换,这为你提供了一种可重复的方法,让 Agent 在不破坏任何真实系统的情况下,针对 422429500 错误进行测试。我们关于在 mock 而不是生产环境中运行 Agent 的文章介绍了更广泛的实践习惯。

建议构建的五个用例:

  • 多字段校验失败。 断言所有问题均在一次响应中返回,且 Agent 的下一次尝试能够一次性修复所有问题,而不是逐个修复。
  • 触发限流并包含等待时间。 断言 Agent 在触发限流时至少等待 retry_after_seconds,而不是进行频繁的重试攻击。
  • 写入操作时的服务端错误。 断言 Agent 在重试时不会默默创建重复记录。
  • 鉴权失败。 断言 Agent 会停止重试,因为无论等待多久都无法修复无效的 Token。我们关于 Agent 最小权限 API key 的文章涵盖了凭据方面的内容。
  • 异常的错误 body。 返回非合法的 JSON 格式,确认 Agent 是否能优雅降级。上游代理最终都会遇到这种情况。

将这些测试集保存为测试场景,以便在 CI 中运行。错误处理逻辑通常会在某人重构序列化器时悄然退化,而 happy-path 测试套件往往无法察觉。

更好的错误信息有何价值

这种价值体现在三个方面,一旦你开始关注,就很容易衡量。

减少浪费的重试。 面对 {"error": "invalid input"} 的 Agent 通常会在退出前重试相同的数据载荷两到三次。每次尝试都会消耗一次模型调用,并占用完整的对话上下文。如果响应能明确指出缺失的字段,通常只需一次修正即可成功。这对于常规的校验失误来说,意味着调用次数从四次减少到了两次。

减少升级到人工处理的次数。 无法自行恢复的 Agent 会将任务交给人工。每一次本可避免的移交,都是 Agent 本应防止的高昂成本。能明确指出修复方案的错误信息,可以将运行过程保持在自动化流程之内。

缩短调试时间。 当确实需要人工介入时,request_id 加上精确的 detail 可以将日志检索过程简化为单次查询。这与我们的 API 可观测性指南中关于关联 ID 的观点一致,只是将其应用到了程序运行中断的时刻。

还有一个容易被忽略的第四点:同样的改进也能帮助人类开发者。从来没有人会抱怨错误信息对于指出哪个字段出错描述得太具体。

为移交做好设计

有些错误确实是 Agent 无法恢复的,例如缺失作用域、账号被封禁、或者需要人工决策的规则。对于这些情况,错误的作用就是进行清晰的移交:说明发生了什么,说明人工需要做什么,并携带使移交成本降低的关联 ID。

该回复必须发送到人类可以阅读的地方。如果 Agent 是一个执行分配任务的代码运行时(runtime),那么它通常会发送到所属的平台。HiFox 将 Agent 的结果和执行追踪记录保留在任务(Task)中,并将需要回复或审查的项目路由到收件箱(Inbox),从而使阻塞的任务能够被视为待办工作,而不是日志中一行冷冰冰的记录。你的错误文本正是使这种移交变得有意义的关键,因为显示为 “invalid input” 的消息对于审核人员来说,其作用并不比给 Agent 的多多少。

不要让 Agent 解析纯文本描述

最后一个反模式,在那些自然演进的 API 中很常见。状态码是正确的,但 body 却是一个句子,而且每种不同的失败情况,措辞都不一样:

 { "message": "Sorry, that didn't work. Please check your details and try again." }

Agent 只能通过猜测来对此做出响应。更糟糕的是,开发团队经常将其与 200 状态码搭配使用,导致客户端库甚至根本感知不到失败。

两条规则可以解决这个问题。第一,为每种不同的失败提供一个稳定的、机器可读的代码,以便 Agent 能够根据 insufficient_funds 进行条件分支处理,而不是去匹配 “not enough” 这种文本短语。第二,无论出于何种客户端便利性的考虑,绝不要在返回失败时使用成功的状态码。一个包裹了错误信息的 200 响应,对于你所拥有的任何重试策略、监控仪表盘和告警系统来说,都是完全隐形的。

Agent 可读错误的检查清单

  • 整个 API 中所有错误都使用统一且一致的结构化格式。
  • detail 用于说明具体的字段或条件,绝不能只给出一个宽泛的分类。
  • 校验错误应一次性返回所有问题,并附带字段路径。
  • 每个错误中都包含一个 retryable 布尔值。
  • 可重试的错误在 header 和 body 中都携带了以秒为单位的等待时间。
  • 写入失败时,应明确说明是否有任何内容被创建或修改。
  • 每个错误都携带一个可以在日志中解析关联的 correlation ID。
  • 不包含堆栈轨迹(stack traces)、框架字符串或 SQL 语句。
  • 错误响应在接口定义/规范中进行了文档化,并配有 Agent 可读的描述。
  • 为每个错误都配置了 mock,并在 CI 中运行已保存的测试来对其进行验证。

错误也是一种接口。请为你实际面对的调用方来设计它们——而如今,这个调用方正越来越多地变成一个模型,它会完全按照你的响应 body 所指示的内容去执行。下载 Apifox 来定义错误结构,并在 Agent 真正遇到这些错误之前进行 mock 调试。

常见问题解答

我应该使用 RFC 9457 还是自定义的错误格式? 除非你在生产环境中已经有了统一的格式,否则建议使用 RFC 9457。一致性胜过标准化:将一半的接口切换到新格式,比在所有地方保持同一种格式更糟糕。无论你使用哪种格式,都要添加 retryablenext_action 扩展字段。

在 API 响应中放入 next_action 文本安全吗? 安全,前提是你的服务端是基于一组固定的模板生成该文本的。绝不要在这个字段中直接回显用户提供的内容,因为 Agent 会将其视为指令,这会带来提示词注入(prompt-injection)风险。我们关于针对不可信输入测试 API 的文章详细介绍了这一风险。

校验错误应该返回 400 还是 422 当请求格式错误(例如 JSON 格式损坏)时,使用 400;当请求解析成功但未通过业务规则校验时,使用 422。Agent 能从这种区分中受益,因为它们的修复方式不同。如果你已经将其中一个代码同时用于这两种情况,请在文档中对其进行说明,而不是直接修改它。

细节到什么程度算过剩? 在调用方获得足够采取行动的信息时即可停止。字段名称、规则和示例值通常就足够了。内部标识符、查询文本和堆栈帧则超出了必要范围。

错误信息会占用上下文窗口吗? 会的,而且在多次重试中重复出现的冗长错误会迅速累积。请将它们保持在几百个 Token 以内。我们关于为 Agent 裁剪 API 响应的文章同样适用于失败和成功的情况。

如何阻止 Agent 重试不可重试的错误? 设置 retryable: false,在 next_action 中进行说明,并在工具包装器中强制执行,这样模型的判断就不是唯一的防线。在这里,双重保险(Belt and braces)才是正确的做法。

开发必备:API 全流程管理神器 Apifox

介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。

如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用

Apifox

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案

获取专属报价与部署方案

icon 详细的私有化部署系统架构与安全白皮书
icon 针对您公司规模的专属报价单
icon 免费的 1v1 专属产品演示 (Demo) 机会
获取部署方案
* 提交后,我们的客户经理将在 1 个工作日内与您联系
林俊锋 企业微信
@Apifox 专属顾问
扫码备注: 私有化 + 公司名