首页
/ OmniRoute 配额遥测(Quota Telemetry)深度解析:五种可信状态、数据源优先级与自适应路由的衔接实现

OmniRoute 配额遥测(Quota Telemetry)深度解析:五种可信状态、数据源优先级与自适应路由的衔接实现

2026-09-07 13:17:08作者:胡唯隽

导读

OmniRoute 是一个面向多供应商(352+ providers、1200+ 模型)的 AI 网关,其调度器在把请求转发给上游供应商之前,必须先回答一个问题:“这条 provider 连接当前还有多少可用的配额?”本文围绕 docs/OMNIROUTE_QUOTA_TELEMETRY.md 展开,系统讲解 OmniRoute 如何把“供应商配额遥测”与“Ghostlight 记账”两类概念严格分离,如何用五种有据可依(truthful)的状态描述配额,如何按固定优先级合并来自官方 API、认证 usage API、显式映射的响应头、管理员配置与本地估算等异构数据源,并最终把结论喂给自适应路由做扣分、排除与故障转移决策。读完你将掌握这套状态机的数据模型、判定算法、响应头规范化规则,以及它与路由评分、请求前预算账本(pre-request budget ledger)的衔接关系——全部都有源码与测试佐证。

一句话核心设计原则:遥测 ≠ 记账

文档的第一句话就点明了整个设计的地基:OmniRoute separates provider quota telemetry from Ghostlight accounting(OmniRoute 将供应商配额遥测与 Ghostlight 记账分离)

两者虽然都关心“额度”,但语义完全不同:

  • 供应商配额遥测(provider quota telemetry):描述的是外部世界——上游供应商(OpenAI、Anthropic、Gemini、Codex、GLM、Kimi、DeepSeek 等)向某条连接报告的、客观存在的剩余容量。它可以来自官方 API、usage API、响应头或管理员显式配置,本质上是“外部容量”的观测。
  • Ghostlight 记账 / Ghostlight internal budgets:描述的是内部治理——管理员定义的配额池(quota pool)与预算上限,决定“哪些 API Key 可以消费某个 provider 池、适用 hard/soft/burst 哪种策略”。

这一区分在同仓库的另一份姊妹文档 docs/OMNIROUTE_ALLOCATION_HANDOFF.md 里表述得更直白:“Quota pools define which API keys may consume a provider pool… Provider quota is external capacity reported by a provider or an explicitly configured source. Ghostlight internal budgets are governance limits defined by the administrator.”

把两类数据分开建模的价值在于:遥测层永远只描述“供应商实际告诉我/我观测到的现实”,不允许用内部策略或猜测去冒充外部事实。这在后面的五种状态与数据源优先级中贯穿始终。

五种“有据可依”的配额状态(Truthful States)

文档给出了五种状态,每种都有严格、收敛的语义。在源码 src/lib/quota/providerQuotaTelemetry.ts 中它们被定义为一个字符串联合类型:

export type ProviderQuotaStatus =
  "healthy" | "approaching_limit" | "exhausted" | "unavailable" | "unknown";

逐个解释其“真实语义”:

状态 触发条件 关键纪律
healthy 某个数据源报告了可用的剩余容量 有正向证据,才允许说健康
approaching_limit 数据源报告的剩余容量 ≤ 配置的阈值(默认剩余比例 20%) 有正向数据、但已逼近上限
exhausted 数据源报告容量为 0,或用量已达到上限 只有当数据源报告“归零/到顶”时才可发出;不得由遥测系统自己脑补
unavailable 存在受支持的数据源,但全部读取失败/未返回数据 说明“有源可查但没查到”,不等于耗尽
unknown 不存在受支持的数据源,或没有任何已知的 provider 限额 中立、无证据状态

这条语义划分里最容易被误读、也最关键的是 unknown:文档明确写出 “Unknown is not exhausted and does not disable a provider(unknown 不是 exhausted,不会禁用一个 provider)”。也就是说,对于没有接入任何遥测源的供应商,网关宁可放行,也不因为“我不了解它”而惩罚它。这与 src/lib/quota/providerQuotaState.ts 中预算账本的 fail-open(失败开放) 设计原则一致:配额追踪未配置时,未知预算应被当作可用,保留既有的路由行为。

