koach 开发者文档

错误码与重试

所有接口共用同一套错误结构。这一页的重点是哪些该重试、哪些不该 —— 对不可重试的错误做重试,只会更快耗尽你的限流额度。

错误结构

{
      "error": {
        "type": "rate_limit_error",
        "code": "rate_limit_exceeded",
        "message": "每分钟请求数超出上限,请在 1.4 秒后重试。",
        "param": null,
        "request_id": "req_01JQ7X2M9K"
      }
    }
type
错误大类,用于程序分支判断
code
具体错误码,用于精确定位与埋点
message
面向开发者的中文说明。不要直接展示给终端用户
param
出错的字段路径,参数类错误才有
request_id
本次请求的唯一标识,报障时务必提供

状态码

状态码含义可重试处理建议
400请求格式或参数有误 param 定位字段,修代码
401鉴权失败 检查密钥是否正确、是否已吊销、环境是否匹配
403无权访问该资源 模型或 Skill 未开通,联系商务
404资源不存在 检查模型 ID、Agent ID、文件 ID 是否拼错
409状态冲突 如对已完成的微调任务重复取消
422语义上无法处理 含安全边界拦截,属预期行为,见下
429触发限流或额度耗尽 指数退避重试;额度耗尽则需充值
500服务端内部错误 退避重试;持续出现请带 request_id 报障
503服务暂时过载 退避重试,或降级到 lite 型号
504处理超时 长任务改用流式或批量接口

错误码表

鉴权与权限

codetype说明
invalid_api_keyauthentication_error 密钥不存在或格式错误
api_key_revokedauthentication_error 密钥已在控制台吊销
environment_mismatchauthentication_error sk-test- 访问了生产域名,或反过来
permission_deniedpermission_error 账号未开通该模型或 Skill
ip_not_allowedpermission_error 请求来源 IP 不在白名单内(开启了 IP 限制时)

请求参数

codetype说明
invalid_requestinvalid_request_error 请求体不是合法 JSON,或缺少必填字段
model_not_foundinvalid_request_error 模型 ID 不存在。注意微调模型 ID 是完整的 ft:base:org:suffix:hash
context_length_exceededinvalid_request_error 输入加输出超过该型号上下文上限,需要裁剪历史
unsupported_parameterinvalid_request_error 该型号不支持此参数,如对 embedding 模型传 temperature
invalid_imageinvalid_request_error 图像无法下载或格式不支持。支持 JPEG / PNG / WebP,单张 20 MB 以内

限流与额度

codetype说明
rate_limit_exceededrate_limit_error RPM 或 TPM 触顶。可重试
concurrency_limit_exceededrate_limit_error 并发数触顶。可重试
insufficient_quotaquota_error 额度用尽。重试无用,需充值或提额
sandbox_quota_exhaustedquota_error 沙箱免费额度用尽,联系商务转生产

安全边界

codetype说明
safety_boundary_triggeredsafety_error 触及医疗诊断、用药等硬边界。响应里带 koach.suggested_reply,可直接展示给用户
content_filteredsafety_error 输入或输出命中内容安全策略
minor_protectionsafety_error 档案显示为未成年人,请求的训练强度或减重目标超出安全范围
安全类错误不要重试

它们是确定性的 —— 同样的输入永远得到同样的拒绝。 正确做法是把 koach.suggested_reply 展示给用户, 并在你侧埋点统计触发频次。如果某类正常问题被频繁误拦, 告诉我们,这属于评测集要覆盖的 false_refusal_rate 指标。

Skill 与 Agent

codetype说明
skill_not_enabledpermission_error 该 Skill 未在合同范围内
skill_input_invalidinvalid_request_error input 不符合该 Skill 的 schema,param 指出具体字段
media_unprocessableinvalid_request_error 图像或视频里没检测到人体 / 食物,无法分析
profile_not_foundinvalid_request_error profile 对应的记忆档案不存在,需要先创建
max_steps_exceededagent_error Agent 工具调用轮数超过 max_steps, 通常是 instructions 让它陷入了循环
tool_execution_timeoutagent_error 你侧的函数在 30 秒内没返回

重试策略

只对 4295xx 重试,用指数退避加抖动。 抖动很重要 —— 没有它,被同时限流的客户端会在同一时刻一起重试, 形成新的尖峰。

import random, time
    import httpx

    RETRYABLE = {429, 500, 502, 503, 504}

    def call_with_retry(payload, max_attempts=5):
        for attempt in range(max_attempts):
            r = httpx.post(
                f"{BASE_URL}/chat/completions",
                headers={"Authorization": f"Bearer {API_KEY}"},
                json=payload,
                timeout=60,
            )
            if r.status_code not in RETRYABLE:
                r.raise_for_status()
                return r.json()

            # 额度耗尽不是限流,重试没有意义
            if r.json().get("error", {}).get("code") == "insufficient_quota":
                raise RuntimeError("额度已用尽,请充值")

            # 服务端给了明确的等待时间就照做,否则指数退避
            retry_after = r.headers.get("retry-after")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay + random.uniform(0, 0.5))   # 加抖动

        raise RuntimeError(f"重试 {max_attempts} 次后仍失败")
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

    async function callWithRetry(payload, maxAttempts = 5) {
      for (let attempt = 0; attempt < maxAttempts; attempt++) {
        const res = await fetch(`${BASE_URL}/chat/completions`, {
          method: "POST",
          headers: {
            Authorization: `Bearer ${API_KEY}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(payload),
        });

        if (!RETRYABLE.has(res.status)) {
          if (!res.ok) throw new Error(await res.text());
          return res.json();
        }

        const body = await res.json();
        if (body?.error?.code === "insufficient_quota") {
          throw new Error("额度已用尽,请充值");
        }

        const retryAfter = res.headers.get("retry-after");
        const delay = retryAfter ? Number(retryAfter) * 1000 : 2 ** attempt * 1000;
        await sleep(delay + Math.random() * 500);
      }
      throw new Error(`重试 ${maxAttempts} 次后仍失败`);
    }
官方 SDK 已内置重试

koach@koach/sdk 默认对可重试错误做两次退避重试, 可以通过 max_retries 调整。上面的代码只在你直接发 HTTP 请求时才需要。

幂等

重试带来一个新问题:第一次请求其实成功了,只是响应在网络上丢了, 重试就会产生重复扣费。对会产生副作用或按次计费的接口(Skill 调用、 文件上传、微调任务、批量任务),带上 Idempotency-Key

curl "$KOACH_BASE_URL/skills/training.plan.generate/invoke" \
      -H "Authorization: Bearer $KOACH_API_KEY" \
      -H "Idempotency-Key: plan-usr_8f2a41-20260817-w34" \
      -H "Content-Type: application/json" \
      -d '{"profile": "usr_8f2a41", "input": {"…": "…"}}'

24 小时内相同 key 的请求直接返回首次的结果,不重复执行也不重复计费。 key 建议用业务上天然唯一的组合,比如 {用途}-{用户}-{日期}-{周次},而不是随机 UUID —— 随机值在重试时无法复用,等于没做幂等。

降级

持续 503 通常意味着该型号所在集群过载。 如果你的场景对专业度要求没那么极致,可以做一层自动降级:

这三条降级路径在私有化部署里同样适用, FDE 会在实施阶段帮你把降级链路和告警阈值一起配好。

本页内容属于 koach 商业化合作方案的一部分。接口定义可能在正式签约前调整, 以合同附件中的版本为准。

koach · AI by Keep 隐私政策 用户协议 联系我们