排障专题 · API 报错与限流 · 2026-09-27 核对

Jev API 报错与限流:完整排障指南

逐一核对 Jev API 官方文档中的每个状态码与 SDK 错误类,并给出可直接落地的修法——429 限流、401 鉴权、422 载荷、529 过载、超时重试——外加「为什么没有流式」,以及没有官方状态页时如何确认服务状态。

速查直答

429 表示超出限流:先等 Retry-After 响应头要求的时间(SDK 暴露为 retry_after_ms / retryAfterMs),再用指数退避加抖动重试——官方 SDK 默认就是这么做的(默认 2 次重试、0.5 秒起翻倍至 5 秒封顶、自动遵守 Retry-After)。400/401/403/422 永远不要重试——那是你的 bug,不是服务器的脾气。529 表示 TypeSafe 自身过载:退避,或切换备用端点。没有流式:Jev 每次请求只返回一个完整的类型化决策。也没有公开状态页:用最小请求探测,读状态码下结论。

本页状态码与官方指引已于 2026-09-27 对照 docs.typesafe.ai 核实:HTTP API 参考、Python SDK 的 Exceptions 与 Retries 参考、JavaScript SDK 错误类,以及 System One 概念页。官方未公开的部分——具体限额数值、状态页、流式模式——我们如实说明,并以明确标注的通用 HTTP 工程实践承接。

错误速查表

API 与 SDK 文档覆盖到的每个状态,一行一个:什么触发、怎么修、重试有没有用。HTTP 参考明确列出 401、422、429、529 四个;其余由 SDK 参考以类型化错误类覆盖。

状态码SDK 错误类(Python · JS)含义怎么修重试?
400 Bad RequestTypeSafeBadRequestError · BadRequestErrorSDK 异常页原文:"请求无效"。HTTP 参考把请求体校验问题归到 422;400 通常是 JSON 本身格式损坏,或请求在校验开始前就已经坏了。读错误响应体,在本地用 zod / pydantic 校验 JSON 载荷,并确认 Content-Type 头是 application/json。不重试——先修请求
401 UnauthorizedTypeSafeAuthenticationError · AuthenticationError官方原文:"API 密钥缺失或无效。请检查 Authorization 头。"确认密钥存在、处于启用状态、来自正确的环境——并且调用发生在服务端。泄漏在浏览器包里的密钥,最后都是靠「紧急轮换」收场的。不重试——先修凭证
403 Permission DeniedTypeSafePermissionDeniedError · PermissionDeniedErrorSDK 文档表述为"访问被拒绝":密钥认证通过但无权使用该资源——按通用 REST 语义,通常是套餐、区域或授权问题。HTTP 参考没有详述这个状态码,请以响应体为准。到服务商后台检查密钥的套餐与权限;确认你的套餐实际授权的模型 ID。不重试——授权问题
404 Not FoundTypeSafeNotFoundError · NotFoundError路径或方法错了。评测端点是 POST https://api.typesafe.ai/v1/systemone——写成 /v1/systemOne 这类大小写笔误,或误用 GET,都会落在这里。把 URL 与 /jev-api 页的端点表逐字符对照。网关路径不同:Vercel AI Gateway 与 OpenRouter 各有自己的路径。不重试——先修 URL
422 Unprocessable EntityTypeSafeUnprocessableEntityError · UnprocessableEntityError官方原文:"请求体未通过校验——比如缺少必填字段,或问题定义格式错误。"响应体会指出具体是哪个字段。每个请求必须有 model、state、questions;每个 question 必须声明 type(choice / score / noul)、instructions 与 criteria。按响应体指出的字段修完再发。不重试——先修 schema
429 Too Many RequestsTypeSafeRateLimitError · RateLimitError官方原文:"你已超出速率限制。请退避并在短暂延迟后重试。"如果响应带 Retry-After 头,SDK 会把要求等待的时间解析成 retry_after_ms / retryAfterMs。按响应头要求的时间等待(没有头就用指数退避加抖动),客户端自己限住并发,最后才考虑找服务商提额。可重试——先退避
529 OverloadedTypeSafeInternalServerError family · not a standard statusTypeSafe 特有、且官方原文如此:"TypeSafe 暂时过载。请在短暂延迟后重试。"这是唯一一个明确属于服务商侧状况、而不是你的问题的状态码。先退避;若持续过载就切换备用端点——/jev-api 指南里用 1500ms 熔断器实现的就是这个模式。可重试——或直接切换
5xx server errorsTypeSafeInternalServerError · InternalServerError官方表述:"服务器处理请求失败。"默认按「瞬时故障」处理——直到同一个端点反复失败、而另一个端点正常为止。退避重试——SDK 的默认可重试集合包含全部 5xx。若一个端点持续失败而另一个正常,坚持重试不如直接切换。可重试——SDK 默认
No HTTP responseTypeSafeAPIConnectionError · APIConnectionError请求根本没拿到响应——DNS、TLS、防火墙,或你自己的出网规则。与 API 本身无关。从生产所在的那台机器测网络路径——笔记本上的探测证明不了服务器的任何事。SDK 默认会重试连接错误。可重试——SDK 默认
Request timeoutTypeSafeAPITimeoutError · APITimeoutError请求超出了你配置的超时——SDK 错误对象会带着触发的那项超时设置。决策是单次前向传播,量级在几十到一二百毫秒,所以超时指控的是预算或网络路径,不是模型。把超时放宽到你的 p99 水位,或保持紧预算并直接切换——/jev-api 的模式用 1500ms。SDK 默认会重试超时。可重试——SDK 默认
Invalid response bodyTypeSafeAPIResponseValidationError · —拿到了 2xx,但响应体缺失或结构上不符合必需数据——Python 类会暴露 field_path,如 answers.tone.confidence(官方文档自己的例子)。这是 SDK 侧的校验失败,不是 HTTP 错误。记录 field_path 和你发送的模型 ID;把模型标识钉死,服务商更新模型后重点回归——而不是盲目重试。先排查——不要死循环

