Skip to content

智能路由技术参考 ​

智能路由也称模型路由。本页供需要直接配置 Gate、调用管理接口或核对路由细节的管理员与开发者查阅。概念与常用场景请先阅读智能路由指南。

插件配置 ​

在 Gate 的 config.yaml 中启用智能路由插件:

yaml
plugins:
  - name: routing
    enabled: true
    pre_order: 20
    post_order: 15
    error_policy: fail_open
    config:
      definition_file: "routing.yaml"   # 规则定义文件,相对 config.yaml 所在目录
      allow_request_bypass: false       # 是否允许调用方用 hcd-og-route: off 跳过路由
      complexity:
        classifier: llm                 # heuristic | heuristic_first | llm | embedding;省略表示不做复杂度分级
        llm:
          url: "http://<平台分类模型>/v1/chat/completions"
          model: "qwen3-14b"
          api_key: "${ROUTING_CLASSIFIER_KEY}"
          extra_body: {chat_template_kwargs: {enable_thinking: false}}   # Qwen3 关闭思考
字段默认值说明
definition_filerouting.yaml规则定义文件路径。文件修改后自动热更新
allow_request_bypassfalse为 true 时,带 hcd-og-route: off(不区分大小写)的请求跳过路由
complexity不分级复杂度分级配置,见复杂度分级
error_policyfail_open插件自身出错时原样转发请求,响应头 og-route: ERROR

config 块内的未知字段会让加载失败,拼错的字段不会被静默忽略。

与其他插件的顺序约束 ​

网关在加载配置时强制检查以下顺序,违反时直接报错:

约束原因
安全护栏的 pre_order 小于智能路由的 pre_order被拦截的请求不产生复杂度判断的开销;判断看到的是已脱敏的文本
智能路由的 pre_order 小于语义缓存、Token 压缩的 pre_order缓存身份与压缩预算按改写后的模型计算
日志插件的 pre_order、post_order 都小于智能路由对应的值日志包在外层,才能记录路由决策和每次尝试

规则定义文件 ​

yaml
schema_version: 1
max_chain_depth: 5
allowed_models: [qwen-plus, deepseek-v3, claude-sonnet-4-5, gpt-4o]
fallback:
  max_attempts: 3
rules:
  - id: auto-simple
    name: og-auto 简单请求
    priority: 10
    match: {models: [og-auto], complexity_tiers: [SIMPLE]}
    targets: [{model: qwen-plus}]
    fallbacks: [gpt-4o]

顶层字段 ​

字段默认值说明
schema_version1格式版本
max_chain_depth5链式规则最多连续触发的层数,取值 1–20
allowed_models不限制目标模型与候选模型的白名单,不在列表内的模型在加载时报错。只是静态白名单,不核验供应商侧的定价、授权与可用渠道
fallback见切换策略失败切换的判定策略
rules[]规则列表

规则字段 ​

字段必填说明
id是唯一标识,最多 128 个字符;出现在响应头 og-route-rule 与日志中,改 id 会让历史统计断开
name / description否展示用
enabled否省略为启用
priority是越小越先匹配;启用的规则之间不能重复
match否声明式匹配条件,见匹配条件
when否CEL 表达式,与 match 同时满足才命中,见CEL 条件
targets否目标模型列表 [{model, weight}]。多个目标时按权重随机选择(权重不需要加起来等于 100,权重为 0 的目标不参与选择);只有一个目标时忽略权重,即使写 0 也会选中它,要停用规则请用 enabled: false。为空表示不改模型,只附加候选模型
fallbacks否候选模型,按顺序尝试
fallback_policy否只对本规则候选生效的切换策略,字段与顶层 fallback 相同;写了的字段替换顶层值,省略的沿用顶层,见切换策略。必须同时有 fallbacks
chain否为 true 时,用选出的模型从第一条规则重新匹配,用于"虚拟模型先归一、再按条件分发"。每条规则最多触发一次,不会出现死循环
builtin否标记为内置规则,管理界面上只能启停
complexity_profile否选用的命名分类器:本规则的 match.complexity_tiers 与 CEL 里的 complexity_tier 由它判断。省略时用默认分类器;只能写在引用档位的规则上
scenario否场景标记 {id, type},管理界面据此把多条规则归为一个场景,不影响路由结果。id 最多 128 个字符,同一场景的规则共用;type 为 1–64 个小写字母、数字、_、-,以字母开头。同一个 id 下 type 不一致时加载失败

一条规则至少要有 targets 或 fallbacks 之一;链式规则必须有 targets。

