Files
ModelRouter/docs/routing.md
JianFeeeee f7e926ccb7 docs: README 拆分为主题文档 + 中性化竞品表述
README 从 642 行降到 131 行,只保留简介、核心能力、最小快速开始、文档索引、
改名说明、目录结构与测试入口。其余内容迁到 docs/,中英文索引一一对应:

  docs/features.md       实现与完整特性清单(原 README 的"实现/特性")
  docs/configuration.md  配置文件、自动探测模型、多密钥与配额
  docs/routing.md        模型路由、AUTO 链、冷却与半冷却、源/适配器、源模板
  docs/performance.md    实测内存占用与调优、thinking 预算
  docs/webui.md          WebUI 使用
  docs/adapters.md       Lua 适配器协议与内置适配器清单
  docs/gui.md            桌面端 Headless/Desktop 两种产物与构建
  docs/development.md    目录职责、开发协作、测试
  docs/deployment.md     部署、发行包、目录权限、llmsproxy 迁移与验证清单

中性化:README / README_EN / GUI 注释里的 `Clash Verge` 类比全部改为
「桌面壳 / desktop shell」,仓库内(源码与文档)已无该表述。
2026-10-26 22:28:24 +08:00

6.7 KiB
Raw Blame History

路由与调度

本文档由 README 拆分而来:模型路由、AUTO 链、冷却与半冷却、源与适配器、源模板。 返回 README。

模型路由

/v1/chat/completions 的 model 解析顺序:

  1. source/model 或 source:model 前缀 → 指定源;
  2. 精确匹配某个源的 model;
  3. 配置 default_model: AUTO 时 → 走优先级页保存的 AUTO 链(见下);
  4. 否则回落(返回错误)。

任何 OpenAI 客户端,只要 model 设为某个源的 name/任意名,即可锁定走该源; 设为 AUTO(或网关配了 default_model: AUTO)即自动按优先级链选源。

AUTO 链(优先级页)

AUTO 调度只由优先级页保存的规则决定,源配置里的 models[].priority 数字不再 参与调度、也不再显示。聊天与生图是两套独立的链,在优先级页面通过「聊天 / 生图」 按钮切换编辑:

  • 聊天链(runtime_file 的 auto 字段):服务 POST /v1/chat/completions 的 model: AUTO 请求;只接受 kind: chat 模型,配置在链里的生图槽位会被忽略。
  • 生图链(runtime_file 的 auto_image 字段):服务 POST /v1/images/generations 的 model: AUTO 请求;只接受 kind: image 模型。未配置生图链时,AUTO 生图回落 到「所有含生图模型的源、按注册顺序」的旧行为,兼容旧配置。

每条规则 = 一个「槽位」:{ model, source, tier, token_quota, period, hours }。

  • tier 表示优先级档位:同一档的模型并排、共享该优先级;档位从上到下递减。
  • 同一模型可配置多个槽位(如 A 源低配 → B 源低配 → A 源高配 → B 源高配),按档位顺延。
  • token_quota > 0 时该槽位在重置周期内用满即顺延到下一槽位;period 支持 hour / week / month / nhour(配合 hours),空 = 不限。
  • 同 tier 内按「偏好分」排序,失败槽位冷却后自动跳过;冷却 / 配额耗尽 / hard 错误会顺延到下一 tier,全部失败时返回 503 并附上每档失败摘要。

冷却与半冷却探测(自愈调度)

槽位失败后进入指数退避冷却(5s → 10s → 20s …,上限 5 分钟)。冷却不是全时段 硬闸:

  • 前半段完全静默:刚失败的槽位不接任何流量,避免对故障上游持续施压。
  • 后半段放行 1 个探测:窗口过半后,同一 (源, 模型) 最多允许 一个 请求作为 探测通过。探测槽数 = clamp(max_concurrent / 10, 1, 2)——max_concurrent: 10 的源恰好放行 1 个探测请求。
  • 探测排在最后:同 tier 内探测候选永远排在健康候选之后,只有没有正常槽可用时 才承接真实流量,因此探测不会抢走可用容量。
  • 探测即真实请求:成功则立即清零失败计数与冷却,槽位当场回到正常轮询—— 上游恢复/额度恢复后不必等满整个冷却窗口。失败则重开一个新窗口,下次探测顺延到 新窗口的中点,不会连续重试。

