> ## Documentation Index
> Fetch the complete documentation index at: https://docs.playapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 限流与有界重试

> 识别 429、Retry-After 与多层重试，运行只读诊断示例。

## 先确定限制来自哪里

限制可能来自反向代理、网关用户或模型限制、渠道容量以及上游供应商。具体数值和窗口由部署配置决定；不要把 OpenRouter 的套餐限额套用到自己的实例。增加 API Key 不保证增加用户级总额度。

| 结果 | 建议处理 |
| - | - |
| 429 | 降低并发、控制队列；检查可用的 `Retry-After` 和请求日志 |
| 503 | 可能是容量或限流检查失败；结合请求 ID 判断原因 |
| 401 / 403 | 检查凭据和授权，不循环重试 |
| 400 / 404 | 检查字段、路径和模型；重试不会补出不存在的接口 |
| 连接中断 / 超时 | 执行结果未知；生成和任务提交不能直接当作未执行 |

当前 `middleware/model-rate-limit.go` 的 Redis 拒绝路径会设置秒数形式的 `Retry-After`，内存实现的拒绝可能没有该头且响应体为空。其他限制路径和上游行为不同，因此不能强制要求每个 429 都有 JSON 错误体或统一的限流响应头。

`Retry-After` 可以表示秒数，也可以表示 HTTP 日期。它只建议最早再试时间，不保证下一次成功。缺失或无效时使用带随机抖动的退避；等待超过总截止时间时应停止，不要把服务端要求的等待截短后提前请求。

## 可运行：读取模型列表并记录诊断元数据

保存 `examples/diagnostics.mjs` 及同目录的 [http-client.mjs](/zh/guides/http-runtime)。在文档目录运行：

```sh theme={null}
export API_ROOT_URL='https://gateway.example.com'
export API_KEY='YOUR_API_KEY'
node examples/diagnostics.mjs
```

`API_ROOT_URL` 保留部署路径前缀，不包含 `/v1`。脚本只请求 `GET /v1/models`；最多 3 次尝试，总截止时间 10 秒，只对 429 / 502 / 503 / 504 重试。网络错误、重定向、其他 HTTP 错误和无效成功响应立即结束。错误体不打印；成功响应最多读取 1 MiB，只输出模型数量。日志写到 stderr，每次收到响应头时记录状态、尝试次数、累计时间和经过格式检查的请求 ID。

```javascript theme={null}
import { setTimeout as delay } from 'node:timers/promises';
import { readBody } from './http-client.mjs';

// http-client validates API_ROOT_URL and API_KEY on import. The root excludes /v1.
const url = `${process.env.API_ROOT_URL.replace(/\/+$/, '')}/v1/models`;
const started = performance.now();
const deadline = started + 10_000;
const signal = AbortSignal.timeout(10_000);
try {
  for (let attempt = 1; attempt <= 3; attempt++) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.API_KEY}` },
      redirect: 'error', signal,
    });
    const id = response.headers.get('x-gateway-request-id')
      || response.headers.get('x-oneapi-request-id');
    console.error(JSON.stringify({
      event: 'models_response', attempt, status: response.status,
      elapsed_ms: Math.round(performance.now() - started),
      request_id: id && /^[A-Za-z0-9._:-]{1,128}$/.test(id) ? id : null,
    }));
    if (!response.ok) {
      await response.body?.cancel();
      if (attempt === 3 || ![429, 502, 503, 504].includes(response.status)) {
        throw new Error('Non-retryable status or attempt limit.');
      }
      const header = response.headers.get('retry-after')?.trim();
      let retryAfter = 0;
      if (header) {
        const parsed = /^\d+$/.test(header) ? Number(header) * 1000 : Date.parse(header) - Date.now();
        if (!Number.isNaN(parsed)) retryAfter = Math.max(0, parsed);
      }
      const backoff = Math.random() * 200 * 2 ** (attempt - 1);
      const wait = Math.max(retryAfter, backoff);
      // Never shorten a server-requested wait to fit the total deadline.
      if (wait >= deadline - performance.now()) throw new Error('Retry exceeds deadline.');
      await delay(wait, undefined, { signal });
      continue;
    }
    if (!/^application\/json(?:\s*;|$)/i.test(response.headers.get('content-type') || '')) {
      await response.body?.cancel();
      throw new Error('Expected JSON.');
    }
    const result = JSON.parse((await readBody(response, 1024 * 1024)).toString('utf8'));
    if (!result || result.error || result.success === false || result.object !== 'list'
      || !Array.isArray(result.data)
      || result.data.some((item) => !item || typeof item.id !== 'string' || !item.id.trim())) {
      throw new Error('Invalid model list.');
    }
    console.log(JSON.stringify({ models_count: result.data.length }));
    break;
  }
} catch {
  console.error('Model discovery failed. Check the status and request ID above. No model generation was attempted.');
  process.exitCode = 1;
}
```

本例展示只读操作的有界恢复；不要原样套到生成、上传或任务创建。生成示例保持 `maxRetries: 0`。即使只读模型查询成功，也没有验证实际推理能力；完整验收见[生产接入](/zh/guides/production)。

## 应用侧控制

设置最大并发、排队时长和队列长度；按实际用户或业务任务分配预算。将队列、SDK 和网关内部尝试一起纳入总截止时间。流式已经收到内容时保留中断状态，不静默追加一次重试的内容。任务轮询只重查已有任务，不重新提交原任务。

[模型路由](/zh/guides/model-routing) · [请求观测](/zh/guides/observability) · [错误处理](/zh/guides/errors)

[Read in English](/en/guides/rate-limits)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.