求值规则 ​

  1. 启用的规则按 priority 升序依次匹配,第一条命中的规则生效。
  2. 链式规则命中后,用新模型从头再匹配,直到命中非链式规则、没有规则命中,或达到 max_chain_depth。
  3. 链上最后命中的规则决定候选模型及其切换策略(fallback_policy)。
  4. 某条规则求值出错(例如 CEL 读取了不存在的请求头)时只跳过这条规则,请求继续往下匹配,不会被拦截。

加载期校验 ​

以下问题都会让加载失败,加载失败时保留上一份可用的规则:

  • 未知字段(如把 fallbacks 写成 fallbaks);
  • 规则 id 为空、重复或超过 128 个字符,启用规则之间 priority 重复;
  • CEL 语法错误、结果不是布尔值、请求头键名使用大写;
  • 目标或候选模型不在 allowed_models 内,候选模型重复;
  • scenario 缺少 id、id 首尾有空白、type 格式不对,或同一个 id 对应不同的 type;fallback_policy 没有对应的 fallbacks、取值越界、合并后没有任何可切换的状态码或错误码,或 max_attempts 为 1(等于不切换);
  • 在不引用档位的规则上写了 complexity_profile;启用的规则用 complexity_profile 引用了插件配置中未定义的命名分类器(停用的规则不检查。:validate 只校验规则文件本身,这一项在 warnings 里提示,真正写入或热更新时才拒绝加载);
  • 非法的档位、协议或模型通配符,权重为负数;
  • 定义文件超过 4 MB。

YAML 格式错误(含未知字段)在解析阶段就会报错并直接返回;解析成功后,语义问题会一次全部列出。

不要用截断写的方式直接改文件

cat > routing.yaml 这类"先清空再写入"的方式,中间会出现一个 0 字节文件。空文件是合法定义(没有规则),网关会在这一瞬间按"无规则"处理请求。请使用管理界面、规则管理接口,或"写临时文件再改名"的方式更新规则。

匹配条件 ​

match 中不同字段之间是"且",同一字段的多个值之间是"或";全部省略表示匹配所有请求。

字段说明
models模型通配符,整串匹配、不区分大小写。*、? 可匹配任意字符(包括 /),[45]、[!x] 为字符类。gpt-4o 不会匹配 gpt-4o-mini
tenants精确匹配 hcd-og-tenant-id 请求头;配置后,没有带租户头的请求不会命中
headers请求头精确匹配,键名不区分大小写,值区分大小写
protocolsopenai、claude、gemini;OpenAI Responses 接口属于 openai
streamtrue 或 false;省略表示不限制
complexity_tiersSIMPLE、MEDIUM、COMPLEX;用到时才会做复杂度判断

租户与请求头条件不是安全边界

hcd-og-tenant-id 和其他请求头都可以由调用方随意设置。按租户或请求头分流只能作为业务分流手段,不能用来做权限或计费隔离。

请求中的模型 ​

  • 模型取自请求体的 model;Gemini 原生接口取自 URL 路径,请求体里的 model 不参与路由,也不会被改写。
  • 请求体顶层有重复或大小写不同的 model 键时(如 {"Model": "a", "model": "b"}),网关按"不区分大小写、重复时取最后一个"的规则取值,并合并成一个 model 后再转发,与供应商侧的解析结果一致。
  • Gemini 路径中的冒号写成 %3A 等编码形式也会被识别。

CEL 条件 ​

when 使用 CEL 表达式,结果必须是布尔值。

变量类型说明
modelstring当前模型(链式匹配中每一步都会更新),保留调用方原样大小写
protocolstringopenai / claude / gemini
endpointstring请求路径(含查询串)
streambool是否流式
tenant_idstringhcd-og-tenant-id 请求头
headersmap请求头,键名小写,多值头取第一个值
paramsmap请求体顶层的标量字段(如 temperature、max_tokens)
input_tokensint整个请求体的估算 token 数
message_countint消息条数
has_tools / has_imagebool是否带工具定义、是否含图片
complexity_tierstring复杂度档位,由本规则 complexity_profile 指定的分类器判断(省略时为默认分类器)

常用写法:

text
"x-tier" in headers && headers["x-tier"] == "premium"
model.startsWith("gpt-") && input_tokens > 120000
has_tools && message_count > 10
has(params.temperature) && params.temperature < 0.3
complexity_tier == "COMPLEX" || ("x-vip" in headers && headers["x-vip"] == "1")

注意:

  • 读取不存在的请求头会让表达式出错,这条规则不命中(== 和 != 都不命中)。先用 "x" in headers 判断是否存在。
  • CEL 的 model.matches("gpt-4") 是正则部分匹配,会命中 openai/gpt-4o;需要整串匹配时用 match.models 或 ==。
  • 拿不到复杂度档位时,complexity_tier 的值是"未知":complexity_tier != "SIMPLE" 不命中,而 complexity_tier == "COMPLEX" || 其他条件 在其他条件为真时仍然命中。
  • 表达式有求值开销上限,超出时按出错处理、跳过该规则。
  • 没有额度、预算或限流相关的变量:这些数据在供应商侧,网关拿不到。

