JS SDK 的错误类与重试策略
先说结论
- 官方 JS SDK 把错误分成连接/超时和 HTTP 错误两套;HTTP 错误统一继承自
APIError,并由AuthenticationError、RateLimitError等子类细分。 - 本文归纳的重试决策:连接失败、超时、429、5xx 适合退避重试;400、401、403、404、422 不应重试,应改输入、改权限或改代码。
- SDK 已经内建有限次重试,默认重试 408、429、500–599,最多额外重试 2 次,并遵守
Retry-After。但业务层仍然需要幂等键、降级和最终失败处理。 - 官方 RetryPolicy 接口暴露 9 个配置项,可按需覆盖;这些默认值是官方数据。
官方错误类怎么归类
先看 官方 JS SDK:APIError。这一页的定义很简短:
An unsuccessful HTTP response from the API.
同一页还列出了它的子类:
Extended by AuthenticationError BadRequestError InternalServerError NotFoundError PermissionDeniedError RateLimitError UnprocessableEntityError
也就是说,所有 HTTP 非 2xx 响应在 SDK 里都会落到 APIError 家族。按类名可以分成三组:
- 认证/权限:
AuthenticationError、PermissionDeniedError - 请求类:
BadRequestError、UnprocessableEntityError、NotFoundError - 服务端与限流:
InternalServerError、RateLimitError
这是依据官方 APIError 页面的 Extended by 列表做的归类。注意,官方这页没有逐个写明每个子类对应哪个 HTTP 状态码;本文后面把 401/403/400/422/404 映射到错误类名,属于工程上常见的 HTTP 语义归类,不是官方页面原文。
连接类不在 APIError 子类列表中,但在 官方 RetryPolicy 接口里出现了。官方原文是:
Retry connection failures, including interrupted response bodies ( APIConnectionError ). Default: true.
以及:
Whether to retry APITimeoutError . Default: true.
所以 APIConnectionError、APITimeoutError 属于连接/超时失败,SDK 把它们作为独立于 HTTP 错误的两类来处理。
RateLimitError 的官方页面则直接写明:
HTTP 429: the rate limit was exceeded.
这是官方明确给出的状态码映射。来源见 官方 JS SDK:RateLimitError。
重试决策表
下面的表是本文归纳,不是官方建议;分类依据是官方错误类名和 RetryPolicy 默认行为。
| 情况 | 官方相关证据 | 本文建议 |
|---|---|---|
| 连接失败/中断、超时 | RetryPolicy 中 APIConnectionError、APITimeoutError 可配置重试 |
指数退避重试 |
| 429 限流 | RateLimitError 页面写明 HTTP 429;RetryPolicy 默认重试 429 |
退避重试,并遵守 Retry-After |
| 5xx 服务端错误 | InternalServerError 在 APIError 子类;RetryPolicy 默认重试 500–599 |
退避重试,但多次失败要熔断 |
| 400、422 请求形状错误 | BadRequestError、UnprocessableEntityError 在 APIError 子类 |
不重试,改输入 |
| 401、403 凭据/权限问题 | AuthenticationError、PermissionDeniedError 在 APIError 子类 |
不重试,检查 key 与权限 |
| 404 未找到 | NotFoundError 在 APIError 子类 |
不重试,改请求路径或资源 |
这里再次强调:官方 APIError 页面没有写下 401/403/400/422/404 这些具体状态码;上表中的 HTTP 状态码映射是本文根据类名和常用 HTTP 语义做的工程归类。
为什么内建重试还不够
官方 Models 页对限流和 SDK 重试有直接说明:
Rate limits: Measured in tokens per second and requests per minute. A request over either limit returns 429 Too Many Requests . Our client SDKs retry with backoff by default and honor the retry-after header when the response carries one.
同一页给出的当前 Jev 1.13 限流数字是官方数据:
250,000 tokens per second / 1,200 requests per minute
另外,官方 RetryPolicy 接口显示默认 maxRetries 为 2,默认 httpStatuses 为 408, 429, and 500–599。换句话说,SDK 已经把“暂时性错误”自动重试了一轮。
但 SDK 的重试是传输层的,它不知道你的业务是否安全重复执行。比如你已经成功收到模型结果、但客户端在落库前超时,SDK 重试一次,你的业务可能重复处理同一请求。因此业务层还需要做幂等键或去重表。这是工程上的常见做法,官方文档没有展开。
另一个原因是:SDK 重试耗尽后仍会抛错。比如连续 429、连续 5xx,最终需要业务层决定是快速失败、切换到规则判定,还是熔断。这个“最后一公里”不能依赖 SDK 默认行为。
一个可用的处理骨架
下面这段不是官方 SDK 方法名规范,而是基于官方 API 参考的 HTTP 端点和 SDK 的 APIError.fromResponse 构建的兜底/降级骨架。幂等键、超时降级函数都是工程做法,不是官方要求。
import { APIError, RateLimitError, InternalServerError } from "@typesafe-ai/sdk";
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const seenKeys = new Set<string>();
async function callSystemOneWithFallback(payload: unknown) {
const key = crypto.randomUUID();
// 本地幂等去重:实际应用中应返回缓存结果或等待已完成请求
if (seenKeys.has(key)) return null;
seenKeys.add(key);
const url = "https://api.typesafe.ai/v1/systemone";
let attempt = 0;
const maxAttempts = 4;
while (attempt < maxAttempts) {
attempt += 1;
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const text = await res.text();
const body = res.ok ? JSON.parse(text) : text;
if (res.ok) return body;
const err = APIError.fromResponse(res.status, body, res.headers);
if (
err instanceof RateLimitError ||
err instanceof InternalServerError ||
err.status === 408
) {
const retryAfterHeader = res.headers.get("retry-after-ms");
const retryAfter = Number(retryAfterHeader || 0);
const backoff = Math.max(
retryAfter || 500 * 2 ** (attempt - 1),
5000
);
await sleep(Math.min(backoff, 60000) * (1 - Math.random() * 0.25));
continue;
}
// 4xx 不重试:认证、权限、请求形状、未找到
return ruleBasedFallback(payload, err.status);
}
throw new Error("max retries exceeded");
}
function ruleBasedFallback(payload: unknown, status: number) {
// 降级到确定性规则判断,不调用模型
return { status, fallback: true, payload };
}
代码里的 retry-after-ms header 名称来自 RetryPolicy 页面原文:
Honor Retry-After and retry-after-ms up to maxRetryAfterMs .
APIError.fromResponse 也是官方 APIError 页面列出的静态方法,用于根据 HTTP 状态码创建对应的错误子类。
RetryPolicy 配置项速查
以下默认值来自官方接口页,均为官方数据:
| 配置项 | 类型 | 默认值 |
|---|---|---|
apiConnectionError |
boolean | true |
apiTimeoutError |
boolean | true |
backoffInitialMs |
number | 500 |
backoffJitter |
number | 0.25 |
backoffMaxMs |
number | 5000 |
httpStatuses |
ReadonlySet<number> | 408, 429, 500–599 |
maxRetries |
number | 2 |
maxRetryAfterMs |
number | 60000 |
respectRetryAfter |
boolean | true |
官方页面还说明,这是“partial overrides inherit unset fields from the client or SDK defaults”,也就是部分覆盖时,未设置的字段会继承客户端或 SDK 默认值。
上线前清单
- 不要随便把
maxRetries设为0,除非你完全接管重试逻辑。 - 对写操作或带副作用的业务,必须接入幂等键或去重表。这是工程要求,不是官方要求。
- 为 429 和 5xx 设置外部熔断,避免 SDK 重试把上游压垮。
- 对 401、403、400、422、404 快速失败,结构化记录错误,避免重复消耗额度。
- 超时降级路径要有规则判定兜底,不能只依赖模型结果。
- 上线前模拟非幂等请求在超时、429、5xx 三种情况下的表现。
这次没核实的
- 官方
APIError页面没有逐个列出AuthenticationError、PermissionDeniedError、BadRequestError等各自对应的 HTTP 状态码;本文对 401/403/400/422/404 的映射是工程上常见 HTTP 语义的归类,不是官方页面原文。 - 官方 Models 页提到“If you call the HTTP API directly, see Handling rate limits”,但本次给出的来源未包含该 Handling rate limits 子页面正文,其具体建议未能核实。
- 官方 SDK 的评估调用方法名未出现在本次给出的 API reference 页面中;因此上面的处理骨架没有使用编造的 SDK 方法,只用
APIError.fromResponse和 HTTP 端点。
参考来源
评论区
登录后可评论。