代理 API

应用方调网关的接口。形态上是 完全透传 —— 网关只在中间做认证、限流、改写凭证、记录日志、按需缓存/回退,不做任何上游 API 的封装或参数翻译

路径形态

/v1/{namespace}/{...upstream_path}
  • {namespace} 是 route 的对外别名。可以与某个 providers 表里的 key 同名(常见 1:1 配法,无需写 match.namespace),也可以独立命名,通过 match.namespace 显式绑定到一个 provider(透明迁移场景)。
  • {...upstream_path} 原样拼到命中的 provider 的 base_url 后。

例如:

客户端调用网关转发到
POST /v1/openai/v1/chat/completionsPOST https://api.openai.com/v1/chat/completions
POST /v1/anthropic/v1/messagesPOST https://api.anthropic.com/v1/messages
GET /v1/openai/v1/modelsGET https://api.openai.com/v1/models

认证

请求头携带 Gateway API Key:

Authorization: Bearer sk-gw-live-xxxxxxxxxxxx

网关会:

  1. 用 BLAKE3-keyed-hash 算出 hash,常时间对比数据库里的 hash。
  2. 检查 Key 状态(active / revoked)和 expires_at
  3. 异步更新 last_used_at

不会把这个 header 透传给上游 —— 网关会把它换成 providers.{name}.credential 解出的真实上游凭证。

请求体

直接按上游格式写,网关不做改写。但有两点会被网关读取:

  • model 字段 — 用来做 routes[].match.model_prefix 匹配,以及计入成本日志。
  • 整个 body 的 blake3 摘要 — 用作响应缓存的 key(若该路由 cache.enabled: true)。

流式响应

如果请求带 stream: true(SSE/chunked),网关会以流式方式回给客户端,不做整体缓冲(chunk 边发边转)。

流式响应会被缓存(若该路由 cache.enabled 且其他条件满足):chunks 在转发的同时被记录,流正常结束后整段写入 KV;命中时用 Body::from_stream 按原 chunk 边界 replay,客户端看到的 SSE 序列与首次一致。

流式的 token 用量在最后一帧到达时统计写入日志。

头部处理

类型行为
Authorization替换为上游真实凭证。
Host改写到上游主机名。
User-Agent透传,并写入日志。
Content-Type透传。
配置里的 providers.{name}.headers追加到上游请求(覆盖同名)。
上游响应 Content-Length / Transfer-Encoding由网关重新计算。
其他透传。

路由选择

按数组顺序逐条评估,第一条同时满足以下条件的路由被选中:

  • URL /v1/{namespace}/... 段等于 match.namespace(未显式设置时,默认取 primary.provider)
  • match.model_prefix 有值,请求体 model 字段以该前缀开头(没有 model 字段视为不匹配)

namespace 是对外暴露的 URL 段,primary.providerproviders 表里的 key —— 是两个独立概念,允许同一个 namespace 在后端切换到不同 provider。详见 配置参考 > routes。命中后:

  1. 检查 cache.enabled,且请求体是确定性的(temperature == 0top_p >= 0.999,或路由配了 cache.allow_nondeterministic: true,或带 X-Gateway-Cache-Force 头),则查缓存,命中则直接返回,响应头加 X-Gateway-Cache-Status: hit
  2. 否则按 primary 转发,失败重试至 retry.max_attempts 次。
  3. 仍失败且 trigger 命中 → 切到下一个 fallbacks[]
  4. 成功响应若满足缓存条件(≤ 20 MB、状态 2xx),写回 KV;流式响应也会缓存,replay 时保留 chunk 边界。
  5. 写日志,返回响应。

没有匹配的路由时,网关直接返回 404(错误码 not_found),不会再把 namespace 当作 providers 表的 key 做兜底转发。想暴露 /v1/<name>/...,必须在 routes[] 里写一条对应的条目(最简写法 - primary: { provider: <name> })。

响应头

网关附加的特殊头(其他都透传):

Header含义
X-Gateway-Request-Id本次请求的内部 ID,与 /admin/logs 中的 id 对应。
X-Gateway-Cache-Statushit / miss / refresh / bypass
X-Gateway-Cache-Key命中/写入的缓存 key 前缀(截断到 48 字符,便于排障)。
X-Gateway-Cache-Age仅 HIT 时出现,缓存条目年龄(秒)。
X-Provider实际命中的供应商(可能是 fallback)。

错误码

HTTP场景
401缺少 Authorization 或 Key 无效 / 已撤销 / 已过期。
402命中 budgets[].thresholds[].action: block
404URL 中的 {namespace} 没有对应的 route。
408请求超时(server.request_timeout_ms 触发)。
429命中 limits[] 中的 RPM / TPM / 并发上限。
502上游所有重试 + fallback 都失败。
504上游超时,且无可用 fallback。

错误响应体格式:

{
  "error": {
    "code": "rate_limited",
    "message": "request exceeded RPM limit"
  }
}

健康检查 / 指标

路径用途
GET /healthz进程存活,总是 200 OK
GET /readyz数据库 + 上游可达性检查,失败返回 503
GET /metricsPrometheus text 格式指标,详见 可观测性

这些路径无需认证。