复杂度分级 ​

只有匹配到引用了 complexity_tiers 或 complexity_tier 的规则时才会判断,每个分类器每个请求最多判断一次。complexity 下是默认分类器,没有写 complexity_profile 的规则共用;不同虚拟模型要用不同分类器时见命名分类器。

分类器 ​

classifier原理依赖额外耗时
heuristic本地打分(移植自 LiteLLM complexity_router 的默认分类器):按长度、代码词、推理词、技术词、简单问句、多步骤、多问号 7 个维度加权打分,按分界线定档无不到 1 毫秒,不调用任何模型
heuristic_first先本地打分;有触发信号、且档位不高于 heuristic_first_max_tier 时直接用,否则调用 llm 分类器同 llm本地定档时不到 1 毫秒;升级时同 llm
llm调用平台提供的 OpenAI 兼容小模型,要求返回 {"tier": "..."};使用平台自己的模型与凭据,不使用调用方的 API Key 和模型一个 Chat Completions 端点一次模型调用,默认超时 1.5s
embedding把请求与三档预置样例句比相似度,取最相近的若干条按相似度加权投票一个 Embedding 端点(OpenAI 兼容或 Ollama)一次向量化调用;内网部署 Embedding 模型的测试中约几十到一百毫秒,实际取决于部署位置与网络

不配置 classifier 时不做复杂度分级:引用档位的规则一律不命中,日志中 complexity_mechanism 记为 skipped;加载时如果有规则引用档位,网关日志会出现一条告警,列出这些规则;通过管理接口读取、保存或校验路由规则时,响应里的 warnings 也会给出同样的提示。

主分类器超时、失败、返回无法解析的档位、并发已满或弃权时,可以改用 fallback 指定的另一种分类器(例如 classifier: embedding 搭配 fallback: llm;fallback: heuristic 用本地打分兜底,不花钱、一定有结果),或使用 fallback: llm_backup 调用第二个独立 LLM。主模型成功时绝不调用备用模型;每次新的分级都先尝试主模型。两个分类器都失败时拿不到档位。备用档位只用于当前请求,不写入会话,调整备用模型不重置主模型的会话表。

classifier: embedding 可搭配 fallback: llm 或 fallback: llm_backup:前者读取 llm 配置,后者读取独立的 llm_backup 配置,后者不使用 llm 块。

分类在发往上游之前执行,耗时会直接增加首字节等待时间。当前每次新的分级都先尝试主分类器,没有熔断;主模型持续超时时,每个请求都会等满主超时。按主服务实测高分位延迟设置较短的主超时,备用独立设置超时;凭据探测、主分类和备用分类的最坏等待应一起计入请求延迟预算。

两个 LLM 配置示例(routing 插件的 config.complexity):

yaml
classifier: llm
fallback: llm_backup
llm:
  url: "http://<primary-classifier>/v1/chat/completions"
  model: "qwen3-14b"
  api_key: "${ROUTING_CLASSIFIER_KEY}"
  timeout: "1500ms"
  extra_body: {chat_template_kwargs: {enable_thinking: false}}
llm_backup:
  url: "https://api.siliconflow.cn/v1/chat/completions"
  model: "Qwen/Qwen2.5-7B-Instruct"
  api_key: "${ROUTING_BACKUP_CLASSIFIER_KEY}"
  timeout: "5s"
  max_concurrent: 4

备用 API Key 应独立注入网关进程或容器环境。免费模型仍需要账号凭据,限额与价格以服务商当前政策为准。主备分别使用自己的超时,失败时等待可能累加。旧版网关不认识 llm_backup,需先升级程序再启用配置。

输入范围:只取最近 message_window(默认 1)条用户本人写的消息。系统提示词、助手回复、工具返回结果都不参与;<system-reminder>、<environment_context> 等 Agent 工具自动附加的标签会被去掉。最后一条用户消息之后还有助手回复或工具结果(Agent 的工具调用循环)时,视为续写而不是新问题。

通用参数 ​

字段默认值说明
classifier不配置heuristic / heuristic_first / llm / embedding;不配置表示不做复杂度分级
fallbacknone主分类器没有结果时改用的分类器:none、heuristic、另一种分类器或 llm_backup;备用配置必须完整。classifier: heuristic 时只能是 none;classifier: heuristic_first 时不能是 llm
heuristic_first_max_tierSIMPLE只用于 heuristic_first:本地打分最多自己定到的档位,SIMPLE 或 MEDIUM
llm_backup未配置fallback: llm_backup 时必填;字段与 llm 相同,端点、模型、密钥、超时、并发上限、提示词、附加字段与凭据校验独立配置,不继承主模型的设置
message_window1参与判断的最近用户消息条数

