# 路由与调度 > 本文档由 README 拆分而来:模型路由、AUTO 链、冷却与半冷却、源与适配器、源模板。 > 返回 [README](../README.md)。 ## 模型路由 `/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 的具体源。