你的 API 返回了 400 Bad Request,body 内容为 {"error": "invalid input"}。人类开发者会打开接口文档,检查 Payload,找出缺失的字段,并在几分钟内将其修复。而 Agent 读到相同的这两个单词时,却没有任何可以作为操作依据的信息,只能做出它唯一能做的事:重复发送完全相同的请求。一次又一次。最终它选择放弃,并告诉用户该 API 坏掉了。
错误响应是 Agent 最为依赖、却往往被开发团队最后才去设计的 API 环节。一个良好的错误提示会告知调用方出了什么问题、重试是否有用以及需要修改什么。Agent 可以针对这三点做出相应的调整。而含糊不清的错误提示则会将一个本可恢复的问题变成一个失败的任务。
本指南专为 API 服务端设计。我们关于 Agent 错误恢复的文章介绍了客户端在重试、退避(backoff)和熔断(circuit breakers)方面应该做些什么。而本文则探讨了为了让客户端逻辑能够正常生效,你的 API 服务端究竟需要返回哪些内容。
Apifox 在这里至关重要,因为错误响应是大多数 API 中最少被测试的部分。你可以在定义接口规范的同时定义它们,并在测试正常流程的同一位置对它们进行 mock 和断言。
AI Coding 交流群
如果你也在用 AI 写代码,或者正在研究 Cursor、Claude Code 这些工具,欢迎加入以下交流群。群里平时会聊一些 AI 编程的实际用法、开发工作流,还有各种新工具和新玩法。
错误响应必须回答的三个问题
Agent 接收到的每一个错误响应,都应该能让它无需猜测就能回答以下三个问题:
这是我的问题还是你的问题? 4xx 状态码意味着请求本身有误,如果不做修改直接重复请求必然会再次失败;5xx 状态码则意味着服务端出现了问题,完全相同的请求稍后可能会成功。无法区分这两者的 Agent,要么会在校验错误上陷入无限重试,要么会在瞬时的服务端偶发故障中直接放弃。
我应该重试吗?何时重试? 某些 4xx 错误是可以重试的,而另一些则不行。429 在等待一段时间后可以重试;409 在重新读取状态后可能可以重试;422 在不修改 Payload 的情况下是不可重试的。请明确告知 Agent 属于哪种情况。
我究竟需要修改什么? 这是大多数 API 都会忽略的字段。“Validation failed”(校验失败)毫无用处。而“当 country 为 US 时,customer.postal_code 字段为必填项”则是一个 Agent 可以在下一次尝试中直接应用的修复方案。
只要在每个错误响应中提供这三点信息,大多数 Agent 重试风暴就会销声匿迹。
使用结构化的错误格式
不要随心所欲地自创数据结构。RFC 9457 (Problem Details for HTTP APIs) 已经对此做出了定义,并且得到了广泛的支持:
{
"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 是一个完整的句子,明确指出具体的字段和规则,而不是一个分类概念。它描述的是本次请求(request)中具体失败的原因。
errors 数组是机器可读的,每个问题对应一个条目,包含 Agent 可以映射回其发送的 payload 的字段路径。应一次性返回所有失败项。如果一次只返回一个错误,会导致原本只需修复一次的问题演变成五次往返交互。
retryable 是一个布尔值,而不是需要通过状态码去推断的属性。这是对 Agent 帮助最大的扩展字段,且只需占用一个字段的空间。
next_action 是纯文本指令。大模型在响应(response)body 中遵循明确的指令,远比根据错误码去推理更为可靠;这里的一句提示往往就能将一个失败的任务转变为已完成的任务。
Google 的 API 错误设计指南 从不同角度得出了类似的结论,特别是强调错误细节应当包含在结构化列表中,而不是散文中。
明确告知何时重试
对于任何暂时的错误,明确告知需要等待多长时间。知道要等待 30 秒的 Agent 就会等待 30 秒;而不知道的 Agent 会随机选择一个等待时间,通常这个时间都太短了。
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 中重复包含该字段给模型。冗余的开销极小,却能让两类消费端都以最适合的方式获取数据。速率限制的详细信息可以在我们的速率限制超限指南以及面向服务端(server 端)的如何实现 API 速率限制教程中找到。
同样的模式也适用于维护期间的 503 错误以及资源被锁定时的 409 错误。任何将“等待”作为正确处理方式的错误都应当附带一个具体的数值(秒数)。
绝不泄露内部细节,绝不返回空响应
有两种处于极端的失败模式,它们都会对 Agent 造成伤害。
第一种是堆栈追踪信息(stack trace)。返回内部异常文本会暴露框架版本、文件路径,甚至查询片段。这首先是一个安全问题,其次才是 Agent 的问题,我们在针对不可信输入测试 API 的文章中提及的隐患在这里同样适用。此外,它还会让模型的上下文窗口充斥大量无法被处理的文本。
第二种是空错误:没有任何 body 的 500 响应,或者仅仅返回 {"error": true}。Agent 无法从中获得任何有效信息,唯一的选择只能是重试或放弃。
折中的解决方案是返回包含关联 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 幂等键(idempotency key)的文章中所阐述的模式。
当人工最终核对对话记录时,request_id 能帮你溯源到日志。结合我们 API 可观测性指南中的实践做法,确保该 ID 能真正解析并关联到具体日志。
错误定义应纳入接口规范
如果错误格式没有在你的 OpenAPI 文档中定义,那么对于生成的客户端、mock 和 Agent 工具来说,它就相当于不存在。大多数规范(spec)都详细描述了 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”,写明“retryable, wait first”(可重试,请先等待)的描述能让 Agent 做出更好的行为反应。
测试错误路径,而不只是成功路径
错误路径往往是测试覆盖率大幅下降的地方,因为触发这些错误需要付出不少努力。而 Mock 则能消除这种负担。

在 API 项目中定义每个错误响应,然后对它们进行 mock,以便 Agent 可以根据需要触发各种情况。在 Apifox 中,你可以将失败响应添加到接口定义中,并在不同的 mock 之间切换,这样你就可以在一个可重复的环境中针对 422、429 和 500 运行 Agent,而不会破坏任何真实数据。我们在关于针对 mock 而非生产环境运行 Agent 的文章中更全面地介绍了这种习惯。
需要构建的 5 个用例:
- 包含多个错误字段的校验失败。 断言所有问题都会在单个响应中返回,并且 Agent 的下一次尝试会修复所有这些问题,而不是只修复其中一个。
- 带等待时间的速率限制。 断言 Agent 至少等待
retry_after_seconds,而不是频繁发起重试。 - 写入操作上的服务端错误。 断言 Agent 在重试时不会静默地创建重复数据。
- 鉴权失败。 断言 Agent 会停止运行而不是不断重试,因为再怎么等待也无法解决无效 Token 的问题。我们在关于 Agent 最小权限 API 密钥的文章中涵盖了凭据方面的内容。
- 格式错误的错误 body。 返回无效 JSON 内容,并确认 Agent 能够优雅降级。上游代理最终总会遇到这种情况。
将这些测试用例保存为测试场景,以便它们能在 CI 中运行。错误处理逻辑往往会静默发生退化(通常是在有人重构序列化器时),而正常流程测试套件是无法察觉这一点的。
更完善的错误提示价值何在
其价值体现在三个方面,而且一旦你开始关注,就很容易衡量:
减少无谓的重试。 面对 {"error": "invalid input"} 的响应,Agent 通常会在放弃前用完全相同的 Payload 重试两到三次。每次尝试都会消耗一次模型对话轮次,并将完整的上下文传入。如果响应中明确指出了缺失的字段,通常只需一次修正后的尝试即可解决。在日常的校验疏忽中,这就是四次调用与两次调用之间的差别。
减少问题升级(人工接管)。 无法自动恢复的 Agent 会将任务移交给人工处理。每一次本可避免的人工接管,都是 Agent 原本应该阻止的昂贵后果。指明了修复方案的错误提示能够让运行过程保持在自动化流程之内。
缩短调试时间。 当确实需要人工干预时,request_id 加上精准的 detail 就能将原本漫长的日志排查简化为一次精确定位。这与我们在 API 可观测性指南中关于关联分析(correlation)的观点一致,同样适用于运行中断的时刻。
还有第四个容易被忽视的好处:这些改进对人类开发者同样大有裨益。从来没有人会抱怨错误信息过于具体地指出了哪个字段出错。
也要为人工接管进行设计
有些错误确实是 Agent 无法自动恢复的。例如缺少作用域(scope)、账户已被关闭、或者需要人工决策的规则。对于这些错误,错误提示的作用就是干净利落地进行人工接管:说明发生了什么,说明人员需要做什么,并附带能降低接管成本的关联 ID。
该回复必须呈现在人类能够阅读的地方。如果 Agent 是一个处理指定任务的编码运行时,宿主平台通常就是它最终展示的地方。HiFox 将 Agent 的结果和执行轨迹保留在任务(Task)上,并将需要回复或审核的项目路由到收件箱(Inbox)中,这样被阻塞的运行就会以具体工作的形式呈现,而不是仅仅作为日志中的一行记录。你的错误文本才是让这次接管发挥价值的关键,因为一条显示“invalid input”的消息给审核人员提供的信息,并不比给 Agent 的更多。

不要让 Agent 解析自然语言文本
最后一个反模式(anti-pattern)在自然演进的 API 中很常见:状态码虽然正确,但 body 却是一句话,而且每种不同的失败情况都有不同的文本描述:
{ "message": "Sorry, that didn't work. Please check your details and try again." }这些描述并不是摆设。当你从规范生成 Agent 工具时(如我们在将 OpenAPI 规范转换为 Agent 工具的指南中所述),这些文本就会成为大模型阅读和理解失败情况的依据。相比一句简单的“Too Many Requests”,写明“retryable, wait first”(可重试,请先等待)的描述能让 Agent 做出更好的行为反应。
两条规则可以解决这个问题:首先,为每一种不同的失败提供一个稳定的、机器可读的错误码(code),以便 Agent 可以根据 insufficient_funds 进行逻辑分支判断,而不是去匹配“余额不足”这样的短语。其次,无论出于什么客户端使用便利性的考虑,都绝不要在返回失败时使用表示成功的状态码。一个包含错误内容的 200 响应,对你配置的所有重试策略、监控大盘和告警系统来说都是完全不可见的。
Agent 可读错误设计的清单
- 在整个 API 中,所有错误都采用统一且结构化的格式。
detail指明具体字段或条件,绝不使用宽泛的分类名称。- 校验错误会一次性返回所有问题,并附带字段路径。
- 每个错误中都包含一个
retryable布尔值字段。 - 可重试的错误会在 header 和 body 中都带上以秒为单位的等待时间。
- 写入操作失败时,要明确说明是否创建或修改了任何内容。
- 每个错误都携带一个可以在日志中追溯的 correlation ID。
- 不包含堆栈轨迹(stack traces)、框架字符串或 SQL 语句。
- 错误响应需在接口定义/规范(spec)中记录,并附带 Agent 可读的描述。
- 为每个错误准备 mock,并在 CI 中运行已保存的测试。
错误也是一种接口。请为你真实的调用者设计它们——如今这个调用者越来越倾向于是一个会严格按照你的响应 body 指示去行动的模型。下载 Apifox 来定义错误结构并在 Agent 真实对接前对其进行 mock 吧。
常见问题
我应该使用 RFC 9457 还是自定义的错误格式? 除非你在生产环境中已经有了统一的格式,否则建议使用 RFC 9457。一致性胜过标准化:把一半的接口迁移到新结构,远比在所有地方保持同一种结构更糟糕。无论你使用哪种格式,都可以加上 retryable 和 next_action 扩展字段。
在 API 响应中放入 next_action 文本安全吗? 安全,前提是你的服务根据一组固定的模板来生成它。切勿在该字段中直接回显用户提供的内容,因为 Agent 会将其视为指令读取,这会构成提示词注入(prompt-injection)攻击路径。我们关于针对不可信输入测试 API 的文章深入介绍了这一风险。
校验错误应该使用 400 还是 422? 当请求本身格式错误(如 JSON 损坏)时使用 400;当请求解析成功但未能通过业务规则校验时使用 422。Agent 能从这种区分中获益,因为两者的修复方式不同。如果你已经对这两种情况统一使用了其中一个状态码,请记录在文档中,而不是直接去修改它。
多详细才算过头? 止于调用方拥有足够信息来采取行动的程度即可。字段名称、规则以及示例值通常就足够了。而内部标识符、查询文本和堆栈帧则超出了合理的界限。
错误信息会占用上下文窗口吗? 会的,而且在多次重试中重复出现的冗长错误信息会迅速累积占用。请将它们控制在几百个 Token 以内。我们关于为 Agent 裁剪接口响应的文章,既适用于成功场景,也同样适用于失败场景。
如何阻止 Agent 重试不可重试的错误? 设置 retryable: false,在 next_action 中予以说明,并在工具封装中强制执行,确保模型的判断不是唯一的防线。在这种情况下,采取双重保险(Belt and braces)是正确的做法。
开发必备:API 全流程管理神器 Apifox
介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、调试、设计、测试、Mock、自动化测试于一体的工具,Apifox 是目前提升研发效率的首选。
如果你正在开发项目,不妨试试其极其友好的界面设计,它完全兼容 Postman 和 Swagger 数据格式,导入数据非常方便,,即使是新手也能很快上手,点击这里即可注册使用。

值得一提的是,除了个人和常规团队使用,针对有高安全合规要求、或需要在内网环境协作的企业,Apifox 还提供了深度定制的私有化部署方案。
获取专属报价与部署方案
详细的私有化部署系统架构与安全白皮书
针对您公司规模的专属报价单
免费的 1v1 专属产品演示 (Demo) 机会