本地打分(heuristic) ​

参数位于 complexity.heuristic,全部可选,键名与 LiteLLM 一致:

字段默认值说明
tier_boundaries.simple_medium / .medium_complex0.15 / 0.35加权分数低于前者为简单,低于后者为中等,其余为复杂
token_thresholds.simple / .complex15 / 400估算 token 数(中文每字 1 个)低于前者减分、高于后者加分
dimension_weights见下各维度权重(0–1);写了的覆盖默认值,没写的沿用默认值
code_keywords / reasoning_keywords / technical_keywords / simple_keywords内置列表写了就整体替换内置列表;内置列表是 LiteLLM 的英文列表加中文对应词
维度默认权重打分
tokenCount0.10短于阈值 -1,长于阈值 +1
codePresence0.30代码词命中 1 个 0.5,2 个以上 1
reasoningMarkers0.25推理词命中 1 个 0.7,2 个以上 1;命中 2 个以上且分数不低于 simple_medium 时直接定为复杂
technicalTerms0.25技术词命中 2 个 0.5,4 个以上 1
simpleIndicators0.05简单问句词命中即 -1
multiStepPatterns0.03first…then、step 1、1. 、首先…然后、第一步 等,0.5
questionComplexity0.02问号超过 3 个,0.5
  • 只看用户本人写的文本,不看系统提示词。英文关键词要求两端是词边界(api 不命中 capital),中文关键词按子串匹配。
  • LiteLLM 有第四档 REASONING,这里并入复杂档,所以没有 complex_reasoning 分界线。
  • 本地打分不花钱,不做调用方凭据校验,有人写文本时一定给出档位。它是路由提示,不是安全边界。
  • 关键词只扫描最近 16 KB 文本,长度仍按全文估算;16 KB 和 64 KB 的输入都约 0.6 毫秒。
  • 准确率:在 36 条中英文标注句上,单用本地打分命中 50%(简单档全对,但较多中等、复杂请求被判成简单),平台 qwen3-14b 命中 97%,heuristic_first 命中 89% 且省掉 39% 的 LLM 调用。建议用 heuristic_first,不要单用 heuristic。
  • heuristic_first 下,没有任何维度触发时(分数 0)不算证据,一律交给 LLM;本地档位和 LLM 档位都会写入会话档位;fallback: heuristic 直接复用已经打好的分数。
yaml
complexity:
  classifier: heuristic_first
  fallback: heuristic
  heuristic_first_max_tier: SIMPLE
  llm:
    url: "http://<platform-llm>/v1/chat/completions"
    model: "qwen3-14b"
    api_key: "${ROUTING_CLASSIFIER_KEY}"
  heuristic:
    technical_keywords: [kubernetes, 分布式, 订单中心]

LLM 分类器(llm) ​

字段默认值说明
url必填OpenAI 兼容的 chat/completions 完整地址
model必填分类模型
api_key空平台自己的凭据,建议用 ${ENV_NAME} 引用环境变量
prompt内置指引覆盖默认的分级指引;防注入段落固定附加在后面
timeout1500ms单次分类超时
max_concurrent16同时进行的分类调用上限;满了立即放弃(有兜底分类器时改用它),不排队
extra_body空合并进分类请求体的额外字段,用于端点特有的开关。例如 Qwen3 系列默认先输出思考过程,单次分类要几秒,设 {chat_template_kwargs: {enable_thinking: false}} 后约 0.2 秒。model、messages、max_tokens、max_completion_tokens、stream、temperature、n、tools、tool_choice 由网关控制,不能设置(键名不区分大小写)

网关只接受合法 JSON 里的 tier 字段,或整条回复恰好是一个档位词;推理模型输出的 <think>…</think> 段会先去掉。用户文本里伪造的数据边界会被转义,不能借此篡改分类结果。

Embedding 分类器(embedding) ​

默认不开启,需要显式设置 classifier: embedding。