状态推导算法(状态机如何“合成为单一状态”)

单一连接的配额状态最终由一个综合函数从若干 QuotaValue(见下节)推导而来,核心实现在 statusForValues(),位于 src/lib/quota/providerQuotaTelemetry.ts

function statusForValues(values: QuotaValue[], approachingThreshold: number): ProviderQuotaStatus {
  if (values.length === 0) return "unknown";

  const exhausted = values.some(
    (value) =>
      (finite(value.remaining) && value.remaining <= 0) ||
      (finite(value.used) && finite(value.limit) && value.used >= value.limit)
  );
  if (exhausted) return "exhausted";

  const approaching = values.some((value) => {
    if (!finite(value.remaining) || !finite(value.limit) || value.limit <= 0) return false;
    return value.remaining / value.limit <= approachingThreshold;
  });
  return approaching ? "approaching_limit" : "healthy";
}

推导规则可以概括为三条,越靠前优先级越高:

  1. 耗尽优先:任一维度出现 remaining <= 0,或 used >= limit,立即判为 exhausted
  2. 逼近其次:存在任一维度满足 remaining / limit <= approachingThreshold,判为 approaching_limit
  3. 默认健康:否则为 healthy

注意两点实现细节:approachingThreshold 的默认值是 0.2(即剩余不足 20% 才进入 approaching_limit),且该值是通过 collectQuotaState()options.approachingThreshold 传入的(见 providerQuotaTelemetry.ts),属于可调参数而非魔法常量;同时“耗尽”与“逼近”都按维度(dimension)粒度逐项判定——requests 耗尽不意味着 credits 也耗尽,两者会独立参与后续的数据源择优。

数据模型:把遥测压缩成结构化的 QuotaValue

为了让不同供应商、不同协议返回的异构配额信息可统一计算,OmniRoute 定义了一套 provider 无关的契约(文件注释自称 “Provider-neutral quota telemetry contracts and header normalization”)。核心对象是 QuotaValue,见 providerQuotaTelemetry.ts

export type QuotaDimensionName =
  | "requests" | "tokens" | "input_tokens" | "output_tokens"
  | "credits" | "currency"
  | "daily_requests" | "weekly_requests" | "monthly_requests"
  | "rate_limit" | "unknown";

export type QuotaValueSource =
  "provider_api" | "response_headers" | "configured" | "estimated" | "unknown";

export type QuotaConfidence = "authoritative" | "high" | "medium" | "low" | "unknown";

export interface QuotaValue {
  dimension: QuotaDimensionName;   // 度量维度
  limit?: number;                   // 上限
  used?: number;                    // 已用
  remaining?: number;               // 剩余
  resetAt?: string;                 // 重置时间(ISO)
  unit?: string;                    // 单位,如 "USD"、"tokens"
  source: QuotaValueSource;         // 来自哪类数据源
  confidence: QuotaConfidence;      // 置信度
}

这些字段与文档主题直接呼应:requests/tokens/credits/currency/daily_requests/weekly_requests/monthly_requests/rate_limit 等维度覆盖了“请求数配额”“Token 配额”“余额/积分”“滚动时间窗配额”等常见供应商限额形态;而 sourceconfidence 则是“有据可依”纪律的数据化表达——每条遥测数据都必须声明它来自哪里有多可信

一条连接的聚合结果由 ProviderQuotaState 承载:

export interface ProviderQuotaState {
  providerId: string;
  connectionId: string;
  supported: boolean;   // 是否存在任何受支持的数据源
  fetchedAt: string;    // 抓取时间
  values: QuotaValue[];
  status: ProviderQuotaStatus;
  error?: string;
}

数据源优先级:五级阶梯与按维度择优

文档给出的数据源偏好顺序为:官方 provider API → 认证后的 usage API → 显式映射的响应头 → 管理员配置 → 本地估算 → unknown。源码用一张有序表把它固化下来,见 providerQuotaTelemetry.ts

