API 返回 400 或 upstream error,应该改参数还是重试?

API 400 和 upstream error 不一定是模型故障。先看原始错误正文,再核对 Responses、Chat Completions 与 Messages 的参数格式,判断何时重试,以及流式输出中断后如何避免重复执行。

API 返回 400,界面只留下一句 upstream error。再点一次,还是同一句。最让人烦躁的不是请求失败,而是根本不知道下一步该动哪里:密钥、模型名、参数,还是服务器?

先别急着换模型。400 通常指向请求本身,upstream error 则只说明错误来自调用链的上游,不能单凭这两个词判断服务宕机。对正在给应用接入大模型的开发者来说,真正有用的是找回被界面或 SDK(客户端开发库)省略的错误正文,再决定改请求还是等待。以下以 SandBase 公开的兼容接口文档为例,不把不同协议的错误格式混为一谈。

先说结论

  • API 400 通常先查请求体和模型能力,修改前反复发送同一请求没有帮助。
  • upstream error 不是具体病因,应同时保留 HTTP 状态码、错误正文和接口路径。
  • 429 与部分 5xx 可以考虑有限重试,但必须先确认重复执行不会产生重复操作。
  • 流式响应收到文字不代表完成;没有正常结束信号,就不能标记成功。

先看服务端到底回了什么

“调用失败,请稍后重试”是产品界面上的提示,不一定是服务端的完整回答。开发者应先看实际 HTTP 响应:如果应用后端发起请求,就查后端收到的响应;如果请求由浏览器直接发出,则查看开发者工具的 Network 面板。只盯着前端提示,很可能一直在排查被简化过的信息。

SandBase 的错误文档明确说明,不同接口没有统一的错误 JSON。OpenAI 兼容操作通常有 error 对象,Anthropic Messages 使用自己的兼容结构,部分平台接口则可能返回扁平的 error 字符串。不能写一个只认 error.param 的解析器,然后把解析失败也当作“上游错误”。

假如响应明确指出某个字段不支持,下一步就该核对该字段,而不是重置密钥。假如正文仍然很笼统,也不要自己补一个原因;保留状态码、时间和可用的请求标识,后续才能让支持人员查到同一次调用。

同一个模型,换个接口就不能照抄参数

接入时一个很容易忽略的差别是:模型一样,不代表请求格式一样。

SandBase 的 Responses API 使用 input 接收输入,生成项目放在 output。Chat Completions 则使用 messageschoices。如果只是把 URL 改成 /v1/responses,仍然发送原来的 Chat Completions 请求体,就应该先对照字段,而不是从网络稳定性查起。

Responses 文档列出 input 和 output,并说明它们与 messages 和 choices 的区别

改变接口时,要一起核对请求字段与读取结果的方式。

Anthropic Messages API 还有另一处区别:系统指令放在顶层 system,会话消息使用 userassistantmax_tokens 是必填的输出长度上限。图片、工具和扩展思考并不是任意模型都支持的通用开关;相关限制还要看具体模型。

因此,排查一个复杂请求时,可以先保留接口和模型,把内容缩到一句普通文本。能成功,再一次加回一类内容:先图片,再工具定义,最后是额外参数。这样至少能知道问题从哪一步开始。一次同时改模型、密钥和三个参数,即使成功了,也不知道究竟改对了什么。

这类最小复现仍会实际调用模型,可能产生费用。应控制调用次数,并保留原始失败的脱敏记录,不要让后来的成功请求覆盖掉最初的线索。

什么时候重试,什么时候先停下来

400、429 和 503 都可能让用户看到“失败”,但应用不该给它们安排同一个重试循环。官方错误处理建议给出了不同方向:

状态码更值得先检查的事情
400 / 422请求格式、参数或模型是否支持该能力
401密钥是否缺失、失效或被撤销
403密钥是否有使用该资源的权限
402余额或支出控制是否允许此次请求
429是否返回 Retry-After,并按要求等待
500 / 502 / 503 / 504重复操作是否安全,再考虑有限重试

错误文档按状态码区分修改参数、检查权限和退避重试

状态码相邻,处理方式却可能完全不同;并非每个接口都会返回表中所有状态。

具体到 Responses,文档把 502 解释为上游响应无法被安全解析和脱敏,503 则对应路由失败或没有可用候选服务。因此,不能见到 502 就判断“服务器忙”。如果相同请求持续报错,应保留诊断信息,而不是不断增加重试次数。

退避是逐渐延长两次尝试之间的等待,再加一点随机延迟,避免大量客户端同时重试。如果响应提供 Retry-After,应尊重它,而不是立刻再发。

还有一个容易藏起来的放大器:SDK 自己可能已经重试,外层业务代码又重试一遍。应该只让一层负责,或者把两层都算进总次数和超时预算。否则你以为应用“再试两次”,实际上可能产生更多请求。

已经输出半段话,为什么仍然算失败

HTTP 200 只能说明这次 HTTP 响应成功建立,不能单独证明模型完整生成了答案。流式输出中途还可能出现错误事件或连接中断。

Anthropic Messages 的事件说明包括 message_start、内容块事件和 message_stop。应用要按所用协议识别正常结束信号,并处理错误事件。只收到几段文字就写入“已完成”,会让半截摘要或缺了一半的代码混进正常结果。

Messages 文档要求处理流式错误事件并识别 message_stop

内容到达与消息完成是两件事,界面应保留这种区别。

更棘手的是工具已经执行的情况。假设 Agent 已创建订单,随后回答断开,重新生成文字和重新创建订单显然不是一回事。超时不证明服务端什么都没做;应先查任务或资源状态,再决定是否重发。只有接口明确提供幂等机制,也就是重复提交不会重复执行,才能按它的约定使用,不能自己加个请求头就假定生效。

排查记录里,留下什么就够了

值得保留的是接口路径、公开模型名、请求时间、状态码、可用的错误类型、尝试次数、耗时,以及失败前有没有收到内容。有请求标识也一起记录。多次工具调用怎样串起来,可以参考Agent 日志与调用追踪

密钥、授权头、客户原文和未经处理的上游响应不该出现在公开工单里。记录具体,不等于记录所有内容。先把敏感数据剔除,再提交能够复现问题的最小请求,通常比一整屏混杂的日志更容易排查。

常见问题

API 400 是余额不足吗?

不能这样判断。SandBase 文档把余额或支出控制对应到 402;400 通常先查请求。仍要结合实际接口的错误正文,不要只根据界面的一句话猜测。

upstream error 就是模型服务宕机吗?

不是。这个标签本身没有说明具体原因。结合状态码和可用错误正文,才能区分参数、限流与临时服务问题。

没有 param 或 request_id 怎么办?

这些并非所有接口保证返回的字段。解析器要接受缺失值,保留已有的状态和正文,不能为了读取可选字段再抛出一次异常。

流断了,能直接从断点继续吗?

不要默认可以。重发请求通常是一次新的生成。尤其有工具执行时,应先确认已经发生的操作,具体行为以对应流式接口文档为准。

真正有帮助的排查结果,不是把所有失败都变成自动重试,而是知道这一次究竟该改哪个地方。让应用保留完整而脱敏的错误,往往就是找到答案的第一步。