字段默认值说明
url必填完整地址:OpenAI 兼容为 …/v1/embeddings,Ollama 为 …/api/embed
model必填Embedding 模型,如 bge-m3
api_key空平台自己的凭据
formatopenaiopenai 或 ollama
timeout1s单次向量化超时
max_concurrent16同时进行的调用上限
top_k5参与投票的最相近样例数;平票时取更高的档位
min_similarity0(不设下限)最相近样例的相似度低于它时放弃判断(有兜底分类器时改用它)
exemplars.simple / .medium / .complex内置样例(每档 36 条,中英各半)覆盖某一档的样例;省略的档位沿用内置样例。每档最多 500 条,每条最多 512 字符
yaml
complexity:
  classifier: embedding
  fallback: none
  embedding:
    url: "http://<ollama-host>:11434/api/embed"
    model: "bge-m3:latest"
    format: ollama
    top_k: 5
  • 网关加载时在后台向量化样例并保存在内存里,不阻塞加载和请求;未就绪期间没有结果(有兜底分类器时改用它),失败时自动重试。状态见指标 og_routing_embedding_ready{state}(warming / ready / failed)。
  • 端点、模型、格式与样例都没变时,热更新不会重新向量化。
  • 内置样例只是通用起点。在一组 18 句的中英文标注样本上测试(bge-m3),简单与复杂两档基本准确,中等档误判较多,整体约三分之二;样本量小,只能作为参考。生产使用前建议用业务语料替换样例:按"要做的事"而不是话题分档,同一话题在三档都放一些;中英文、问句与祈使句、长短在三档保持均衡。

调用方凭据校验 ​

LLM 和 Embedding 分类器使用的是平台自己的凭据,而路由发生在供应商侧验证调用方身份之前。为了不让无凭据或伪造凭据的请求先消耗一次付费分类调用,默认会先用调用方的凭据向供应商侧的 /v1/models 探测一次,通过才调用分类器。主分类器和兜底分类器各按自己的 auth_validation 决定是否校验,例如主分类器是本地免费模型时可以关掉校验,付费的兜底分类器仍然受保护;一次请求最多探测一次,两个分类器共用结果。需要校验的分类器在校验不通过时不会被调用,两个都调不了时这次请求拿不到档位。

字段(llm.auth_validation / embedding.auth_validation)默认值说明
enabledtrue只有入口已经做过可信认证时才关闭
endpoint/v1/models探测路径
ttl / negative_ttl5m / 30s有效 / 无效凭据的缓存时长
timeout3s探测超时

配置了分类器且开启校验时,Gate 必须配置 proxy.upstream,否则加载失败。Gemini 原生客户端使用 x-goog-api-key 传凭据时不支持校验,这类请求拿不到档位。

命名分类器(complexity.profiles) ​

不同虚拟模型可以用不同的分类器,例如闲聊入口用本地打分、编码入口用 LLM 加编码专用的分级指引。参照的是 LiteLLM 每个复杂度路由各带一套分类器配置的做法。

yaml
complexity:                     # 默认分类器
  classifier: heuristic
  profiles:
    coding:                     # 规则里写 complexity_profile: coding 选用
      classifier: heuristic_first
      fallback: heuristic
      llm: {url: "http://<platform-llm>/v1/chat/completions", model: "qwen3-14b", api_key: "${ROUTING_CLASSIFIER_KEY}"}
      session: {enabled: true}
yaml
rules:
  - {id: code-complex, priority: 10, match: {models: [og-coding], complexity_tiers: [COMPLEX]}, targets: [{model: strong}], complexity_profile: coding}
  • 每个命名分类器是一份完整、独立的配置,字段与 complexity 相同,不继承默认分类器,必须写 classifier,不能嵌套,最多 16 个;名字为小写字母开头的小写字母、数字、_ 或 -。
  • 规则的 complexity_profile 决定这条规则的档位条件和 CEL 里的 complexity_tier 由哪个分类器判断。只能写在引用档位的规则上;启用的规则引用了未定义的命名分类器时,网关拒绝加载(保存时保留旧规则)。
  • 同一请求里,每个分类器最多判断一次;链式规则用到不同分类器时各判各的,不会借用彼此的档位。
  • 日志、响应头 og-route-tier 记录的是最后命中规则所用分类器的档位:链上后面没有命中的规则即使读了另一个分类器,也不会覆盖它;最后命中的规则不看档位时,才记录最后一次被规则读到的档位。试运行结果里的 complexity_profile 说明档位来自哪个命名分类器。
  • 每个命名分类器有自己的分类调用并发上限、凭据校验和会话档位表;同一会话在不同虚拟模型上的档位互不影响,只修改某个命名分类器时只重置它自己的会话档位。

会话档位(session) ​

字段默认值说明
enabledfalse同一会话内已记录的档位只升不降
ttl24h会话空闲多久后过期
max_entries100000最多保存的会话数
  • 会话 ID 取自 hcd-og-session-id 或 x-claude-code-session-id,与调用方凭据一起计算,不同凭据之间互不影响;没有凭据头的请求不启用会话档位。
  • 只有请求成功返回后才写入会话档位;只有主分类器给出的档位才写入,兜底分类器的结果只用于本次请求。
  • 已经升到复杂的会话不再调用分类器;只有工具结果、没有新问题的续写请求直接沿用会话档位。
  • 开启会话档位必须配置 classifier。
  • 修改分级口径(主分类器、message_window,以及主分类器的端点、模型、提示词、extra_body、样例,或本地打分参数与 heuristic_first_max_tier 等)会换一张新的会话表;只改超时、并发、会话的 ttl / max_entries,或增删、调整兜底分类器不会(兜底分类器的档位本来就不写进会话)。
  • 会话档位保存在单个网关进程内,多实例之间不共享。