const SOURCE_PRIORITY: QuotaSourceKind[] = [
  "provider_api",     // 0:供应商官方配额/用量接口(权威)
  "response_headers", // 1:经显式映射的响应头
  "configured",       // 2:管理员显式配置
  "estimated",        // 3:本地估算
  "unknown",          // 4:未知
];

(文档措辞里 “authenticated usage API” 对应 dashboard 侧大量基于登录态抓取的供应商配额接口;在遥测抽象层以 provider_api 作为最高权威源。)

每个维度独立择优:collectQuotaState

文档强调的“来源按优先级取最优”并不是在整条连接级别粗暴取第一个,而是按维度(per dimension)择优。核心聚合逻辑在 collectQuotaState()providerQuotaTelemetry.ts):

export interface QuotaSourceAdapter {
  kind: QuotaSourceKind;
  supports(providerId: string): boolean;
  read(connection: ProviderConnectionForQuota): Promise<QuotaValue[]>;
}

export async function collectQuotaState(
  connection: ProviderConnectionForQuota,
  adapters: QuotaSourceAdapter[],
  options: { approachingThreshold?: number; fetchedAt?: string } = {}
): Promise<ProviderQuotaState> {
  // ...
  for (const adapter of adapters) {
    if (!adapter.supports(connection.provider)) continue;
    supported = true;
    try {
      const values = await adapter.read(connection);
      for (const value of values) {
        const current = byDimension.get(value.dimension);
        // 仅当新值的来源优先级更靠前时才覆盖旧值
        if (!current || sourceRank(value.source) < sourceRank(current.source)) {
          byDimension.set(value.dimension, value);
        }
      }
    } catch (error) { /* 记录 lastError,不中断 */ }
  }
  // ...
  status:
    values.length > 0
      ? statusForValues(values, options.approachingThreshold ?? 0.2)
      : supported ? "unavailable" : "unknown",
}

这里有三个容易被忽略、但正是文档纪律落地的设计点:

  1. 适配器是插拔式的:每种数据源实现一个 QuotaSourceAdapter(声明自己的 kind、回答 supports(providerId)、实现 read())。新增一个供应商的官方配额源,只需新增一个适配器,不必改动聚合逻辑。
  2. supported 与状态解耦:只要存在“受支持但读取失败”的情况,最终状态是 unavailable;只有“根本没有任何受支持的源”才是 unknown。这精确复刻了文档中两个状态的定义。
  3. 失败不致命:单个适配器抛错只记录 lastError,不会让整条连接失去其他更优来源的数据。

本地估算的红线

文档特别强调:“Local estimates are never presented as provider billing data(本地估算绝不被当作供应商计费数据呈现)。” 这体现在两点:estimatedSOURCE_PRIORITY 中仅高于 unknown,永远打不过任何真实来源;同时所有估算值都带 source: "estimated" 与低置信度标记,下游渲染与路由层据此识别其非权威身份,不会把“我猜的大概额度”显示成“供应商账单”。

响应头规范化:只认显式映射,绝不全局猜名字

供应商常在 HTTP 响应头中夹带限额信息(如 x-ratelimit-remaining-tokens)。文档对此给出了一条硬约束:

Response headers are parsed only through an explicit provider mapping. Generic header names are not assumed globally.(响应头只通过显式映射解析,不全局假设通用头名。)

这背后的原因是现实世界头名极度混乱:同样是“剩余限额”,OpenAI 用 x-ratelimit-remaining-tokens,Anthropic 用 anthropic-ratelimit-input-tokens-remaining,OpenRouter 可能只给 x-ratelimit-remaining。如果网关为图省事对所有 provider “猜”同一套头名,就会把 A 供应商的含义套到 B 供应商头上,制造出看似可信实则错误的配额。

显式映射模型 RateLimitHeaderMapping

抽象层提供了一个“显式映射”结构,见 providerQuotaTelemetry.ts

