mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
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」,仓库内(源码与文档)已无该表述。
184 lines
9.4 KiB
Markdown
184 lines
9.4 KiB
Markdown
# 配置
|
||
|
||
> 本文档由 README 拆分而来:网关配置、模型自动探测、多密钥。
|
||
> 返回 [README](../README.md)。
|
||
|
||
## 配置文件
|
||
|
||
首次运行在 `-config` 路径自动生成默认配置(默认 `./config.yaml`),核心字段:
|
||
|
||
```yaml
|
||
listen: 127.0.0.1:8080 # 网关监听地址(建议绑内网/回环)
|
||
gateway_keys: [sk-gw-<随机>] # 初始 admin 密钥(每次安装随机生成,仅首启写入运行时存储用)
|
||
default_model: AUTO # model 无法路由时自动按优先级链选源
|
||
adapter_dir: adapters # Lua 适配目录;目录不存在时首启创建并 seed 内置,存在则只读
|
||
runtime_file: runtime.json # WebUI 编辑的源/密钥/AUTO 链持久化到此文件
|
||
|
||
sources:
|
||
- name: deepseek
|
||
base_url: https://api.deepseek.com
|
||
api_key: sk-... # 也可用 api_key_env: SOME_ENV 引用环境变量(不落盘明文)
|
||
adapter: deepseek
|
||
max_concurrent: 8
|
||
models:
|
||
- id: deepseek-v4-flash
|
||
priority: 100 # YAML 源首次启动会 seed 进 AUTO 链的初值
|
||
kind: chat
|
||
- id: deepseek-v4-pro
|
||
priority: 60
|
||
kind: chat
|
||
# 静态请求头(比适配器默认优先)
|
||
headers: { X-Tenant: prod }
|
||
# 传给 Lua build_headers 的透传元数据
|
||
meta: { app_id: x, app_secret: y }
|
||
temperature: 0.7
|
||
max_tokens: 4096
|
||
timeout: 120s # 请求超时,默认 120s
|
||
```
|
||
|
||
|
||
## 自动探测模型列表
|
||
|
||
不想手抄模型 id 时,只填 URL 和 key,让网关去扫:
|
||
|
||
```bash
|
||
# 只探测,不写任何东西
|
||
curl -X POST http://127.0.0.1:8080/api/sources/discover \
|
||
-H "Authorization: Bearer sk-gw-…" -H "Content-Type: application/json" \
|
||
-d '{"base_url":"https://api.vendor.com/v1","api_key":"sk-…"}'
|
||
# → {"ok":true,"base_url":"…","endpoint":"","models":["gpt-x",…],
|
||
# "image_models":["gpt-image-2"],"models_path":"/models","latency_ms":1387,…}
|
||
|
||
# 探测后直接落库
|
||
curl -X POST http://127.0.0.1:8080/api/sources/discover/apply \
|
||
-H "Authorization: Bearer sk-gw-…" -H "Content-Type: application/json" \
|
||
-d '{"base_url":"https://api.vendor.com/v1","api_key":"sk-…","name":"vendor"}'
|
||
```
|
||
|
||
WebUI 源编辑框里也有「自动探测模型」按钮:填好 URL 和 key 点一下,列表自动
|
||
填好,仍需点保存才生效。
|
||
|
||
几点值得知道:
|
||
|
||
- **URL 形态不用猜。** 厂商对「`/v1` 放 base 还是放 path」没有共识,探测会
|
||
依次尝试若干候选并返回答对的那个,同时给出归一化后的 `base_url` +
|
||
`endpoint`(`endpoint` 平时留空,因为 openai adapter 自带 `/chat/completions`)。
|
||
- **`image_endpoint` 是另一套规则。** 网关的图像默认路径是**绝对**的
|
||
`/v1/images/generations`,而 chat 默认是相对的。所以 `base_url` 带 `/v1` 时
|
||
必须显式给 `image_endpoint: /images/generations`,否则图像请求 404。
|
||
探测会替你填好。
|
||
- **图像模型只看上游自己声明的 modality**,绝不从模型名猜。`gpt-image-2`
|
||
这类 id 也出现在同时提供对话服务的目录上,猜错会让 AUTO 链选中一个只支持
|
||
图像生成的模型,其后每一层都被它拖垮。
|
||
- **不会逐模型探活。** 34 个模型的目录跑一轮要花钱且耗时数分钟,`/models`
|
||
是足够便宜的信号;模型是否真能应答留给正常请求去发现。
|
||
- **对已存在的源,必须显式 `replace_models: true`** 才会覆盖模型列表。重新
|
||
探测顺手抹掉为 AUTO 链手工调过的 priority,是最难排查的一类数据丢失。
|
||
- **`api_key` 留空表示沿用已存值**,不会把凭据清空。
|
||
|
||
|
||
`config.yaml`、`runtime.json`、`master.key`、`adapters/` 均不提交版本库(见
|
||
`.gitignore`),请随备份带走——`master.key` 丢失后运行时密文无法解密。
|
||
|
||
运行文件生成在 `-config` 所在目录:`adapter_dir` / `runtime_file` 自动指向
|
||
配置文件旁边的 `adapters/` 与 `runtime.json`,从任意工作目录启动都可用。
|
||
|
||
运行时文件(`runtime_file`)中的敏感字段自动加密:
|
||
|
||
- 加密算法 AES-256-GCM,格式 `enc:v1:<base64>`。
|
||
- 主密钥来源:环境变量 `MODELROUTER_MASTER_KEY`(64 位 hex);否则读取
|
||
`runtime_file` 同目录的 `master.key`;都不存在时首次启动自动生成 `master.key`(0600)。
|
||
- 升级时旧明文文件自动兼容:首次运行正常读取,任何 UI 保存操作触发全文件加密迁移。
|
||
- 注意:**master.key 丢失后密文无法解密**,请随配置一起备份;切勿提交到版本库。
|
||
|
||
## 网关密钥(多密钥)
|
||
|
||
网关鉴权采用「多密钥 + 角色 + 模型范围」架构,密钥持久化在 `runtime_file` 的
|
||
`keys` 字段(加密存储):
|
||
|
||
- `gateway_keys` 配置只是**初始 admin 密钥种子**:首次启动迁移为运行时 admin
|
||
key,之后不再参与鉴权管理。
|
||
- **重要:首次启动后请在 WebUI「密钥」页更换管理员密钥**——初始密钥明文写在
|
||
`config.yaml` 里(每次安装随机生成),继续使用存在被盗风险;用新密钥登录后删除初始密钥。
|
||
- WebUI **密钥页**可创建/删除密钥;每个密钥可指定 `admin`(管理全部)或
|
||
`user`(仅看自己的 key)角色,并配置**模型范围**(模型 + 源 + token 配额 +
|
||
重置周期)。
|
||
- 客户端用任意一个已授权的密钥明文作为 Bearer(`Authorization: Bearer <key>`)。
|
||
- 删除密钥即从运行时存储移除,立即失效。
|
||
|
||
### 密钥用量配额
|
||
|
||
**配额按模型单独设置**:每个密钥的 `models[]` 里,每一条模型范围各自带一份
|
||
token 配额与请求数配额。一个模型用满只影响该模型,同一密钥的其它模型照常工作。
|
||
|
||
```yaml
|
||
keys:
|
||
- key: sk-gw-<hex>
|
||
role: user
|
||
name: agent-alice
|
||
models:
|
||
- model: deepseek-v4-flash
|
||
token_quota: 1000000 # 本周期内该 key 用这个模型的 token 预算
|
||
req_quota: 20000 # 本周期内的请求次数
|
||
period: nhour # "" | hour | week | month | nhour
|
||
hours: 6 # 仅 nhour
|
||
- model: AUTO
|
||
token_quota: 5000000 # AUTO 也是一条独立配额
|
||
period: day # ← 这种写法会被拒绝(词表只有 hour/week/month/nhour)
|
||
- model: kimi-k3 # 未列配额 = 无限
|
||
```
|
||
|
||
- **没有「整钥总配额」**:这是刻意的设计。整钥总额会让「把 A 模型的额度挪给
|
||
B 模型」变成一次全局重分配;按模型独立计费则每个模型各自可控,运维可以
|
||
看出哪个模型吃掉了预算。
|
||
- `period` 词表:空 = 永不过期(累计总量),`hour` / `week` / `month` = 固定窗口,
|
||
`nhour` + `hours` = 自定义小时数。**拼错的周期在写入时就被拒**,不会静默变成
|
||
永不过期。
|
||
- 配额严格按密钥隔离,且**同一密钥内按模型隔离**:A 密钥用满 `m1` 不会消耗
|
||
B 密钥的额度,同一密钥的 `m2` 也不受影响。
|
||
- 配额统计含聊天、流式、生图,跨重启从审计日志回放(保留 40 天,覆盖最长的
|
||
month 窗口)。
|
||
- 配额耗尽返回 **429 + `Retry-After`**(`rate_limit_exceeded`),消息里点名是哪个
|
||
模型用满了,客户端可等窗口重置后自动恢复;模型越权才是 403。
|
||
**admin 密钥永不受配额限制**(其 scope 上的配额也不强制),避免把管理员
|
||
锁在门外。
|
||
- 窗口用量按整点小时分桶统计,实际释放比配置窗口最多晚 1 小时(配额宁可晚释放
|
||
也不超发)。
|
||
- `PUT /api/keys/{key}` 提交 `models` 即同时提交它们的配额(配额就是 scope 的一部分,
|
||
不存在会与模型列表脱节的第二份预算)。显式 `0` = 解除该模型的限制。
|
||
|
||
#### 配额拒绝 vs 容量拒绝:两种「拒绝」含义不同
|
||
|
||
配额在**入口**检查,不占用任何上游槽位,因此是廉价拒绝;容量不足则要走
|
||
`busyWait` 有界等待后才返回 503。实测(容量 8、0.6s/请求、100 并发):
|
||
|
||
| 情况 | 状态码 | 延迟 | 是否打到上游 |
|
||
|---|---|---|---|
|
||
| 配额用尽(`req_quota` 50 → 100 并发) | `429 rate_limit_exceeded` + `Retry-After` | **19–21ms** | 否 |
|
||
| 容量打满(同一拓扑,未限配额) | `503 upstream_error` | ~2.6s(`busyWait`) | 超出部分否 |
|
||
|
||
客户端因此可以区分「等窗口重置」与「等上游腾出容量」:前者只需等待,后者
|
||
通常该降并发或换源。
|
||
|
||
#### 配额的开销
|
||
|
||
配额桶按 (密钥, 模型, 整点小时) 分桶保留 40 天,实测(AMD 7840HS):
|
||
|
||
- 窗口查询按窗口长度而非保留总量扫描:1h 窗口 49ns、24h 窗口 55ns、
|
||
30d 窗口 3.9μs(此前全扫保留总量,960 桶时 5.9μs)。
|
||
- 记录一条请求:283ns、3 分配(与引入配额前相同,分配来自 ring buffer)。
|
||
- 内存:生产形态(7 密钥 × 8 模型 × 2 源 × 满 40 天 retention)约 **3.7MB**。
|
||
按源 pin 的 `source::model` 桶**惰性创建**——只有当某条配额真的 pin 了
|
||
某个源时才维护,否则每条记录多写一份桶,在 20 密钥 × 8 模型 × 3 源下会
|
||
多占 18MB。
|
||
代价:配置 pinned 配额之前的历史用量无法事后按源拆分,所以 **pinned 配额的
|
||
首个窗口可能少算**。
|
||
|
||
```bash
|
||
# 配额耗尽时客户端看到(点名了具体模型)
|
||
HTTP/1.1 429 Too Many Requests
|
||
Retry-After: 2100
|
||
{"error":{"type":"rate_limit_exceeded","message":"token quota exceeded for \"deepseek-v4-flash\" (5000000/5000000)"}}
|
||
```
|
||
|