切换策略 ​

yaml
fallback:                     # 以下为默认值
  max_attempts: 3             # 含第一次尝试,取值 1–10;1 表示不切换
  total_timeout: 180s         # 超过后不再开始新的尝试(不中断进行中的尝试)
  on_status: [429, 500, 502, 503, 529]
  on_codes: [model_not_found, upstream_unreachable, read_response_failed, do_request_failed]
  exclude_codes: [model_price_error, insufficient_user_quota, sensitive_words_detected, new_api_error]

判定顺序:先看 exclude_codes,命中即不切换;再看 on_codes,命中即切换;最后看 on_status。错误码从供应商侧错误体中提取(OpenAI 格式取 error.code,Claude 格式取 error.type)。

按规则覆盖:规则的 fallback_policy 字段与上面相同,只对这条规则的候选生效。写了的字段整体替换顶层值,省略(或写 0)的沿用顶层,显式写空列表表示清空。因为是整体替换,要在默认错误码之外追加时需把默认值一并写上:

yaml
  - id: long-context
    priority: 5
    when: 'input_tokens >= 120000'
    targets: [{model: gemini-2.5-pro}]
    fallbacks: [claude-sonnet-4-5]
    fallback_policy:
      on_status: [429, 500, 502, 503, 504, 529]   # 长任务接受叠加耗时,504 也切换
      on_codes: [model_not_found, upstream_unreachable, read_response_failed, do_request_failed, context_length_exceeded]
      max_attempts: 2

错误码按入口协议取值:context_length_exceeded 是 OpenAI 格式的错误码,Claude 格式下上下文超长的 error.type 是 invalid_request_error(也涵盖其他请求错误)。覆盖 exclude_codes 时建议保留 new_api_error。试运行结果的 fallback_policy 字段给出合并后实际生效的策略。

供应商侧返回默认原因
503 model_not_found(无可用渠道)切换该模型已没有可用供应商
429、500、502、503、529、连接失败切换供应商侧的同模型重试已用尽
504 / 524不切换供应商侧本身不重试超时,再换模型会让耗时叠加;需要时加入 on_status
400 model_price_error、403、额度不足不切换换模型解决不了,还可能借切换绕过授权
敏感词拒绝不切换同样的内容在其他模型上也会被拒绝
Claude 格式 500 + error.type: new_api_error不切换无法区分具体原因;文案为 upstream error: do request failed 时按连接失败处理并切换
  • 流式请求只在首字节之前切换;已经开始输出后上游中断,不再切换。
  • 候选全部失败时,返回最后一次尝试的状态码与错误体;遇到不可切换的错误立即停止并原样返回,后面的候选不再尝试。
  • 客户端断开后不再开始新的尝试,也不计为候选模型失败。
  • 失败尝试的预扣费由供应商侧退回,只有成功的那次计费。

响应头 ​

非流式响应在返回时附带;流式响应在第一段内容之前写出。

响应头说明
og-routePASS / ROUTED / FALLBACK / BYPASS / ERROR
og-route-rule最后命中的规则 id
og-route-model路由后发给供应商的对外模型 ID,也是计费所用的模型;发生切换时是最后一次尝试的模型。与响应体里供应商回报的模型名可能不同
og-route-requested-model调用方原本请求的模型,只在最终模型与原模型不同时出现
og-route-attempts上游尝试次数(含第一次)
og-route-tier复杂度档位,只在做过判断时出现

FALLBACK 只表示发生了切换,不代表候选一定成功:流式响应头写出时还不知道候选能否完整结束,最终结果以状态码与日志为准。不切换的错误(如 403)显示为 ROUTED。

日志与指标 ​

请求日志(标准输出与日志存储的 logs 表)新增以下字段:

字段说明
requested_model调用方原本请求的模型
routing_rule_id命中的规则 id
routing_outcome同 og-route
routing_attempts尝试次数
complexity_tier复杂度档位
complexity_mechanism档位来源:heuristic / llm / llm_backup / embedding / session / skipped;heuristic_first 按实际定档的一方记为 heuristic 或 llm

日志中 model 字段的口径:先记为路由后发给供应商的模型(发生切换时为最后一次尝试的模型);非流式请求如果响应体里带有 model,会被供应商回报的模型名覆盖,流式请求不会。因此同一个路由结果,非流式与流式日志中的 model 可能不同;核对路由选择时以 requested_model、routing_rule_id 与响应头 og-route-model 为准,核对费用以供应商侧的计费明细为准。

