mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
refactor(billing)!: 计费全部沉入插件,内核零 billing 代码;规则由插件自持
按维护者要求(issue #1 回复方向):内核不处理任何 billing 特有逻辑;插件有完整
OS 权限,自己保存配置文件。据此不再保留任何"内核 ↔ billing 插件"的专用对接。
- `internal/billing`(Go 编译器 + 测试):删除;
- `internal/core` 的 billing 接口/降级应用/规则加载:删除(含 `billingPath`);
- `internal/gateway` 的 `/api/plugins/billing/rules` 端点与测试:删除;
- `config` 的 `BillingDSL/Profile/Rule/Token/Peak/LongContext` 与校验、
`billing:`/`billing_file:` 配置段:删除。系统配置里不再有 billing 段。
内核只保留所有插件通用的能力:加载 .lua、插件状态持久化、插件目录
(`__llmsproxy_plugin_dir`)、`plugin.http` 路由、插件可用的 io/os。
- 规则文件移到**插件自己的目录**:`<plugin_dir>/billing.json`,由插件用 io 自己
读写(tmp + rename 原子替换);加载插件时自己读文件并编译;
- 规则以**源名** `source` 为键(`url:"*"` 仅表示全局模型价目表):URL→源名映射
属于内核配置,插件不为它和内核对接;编辑页在浏览器里取 `/api/sources` 选源名;
- 规则编辑 API 改为插件自己的 `/api/plugins/http/billing/rules`
(GET/PUT/POST/DELETE,roles=admin),写盘也在插件内完成;
- 计费账目仍在插件状态文件(内核通用持久化)`<plugin_dir>/.billing.state.json`;
- `priceFor` 落地 profile 级 `default` 策略与显式 `mode=unpriced` 的优先级。
生产已迁移:`config.yaml` 的 `billing:` 规则转为 `plugins/billing.json`(按源名
重写),并从系统配置中移除;规则文件与系统配置从此各自独立保存。
- `internal/lua/billing_rules_test.go`:插件自写 billing.json 并重启恢复、
free/subscription/token/通配符编译语义、1e6+1e6 token = 0.75 端到端、
非法规则被拒且不覆盖旧文件、reasoning/long_context/peak;
- `internal/lua/billing_test.go`:profile default 策略三态 + 显式 unpriced 优先;
- `internal/lua/billing_ui_test.go`:规则编辑页仍挂在 billing 页内、列宽/列数守卫。
(cherry picked from commit 7941b4110a)
This commit is contained in:
@ -503,15 +503,57 @@ end
|
||||
|
||||
`plugins/billing.lua`,默认随首启 seed 进来,开箱可用。
|
||||
|
||||
### 7.1 计费维度
|
||||
### 7.0 分层:billing 完全在插件里,内核没有任何 billing 代码
|
||||
|
||||
| 维度 | 用途 | 优先级 |
|
||||
`billing` 是**插件自己的事**:规则文件、编译、计价、账目、规则编辑页都由
|
||||
`plugins/billing.lua` 负责。内核里没有 billing 的配置结构、端点、编译或迁移逻辑
|
||||
—— 它只提供所有插件通用的能力(加载 `.lua`、插件状态持久化、插件目录、
|
||||
`plugin.http` 路由、插件可用的 `io`/`os`)。
|
||||
|
||||
| 项 | 位置 |
|
||||
|---|---|
|
||||
| 规则文件 | `<plugin_dir>/billing.json`(插件自己读写;内核不知道这个文件) |
|
||||
| 计费账目 | 插件状态文件 `<plugin_dir>/.billing.state.json`(内核的通用状态持久化) |
|
||||
| 规则编辑 API | `/api/plugins/http/billing/rules`(GET/PUT/POST/DELETE,插件路由) |
|
||||
| 编译/计价 | 插件内 `compileRules()` / `priceFor()` / `costFor()` |
|
||||
|
||||
规则以**源名**(`source`)为键,而不是 URL:URL→源名的映射属于内核配置,插件
|
||||
不需要为了它和内核对接;编辑页在浏览器里,直接取 `/api/sources` 给运营选源名。
|
||||
`url: "*"` 只表示"全局模型价目表"(模型维度本来就不分源)。
|
||||
|
||||
> 历史:早期 `billing:` 写在 `config.yaml`、编译在 Go 的 `internal/billing` 里。
|
||||
> GitCode issue #1 的根因正是"Go 与 Lua 各写一半覆盖语义",组合起来把模型价
|
||||
> 静默清零。现在内核侧 billing 代码已全部删除,规则文件也由插件自持。
|
||||
|
||||
### 7.1 计费维度### 7.1 计费维度
|
||||
|
||||
| 维度 | 用途 | token 价覆盖顺序 |
|
||||
|---|---|---|
|
||||
| `sources` | 源的固定价(如按请求计费的转发服务) | 中 |
|
||||
| `models` | 模型的 per-token 价 | 高 |
|
||||
| `default` | 兜底价(通常全 0) | 最低 |
|
||||
| `models` | 模型的 per-token 价 | 中 |
|
||||
| `sources` | 源级价:固定价,或覆盖该源全部模型的 token 价 | 高 |
|
||||
| `keys` | 单个网关 key 的覆盖价 | **最高** |
|
||||
|
||||
**token 价**优先级:`keys` > `models` > `default`。
|
||||
**token 价**优先级:`keys` > `sources` > `models` > `default`。
|
||||
(实现顺序就是"后写覆盖前写":`default` → `models` → `sources` → `keys`;
|
||||
所以**源级 token 价会覆盖模型价**。这是有意的:运营要能给某个源整体改价。)
|
||||
|
||||
⚠️ 正因为 `sources` 会覆盖 `models`:**`mode: token` 的规则不产生任何源条目**。
|
||||
规则未列出的模型会进入未定价统计(可见),而不是静默计 0 —— 这是 issue #1 / PR #2
|
||||
的收口语义。只有 `free` / `subscription`("边际成本为零")会写有意为之的
|
||||
`{prompt:0, completion:0}` 零价条目。
|
||||
|
||||
规则写法:
|
||||
|
||||
```json
|
||||
{"active":"current","profiles":[{"id":"current","default":"unpriced","rules":[
|
||||
{"source":"localzen","mode":"free"},
|
||||
{"source":"codearts","mode":"token",
|
||||
"models":{"glm-5.3":{"prompt":"0.15","completion":"0.60"}}},
|
||||
{"url":"*","mode":"token","models":{"some-model":{"prompt":"1.0","completion":"2.0"}}}
|
||||
]}]}
|
||||
```
|
||||
|
||||
**`per_request` 固定价是叠加的**(不覆盖),所以一个生图模型可以既算 token 又收固定费。
|
||||
**`cache_discount`** 见 §7.7。
|
||||
|
||||
@ -599,6 +641,17 @@ curl -H "Authorization: Bearer $ADMIN_KEY" \
|
||||
|
||||
仪表盘上有 "Unpriced" 卡片。**这个数应该是 0**;不是 0 就去补价目。
|
||||
|
||||
**profile 级兜底**:`profiles[].default` 可设为 `free` 或 `unpriced`(默认)。
|
||||
它是插件运行时的策略(编译期写入 `prices.default_mode`):
|
||||
|
||||
| `default` | 没有单独规则的源 | `unpriced_reqs` |
|
||||
|---|---|---|
|
||||
| `unpriced`(省略时) | 零价,计入未定价债务 | +1 |
|
||||
| `free` | 零价,视为**刻意免费** | 不增加 |
|
||||
|
||||
显式 `mode: unpriced` 的源即使在 `default: free` 下也仍然计入("只统计不收费"
|
||||
的声明不能被兜底策略吞掉)。
|
||||
|
||||
注意"未定价"不等于"免费":这些请求的 `requests` / token 数**照常计入**
|
||||
`total` 与各维度,只有金额是 0。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user