错误处理
失败的请求会返回 HTTP 状态码,以及带有错误类型和消息的 JSON 响应体。本页列出每个状态码,展示各 API 格式的错误响应体,并说明哪些错误值得重发。
SDK 会抛出一个错误对象,其中包含状态码和响应体的字段。此请求指定了一个不存在的模型:
import openai
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1",
)
try:
client.chat.completions.create(
model="no-such-model",
messages=[{"role": "user", "content": "Hello"}],
)
except openai.APIStatusError as error:
print(error.status_code) # 400
print(error.type) # invalid_request_error
print(error.body["message"]) # unknown model: no-such-model
print(error.response.headers.get("x-request-id")) # the id of this request import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com/v1",
});
try {
await client.chat.completions.create({
model: "no-such-model",
messages: [{ role: "user", content: "Hello" }],
});
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.log(error.status); // 400
console.log(error.type); // invalid_request_error
console.log(error.error.message); // unknown model: no-such-model
} curl -i https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "no-such-model",
"messages": [{"role": "user", "content": "Hello"}]
}' 它返回状态码 400 和如下响应体:
{
"error": {
"type": "invalid_request_error",
"message": "unknown model: no-such-model"
}
} 错误响应体
有两种响应体。/v1/messages 以 Anthropic 格式作答;其他所有端点以 OpenAI 格式作答。两者的详细信息都在 error.type 和 error.message 中,因此一个解析器即可适用于所有端点。
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "invalid_api_key",
"param": null
}
} {
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
} | 字段 | 类型 | 说明 |
|---|---|---|
error.type | string | 错误的种类。见下文的错误类型。 |
error.message | string | 给人看的一句话。下表中的文本除其可变部分(显示为 <id>、<n> 或 …)外是稳定的。 |
error.code | string | null | 出现在部分 OpenAI 格式的错误中:401 对应 invalid_api_key,413 对应 request_too_large,对话过长时为 context_length_exceeded,其他情况为 null 或不存在。请先根据状态码和 error.type 判断,把 code 视为补充信息。 |
error.param | null | 与 code 出现在相同的错误中。始终为 null。 |
type | string | 仅限 Anthropic 格式。始终为 error。 |
错误类型
| 类型 | 状态码 | 说明 |
|---|---|---|
invalid_request_error | 400, 413, 415, 422 | 请求本身有误:某个字段、请求体、模型 id,或模型不接受的输入。请修改请求后再重新发送。 |
authentication_error | 401 | API 密钥缺失、未知或已被撤销。 |
not_found_error | 404 | 路径不存在。 |
rate_limit_error | 429 | 余额不足以支付该请求、被限流保护拦下,或模型繁忙。消息会说明是哪一种。 |
api_error | 500, 502, 503, 504 | 请求没有问题,故障出在我们这一侧。请重新发送。 |
server_error | 500 | 请求没有问题,故障出在我们这一侧。请重新发送。 |
状态码
| 状态码 | 类型 | 消息 | 何时 |
|---|---|---|---|
400 | invalid_request_error | Failed to parse the request body as JSON: … | 请求体不是有效的 JSON。 |
400 | invalid_request_error | unknown model: <id> | model 不是已发布的 id。在 /v1/responses 上,缺少 model 时也会如此。 |
400 | invalid_request_error | No user message provided | Shannon 档位:请求不包含用户文本。 |
400 | invalid_request_error | <id> does not accept image input | 向不支持图片输入的托管开源权重模型发送了图片。 |
400 | invalid_request_error | audio and video input are not supported by any hosted model | 向托管开源权重模型发送了音频或视频部分。 |
400 | invalid_request_error | <id> does not accept response_format | 向不支持结构化输出的托管开源权重模型请求了结构化输出。 |
400 | invalid_request_error | <id> does not accept response_format json_schema; use json_object | 向只接受 json_object 的托管开源权重模型发送了 JSON schema。 |
400 | invalid_request_error | unknown reasoning effort '<value>'; expected off, low, medium or high | 推理强度不是所列的值之一。适用于托管开源权重模型。 |
400 | invalid_request_error | count_tokens is available for the hosted open models; unknown model: <id> | 对托管开源权重模型之外的模型请求了 tokens 计数。 |
401 | authentication_error | Missing authentication | 未发送 API 密钥。 |
401 | authentication_error | Invalid API key | 密钥未知、已被更新的密钥替换,或已被撤销。 |
404 | not_found_error | Not Found | 路径不存在,例如 /v1/embeddings。 |
405 | api_error | Method Not Allowed | 路径存在,但不支持此 HTTP 方法。 |
413 | invalid_request_error | Failed to buffer the request body: … | 请求体大于 32 MiB。 |
415 | invalid_request_error | Expected request with `Content-Type: application/json` | Content-Type 请求头缺失,或不是 application/json。 |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type: … | 缺少必需字段(如 messages),或某个字段的 JSON 类型有误。 |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | 请求的输出上限大于你余额的剩余量。 |
429 | rate_limit_error | Too many requests. Retry in <n>s. | 限流保护:你的账户在一分钟内发送了超过 120 个请求。<n> 是距这一分钟结束的秒数。 |
429 | rate_limit_error | Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan | /v1/chat/completions 和 /v1/messages 上的 shannon-coder-1:你的计划在当前 4 小时窗口内的 Shannon Coder 调用次数已用完。 |
429 | rate_limit_error | Shannon routes are temporarily busy. Please retry. | 模型此刻繁忙。 |
500 | server_error | The model backend failed to answer. Please retry. | 模型未生成回复。 |
500 | server_error | An internal error occurred. Please retry. | 准备请求时发生内部故障。 |
502 | api_error | The model backend failed to answer. Please retry. | 模型未生成回复。适用于 Shannon 3 系列和托管开源权重模型。 |
503 | api_error | Could not verify your quota right now. Please retry. | 此刻无法读取你的余额。 |
503 | api_error | token counting is temporarily unavailable for this model | 此模型此刻无法进行 tokens 计数。 |
504 | api_error | upstream timed out | 托管开源权重模型耗时超过了非流式请求的时间限制。 |
检查按以下顺序进行:API 密钥、请求体、模型 id、限流保护、余额。因此,密钥错误且请求体也错误的请求会返回 401。以 4xx 状态被拒绝的请求不收费。
余额与限流保护
每个请求一视同仁:没有速率档位,也没有单独的 API 配额。仍有两种情况会返回 429,类型均为 rate_limit_error。可通过消息区分。
余额
你的余额是当日计划额度(在 00:00 UTC 重置),以及之后的已购买信用额度。聊天和 API 使用同一份余额。
每个请求会先预留它的输出上限:max_tokens,在 /v1/responses 上是 max_output_tokens,两者都未发送时为 4,096。回复完成后,预留的数量会被请求实际用掉的数量取代。如果该数量超过剩余余额,请求会在模型启动之前返回 Quota exceeded。
同时运行的请求各自预留自己的数量。因此,许多 max_tokens 很高的并行请求,即使每个回复都很短,也可能在一瞬间占满余额。较低的 max_tokens 会少预留一些。
限流保护
限流保护允许每个账户每分钟 120 个请求。它按账户而非密钥计数,/v1/chat/completions、/v1/responses 和 /v1/messages 合并计算。这一分钟从你的第一个请求开始。
该分钟内的第 121 个请求会返回 Too many requests. Retry in <n>s.,且不收费。被拒绝的请求同样计数,因此不等待就重发的客户端会一直被拒绝,直到这一分钟结束。请按消息中的 <n> 秒等待。
并行发送的请求不会因为并行而被拒绝。它们会排队,依次得到答复。
GET /v1/models 和 tokens 计数端点不计入。
关于限制和余额的更多内容,以及在哪里查看你自己的余额和用量: 限制与余额 密钥与用量
哪些应该重试
只有当同一个请求可能成功时才重发。OpenAI 和 Anthropic SDK 本身已会对 429 和 5xx 回复自动重试两次;设置 max_retries(JavaScript 中为 maxRetries)可更改此行为。
| 错误 | 是否重发? | 做法 |
|---|---|---|
400, 404, 405, 413, 415, 422 | 否。请先更正请求。 | 消息会指出有误的字段、模型或输入。 |
401 | 否。请使用有效的密钥。 | 在密钥与用量页面创建或替换密钥。 |
429 Too many requests | 是,等待之后。 | 按消息中的秒数等待,然后发送相同的请求。 |
429 Quota exceeded | 不能原样重发。 | 降低 max_tokens,等待 00:00 UTC 的每日重置,或充值。 |
429 Shannon Coder call quota reached | 是,在下一个窗口。 | 窗口每 4 小时从 00:00 UTC 起算。换用其他模型可立即作答。 |
429 … temporarily busy | 是。 | 等待几秒,之后每次再重试时等待更久。 |
500, 502, 503 | 是。 | 等待 1 秒,然后 2、4、8 秒。尝试几次后停止,并报告请求 id。 |
504 | 是。 | 将 stream 设为 true 后发送:时间限制只适用于非流式回复。 |
遵循此表的重试循环:
import re
import time
import openai
from openai import OpenAI
# max_retries=0: this loop does the retrying, the SDK does not add its own.
client = OpenAI(api_key="YOUR_API_KEY", base_url="https://api.shannon-ai.com/v1", max_retries=0)
def create_with_retry(attempts=5, **request):
for attempt in range(attempts):
try:
return client.chat.completions.create(**request)
except openai.RateLimitError as error:
text = str(error)
flood = re.search(r"Retry in (\d+)s", text)
if flood:
time.sleep(int(flood.group(1))) # flood protection: wait out the minute
elif "temporarily busy" in text:
time.sleep(2 ** attempt)
else:
raise # balance: a wait of seconds does not help
except (openai.InternalServerError, openai.APIConnectionError):
time.sleep(2 ** attempt) # 1, 2, 4, 8 seconds
raise RuntimeError("the request failed after all attempts")
response = create_with_retry(
model="shannon-3",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content) import OpenAI from "openai";
// maxRetries: 0 — this loop does the retrying, the SDK does not add its own.
const client = new OpenAI({ apiKey: "YOUR_API_KEY", baseURL: "https://api.shannon-ai.com/v1", maxRetries: 0 });
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
async function createWithRetry(request, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.chat.completions.create(request);
} catch (error) {
const text = String(error.message);
const flood = text.match(/Retry in (\d+)s/);
if (error.status === 429 && flood) {
await sleep(Number(flood[1])); // flood protection: wait out the minute
} else if (error.status === 429 && text.includes("temporarily busy")) {
await sleep(2 ** attempt);
} else if (error.status >= 500 || error instanceof OpenAI.APIConnectionError) {
await sleep(2 ** attempt); // 1, 2, 4, 8 seconds
} else {
throw error; // wrong request, wrong key or balance
}
}
}
throw new Error("the request failed after all attempts");
}
const response = await createWithRetry({
model: "shannon-3",
messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content); 流内的错误
流从第一个字节起状态码就是 200。之后的失败会以流内事件的形式到达:/v1/chat/completions 上是带 error 成员的数据块,/v1/messages 上是 error 事件,/v1/responses 上是 response.failed 或 error 事件。流的结束事件仍会随后到来。请将这样的回复视为失败,并像对待 5xx 状态一样重新发送请求。
比模型上下文窗口更长的对话会以这种方式报告,类型为 invalid_request_error,代码为 context_length_exceeded。重新发送之前,请缩短对话。
具体的帧在流式传输部分说明。 流式
请求 id
每个回复(包括错误)都有 x-request-id 请求头。它是你在自己的 x-request-id 请求头中发送的值;若未发送,则是 12 位十六进制字符。请记录它,并在就某个请求寻求帮助时一并提供。