SDK 列把 Python 类与 JavaScript 侧的孪生类成对列出。每个 HTTP 错误还带 x-typesafe-request-id 响应头(SDK 错误对象上暴露为 request_id / requestId)——联系支持时附上它。HTTP 参考未列出的状态码,按 SDK 参考与标准 HTTP 语义解读。

把报错的调用与可运行的示例请求对照一遍

限流机制:官方承诺了什么,工程实践补什么

官方的话很短,能落地的策略不短。

官方文档实际说了什么

  • 429 的官方原文:"你已超出速率限制。请退避并在短暂延迟后重试。"529 补充:"TypeSafe 暂时过载。请在短暂延迟后重试。"
  • API 参考要求"用指数退避重试,而不是立即重试",并说明官方客户端 SDK 会通过默认重试策略自动处理。
  • 官方没有公布任何数值——没有每秒请求数、没有并发上限、没有日配额。谁给你一个精确的 Jev RPS 数字,谁就是在猜。从你实际收到的 429 和服务器发来的 Retry-After 头反推自己的天花板。
  • SDK 默认把 429 视为可重试,且默认遵守 Retry-After(respect_retry_after=True,同时读取 Retry-After 与 retry-after-ms)。
  • 每个 HTTP 错误都带 x-typesafe-request-id 响应头——联系支持前先记下它。

通用 HTTP 工程实践补充(标注:非 Jev 官方行为)

  • 指数退避加抖动:delay = random(0, min(cap, base × 2^n))。形状与 SDK 默认一致(0.5 秒翻倍至 5 秒);规模越大抖动越重要——一千个客户端在同一秒重试,会把限流重新挤成一次洪峰。
  • 出现 Retry-After 就遵守。它是服务器替你回答了"等多久";更快地轮询只会让它重置。
  • 给重试设预算:SDK 默认是单次调用 30 秒总预算内重试 2 次。裸 HTTP 客户端也该有同样的停止条件,而不是无限循环。
  • 把 429 拆成两种病因。突发节奏是你自己的——用退避和客户端并发上限解决。配额或余额耗尽是账单问题——退避救不了;盯住后台,在撞顶之前告警。
  • 如果流量合法但 429 持续,就该卸载:把请求路由到备用端点或确定性兜底规则,别让用户排在重试长队后面。
退避救不了的时候:置信度门控降级链

重试手册:Python 与 TypeScript

可直接复制的起点。前两个是把上述工程实践写进代码的裸 HTTP 客户端;第三个是官方 SDK 替你做完全部。

Python 裸 HTTP 重试(requests)

python · raw http

什么时候用

你用 requests 或 httpx 直调 POST /v1/systemone,并希望重试逻辑在自己代码里看得见。它重试 408、429 与整个 5xx 家族(含 529),遵守 Retry-After,且绝不重试校验或鉴权类错误。

python · raw http
# jev_retry.py — raw-HTTP retry for POST /v1/systemone.
# Documented statuses: 401 · 422 · 429 · 529 (docs.typesafe.ai/api.md).
# The retry mechanics below are general engineering practice — the official
# docs publish no numeric limits, so none are assumed here.
import os
import random
import time

import requests

