OpenRouter 的延迟从哪来:把排队、路由与生成分开看

2026-08-18

接入层这类东西最难查的地方在于:请求慢了,你手上只有一个总时间。到底是这一层多绕了一圈,还是模型本身就在慢慢吐,从调用方看是一模一样的。OpenRouter 的说明散在四五页文档里,得自己拼。

下面按官方文档能核到的口径把一次请求分段,逐段说怎么判定、怎么调、调完怎么验、以及什么情况说明根本不该往这个方向查。文中不涉及任何延迟数值与快慢比较——那类数字随供应商和时段变。

一次请求被切成哪几段

《Latency and Performance》页(openrouter.ai/docs/guides/best-practices/latency-and-performance)自述了这一层的构成:用 Cloudflare Workers 做边缘计算以尽量靠近调用方、在边缘缓存用户与 API key 数据、以及路由逻辑本身的处理时间。这三条对应的是「进门到选定端点」这一段。

同一页还点了三处会让这一段变长的情况:

  • 缓存冷启动:某个区域刚开始承接请求时边缘缓存是空的,填充完之后这部分回到常态(文档自述)。
  • 余额检查:文档写明,当账户余额偏低、或某个 API key 接近它自己配置的信用额度上限时,会做额外的数据库检查,并在这种状态下更激进地让缓存过期以保证计费准确,直到补上额度为止。账户状态会影响这条链路,这点最容易漏。
  • 回退:用模型路由或供应商路由时,首选的模型或供应商失败会自动试下一个。文档明说失败的首轮会给这一次调用加上等待;同时 OpenRouter 会记录供应商的失败情况并尽量绕开不可用的供应商,让这份等待不是每次都付。

再往后是 router 那条流水线。《Router Metadata》页(openrouter.ai/docs/guides/features/router-metadata)开头写明,router 每个请求要走多阶段流水线:挑供应商、可能压缩上下文、可能跑 guardrail、可能调服务端工具、可能对回退重试;默认这些一律不出现在响应里——你平时看到的响应把中间发生的事全藏起来了。

最后一段才是供应商真正生成。FAQ 页写明平台会给出每个模型在各供应商上的 latency(口径是 time to first token)与 token 吞吐两个指标。首字慢和吐得慢是两件事,调的也不是同一个字段

怎么确认卡在哪一段

别靠猜,先把 router 那段打开。X-OpenRouter-Metadata逐请求 opt-in 的请求头,取 enabled 时响应里会多出 openrouter_metadata

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -H "X-OpenRouter-Metadata: enabled" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{ "role": "user", "content": "Hello" }]
  }'

(示例里的 model slug 是官方文档当时写的示例值,平台上有哪些模型随时在变,别当清单用。)

文档写明该头大小写不敏感,只认 enableddisabled,其它任何值——包括拼错的、空字符串——一律落回 disabled,不带这个头时也是 disabled。四条补全路由都接了它:/api/v1/chat/completions/api/v1/messages/api/v1/responses、旧版 /api/v1/completions。流式下这个字段在 data: [DONE] 之前的最后一个 chunk 上给出(Chat Completions / Responses),Anthropic Messages 则挂在终止的 message_stop 事件里。

拿到之后照字段读,每个字段回答的是不同的问题:

字段它能告诉你什么
strategy这次走的路由策略,取值有 directautofreelatestaliasfallbackparetobodybuilderfusion
attempt从 1 开始计数的「第几次尝试成功」。大于 1 意味着前面的尝试失败过、发生了回退
attempts可选。重试时每一次的 provider / model / status
endpoints候选端点快照,含 total 与哪一个被 selected
region处理该请求的边缘区域(拿得到时才有)
pipeline可选。真正改动了请求或响应的插件阶段
is_byok这次是否用了 BYOK 的供应商 key

attempt 大于 1,说明这次确实为失败的首轮付了等待,对应上面那条「回退」;pipeline 非空,说明时间还花在了别的地方。文档给出的阶段类型有五类:guardrailpluginserver_toolsresponse_healingcontext_compression,并写明没实际跑的插件不会出现——比如上下文压缩发现输入本来就装得下,这个阶段就不会占位。这份清单会继续加,未知的阶段类型当作不透明处理即可。

