详解 OpenRouter 三层限速机制(服务级/提供商级/账户余额)及各层应对策略:退避、减并发、切模型、调额度。
当通过 OpenRouter 网关向 AI 模型发送大量请求时,客户端应用经常会遇到 HTTP 429 Too Many Requests 状态码。由于 OpenRouter 聚合了数十个独立的推理提供商,此类错误可能完全发生在基础设施的不同层级。简单地立即重试往往导致客户端被限流或浪费重试次数。要恢复稳定的请求流,需要识别出故障的确切层级,并选择对应的修复方式:退避、降低并发、切换模型提供商,或调整消费上限。
OpenRouter 官方限流文档区分了服务级限流、独立提供商吞吐量以及账户余额强制执行。
在 OpenRouter 定价页面上,基础免费层级对公开可用的免费模型(带有 :free 后缀的模型)强制执行每日 50 次请求的上限。由于准确的每分钟请求数(RPM)限制和层级阈值可能随时间变化,实时数值应始终通过实时限制表进行验证。
在诊断错误时,区分返回 HTTP 429 及相关 HTTP 402 状态的层级至关重要:
OpenRouter 平台级限流。当路由器本身接收请求过快时会发生。此时每日免费池配额属于平台级限制类别(而非独立的第三方限制):当超过免费模型每日 50 次请求限制时,平台会拒绝传入请求,直至每日计数器重置。触发平台级限流时,服务器返回 X-RateLimit-Limit、X-RateLimit-Remaining 和 X-RateLimit-Reset HTTP 头。成功响应(HTTP 200)不包含这些运行状态头,这意味着客户端应用在正常的非错误流量期间无法依赖它们来预测限制。
上游提供商限流。模型在特定上游公司(如 Anthropic、Meta、DeepSeek、Mistral 或专业第三方云主机商)的基础设施上物理托管和运行。如果该上游合作伙伴的基础设施过载,OpenRouter 会将 429 状态码传播回客户端。在响应体结构中,error.metadata.provider_code 字段在可用时包含上游提供商的原始错误码(例如 429),而非提供商名称或字符串标识符。
财务约束(HTTP 402 Payment Required)。OpenRouter 限流文档明确将账单耗尽与频率限流区分开来。402 状态码表示账户余额不足或为负,或单个 API 密钥的消费上限已达到,而非严格地仅代表账户余额为零。
当前关键参数可通过以下 API 请求直接查询:
curl -s -X GET https://openrouter.ai/api/v1/key \
-H "Authorization: Bearer $OPENROUTER_API_KEY"
响应载荷返回 usage、limit_reset 和 limit_remaining 字段。limit_remaining: null 表示该特定 API 密钥未设置本地消费上限。该值不会验证组织主余额上是否存在信用额度;它仅确认此单个令牌未配置人工消费上限。
客户端应用在请求失败时如何响应,取决于对元数据的检查。以下是两个假设场景(示例说明,非真实账户观察):
在这个假设场景中,后台工作进程收到 HTTP 402 拒绝。这里明确假设组织余额为正,并通过 Web 仪表板单独验证(密钥端点响应本身不确认组织级余额)。查询 https://openrouter.ai/api/v1/key 端点返回:
{
"data": {
"label": "worker-key",
"usage": 25.04,
"limit": 25.0,
"is_free_tier": false,
"limit_remaining": 0.0,
"limit_reset": null
}
}
虽然主账户余额按假设为正,但 limit_remaining 字段已达到零。该令牌已达到管理员配置的消费上限 25 美元。任何使用此密钥的自动重试都将以相同的 402 错误反复失败。工作进程应立即终止,并提醒管理员调整密钥的消费上限。
作为示例,假设某个传入请求因上游故障返回 HTTP 429。收到 429 状态码无法明确判断整体网关健康状况或剩余账户余额。错误响应体可能包含一个可选的元数据块:
{
"error": {
"message": "Provider returned rate limit error",
"code": 429,
"metadata": {
"provider_code": 429
}
}
}
error.metadata.provider_code 字段是可选的,在可用时传递上游提供商的原始状态码(本例中为 429),而非提供商名称或字符串标识符。仅凭此代码的存在无法揭示具体哪个主机拒绝了调用。
要识别故障提供商,请在管理仪表板中导航至:Activity > 特定请求 > View Raw Metadata。provider_responses 对象列出每个已评估的提供商及其返回的状态,如路由指南中所记录。虽然切换到另一个提供商或更改目标模型可能会解决问题,但无法保证立即恢复。
当 HTTP 429 状态是瞬态时,下次尝试前的延迟通过 Retry-After 头计算。服务器将此头传递为整数秒数或 HTTP 格式的日期字符串。
以下实现完全依赖 Python 3 标准库。它仅处理 HTTP 429 错误,在缺少服务器提供的延迟头时应用指数退避加随机抖动,如果服务器请求的超时时间超过 60 秒则中止执行。
import email.utils
import json
import os
import random
import sys
import time
import urllib.error
import urllib.request
API_KEY = os.environ.get("OPENROUTER_API_KEY")
MODEL_ID = os.environ.get("OPENROUTER_MODEL_ID", "openai/gpt-4o-mini")
MAX_ATTEMPTS = 3
MAX_ACCEPTABLE_WAIT = 60.0
def parse_retry_after(header_value: str | None) -> float | None:
if not header_value:
return None
raw = header_value.strip()
if raw.isdigit():
return max(0.0, float(raw))
try:
parsed_date = email.utils.parsedate_to_datetime(raw)
delay = parsed_date.timestamp() - time.time()
return max(0.0, delay)
except Exception:
return None
def execute_completion(prompt_text: str) -> str | None:
if not API_KEY:
sys.stderr.write("OPENROUTER_API_KEY 环境变量未设置。\n")
return None
endpoint = "https://openrouter.ai/api/v1/chat/completions"
payload = json.dumps(
{
"model": MODEL_ID,
"messages": [{"role": "user", "content": prompt_text}],
}
).encode("utf-8")
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
for attempt in range(1, MAX_ATTEMPTS + 1):
req = urllib.request.Request(endpoint, data=payload, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=30) as response:
status_code = response.getcode()
body = response.read().decode("utf-8")
if status_code == 200:
data = json.loads(body)
return data["choices"][0]["message"]["content"]
except urllib.error.HTTPError as err:
if err.code == 429:
retry_header = err.headers.get("Retry-After")
server_delay = parse_retry_after(retry_header)
if server_delay is not None:
wait_seconds = server_delay
else:
base_delay = 2.0 ** attempt
wait_seconds = base_delay + random.uniform(0.1, 1.0)
if wait_seconds > MAX_ACCEPTABLE_WAIT:
sys.stderr.write(
f"服务器请求暂停 {wait_seconds:.1f} 秒。"
"等待时间超过 60 秒。请求已取消。\n"
)
return None
if attempt == MAX_ATTEMPTS:
sys.stderr.write("HTTP 429 状态已用完 3 次重试上限。\n")
return None
sys.stderr.write(
f"收到 429。第 {attempt} 次尝试失败。"
f"暂停 {wait_seconds:.2f} 秒后再发起请求。\n"
)
time.sleep(wait_seconds)
continue
elif err.code == 402:
sys.stderr.write("错误 402:请检查账户余额和密钥限制。\n")
return None
else:
sys.stderr.write(f"HTTP 错误 {err.code}:请求被拒绝,不予重试。\n")
return None
except urllib.error.URLError as err:
sys.stderr.write(f"网络故障:{err.reason}。重试已取消。\n")
return None
return None
if __name__ == "__main__":
result = execute_completion("Назови три базовых принципа надежности сетевых API.")
if result:
print(result)
由于上述脚本片段逐字节保留了其原始俄语诊断日志字符串,以下是其内部决策树及其本地化等效项的解释:
OPENROUTER_API_KEY 环境变量未设置:表示缺少 OPENROUTER_API_KEY 环境变量;函数立即停止,不进行网络调用。
服务器请求暂停 ... 等待时间超过 60 秒。请求已取消:当服务器的 Retry-After 头要求的等待时间超过 MAX_ACCEPTABLE_WAIT(60 秒)时触发;执行立即取消,而非无限期阻塞工作进程。
HTTP 429 状态已用完 3 次重试上限:表示 HTTP 429 响应已耗尽全部 3 次重试尝试。
收到 429。第 X 次尝试失败。暂停 Y 秒后再发起请求:记录第 X 次尝试时 429 限流的中间失败,并在重试前睡眠计算出的退避周期 Y 秒。
错误 402:请检查账户余额和密钥限制:记录不可恢复的 HTTP 402 支付错误,表明账户余额已耗尽或密钥上限已达到;不进行重试。
HTTP 错误 {code}:请求被拒绝,不予重试:记录任何其他 HTTP 状态码并终止,不进行重试。
网络故障:{reason}。重试已取消:捕获常规套接字或网络错误(URLError)并停止执行,以避免在前一个载荷接收结果未知的情况下重新发出调用。
测试提示词翻译为"说出网络 API 可靠性的三个基本原则"。
在为自主 Agent 循环中的工具调用工作流设计时,重试 HTTP 请求需要极度谨慎。如果模型已在先前轮次中调用了修改了外部状态的外部工具(如写入数据库、发起支付或打开支持工单),盲目重复该链条有重复执行的风险。如果相关业务工具已执行,或网络返回了歧义结果(如连接断开或套接字超时,无法确定服务器是否已接收和处理了提示词),则绝不能自动重试请求。
在上述脚本中,重试严格限于明确以 HTTP 429 状态码拒绝的请求。至关重要的是,绝不应假设文本生成是幂等的或免费的:重新发出调用会消耗额外的令牌配额和消费上限,而模型采样的随机特性意味着后续完成可能会产生不同的输出。如果 Agent 在执行外部命令时遇到故障,系统状态应通过操作日志进行同步,然后再来恢复与语言模型的交互。
在定价页上,Free 层级对免费模型强制执行每日 50 次请求的平台级上限。关键在于,文档中强调的不存在平台级限流适用于切换到付费模型,而非仅仅购买账户信用点——充值资金不会自动解除 :free 端点的限流。确切条款和账户特定配额应始终在实时限制表中进行验证。此外,使用付费模型并不能完全消除上游提供商集群拥塞(虽然切换提供商可以帮助缓解停机,但无法保证立即恢复)。
为确保生产可靠性,工程团队通常会结合多种防御策略:
在 OpenRouter 的 models 请求参数数组中指定备用模型,允许路由器在主要选择失败时自动将流量重定向到备用提供商。
使用任务队列、限流器或令牌桶节流阀在客户端强制执行并发限制。
通过具有兼容请求架构的替代多模型 API 维护独立备份路由,以在长时间网关中断期间进行流量迁移。
原文首次发布于 BetterToken 博客。
BetterToken 通过 OpenAI 兼容和 Anthropic 兼容端点提供 AI 模型 API 按量付费访问——如果您正在将 Claude Code、Codex 或您自己的工具连接到自定义基础 URL,这将非常有用。参阅文档以开始使用。
如需进一步操作,您可以考虑屏蔽此人或举报滥用行为。