URL = "https://api.typesafe.ai/v1/systemone"
# Mirrors the official SDK's default retryable set ({408, 429, all 5xx});
# 529 is listed explicitly — it is TypeSafe's documented overload status.
RETRYABLE = {408, 429, 500, 502, 503, 504, 529}


def evaluate(state: str, questions: dict, max_attempts: int = 4) -> dict:
    delay = 0.5  # first backoff; doubles per attempt, capped at 5 s (SDK shape)
    resp = None

    for _ in range(max_attempts):
        resp = requests.post(
            URL,
            headers={
                "Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}",
                "Content-Type": "application/json",
            },
            json={"model": "jev-latest", "state": state, "questions": questions},
            timeout=5.0,
        )
        if resp.status_code < 400:
            return resp.json()

        if resp.status_code not in RETRYABLE:
            # 400/401/403/404/422: identical bytes in, identical error out.
            resp.raise_for_status()

        # Honor the server's requested wait when it sends one:
        # Retry-After (seconds). The official SDK also parses retry-after-ms.
        wait = resp.headers.get("Retry-After")
        sleep_s = (
            float(wait) if wait else min(delay, 5.0) * random.uniform(0.75, 1.0)
        )
        time.sleep(sleep_s)
        delay *= 2

    resp.raise_for_status()  # budget spent on a retryable status
    raise RuntimeError("retry budget spent")

可重试集合对齐官方 SDK 默认值({408, 429, 全部 5xx}),并把 529 显式列出以便阅读。5 秒封顶对应 SDK 文档中的 backoff_max。

TypeScript:超时 + 退避 + 故障转移

typescript · fetch

什么时候用

服务端 TypeScript 路径。每次尝试带 1500ms 中止预算——与 /jev-api 指南相同的熔断数值——可重试状态码再来一轮,预算耗尽则抛出,由调用方切换备用端点。

typescript · fetch
// lib/jev-retry.ts — per-attempt timeout, backoff with jitter, Retry-After
// handling, and a throw the caller can fail over on. The 1500 ms abort budget
// is the same circuit-breaker number used by the failover adapter on /jev-api.
const PRIMARY = 'https://api.typesafe.ai/v1/systemone';
// Mirrors the official SDK default ({408, 429, all 5xx}) + the documented 529.
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504, 529]);

type Contract = { state: string; questions: Record<string, unknown> };

export async function evaluateWithRetry(
  contract: Contract,
  {
    attempts = 4,
    timeoutMs = 1500,
  }: { attempts?: number; timeoutMs?: number } = {},
): Promise<unknown> {
  let last: Response | undefined;

  for (let attempt = 0; attempt < attempts; attempt++) {
    if (attempt > 0 && last) {
      // Wait at least what the server asked for: retry-after-ms is the
      // millisecond form the official SDK understands; Retry-After is seconds.
      const msForm = Number(last.headers.get('retry-after-ms'));
      const sForm = Number(last.headers.get('retry-after')) * 1000;
      const asked = Number.isFinite(msForm) ? msForm : sForm;
      // ...then add full jitter on top: random(0, min(cap, base * 2^n)).
      const base = Math.min(500 * 2 ** (attempt - 1), 5000);
      await new Promise((r) =>
        setTimeout(r, Math.max(asked || 0, Math.random() * base)),
      );
    }

    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const res = await fetch(PRIMARY, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
        },
        body: JSON.stringify({ model: 'jev-latest', ...contract }),
        signal: controller.signal,
      });
      // 2xx, or a fix-the-request 4xx: hand it back either way.
      if (!RETRYABLE.has(res.status)) return res.json();
      last = res; // 408/429/5xx/529: back off and go around again
    } catch (err) {
      if (attempt === attempts - 1) {
        throw new Error('Jev unreachable after retries');
      }
      // AbortError / network failure: fall through and retry.
    } finally {
      clearTimeout(timer);
    }
  }

  // Budget spent on retryable statuses — surface where it died so the caller
  // can fail over to a secondary endpoint (see the adapter on /jev-api).
  throw new Error(`Jev still failing: HTTP ${last?.status ?? 'no response'}`);
}

完全抖动的延迟以 5 秒封顶,与 SDK 文档的 backoff_max 一致。响应带 retry-after-ms 时按它等待——官方 SDK 解析的也是这个毫秒形态。

交给官方 SDK 重试(Python)

python · typesafe-sdk

什么时候用

无聊但正确的选项。文档化的默认值已经覆盖 408/429/5xx 与连接、超时错误的重试,自带指数退避、抖动与 Retry-After 处理。正确姿势是调参,不是重造。