export interface RateLimitHeaderMapping {
  limit?: string;       // 提供 limit 数值的头名
  remaining?: string;   // 提供 remaining 的头名
  reset?: string;       // 提供重置时间/窗口的头名
  retryAfter?: string;  // 429 时的 retry-after 头名
  dimension?: QuotaDimensionName;  // 这些头归属的维度,默认 "rate_limit"
  unit?: string;
}

export interface RateLimitSnapshot {
  providerId: string;
  connectionId: string;
  capturedAt: string;
  requestLimit?: number;
  requestsRemaining?: number;
  resetAt?: string;
  retryAfterSeconds?: number;
  source: "response_headers";
}

parseRateLimitHeaders()providerQuotaTelemetry.ts)严格按照传入的 mapping 去取头、做数值解析,只有 mapping 里声明过的头名才被读取;若 mapping 指向的头全部缺失则返回 null(意味着“本 provider 没有可解析的头数据”,而不是“配额为 0”)。解析出的结果同时产出两条信息:

  • 一个归一化的 QuotaValuesource: "response_headers"confidence: "high"
  • 一个 RateLimitSnapshot,供调用方记录每次抓取的限额快照。

重置时间解析的工程细节

reset 头的格式在各供应商间五花八门——可能是绝对时间戳(秒/毫秒)、相对秒数、ISO 字符串或带单位的时长。resetHeaderToIso()providerQuotaTelemetry.ts)对此做了稳健归一化:

function resetHeaderToIso(value: string | undefined): string | undefined {
  const parsed = numberHeader(value);
  if (parsed === undefined)
    return value && !Number.isNaN(Date.parse(value)) ? new Date(value).toISOString() : undefined;
  const milliseconds = parsed > 10_000_000_000 ? parsed : parsed * 1000; // >1e10 视为毫秒,否则视为秒
  return new Date(milliseconds).toISOString();
}

即:超过 10_000_000_000 的裸数字按毫秒时间戳处理,否则按时间戳乘 1000;非数字则尝试按 ISO 日期解析;都无法解析就返回 undefined,绝不抛异常或产出 NaN。

Provider 级的头适配层

在遥测抽象层之下,仓库还提供了一套面向具体供应商的头部解析适配器 src/lib/quota/quotaAdapters.ts,按供应商区分支持:

  • Anthropicanthropic-ratelimit-input-tokens-limit / -remaining / -reset
  • OpenAI / 标准 OpenAI 兼容x-ratelimit-limit-tokens / x-ratelimit-remaining-tokens / x-ratelimit-reset-tokens
  • OpenRouter / 通用请求级x-ratelimit-limit / x-ratelimit-remaining / x-ratelimit-reset

其中 parseResetMs()"60s""100ms""0.5s""1m""1h"、ISO 字符串、裸秒/毫秒时间戳、以及大于 1_000_000_000 的 epoch 秒/毫秒值都做了兼容换算。而 applyQuotaHeadersToState()quotaAdapters.ts)则把解析结果直接写回本地配额账本(recordProviderQuotaUsage),让响应头数据参与到请求前预算的累计中去。

从遥测状态到路由决策:扣分、排除与中性

配额遥测单独存在没有意义,它要服务的是调度。OmniRoute 的自适应路由(adaptive routing)把 ProviderQuotaStatus 作为候选评分的一个显式因子。见 src/lib/routing/adaptiveRouting.ts

function quotaFactor(quota: ProviderQuotaStatus): number {
  switch (quota) {
    case "exhausted":        return 0;
    case "approaching_limit": return 0.65;
    case "unavailable":      return 0.9;
    case "unknown":          return 1;
    case "healthy":          return 1;
    default:                 return 1;
  }
}

这组系数精确落实了文档语义,值得逐条对照:

遥测状态 评分系数 对路由的意义
exhausted 0(且候选不可用) 完全出局,避免撞上必然的 429
approaching_limit 0.65 仍可被选中,但被显著降权(默认分数低于同条件 healthy 的候选)
unavailable 0.9 轻微降权:有源可查但没查到,不做重罚
unknown 1(中性) 不升不降,不因未知而禁用
healthy 1 满权重放行