分类冷却策略:

情况 冷却 说明
传输错误 / 5xx 5s→10s→20s…,上限 5min 指数退避
401 / 403 凭据错误 10min 换 key 后由下一次探测自动接回
429 限流 30s 固定 「太快」不等于「坏了」,不进指数阶梯
配额耗尽(insufficient_quota / 余额不足 等) 对齐配额窗口,上限 30min 不进指数阶梯、不涨失败计数,额度恢复即可用

WebUI「优先级」页的槽位健康标签会区分 冷却 / 待探测 / 探测中,悬停可看窗口 起点、探测放行时刻与完全恢复时刻。

源(Source)与适配器(Adapter)的关系

  • 源 是到某个上游的连接描述:name、base_url、api_key、模型列表与优先级。
  • 适配器 是协议转换逻辑(Lua 脚本):把统一 OpenAI 格式请求转成上游原生格式, 再把上游响应/流式分块转回统一格式。
  • 一个适配器可被多个源复用(如 openai.lua 同时服务多个 OpenAI 兼容站点); 同一个源也可以切换不同适配器(改 adapter 字段即可)。
  • 源决定“连谁、暴露哪些模型”,适配器决定“怎么对话”——二者在 sources[] 条目中 通过 adapter 字段关联。

加载流程(internal/core 的 Core 负责装配):

  1. 启动时 lua.NewVM(adapter_dir) 加载全部适配器:内置适配器(编译期 embed)+ adapter_dir 下的同名覆盖文件;内置文件写在代码内,覆盖文件要求更高优先级。
  2. config.Load 读取 config.yaml,config.NewStore(runtime_file) 读 WebUI 改动的 运行时源,二者按名称合并成完整源列表。
  3. rebuildRegistry 为每个源创建 provider.Provider(持有目标适配器),并按源的 并发上限配置适配器 worker 池上限(不预分配,见下)。
  4. 请求进来时 Registry 按 model 路由到 Provider,Provider 调适配器 transform_request → HTTP 发送 → transform_response / transform_stream_chunk。

WebUI 上的"新增/编辑源"、"上传 Lua 适配器"、"改 AUTO 优先级链"与"密钥模型范围" 都即时生效(写入运行时文件或 adapter_dir 后重新装配,无需重启);直接编辑 config.yaml / adapter_dir 下的文件则需要重启进程才会重新加载。

在 WebUI 删除源/适配器时:运行时(WebUI 创建)的源与 .lua 文件会真正移除; config.yaml 中定义的基源无法改写配置文件,采用删除标记隐藏(重启后仍隐藏), 在 UI 里重新添加同名源即可恢复。

源模板(多 key 负载均衡)

多个上游 key 共享同一套配置(URL / 适配器 / 模型列表 / 并发 / rpm)时,逐一复制 整段 source 太冗余。源模板保存除 name 和 api_key 以外的全部源字段, 从模板一键创建多个仅 key 不同的源,天然支持多 key 负载均衡(每个展开源独立调度、 独立冷却、独立健康状态)。

  • WebUI「源」页右上角「模板管理」按钮:列出全部模板,可编辑 / 删除 / 新建。
  • 添加源弹窗右上角两个按钮:
    • 从模板创建 → 选模板 → 表单自动填充(name + key 仍需你填);
    • 存为模板 → 把当前表单非 key/name 字段存为新模板。
  • 模板持久化在 runtime_file 的 source_templates 字段,与运行时源一样热管理, 改完即时生效。模板本身不含密钥,可以安全共享 / 纳管。

模板不会自己变成源——它只是「配方」。真正承担流量的是从模板创建出来的、带 key 的具体源。