日志表结构在网关启动时自动迁移,旧数据保留。每次切换另有一条 routing fallback 日志,带 request_id、来源与目标模型、状态码与错误码。

进程内指标(GET /admin/v1/plugins/routing/metrics,Prometheus 文本格式,网关重启后清零):

指标标签说明
og_routing_decisions_totalrule、outcome路由决策次数
og_routing_fallback_totalfrom、to、result切换次数与候选结果(success / failure)
og_routing_complexity_totaltier、mechanism,命名分类器另有 profile复杂度判断次数
og_routing_evaluation_seconds—规则求值耗时(_sum、_count),含复杂度分级
og_routing_classification_secondsle,命名分类器另有 profile复杂度分级耗时直方图(含凭据探测与兜底分类器),只计真实请求。规则匹配本身的耗时 = 求值耗时 − 分级耗时
og_routing_embedding_readystate,命名分类器另有 profileEmbedding 样例预热状态(用到 Embedding 分类器时出现,作主分类器或兜底均可)
og_routing_embedding_exemplarskind,命名分类器另有 profile已向量化与配置的样例数(同上)

默认分类器的指标不带 profile 标签,与引入命名分类器之前的序列一致。每个指标最多保留 1000 种标签组合,超出后计入 _other。

规则管理接口 ​

需要 Gate 的管理凭据(Authorization: Bearer <ADMIN_TOKEN>)。

方法与路径说明
GET /admin/v1/plugins/routing/definitions返回 {rules, etag, format, content},同时带 ETag 响应头
PUT /admin/v1/plugins/routing/definitions用请求体整体替换定义文件,支持 If-Match;先校验、再原子写入并立即生效,失败时回滚
POST /admin/v1/plugins/routing/definitions:validate只校验,不写入。合法时返回 200 {"valid": true, "rules": n},有需要提醒的问题时另带 warnings(如引用档位的规则没有可用的分类器、规则引用了未定义的命名分类器);不合法时返回 422 {"valid": false, "error": "…"}(注意与 PUT 的错误结构不同)
POST /admin/v1/plugins/routing/definitions:dry-run用样例请求试运行,不改写、不转发;可附带一份未保存的候选定义
GET /admin/v1/plugins/routing/metrics进程内指标,见日志与指标
GET /admin/v1/plugins/routing/metrics/summary按"规则 × 结果""请求模型 → 实际模型""档位 × 来源"聚合日志存储中的数据
GET /admin/v1/plugins/routing/logs带路由信息的请求明细,新记录在前
GET /admin/v1/plugins/routing/config插件条目的 {enabled, config, etag, version, config_hash, applied_at},带 ETag 响应头;config 中的密钥脱敏为 {"configured": true, "redacted": true}
PUT /admin/v1/plugins/routing/config请求体 {enabled, config},替换插件开关与配置(分类器、命名分类器、allow_request_bypass),支持 If-Match;先用候选配置初始化插件,成功后写回主配置文件并立即生效,失败时文件不变。响应同 GET,另带 sessions_reset
POST /admin/v1/plugins/routing/config:validate只校验,不写入、不连接分类器;返回 {valid, enabled, normalized_config, config_hash, sessions_reset},不合法时返回 422
GET /admin/v1/config/status主配置文件的 config_hash(sha256)与最近一次热更新是否失败

插件配置接口 ​

  • 保留密钥:某个密钥字段原样提交 GET 返回的脱敏标记,表示保留当前值(${ENV} 引用保持原样,不会把明文写进文件);要更换密钥时提交新值。当前没有可保留的值时返回 400。
  • definition_file 不能修改:省略时沿用当前值,改成其他值返回 400。
  • sessions_reset:为 true 表示分级口径变了,活跃会话的档位从头计算。
  • 删除仍被启用规则引用的命名分类器会被拒绝,返回 422,validate 会提前报出。
  • 省略 If-Match 时,以读取当前配置时的版本为准,期间配置被改过返回 412。

并发控制与生效确认 ​

GET 返回的 ETag 是定义文件内容的 sha256。PUT 时带上 If-Match,别人已经改过时返回 412,避免互相覆盖。省略 If-Match 也允许写入,因此自动化脚本应始终带上。内容与当前版本完全相同时,PUT 返回 200 且 ETag 不变。

确认规则已生效:GET 返回的 etag 应等于磁盘上定义文件的 sha256。通过 PUT 写入时网关会立即重建,不依赖文件监听。

试运行 ​

按当前生效的规则试运行:

json
{
  "endpoint": "/v1/chat/completions",
  "headers": {"x-tier": "premium", "hcd-og-tenant-id": "acme"},
  "body": {"model": "og-auto", "messages": [{"role": "user", "content": "设计一个多地域容灾方案"}]}
}