候选的“可路由性(eligible)”判定在 scoreCandidate() 中把 quota !== "exhausted"allocation !== "deny"circuit !== "open" 并列(adaptiveRouting.ts)。换言之,在候选被排除的三条硬门槛里,配额维度只有 exhausted 能触发排除,unknown 永远达不到这个门槛。

可观测的预演接口:不做真实请求

这套评分还暴露为一个确定性的只读预演接口POST /api/omniroute/route/previewsrc/app/api/omniroute/route/preview/route.ts)。它接收一批候选(含 quota 枚举 healthy | approaching_limit | exhausted | unavailable | unknown),调用 rankCandidates() 返回排序、得分、eligible 与 reasons,并且永远不会向上游发真实请求(响应中显式返回 liveRequestExecuted: false),同时受管理鉴权保护。这为验证“某状态如何影响选路”提供了零风险的沙盒。

与失败转移(Failover)策略的配合

配额状态是“事前”判断,失败转移是“事中/事后”兜底。二者在 src/lib/routing/adaptiveRouting.ts 中衔接:shouldFailover() 依据失败分类决定是否切换连接——rate_limit(命中 429)与 timeout 默认可重试,authentication_error(如 401)不重试。配额遥测的价值恰恰在于把“事前”做到足够好,让“事中”的 429 尽量不发生:既然 exhausted 已经提前出局、approaching_limit 已被降权,调度器就不会反复把流量打向一个已知没额度的供应商,再来依赖 429 触发的 failover。

遥测的本地化延伸:请求前 Token 预算账本

文档描述的遥测主要回答“供应商还有多少容量”;与之互补的是仓库内另一套面向“内部本地预算”的账本 src/lib/quota/providerQuotaState.ts,它按 (connectionId, model) 记录每个固定时间窗口内已用 Token,供请求发出前查询“这条连接还付不付得起这个请求”。

其注释点明了与遥测的关系与分工(providerQuotaState.ts):遥测解决供应商 429(外部容量),预算账本解决自己的 429(本地 per-window 预算);两者都遵循 fail-open(账本读不到或窗口过期即视为 known: false,当作预算充足,保持既有路由行为)。

调度侧 src/lib/quota/quotaScheduler.tscanAffordRequest() 把账本快照与 estimateChatTokenCost(requestBody) 结合,回答“可不可以走这条连接”;预算不足时返回 affordable: false,原因是 exhaustedinsufficient_budget,由调用方走与 429 相同的 failover 机制换一条连接。值得注意的是该调度器被设计成 永不抛异常、永不阻塞请求路径(fail-open 默认 affordable: true),与 unknown 不惩罚路由、遥测失败不中断聚合是同一条工程哲学的三种体现。

Dashboard 呈现层的“不撒谎”纪律