python · typesafe-sdk
# The official Python SDK retries for you. Defaults below are the documented
# ones (docs.typesafe.ai/sdk/python/api/retries.md), shown explicitly so you
# can tune rather than reinvent:
from typesafe_sdk import RetryPolicy, TypeSafeClient

client = TypeSafeClient(
    retry=RetryPolicy(
        max_retries=2,             # default: 2 retries (three attempts total)
        backoff_initial=0.5,       # first backoff, seconds — doubles each attempt
        backoff_max=5.0,           # ceiling for the exponential growth
        backoff_jitter=0.25,       # random fraction subtracted from each delay
        timeout=30.0,              # total budget per SDK call, in seconds
        # default is {408, 429, all 5xx}:
        http_statuses={408, 429, 500, 502, 503, 504},
        respect_retry_after=True,  # honors Retry-After / retry-after-ms headers
    ),
)

# Connection errors and timeouts are retried by default too. When the budget
# is spent on a 429, the SDK re-raises TypeSafeRateLimitError — its documented
# retry_after_ms attribute carries the server's requested wait in
# milliseconds (None when the header is absent):
#
#     import time
#     from typesafe_sdk import TypeSafeRateLimitError
#
#     try:
#         decision = run_evaluation(client, state, questions)
#     except TypeSafeRateLimitError as err:
#         time.sleep((err.retry_after_ms or 5_000) / 1000)
#         decision = run_evaluation(client, state, questions)

按官方 Retries 参考显式展示默认值:max_retries 2、backoff_initial 0.5 秒、backoff_max 5 秒、抖动 0.25、总预算 30 秒。预算耗尽后 SDK 会重新抛出最后一个错误——捕获 TypeSafeRateLimitError 并读取 retry_after_ms。

重试还是不重试:分类表

SDK 默认可重试集合是 {408, 429, 全部 5xx} 外加连接与超时错误——其余都是你自己要修的。裸 HTTP 同样适用这个分法。

可以重试——等了再试

408 请求超时、429 限流、包括 529 过载在内的整个 5xx 家族、连接失败、你自己的超时。这些状态说"现在不行",不是说"永远不行"。尝试之间要等待——退避加抖动——并限制尝试次数。

绝不重试——先修请求

400、401、403、404、422。同样的字节发过去只会得到同样的错误:格式坏的请求体、缺失或无效的密钥、授权问题、错误路径、schema 违规。读错误响应体——422 会指明出错字段——修好再发。

预算与计费

重试是一次计费调用:每次尝试都会收输入 token 费用,而 Jev 没有输出 token 计费——因为决策不是生成的文本。限制尝试次数、让时间预算显式化,并对 429 频率设告警,而不是默默硬扛。

刚接触这个 API?从入门指南开始

为什么没有流式

「Jev streaming」这个搜索意图,在这里如实回答。

官方文档没有记录评测端点的任何流式或 SSE 模式,概念页也解释了为什么不需要:Jev "返回类型化的决策与概率,而非生成的文本",它"不写回复、不产代码、不生成解释"。没有逐个吐出的 token,因为根本没有 token。一次请求进去,一个完整 JSON 响应出来——按 /jev-api 的对比,三大端点典型延迟在 80–190ms。

所以如果你是搜「Jev streaming」想看着答案逐字成形:答案在你渲染第一个字块之前就已经完整了。你能流式的是你自己的工作流——在唯一那一次请求在途时,从你的处理器推送「排队中 → 评测中 → 已决策」事件。那是感知性能的通用工程手段,不是 API 特性。

对任何第三方「流式 Jev」包装保持怀疑:模型无法吐出半个决策,被「流」出来的东西一定是他们那边缓冲后重新切碎的。你付延迟,买的是表演。

端点、协议与故障转移适配器完整版

状态与健康检查:没有状态页怎么确认 Jev 活着

官方没有记录公开的状态端点。这里是能用的替代办法。

先说诚实的基线:官方文档没有提供 /health 端点,也没有公开状态页——我们在 2026-09-27 核对了完整文档索引。实用的替代是发一个最小冒烟请求,然后正确解读它的状态码。

下面的探测脚本发送最小合法载荷——很小的 state 加一个 Noul 问题——只打印 HTTP 状态。请从生产代码所在的同一条网络路径运行:笔记本上的探测只能说明你笔记本的情况。

如果你走的是本站自己的中继 /api/jev/evaluate,记住那些限制是我们站的、不是 TypeSafe 的:Cloudflare Turnstile 人机验证、每 10 秒 5 次的突发上限、32KB 请求体上限,以及每日配额。中继返回的 429 是你的站点配额,不是上游限流。

