Files
litellm-config/merge-volc-limit-into-newapi.md
T

81 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 火山引擎免费额度单日限制合入 new-api 的可行性分析
> 日期:2026-08-05
> 背景:litellm 目前唯一实质用途是 `volc_limit_sync.py`(火山引擎免费资源包的单日用量限制)。考虑将该功能合入 new-api(另一台服务器的 `~/workspace/new-api`,deploy 分支 fork),从而退役 litellm。
> 证据来源:`volc_limit_sync.py`(本仓库)与 `/root/workspace/new-api` 源码(HEAD `e4324841`,2026-08-01,upstream QuantumNous/new-api 活跃)。
## 一、现状:volc_limit_sync.py 的四个核心能力
单文件 1393 行,挂在 LiteLLM 回调上,由后台守护线程驱动:
| 能力 | 实现 | 位置 |
|---|---|---|
| 火山余额同步 | SigV4 签名调 `ListResourcePackages`,分页拉取资源包;`normalize_volc_name` 去后缀归一化;按监控模型聚合余额 | `fetch_all_packages` / `process_packages_breakdown` |
| 动态限额(Peak-Buffer) | `PeakPool = 当日用量 + 余额`;峰池跟踪(余额跳升 >5% 立即信任、骤降接受、初始化/漂移取当前值);`stable_limit = PeakPool − 15% margin(下限 25w)`;与静态 `HARD_DAILY_LIMITS`(含 api_base hash 精确匹配、模糊匹配、`__DEFAULT__` 兜底)取小得 `effective_limit` | `get_effective_limit` + `active_monitor_loop` |
| 当日用量计数 | 成功回调 `INCRBY total_tokens` → Redis `daily_usage:{北京日期}:{clean_model}:{api_base_hash}`;北京时间 00:00 滚动,TTL 2 天 | `_record_usage` / `get_usage_key` |
| 超限停用/恢复 | `soft_evict_from_router` 把 router 中 model_name 重命名为 `xxx-limit-reached` 软踢;北京时间早 7–13 点每 30 分钟、其余 4 小时检查恢复;启动时 `startup_audit` 预踢超额模型 | `soft_evict_from_router` / `restore_blocked_models` / `startup_audit` |
监控模型自动发现:从 `config.yaml` 解析 `volcengine/` 前缀 + `HUOSHAN_BASE_URL`/`volcengineapi` 的条目,构建 `MONITORED_MODEL_PAIRS`(模型名 + api_base hash);常规版本号模型自动推导别名,特殊别名走 `MANUAL_MONITORED_MODEL_ALIASES`。
要点:**按「模型 + 端点」粒度限制**;**成功后被动踢**(滞后一个请求);靠直接改 LiteLLM router 内部结构实现,较为 hacky。
## 二、new-api 现状调查结论
### ✅ 可直接复用的机制
- **volcengine 是原生渠道**:`relay/channel/volcengine/` 完整适配器(`constant.ChannelTypeVolcEngine`,adaptor.go:242),支持 chat/embedding/images/TTS,`GetRequestURL` 走 `/api/v3/...`。
- **渠道状态机**:`Status`:1=启用 / 2=手动禁用 / 3=自动禁用(common/constants.go:252-254);`service.DisableChannel` / `EnableChannel`(service/channel.go:19-36);自动禁用链路 `processChannelError` → `ShouldDisableChannel` + `AutoBan`(controller/relay.go:399-404)。
- **定时任务基建**:通用 `SystemTaskRunner`(DB 租约去重、多 master 互斥、`ScheduledSystemTaskHandler` 接口 Type/Run/Enabled/Interval/NewPayload,service/system_task.go:29-44;main.go:151-152 启动)。另有订阅配额每日重置任务 `StartSubscriptionQuotaResetTask`(ticker 1min 批量重置,含 daily 对齐次日 0 点的 `calcNextResetTime`,model/subscription.go:352-381,main.go:129)。
- **用量数据**:`quota_data` 表按 **模型 × 渠道 × 小时** 聚合(`LogQuotaData` 截断到小时,model/usedata.go:78-81);`logs` 表有每次请求 token 明细(`RecordConsumeLog`,model/log.go:343)。按模型查当日用量现成。
- **渠道级限流范式**:`service/channel_rate_limit.go`(Redis 固定窗口 RPM 检查,relay 重试循环内排除被限渠道并 429)——与「每日限额」几乎同构,是最佳参考实现。
- **渠道额外设置 JSON 扩展范式**:`ChannelOtherSettings`(relaykit/dto/channel_settings.go:68-91,含 RateLimitEnabled/RateLimitRPM/RateLimitTPM)——每日限额字段可照此扩展。
- **程序化渠道管理 API**:`POST /api/channel/:id/status`(启停)、`PUT /api/channel/`(改配置)、`GET /api/channel/`(列表)(router/channel-router.go:39-90)——旁路方案 B 的基础。
### ❌ 完全缺失的(全仓库 grep `DailyLimit/每日限额` 0 命中)
- 渠道/模型的**每日额度限额**字段与执行逻辑(Channel 表只有累计 `UsedQuota`,只增不减)。
- 令牌配额**每日重置**(一次性扣减;subscription 每日重置是唯一先例)。
- **火山余额同步**(适配器只转发、不查账)。
## 三、可行性结论:可行,且 new-api 架构上更合适
1. **可请求前拦截**:渠道选择在 `middleware.Distribute` / `controller.Relay` 重试循环(controller/relay.go:204-210,与 RPM 限流同款位置)完成,可在请求发出前跳过超限渠道——优于现状「成功后才踢」,杜绝超限瞬间的漏网请求。
2. **停用/恢复有现成状态机**:无需 hack router 内部结构;自动恢复可借鉴周期渠道测试的 PassiveRecovery 模式(controller/channel-test.go:1024)。
3. **同步任务有框架**:火山余额同步做成一个 `ScheduledSystemTaskHandler`,天然多 master 安全。
4. **litellm 可退役**:合并后 litellm + redis + postgres 三个容器下线,消除一个攻击面(docker-compose 注释已提示 litellm 1.82.7/1.82.8 被投毒)。
## 四、两条合入路径对比
| | A. 改 fork 源码(Go 实现) | B. 不改源码:独立守护进程 + 管理 API |
|---|---|---|
| 做法 | 在 deploy fork 上加每日限额功能(字段 + 检查点 + 同步任务 + 前端) | 改造 volc_limit_sync.py 去掉 litellm 依赖:查 new-api DB/Redis 统计当日用量 → 调 `POST /api/channel/:id/status` 停/启渠道;余额同步逻辑原样保留 |
| 拦截时机 | 请求前(跳过渠道 / 429) | 成功后停渠道(与现状一致,滞后一个请求) |
| 成本 | Go 开发 + 前端表单 + fork 维护 | Python 改造 + 一个常驻进程 |
| 优点 | 彻底、架构最干净、UI 可配置 | 不碰 Go、可快速上线、可作 PoC |
| 缺点 | 工作量大;upstream 更新极频繁需持续同步 | 无法请求前拦截;多一个系统要运维 |
**倾向推荐 A**:已在用带定制功能的 deploy fork(RPM 限流、订阅重置、系统任务均为 fork 自加),fork 维护成本已存在;A 方案下「超限恢复」可与渠道自动测试/恢复机制统一。
## 五、路径 A 改造点清单
1. `ChannelOtherSettings` 扩展每日限额配置:`DailyLimitEnabled` / `DailyLimitTokens` / 火山余额同步开关等。
2. 新增 `service/channel_daily_limit.go`:当日用量计数(Redis INCR,北京时间滚动,仿 `service/channel_rate_limit.go` 固定窗口)。
3. 检查点:`controller/relay.go:204-210` 重试循环内跳过超限渠道(或直接 429)。
4. 新增 `SystemTaskHandler`:火山余额同步(SigV4 `ListResourcePackages` + 归一化 + Peak-Buffer 动态限额),结果写 Redis/DB。
5. 超限处理:渠道级用 `DisableChannel`;**粒度注意**——litellm 按「模型」限,new-api 需按「渠道 + 模型」统计(`quota_data` 有 ChannelID+ModelName 双维度,正合适)。
6. 前端渠道编辑表单加每日限额字段。
7. 迁移 config.yaml:别名 1:N 映射(如 `doubao-seed-nothink` → 3 个上游模型)在 new-api 中 model_mapping 是 1:1,需用「多个渠道声明同一对外模型名 + 权重轮询」实现。
## 六、风险提示
- **Peak-Buffer 算法是多年调出来的业务资产**(跳升信任 / 骤降接受 / 15% 边际 / 30w 缓冲 / 空闲感知 + 突发检测的动态同步间隔),移植时须完整保留,勿简化。
- **fork 分支特殊性**:系统任务 / RPM 限流 / 订阅重置等基建**上游 main 没有**;若目标是反哺上游,需在纯上游上重新评估。
- **密钥暴露面**:迁移后 new-api 直接持有火山 AK/SK(目前只存在于 litellm 服务器 env),需评估。
- **北京时间语义**:每日重置必须按 UTC+8 对齐(现有 `get_usage_key` 与订阅重置 `calcNextResetTime` 均有先例)。
## 七、后续可选动作
1. 路径 A 详细技术设计(数据模型 + 关键函数签名 + 前端字段)。
2. 路径 B 快速 PoC(可运行的旁路守护进程验证效果)。
3. 仅迁移规划(config.yaml 别名 → new-api 渠道配置映射表)。