有几处坑得先知道,否则你会以为是自己接错了:

  • 缓存命中永远不带 openrouter_metadata。文档写明流式与非流式的缓存回放都会剥掉这个字段,因为命中时看到的路由信息未必是当初产生这份缓存内容的那次路由。
  • 500 的响应也不带。文档写明 500 会被抹成通用信息、metadata 一并省略;其它 5xx(502503504529)在 opt-in 时仍会带。
  • 认证失败、限流失败以及 API 边缘的校验拒绝不带,因为它们发生在 router 还没有可用状态之前。这类情况文档指的路是:拿响应头 X-Generation-Id,事后用 GET /api/v1/generation 取那条 generation 记录。

流式还有一个判定点:SSE 流里会周期性出现注释行防止连接超时,形如:

: OPENROUTER PROCESSING

文档写明这类注释按 SSE 规范可以安全忽略。反过来讲,能持续收到它就说明连接是活的、只是还没有内容,这跟「连接断了」是两回事。文档同时警告:手写解析时必须跳过以 : 开头的行,把注释行喂给 JSON.parse 会抛异常,没接住就把整个流式循环打崩了。

文档语义给出的可调项

先看默认状态。《Provider Routing》页(openrouter.ai/docs/guides/routing/provider-selection)写明默认策略是跨供应商做偏向价格的负载均衡,共三步:先优先考虑近期没有出现明显故障的供应商,再在这些稳定候选里挑最低价的那一批、按价格的平方反比加权选一个,其余作为回退。注意文档给的例子里,近期有过故障的那一家并没有被剔除,只是被排到最后才试——这是排序,不是过滤。这里有一条容易忽略的联动——一旦设了 sortorder,负载均衡就被关掉,router 改成按顺序依次尝试。

排序字段只有三档,sort"price"(最低价优先)、"throughput"(最高吞吐优先)、"latency"(最低延迟优先)。:nitro 变体是吞吐排序的别名,文档写明它「exactly equivalent」于把 provider.sort 设成 "throughput"

多模型场景下 sort 还能写成对象,带 bypartition 两个子字段。partition 的默认值是 "model"(这是文档写明的默认值,随版本可能变动),含义是先按模型分组再排序,于是首选模型的端点总是先被试,跟它当前的表现无关;设成 "none" 才会跨全部候选模型做全局排序:

{
  "models": ["anthropic/claude-sonnet-4.5", "openai/gpt-5-mini"],
  "provider": {
    "sort": { "by": "throughput", "partition": "none" }
  }
}

(这段 models 里的两个 slug 同样只是官方文档当时写的示例值,不是可用模型清单,平台上有哪些模型随时在变。)

再往上是两个阈值字段:preferred_min_throughput(tokens/sec)与 preferred_max_latency(秒)。两者都可以写成一个数字(文档写明按 p50 生效),也可以写成带 p50 / p75 / p90 / p99 四档百分位的对象;给了多个百分位时,要全部满足才算进入优先组。指标本身按滚动的 5 分钟窗口统计(这是文档写明的口径,随实现可能变动)。

这两个字段最要紧的是强度:文档明写它们不保证你能拿到达到该水平的供应商或模型,只是让达标的被优先;不达标的端点是被移到列表末尾而不是被排除,所以设了不会导致请求跑不起来。文档还专门把它和 max_price 对比——后者达不到时会直接让请求跑不成。这是两种语义,别混着理解。

还有几个跟等待时间直接相关的开关:

  • allow_fallbacks,默认 true(文档写明的默认值)。关掉它,主供应商不可用时就不再试备选——省掉的是失败首轮那份等待,换来的是失败即失败。
  • 响应缓存(openrouter.ai/docs/guides/features/response-caching):加 X-OpenRouter-Cache: true 逐请求开启,或在 preset 里配 cache_enabled。文档写明缓存发生在 OpenRouter 这一层、在请求到达任何供应商之前,不需要供应商侧支持;流式命中会通过同一条流式管线回放。强制刷新用 X-OpenRouter-Cache-Clear: true,只对当前请求对应的那一条缓存项生效,且必须在缓存已开启时才有作用。
  • 流式取消:文档写明中断连接即可取消,对支持的供应商会立即停止模型处理与计费。文档把供应商分成「支持」和「当前不支持」两组列出,具体名单以官方文档最新内容为准。
  • 账户侧:《Latency and Performance》页建议维持健康余额、把自动充值的触发线设高一些,以避免被迫的额度检查(其中的金额建议本文不写,以官方原文为准)。

