跳转到内容
v0.9.1正式版

认证、请求头与错误

所有 Gateway 端点用 Bearer:

Authorization: Bearer gw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

真实的 Gateway Key 是 gw_ 加 43 个字符(32 字节 base64url)。缺失或格式不对返回 401 invalid_api_key。

Anthropic 兼容端点也接受 x-api-key。两个头同时出现且值不一致时直接判失败,返回 401 —— SDK 配了 base URL 又留着环境变量里的旧 key,就是这个场景。

Gateway Key 按用途显式授予 Scope。旧 Key 和没有 Scope 的历史记录只拥有 inference,升级不会 自动扩大权限。

Gateway Key Scope
Scope允许的操作
inference调用模型兼容端点
discovery查询公开别名列表或单个别名;同时需要 inference
work_unit:create创建和关闭 Work Unit
run:create创建和关闭 Run
run:attach把模型请求归入一个现有 Run
governance:read读取本 Project 的 Work Unit、Run 和 Outcome
outcome:write按 Work Unit 冻结的 Definition 上报或修订 Outcome

建议把 Agent 编排 Key 与业务验收 Key 分开:前者通常拥有 inference、生命周期 Scope 和 run:attach;后者只需要 outcome:write,不会因为创建了 Run 而自动获得结果写入权。

OpenAI、Anthropic 和延迟 Responses 调用都通过同一个请求头归入 Run:

X-Halro-Run-ID: run_xxxxxxxxxxxxxxxxxxxxxxxxxx

带此头时,Key 同时需要 inference 和 run:attach。Halro 会在 Provider I/O 前验证 Project、 Run 状态、TTL 和 Run 生命周期预算。不带该头的旧推理调用继续按原路径工作。

下表的 code 是 OpenAI 形状响应使用的字段。Anthropic 调用会得到相同的 HTTP 结果,但响应体没有 error.code,必须读取 error.type;完整映射见Gateway 错误码。

Run 归因请求头
状态OpenAI error.code含义
400invalid_run_idRun ID 格式错误
403run_governance_disabledProject 未启用运行治理
403gateway_key_scope_deniedKey 没有 run:attach
403run_budget_exceededRun 的可用预算不足,请求未到达 Provider
404run_not_foundRun 不存在或不属于当前 Project
409run_not_activeRun 已关闭或过期
503run_governance_unavailable无法验证运行治理权威状态,失败关闭

Gateway 为每个请求生成一个 req_ 加 26 个字符的 ID(16 字节随机数,小写 base32),例如 req_k9x8v5fcwde34gqbn142t2r9qw。它在处理器入口第一步就写入响应,先于鉴权和限流, 所以 401、429 在内的错误响应同样带着它;流式请求的 ID 在首个内容字节之前就已发出。

两个协议门面放的位置不同,各自对齐官方惯例:

请求 ID
端点位置
OpenAI 兼容(/v1/chat/completions 等)响应头 X-Request-ID,不在响应体里
Anthropic 兼容(/v1/messages)响应头 request-id;错误响应体还带顶层 request_id 字段

响应体里的 id(chatcmpl-...)是补全对象的 ID,和请求 ID 是两回事;OpenAI 格式的错误信封 (error.message/type/param/code)里没有请求 ID,只能从响应头拿。

SDK 已经替你解析好了这个头:

# OpenAI Python SDK
response = client.chat.completions.create(...)
print(response._request_id)
# 出错时异常对象上也有
except openai.APIStatusError as e:
print(e.request_id)
// OpenAI Node SDK
const response = await client.chat.completions.create({ ... });
console.log(response._request_id);

Anthropic 官方 SDK 同样通过 _request_id(Python / Node)暴露 request-id 头。 需要完整响应头时,用 SDK 的 with_raw_response(Python)/ withResponse(Node)变体。

用它来报障。 把错误路径的请求 ID 记进应用自己的日志;向 Halro 管理员反馈问题时给出这个 ID, 就能直接定位网关侧对应的记录 —— 比贴 message 文本可靠得多,message 是脱敏后的展示文案, 不保证稳定。

v0.9.0 起,GET /v1/models 和 GET /v1/models/{id} 返回当前路由表中 Project 可见的公开别名。 Key 需同时有 inference 和 discovery;旧 Key 不会自动扩权。单个别名不存在或无权访问均返回 404。 owned_by 固定 halro、created 固定 0,不调用上游、不写 Ledger;列表不证明能力或健康。 这两个端点仍为 experimental。示例与限制见模型发现。

v0.9.0 另外增加发现与资源面的固定上限:每 Key 600 次/分钟、每 Project 5,000 次/分钟,返回 429 rate_limit_exceeded 与 Retry-After;Project RPM=0 不会关闭它。

下表列出推理路径原有的七类触发点;上面的本地请求上限是额外边界。rate_limit_exceeded 可出现在鉴权前的来源限流或鉴权后的不同限制中:

七个会返回 429 的地方
触发点code何时发生
每源地址限流rate_limit_exceeded鉴权之前。匿名调用方能索取的工作量本身是有界的
Project 每分钟请求数rate_limit_exceeded鉴权之后,管理员在 Project 上配置
Project 每分钟 token 数token_rate_limit_exceeded同上
Project 并发concurrency_limit_exceeded同上
Deployment 并发deployment_concurrency_limit_exceeded管理员在 Deployment 上配置
全部可选 Provider 都到并发上限provider_concurrency_limit_exceeded同上
上游服务商限流provider_rate_limit上游,Halro 透传

Token Guard 不在这张表里。它拒绝时返回 403 token_guard_blocked,可能是本次尝试超过成本 上限,也可能是异常使用触发的 temporary block。前者需要调整请求、模型或策略;后者等待 block TTL 或由管理员处置。按 429 立即重试会形成无效循环。

Project/Key 隔离见案例一, Token Guard 调优和预算拒绝见案例三。

Retry-After 只在能算出一个正数等待时长时才写入。每源限流与 Project 限流会带上它;Deployment 并发那条不设置;上游透传的 429 只在上游自己带了可解析的头时才有。客户端必须处理这个头缺失的情况,不能无条件读取。

当前源码基线中公开 Gateway 数据面与 Run Governance 的 code × HTTP 状态 × 处理方式,以及 Anthropic 门面的 error.type 映射,见Gateway 错误码与处理方式。其中 price_unavailable、 sensitive_data_detected 和 unsupported_feature 会因触发阶段不同对应多个 HTTP 状态,客户端不能只按 状态码决定是否重试。