按一份尚未保存的候选定义试运行时,在 definition 中放完整的规则文件内容(YAML 或 JSON 字符串):

json
{
  "body": {"model": "og-auto", "messages": [{"role": "user", "content": "设计一个多地域容灾方案"}]},
  "definition": "rules:\n  - {id: auto-complex, priority: 10, match: {models: [og-auto], complexity_tiers: [COMPLEX]}, targets: [{model: claude-sonnet-4-5}], fallbacks: [gpt-4o]}\n"
}

返回示例:

json
{
  "requested_model": "og-auto",
  "routed_model": "claude-sonnet-4-5",
  "matched": true,
  "rule_id": "auto-complex",
  "steps": ["auto-complex"],
  "fallbacks": ["gpt-4o"],
  "complexity_tier": "COMPLEX",
  "complexity_mechanism": "embedding",
  "complexity_detail": {"similarity": 0.593, "nearest_exemplar": "…", "nearest_tier": "COMPLEX", "votes": {"COMPLEX": 2.41, "MEDIUM": 0.57}}
}
  • definition 可选:提供时只按这份定义求值,不会与当前生效的规则合并,生效规则也不变;内容无法解析时返回 422 dry_run_invalid。
  • endpoint 为 Gemini 原生路径时,与真实请求一样从 URL 取模型。
  • 试运行不会把请求转发给目标模型,但会真实执行复杂度判断:配置为 LLM 或 Embedding(或本地优先需要升级)时会发出一次分类调用,可能产生平台侧费用,样例文本也会发送给分类服务。试运行不写入会话档位,也不做调用方凭据校验。
  • skipped_rules 列出求值出错被跳过的规则及原因;链式规则达到深度上限、候选定义引用了当前插件配置中未定义的命名分类器时,也会在这里提示。
  • complexity_profile 只在档位来自命名分类器时出现,说明是哪一个;用默认分类器时省略。
  • complexity_detail 与 complexity_tier 来自同一个分类器,只在 Embedding 分类器或本地打分参与时出现:Embedding 给出最近样例、相似度与投票,用于校准 min_similarity 与样例;本地打分给出加权分数 score、触发的信号 signals 与本地档位 heuristic_tier,用于调整分界线、权重与关键词。

查询接口参数 ​

metrics/summary 与 logs 支持相同的过滤参数:

参数说明
from / toRFC3339 时间,默认最近 24 小时;to 不含边界
tenant_id租户
rule_id规则 id
outcome路由结果
model匹配实际模型或请求模型
mechanism档位来源(llm、heuristic、session 等);none 表示没有档位
result按最终状态码分类:success;error(全部失败,最终状态码 ≥ 400);caller_error(4xx,429 除外,如令牌无效、额度不足、未定价);model_error(429 与 5xx)
request_id按请求 ID 查找
limit仅 logs,默认 50,最大 200

metrics/summary 的规则行与模型流向行在 errors(最终状态码 ≥ 400)之外,另给 caller_errors 与 model_errors,口径同 result。档位聚合同时计入需要档位却拿不到的请求,档位为空、来源为 skipped。

两个接口都需要启用日志存储,未启用时返回 503 routing_metrics_unavailable。

错误码 ​

状态码说明
400 bad_request定义请求体(PUT、:validate)为空或超过 4 MB;试运行请求体不是合法 JSON 或超过 8 MB
409 definition_drift磁盘上的定义文件已被外部修改,尚未成为当前生效版本
412 definition_precondition_failedIf-Match 与当前版本不一致
422 definition_invalid定义未通过校验
422 dry_run_invalid试运行的样例请求或候选定义无效(如样例缺少模型)
503 plugin_dependency_unavailable定义本身合法,但其他插件初始化失败(如日志插件连不上数据库),插件链无法重建;文件已回滚、旧规则继续生效,带 Retry-After,依赖恢复后重试
503 routing_unavailable智能路由插件未加载

按现象排查(管理侧) ​

现象优先检查
改了规则文件但没生效网关日志是否有 hot-reload failed;GET 返回的 etag 是否等于文件 sha256
PUT 返回 409有人直接改过磁盘文件但网关未加载成功;先修复或还原文件
PUT 返回 503 plugin_dependency_unavailable查看消息里提到的插件及其依赖(多为日志存储的数据库)
档位总是 skipped、没有 og-route-tier是否配置了 classifier;分类器是否可达;Embedding 是否已 ready;调用方凭据是否通过校验
og-route: ERROR网关日志中路由插件的错误;请求已按原模型转发
切换没有发生命中的规则是否有 fallbacks;错误码是否在 exclude_codes 中;流式请求是否已开始输出