配额状态最终要展示给用户。仓库的用量面板在解析来自认证 usage API 的原始配额数据时,同样遵守“真实呈现”的纪律。src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts/dashboard/usage/components/ProviderLimits/quotaParsing.ts) 为不同类型供应商做了差异化归一化:

  • unlimited 空项剔除github 这类“无限额度但字段为空”的条目(isUnlimitedEmpty)直接过滤,不显示成 0 额度;
  • 跨重置窗口的过期数据复位getResetAdjustedQuota() 检测到 resetAt 已过且记录中仍有 pending usage 时,把用量视为 0、剩余比例视为 100%,避免把“上一窗口的老黄历”当成本窗口现状(quotaParsing.ts/dashboard/usage/components/ProviderLimits/quotaParsing.ts#L108-L122));
  • 滚动窗口的规范化排序quotaWindowRank 从配额键形状识别 session/hourweek/7dmonth 等窗口并保持其固有顺序(quotaParsing.ts/dashboard/usage/components/ProviderLimits/quotaParsing.ts#L37-L48)),让 session → weekly → monthly 的层级不被按剩余比例重排所破坏;
  • Provider 级定制:针对 github、GLM 家族、antigravity、codex、claude、deepseek、kilocode、agentrouter 等各有专用解析(如 Claude 把 extraUsage 折叠进配额、AgentRouter/KiloCode 的 USD 余额走 credits 渲染),避免“通用渲染器”误读特定字段。

这些都从侧面印证了遥测数据源(认证 usage API)在仓库中的真实落地形态:供应商以配额对象(如 Codex 的 session/weekly/gpt_5_3_codex_spark_*、GLM 的 session/weekly/mcp_monthly)返回数据,呈现层负责忠实、无歧义地还原,而不是自作主张美化或合并。

测试佐证:未知不杀路由,优先级按维度生效

文档的核心主张都能在同名单元测试 tests/unit/quota-telemetry-adaptive-routing.test.ts 中找到对应的断言,这组测试直接验证了遥测契约与路由的衔接:

  1. “unknown 不禁用路由”(第 12-30 行):空适配器下 collectQuotaState 返回 supported: falsestatus: "unknown"、空 values;随后 rankCandidates 在唯一候选的 quota 为 unknown 时仍选中该 provider。
  2. “数据源优先级按维度生效”(第 32-58 行):同时存在 estimatedprovider_api 两个适配器,都对 requests 维度返回数据,最终保留的是 source 为 provider_api、剩余 80 的那条,状态为 healthy。
  3. “响应头仅经显式映射解析”(第 60-71 行):传入 mapping { limit: "x-limit", remaining: "x-remaining", reset: "x-reset", dimension: "requests" } 时正确解析出 remaining: 12;而当 mapping 指向不存在的头({ limit: "missing" })时返回 null 而非臆造数据。
  4. “耗尽出局、逼近降权”(第 73-120 行):exhausted 候选 eligible: false、open circuit 候选被排除、approaching 候选得分 < 1,最终选中 healthy(未知配额)候选。
  5. “失败分类与 failover 的关系”(第 122-137 行):504 超时属于可转移的 timeout,401 属于不可转移的 authentication_error
  6. “unknown 辅助构造器是显式的”(第 139-148 行):unknownQuotaState("p", "c", ...) 精确返回 supported=false、空 values、unknown 状态的完整结构。

此外,路由预演 API 的 schema 也把 quota 限定为这五个枚举值(preview/route.ts),从入参层就杜绝了“第六种臆造状态”进入评分器的可能。

小结:一套可验证、可追溯、永不撒谎的配额语义

OmniRoute 的配额遥测设计可以浓缩为几条可迁移到任何网关/调度系统的工程原则:

  1. 外部容量观测与内部治理记账分离:遥测只描述供应商事实(docs/OMNIROUTE_QUOTA_TELEMETRY.md、docs/OMNIROUTE_ALLOCATION_HANDOFF.md),防止内部策略污染外部判断;
  2. 状态必须“有据可依”:healthy / approaching_limit / exhausted / unavailable / unknown 各有严格的证据门槛,exhausted 只许由“报告归零或到顶”触发,unknown 永远中性、不惩罚 provider(providerQuotaTelemetry.ts);
  3. 多源按维度择优:provider_api > response_headers > configured > estimated > unknown 的优先级固化在 SOURCE_PRIORITY 中,每个维度独立择优,单源失败不拖累整体,本地估算绝不冒充计费数据;
  4. 响应头只认显式映射:绝不在全局猜测通用头名,靠 RateLimitHeaderMapping 让每个 provider 自己声明头含义(providerQuotaTelemetry.tsquotaAdapters.ts);
  5. 状态直通路由决策:exhausted 出局、approaching_limit 打 0.65 折、unknown/healthy 满权重,配合只读的 preview 接口与 npm run omniroute:verify 等校验手段,让配额语义可被确定性地验证(adaptiveRouting.tspreview/route.ts)。

对希望把“配额感知”做进自己路由层的开发者而言,OmniRoute 这套代码给出了一个比“等 429 再 failover”更优雅的参考实现:在真正发送请求之前,用可验证的遥测把路由引导向仍有容量的供应商。

登录后查看全文
热门项目推荐
相关项目推荐