做监控面板时套用通用实践:把 429 频率、p95 延迟和 529 频次当作事实上的健康指标,对趋势告警,而不是对单次事件。

冒烟测试(cURL)
# Minimal smoke test: tiny state, one Noul question, print only the status.
#   200 = healthy · 401 = key problem · 429 = pacing problem
#   529 = provider overloaded · no response = your network path
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "jev-latest",
    "state": "health check probe",
    "questions": {
      "alive": {
        "type": "noul",
        "instructions": "Confirm the evaluation service responds",
        "criteria": {
          "ok": "The service returned any decision",
          "down": "No decision is available"
        }
      }
    }
  }'

冒烟测试结果解读

200 OK

健康。完整的类型化决策已返回——无事可修。

401 Unauthorized

服务可达,密钥被拒。问题在你这边:密钥缺失、已轮换或环境拿错。不要重试——修凭证。

429 Too Many Requests

服务正常,是你的节奏或配额出了问题。先退避,再跑一次探测确认恢复。

529 Overloaded

服务商侧过载——官方措辞是"暂时过载"。先等待;若持续,切换备用端点。

Timeout / no response

你的网络路径或超时预算问题。先查出网、DNS 和中止计时器,再怪 API。

它只打印状态码——决策内容本身对健康检查毫无意义。探测频率保持在正常速率之内——触发 429 的冒烟测试确实说明了些什么,只是不是你想知道的那件事。

换个方式:在 Playground 里跑一次真实决策

Jev API 报错与限流:常见问题

为什么 Jev API 一直返回 429?

因为你超出了限流——官方文档的处理指令就是"退避并在短暂延迟后重试"。官方没有公布任何数值限额,所以修法是机械的:读 Retry-After 头(SDK 暴露为 retry_after_ms / retryAfterMs),用指数退避加抖动重试,并给客户端限并发。官方 SDK 默认就会重试 429(默认 2 次、遵守 Retry-After)。如果认真退避之后仍然持续 429,病因通常是配额或余额耗尽而不是节奏——去查后台,退避治不了账单。

Jev API 有哪些错误码?

HTTP 参考明确列出四个:401 Unauthorized(密钥缺失或无效)、422 Unprocessable Entity(请求体未通过校验)、429 Too Many Requests(限流)、529 Overloaded(TypeSafe 暂时过载),外加常规 5xx 家族。SDK 侧全部映射为类型化类:BadRequestError(400)、AuthenticationError(401)、PermissionDeniedError(403)、NotFoundError(404)、UnprocessableEntityError(422)、RateLimitError(429)、InternalServerError(5xx),另有连接、超时与响应校验错误类。带修法的完整对照见上方速查表。

Jev API 超时该怎么重试?

超时与连接错误默认就是可重试的——SDK 两者都会重试。给每次调用设明确的超时预算:SDK 默认单次调用总预算 30 秒,而本站 /jev-api 的故障转移模式用 1500ms 熔断并切换端点。尝试之间用指数退避加抖动,并限制尝试次数。Jev 决策是单次前向传播,量级几十到一二百毫秒,所以超时几乎总是你的预算或网络路径问题,不是模型慢。

Jev API 支持流式吗?

不支持。官方文档没有记录任何流式或 SSE 模式,架构上也无需如此:Jev 返回类型化的决策与概率,而非生成的文本——它不写回复、不产代码、不生成解释。一次请求,一个完整 JSON 响应。想要「看起来快」,请在处理器里流式推送你自己的工作流状态("排队中 → 评测中 → 已决策");那是应用层工程,不是 API 特性。对第三方「流式 Jev」包装保持怀疑——模型吐不出半个决策。

有官方的 Jev API 状态页吗?

没有记录。截至 2026-09-27,官方文档没有提供健康检查端点或公开状态页。实用替代:从生产网络路径发一个最小合法请求并读状态——200 健康、401 你的密钥、429 你的节奏或配额、529 服务商过载、无响应你的网络。联系支持时附上失败调用的 x-typesafe-request-id 响应头;SDK 在每个 HTTP 错误上把它暴露为 request_id / requestId。

Jev 的限流具体是多少?每秒能调几次?

官方未公开——官方参考没有公布每秒请求数、并发或日配额数字,任何给出精确数值的来源都在猜。用实证办法反推自己的天花板:从你实际收到的 429 响应和 Retry-After 头里读;客户端用令牌桶限速,比靠撞 429 摸索上限便宜得多。SDK 文档化的重试默认值(2 次重试、0.5 秒翻倍至 5 秒、抖动 0.25)描述的是重试形状,不是限额本身。