Qwen API 有两套常见调用方式:阿里云百炼的原生 DashScope 接口,以及 OpenAI 兼容接口。两者的错误结构不完全一样,但排查顺序一致:先看 HTTP 状态码,再读响应体里的 code 和 message,然后找 request_id,用最小请求复现,最后才改参数或换模型。
本文按 401、404、429、400、5xx 和超时拆开,给出错误码对照、日志字段、重试代码和联系支持前的清单。模型、配额与计费以官方文档和站内模型价格为准。
说明:Qwen、通义千问、阿里云、百炼、DashScope 是阿里巴巴集团及相关权利人的商标。本文仅作技术排查讨论,与上述公司无隶属、代理或合作关系。错误码、限流口径与接口行为可能随版本调整,最终以官方文档和实际响应为准。
一、先分清接口模式与错误结构
原生 DashScope 接口通常在响应顶层返回 code、message 和 request_id;OpenAI 兼容接口沿用 OpenAI 风格,错误体形如 {"error": {"message": "...", "type": "...", "code": "..."}}。兼容模式的 request_id 可能出现在响应头或响应体中,字段名以实际返回为准。
工程上不要只解析一种结构。先把完整响应体原样记进日志,再按 error.code、顶层 code 的顺序取值。这样切换接口模式时,错误处理代码不用重写。
二、401:鉴权失败,先查区域
401 表示身份没有通过验证,和模型能力、余额通常无关。百炼常见的三类错误是:
InvalidApiKey或invalid_api_key:Key 错误、复制不完整,或 Base URL 与 Key 所属区域不一致。invalid access token or token expired:Token 已过期,或用了套餐专属地址却配了普通 Key。NOT AUTHORIZED:WorkspaceId 无效,或账号不在该工作空间。
Qwen 的 API Key 与区域绑定,北京和新加坡的 Key 不能混用。这是最容易漏掉的一条:代码没改,只换了 Base URL,就会稳定复现 401。
排查按顺序做:
- 确认请求头是
Authorization: Bearer <key>,冒号后有一个空格。 - 打印环境变量前缀,确认不是空值、旧值或带换行。
- 核对 Base URL 与 Key 的所属区域是否一致。
- 确认 Key 类型与接口匹配:按量付费、Coding 套餐、Token 套餐的地址不同。
- 换一把新 Key 做最小请求,区分 Key 问题与代理、网关问题。
三、404 与 400:模型名、开通状态和上下文
404 多数不是服务故障,而是模型名或可用性有问题。ModelNotFound 或 model_not_found 要先查三件事:模型名是否拼错、模型是否已开通、该模型在当前区域是否可用。model_not_supported 则表示模型不支持 OpenAI 兼容模式,需要换接口或换模型。
400 的原因更杂。InvalidParameter 是参数越界,例如 temperature 或 top_p 超出范围;InvalidParameter.NotSupportEnableThinking 是模型不支持思考模式参数。上下文超限通常返回 Range of input length should be [1, xxx],底层码为 InternalError.Algo.InvalidParameter,也可能是总消息长度超过模型上限。处理方式是裁剪历史、分块摘要或改用长上下文型号,而不是反复重试。
四、429:先分清四种限流和两种额度
429 是最容易被误处理的错误。百炼的 429 至少包含以下几类:
| HTTP | 错误码 | 含义 | 是否适合重试 |
|---|---|---|---|
| 429 | Throttling.RateQuota、LimitRequests |
每分钟请求数(RPM)超限 | 是,退避后重试 |
| 429 | Throttling.AllocationQuota、insufficient_quota |
每分钟 Token 数(TPM)超限 | 是,退避并降低输入 |
| 429 | Throttling.BurstRate |
短时流量突增触发保护 | 是,平滑发送节奏 |
| 429 | Throttling.Concurrency |
并发请求过多 | 是,降低并发 |
| 429 | AllocationQuota.FreeTierOnly |
免费额度用尽且开启了仅用免费额度 | 否,充值或关开关 |
| 429 | BudgetLimitExceeded |
账户预算已用完 | 否,调整预算 |
| 400 | Arrearage |
账户欠费或状态异常 | 否,先恢复账户 |
| 403 | AccessDenied.Unpurchased |
服务未开通 | 否,先开通 |
限流按阿里云主账号统计,RAM 子账号、工作空间和多个 Key 的调用会合并计算。所以“每个服务一把 Key”并不能绕过总额度,只能避免互相挤占。限流通常在分钟级恢复,等待期间不要高频重试。
五、超时与 5xx:分三段定位
超时要分三段看:连接超时通常是 DNS、出口或代理不可达;首 Token 慢多半是上游排队或输入过长;读取超时则可能是输出太长或推理时间久。三者的处理方式不同,不能只把超时时间调大。
百炼的 RequestTimeOut 表示请求超时,官方说明的请求时限为 300 秒;GatewayTimeout.InputDownload 与输入文件下载有关。OpenAI 兼容接口还可能返回 503,表示引擎过载。流式请求中断时,可能没有错误码,先记录已接收内容、最后一段数据和结束原因,再判断是网络抖动、代理断流还是上游超时。
六、request_id 与日志:排障的凭证
官方说明 request_id 会随响应返回,可能在响应头或响应体中。工程上建议双轨记录:客户端生成 trace_id 贯穿所有重试,同时原样保存响应头里的请求标识和响应体里的 request_id。若没有记录,可在控制台的模型监控日志中查找,前提是已开启推理日志。
日志至少包含:时间、trace_id、Base URL、区域、模型名、HTTP 状态码、错误码、错误信息、request_id、耗时、重试次数、是否流式。Key 只记录前几位或哈希,用户原文和完整响应不要整段落盘。联系支持时,一条带这些字段的记录比“偶尔报错”有用得多。
七、重试与退避:示例代码
下面这段代码关闭 SDK 自动重试,只对可恢复的 429 和 5xx 重试,并把状态码、错误码和 request_id 一起打印。欠费、免费额度用尽和预算超限不在重试范围内。
import os
import random
import time
import uuid
import httpx
from openai import OpenAI, APIConnectionError, APIStatusError, APITimeoutError
client = OpenAI(
api_key=os.environ["DASHSCOPE_API_KEY"],
base_url=os.environ.get(
"DASHSCOPE_BASE_URL",
"https://dashscope.aliyuncs.com/compatible-mode/v1",
),
timeout=httpx.Timeout(connect=5.0, read=180.0, write=30.0, pool=10.0),
max_retries=0, # 关闭 SDK 自动重试,统一由下面的逻辑控制
)
RETRYABLE_429 = {
"Throttling",
"Throttling.RateQuota",
"Throttling.AllocationQuota",
"Throttling.BurstRate",
"Throttling.Concurrency",
"LimitRequests",
"limit_requests",
"ResourceExhausted",
}
def unpack_error(exc: APIStatusError):
body = {}
try:
body = exc.response.json()
except Exception:
pass
err = body.get("error", body) if isinstance(body, dict) else {}
code = err.get("code") if isinstance(err, dict) else None
request_id = None
if isinstance(body, dict):
request_id = body.get("request_id")
request_id = request_id or exc.response.headers.get("x-request-id")
return code, request_id, exc.response.text[:500]
def ask(prompt: str, model: str, max_attempts: int = 4) -> str:
trace_id = uuid.uuid4().hex[:12]
for attempt in range(1, max_attempts + 1):
try:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
except APIStatusError as exc:
code, request_id, body = unpack_error(exc)
print(
f"trace={trace_id} attempt={attempt} "
f"http={exc.status_code} code={code} "
f"request_id={request_id} body={body}"
)
retryable = exc.status_code >= 500 or (
exc.status_code == 429 and code in RETRYABLE_429
)
if not retryable or attempt == max_attempts:
raise
except (APITimeoutError, APIConnectionError) as exc:
print(f"trace={trace_id} attempt={attempt} err={type(exc).__name__}")
if attempt == max_attempts:
raise
wait = min(2 ** (attempt - 1), 30) + random.uniform(0, 1)
time.sleep(wait)
raise RuntimeError("重试次数已用完")
补充三条规则:服务端返回 Retry-After 时优先遵守;重试预算要全局控制,避免多个服务同时放大流量;有副作用的请求先确认是否已执行,再决定是否重发。
八、并发治理:比调大重试更有效
限流问题通常不是靠重试解决的,而是靠入口治理:
- 按业务拆分 Key,并分别设置并发上限,避免一个批量任务拖垮在线服务。
- 批量任务分片、错峰,避免整点流量脉冲。
- 用队列削峰,失败任务进入延迟队列,而不是原地打转。
- 监控 429 占比和 p95 延迟,突增时先降并发。
- 离线任务优先用 Batch 接口,避开实时限流。
- 准备备用模型或备用入口,切换前确认参数兼容。
限流阈值按模型和账号配置,充值不会自动提高 RPM 或 TPM。需要更高额度时,走商务申请,不要照搬别人的数字。
九、联系支持前的清单
- 发生时间、时区,以及故障是否集中爆发。
- Base URL、区域、接口路径、模型名和请求方式。
- HTTP 状态码、错误码和完整错误信息。
request_id;没有就提供自己生成的trace_id。- 一条可复现的最小 curl 请求,Key 做脱敏。
- 是否流式、是否长上下文、是否刚改过配置。
- 影响范围:单 Key、单工作空间还是全部服务。
十、常见问题(FAQ)
1. 429 一定是限流吗?
不一定。429 还可能是免费额度用尽、预算超限;欠费可能返回 400 的 Arrearage,服务未开通可能返回 403。先读错误码,再决定是退避、充值还是改配置。
2. 401 换了新 Key 还是失败怎么办?
重点查区域和 Key 类型。Qwen 的 Key 与区域绑定,北京 Key 配新加坡地址会返回 401;套餐专属地址配普通 Key 也会失败。再检查请求头、环境变量和反向代理是否丢弃认证信息。
3. 上下文超限怎么处理?
先裁剪历史消息,去掉无关附件和重复段落;再把长文档分块摘要或做检索;仍不够时改用长上下文型号。不要靠提高 max_tokens 解决输入超限。
4. 超时应该重试吗?
只对连接失败和读取超时做有限次重试,并设置指数退避和上限。有副作用的业务先确认是否执行过,避免重复下单或重复写入。
5. request_id 在哪里找?
先看响应头和响应体,字段名以实际接口为准;如果都没有记录,再到控制台的模型监控日志中查找,前提是已开启推理日志。联系支持时同时提供 trace_id 和最小复现请求。
下一步
先在中商起航 AI 网关创建测试 Key,把 401、404、429、上下文超限和超时各演练一次,再接入生产监控。可用模型与计费方式见模型价格。