以上为按官方文档中的字段语义组合的示例,未经实测,以官方文档与 API 的实际响应为准。

Windows 侧:上面的 curl 是 Linux/macOS 的 shell 写法。PowerShell 里 curl 默认是 Invoke-WebRequest 的别名,要用 curl.exe-d 后面单引号包 JSON 在 PowerShell 与 cmd 里也不成立,通常改成双引号转义,或把请求体放进文件用 -d "@body.json"。看响应头加 -i。这段是通用命令行做法,不是 OpenRouter 官方文档的内容。

调完怎么验证

验证盯的还是 metadata:

  1. 仍带 X-OpenRouter-Metadata: enabled 重跑,看 attempt 是否回到 1attempts 是否消失——回退那份等待没了才算砍对。
  2. strategy 是不是你以为的那条路径。设了 order 却看到 auto,说明配置没生效在你以为的那层。
  3. endpoints.totalavailable 里谁被 selected。改了排序字段还是选中同一个端点,说明候选池里本来就只有它。
  4. 开了响应缓存就看响应头 X-OpenRouter-Cache-Status 是否从 MISSHITX-OpenRouter-Cache-AgeX-OpenRouter-Cache-TTL 也在这时候读;X-OpenRouter-Cache-Source-Id 指回当初填充这条缓存的那次 generation。
  5. pipeline 里那几个阶段还在不在。文档写明 no-op 的插件不会出现,「它消失了」本身就是可读的信号。

解析这份 metadata 时按文档的说法宽松处理:它的形状是只增不减的,新的可选字段和阶段类型可能不走废弃流程就出现,遇到不认识的忽略掉即可。另外旧的请求头名 X-OpenRouter-Experimental-Metadata 仍被接受,文档建议改用 X-OpenRouter-Metadata。本文提到的请求头、字段名与默认值都会随版本走,一律以官方文档最新内容为准,别把它们硬编码成你系统里的断言。

什么情况说明不是这个原因

这一步最容易被省掉,但它决定你会不会在错误的方向上耗一天:

  • attempt1attempts 不存在、pipeline 是空的:这次请求既没有回退也没有额外插件跑过,时间基本落在供应商生成那一段。再怎么改 provider 偏好也只是换一个端点。
  • 慢的是「吐完」而不是「首字」。平台侧给的两个指标是分开的:latency 的口径是 time to first token,吞吐是另一回事。整体时长长但首字很快时,往 preferred_max_latency 上使劲没有意义,该看吞吐向的字段。
  • endpoints.total 只有一个候选。排序类字段、partition、阈值字段改变的只是「谁排前面」,候选只有一个时它们不会带来任何差别。
  • 响应里根本没有 openrouter_metadata:先分清是缓存命中(文档写明必然不带)、500(被抹掉),还是认证与限流失败(router 还没拿到状态)。这三种都不代表请求头没开对,别去反复改它。
  • 首字迟迟不来、但 SSE 里一直有 : OPENROUTER PROCESSING:连接是活的,不在网络与超时配置上;反过来,解析器在这行上崩了那是客户端的 bug。
  • 只在某段时间或某个区域出现的慢:冷缓存与余额检查都属于状态相关因素,前者随缓存填充回到常态,后者要等额度补上。状态恢复后用同一段 prompt 重跑一次,再决定要不要动路由配置。

这一层能给你的是归因,不是加速。openrouter_metadata 的定位文档里写得很清楚——调试路由决策、归因延迟与成本、审计流水线行为。把时间花在哪一段说清楚了,才知道该动配置、动模型,还是这次本来就不该往接入层查。


本文依据 OpenRouter 官方文档(openrouter.ai/docs)于 2026-08-18 的公开内容整理。 该平台闭源,本文只复述官方文档写明的机制,不推断其内部实现我们没有对文中涉及的功能做过实测,因此不涉及界面外观与运行表现的任何描述。 该平台的供应商、模型与路由策略随时变动,文中不列具体供应商名单与模型清单; 价格、额度与限流的具体数值请以官方定价页与用量说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。