fix: 限额改 v4 冻结快照,余额红线防扣费
v2 的 stable_limit = usage + balance - BUFFER 依赖余额 API 随消耗实时递减, 实测滞后(08-12 快照 1,558,307 在 usage 涨 45 万期间不动)导致池膨胀、限额 虚高,放行到扣费区(当日超余额 13.8 万已扣费)。 扣费口径(用户确认):是否扣费只取决于当下免费额度,超出即扣费,采集量只 决定明日发放。v4:stable_limit = balance - BUFFER 冻结快照,本地 usage 实时 撞线即拦,等价于本地实时扣减余额;effective = min(2M 配额, stable_limit)。 新增 docs/volc-quota-notes.md 额度机制速查;merge-auto-consume.md 补 §4.5。
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
# 火山免费额度自动消耗:auto_consume 合入 volc_limit_sync 设计方案
|
||||
|
||||
> 日期:2026-08-11
|
||||
> 背景:`/root/workspace/huoshan-api-auto_consume/`(自动消耗脚本)与 `/root/workspace/litellm-config/`(LiteLLM 限流回调 + 火山余额同步)合并,使每日 23:00 自动消耗免费额度,防止资源包过期蒸发,避免真实扣费。
|
||||
> 前置文档:《merge-volc-limit-into-newapi.md》(2026-08-05,方向相反:把 volc_limit_sync 功能迁往 new-api,未实施)。
|
||||
|
||||
---
|
||||
|
||||
## 一、火山免费额度业务模型(一切设计的事实基础)
|
||||
|
||||
### 1.1 日返还循环
|
||||
|
||||
```
|
||||
每天任意时刻 真实用户 + 消耗任务 的消耗 = 当日采集量(火山实时统计,每模型单独计算)
|
||||
每天 23:00 CST 消耗任务补齐缺口(可选,见 §4)
|
||||
次日 00:00 起 火山按昨日采集量发放资源包(不定时,可能延迟到上午)
|
||||
```
|
||||
|
||||
### 1.2 五个硬规则(用户确认)
|
||||
|
||||
| 规则 | 值 | 影响 |
|
||||
|---|---|---|
|
||||
| 采集上限 | **2M token/模型/天** | 消耗超过 2M 的部分**不返还**(白烧);所有模型统一 |
|
||||
| 发放额度 | = 昨日采集量(≤2M) | 消耗 X → 次日返还 X,用多少补多少,延迟一天到账 |
|
||||
| 发放时间 | **不定时** | 次日 0:00 至发放时点存在余额空窗 |
|
||||
| 资源包有效期 | **30 天** | 过期额度实打实消失,失去"启动资金" |
|
||||
| 消耗顺序 | **FIFO(先消耗获得时间早的包)** | 队列头部 = 最早获得 = 最早过期(效期统一 30 天时两者等价) |
|
||||
| 余额耗尽 | **请求不失败,按真实价格扣费** | 全部防护的终极目标:**避免扣费** |
|
||||
| 首次开通 | **每模型赠送 2M** | 启动资金的种子(无对应前日采集的免费午餐) |
|
||||
|
||||
### 1.3 关键账目:启动资金守恒
|
||||
|
||||
```
|
||||
余额_{N+1} = 余额_N − 消耗_N + 返还_{N+1}
|
||||
= 余额_N − 消耗_N + 采集_N
|
||||
= 余额_N (消耗即采集,≤2M 内全额返还)
|
||||
```
|
||||
|
||||
**推论**:
|
||||
- **使用不损失额度**(用多少补多少,仅延迟一天)
|
||||
- **过期才损失额度**(额度蒸发 = 启动资金永久减少)
|
||||
- 消耗任务的价值 = 防止"启动资金"被 30 天过期侵蚀,把额度滚成新包永续循环
|
||||
- 真实用量 ≥ 66.7k/天(= 2M/30 天)时,赠送包被自然消耗,脚本自动闲置;低于此值必须靠脚本在最后 9 天窗口洗掉
|
||||
|
||||
### 1.4 扣费风险链(限流系统的真正职责)
|
||||
|
||||
```
|
||||
免费额度耗尽(余额 = 0) → 继续请求 → 真实扣费
|
||||
```
|
||||
|
||||
触发扣费的两种路径:
|
||||
1. **真实用户单日用量 > 当日可用余额**(含空窗期 0:00–发放时点余额不足)
|
||||
2. **消耗任务烧穿余额**(脚本自身引入的扣费,必须结构性杜绝)
|
||||
|
||||
---
|
||||
|
||||
## 二、两项目现状与合并可行性
|
||||
|
||||
### 2.1 当前项目 `volc_limit_sync.py`(1536 行,LiteLLM 回调 + 常驻守护)
|
||||
|
||||
| 能力 | 实现 |
|
||||
|---|---|
|
||||
| 火山余额同步 | SigV4 签名调 `ListResourcePackages`(`Status: Effective` 过滤 + `seen_tokens` 防分页死循环),`normalize_volc_name` 归一化,按监控模型聚合 |
|
||||
| 动态限额 | Peak-Buffer:`PeakPool = 当日用量 + 余额`;`stable_limit = PeakPool − 15% margin(下限 25w)`;与静态 `HARD_DAILY_LIMITS` 取小得 `effective_limit` |
|
||||
| 当日用量计数 | LiteLLM 成功回调 `INCRBY total_tokens` → Redis `daily_usage:{北京日期}:{model}:{api_base_hash}`,北京时间 00:00 滚动,TTL 2 天 |
|
||||
| 超限停用/恢复 | `soft_evict_from_router` 重命名软踢;北京时间 7–13 点每 30 分钟、其余 4 小时检查恢复;`startup_audit` 启动预踢 |
|
||||
| 模型发现 | 解析 `config.yaml` 中 `volcengine/` 前缀 + `HUOSHAN_BASE_URL` 条目 → `MONITORED_MODEL_PAIRS`;版本号自动推导别名 + `MANUAL_MONITORED_MODEL_ALIASES` 手工覆盖 |
|
||||
|
||||
### 2.2 目标项目 `huoshan-api-auto_consume/`(420 行 + 4 辅助)
|
||||
|
||||
| 能力 | 实现 |
|
||||
|---|---|
|
||||
| 语料 | `/var/log` 系统日志分组 + 脱敏(IP/MAC/邮箱/路径)+ 不足时合成日志 |
|
||||
| 计划 | `calculate_plan`:9 天窗口内包 `remaining/ceil(days_left)×1.1`,最后 24h 冲刺全量,多包相加 |
|
||||
| 执行 | 直连 Ark API(`ark.cn-beijing.volces.com/api/v3/chat/completions`),速率伪装(15–40s 随机),指数退避,6h 上限,连续失败 5 次跳过 |
|
||||
| 配置 | `config.py`:`MONITOR_TARGETS`(id/keyword/alias/extra_params) |
|
||||
|
||||
### 2.3 可复用资产与冲突点
|
||||
|
||||
**重叠(合并即消除重复)**:
|
||||
- SigV4 签名(`volc_signer.py` ≈ `get_signed_headers`)——**同一账户同一组 AK/SK**
|
||||
- `fetch_all_packages`(目标版缺 `seen_tokens` 防循环保护,保留当前版)
|
||||
- 资源包→模型映射(当前版 `normalize_volc_name` + 别名体系更健壮,目标版 `keyword` 模糊匹配退役)
|
||||
|
||||
**关键事实:模型配置天然单一化**。`config.yaml` 的 volcengine 上游模型名(剥掉 `volcengine/` 前缀)就是 Ark endpoint id,与 `MONITOR_TARGETS` 的 id 一一对应(`deepseek-v4-flash` 的 `-ga-260731` vs `-0731` 差异已由 `MANUAL_MONITORED_MODEL_ALIASES` 覆盖)→ **`MONITOR_TARGETS` 退役,消耗任务直接由 config.yaml 驱动**。
|
||||
|
||||
**冲突点与解法**:
|
||||
1. 限额 vs 消耗:消耗请求**必须直连 Ark,绝不改走 LiteLLM**(否则被 `quota_interceptor` 拦下自杀)
|
||||
2. 消耗量**必须计入 `daily_usage`**(手动 INCR 同 key)——空窗期防扣费闭环的关键,见 §5.3
|
||||
3. 脚本自身不能烧穿余额(结构性防扣费,见 §4.4)
|
||||
|
||||
---
|
||||
|
||||
## 三、消耗策略设计
|
||||
|
||||
### 3.1 为什么不做"烧满 2M"
|
||||
|
||||
API 请求的 token 消耗不可精确控制(输入按字符估算、输出不确定)。若目标 = 补满 2M:
|
||||
- 烧超采集上限的部分不返还 → 白烧
|
||||
- 烧到余额 0 仍继续 → **直接扣费**
|
||||
- 精确校准需要逐请求调整 → 复杂度不可接受
|
||||
|
||||
### 3.2 目标:洗掉"即将过期的额度"(过期风险驱动)
|
||||
|
||||
```
|
||||
消耗量 = Σ(该模型 ExpiryTime − now ≤ MONITOR_DAYS 的资源包剩余额度)
|
||||
```
|
||||
|
||||
- 消耗量 ≤ 快过期包剩余 ≤ 当前可用余额 → **结构上不可能烧穿余额**(估算偏差只决定多烧/少烧几笔请求,多烧部分来自非过期包,次日照常返还,无害)
|
||||
- FIFO 先烧最早获得 = 最早过期的包 → 脚本烧的恰好是最该洗的包,顺序自动正确
|
||||
- 不追求 2M 精确值 → 无校准问题
|
||||
|
||||
### 3.3 均分逻辑(保留旧 `calculate_plan`,经真实使用验证)
|
||||
|
||||
```
|
||||
对每个 9 天窗口内的包:
|
||||
days_left ≤ 1.0 → plan = remaining # 24h 冲刺,全量兜底
|
||||
days_left > 1.0 → plan = remaining / ceil(days_left) × 1.1 # 均分 + 10% 提前量
|
||||
总 plan = Σ(各包 plan)
|
||||
```
|
||||
|
||||
**为什么均分优于一次性洗光**(在新模型下):
|
||||
1. **最小干扰真实用户**:只在额度即将死亡(进窗口)后才每天接管 1/D,其余留给真实用户 → 免费额度利用率最大化,空窗风险最小
|
||||
2. **请求负载分摊**:每天 3–5 次请求(而非 25–40 次),速率伪装自然,风控友好
|
||||
3. **余额下降平缓**:23:00 后余额仍保留大部分 → 扣费只发生在余额真正归零时,均分下脚本几乎不可能制造归零
|
||||
4. **FIFO 自动一致**:效期统一 30 天 → 获得早 = 过期早 → 队列头部 = 窗口内包,计划与执行不打架(分包明细仅用于定总量)
|
||||
|
||||
**与日返还循环的契合**:旧脚本本就是"每天跑一次、分 D 天烧完"的设计;自动化后每天重算(余额/天数变化 → div 递减 → 当日份额递增),节奏天然吻合。9 天窗口 + 1.1 系数 + 24h 冲刺:只要每天触发成功,任何进窗口的包都赶在过期前清空。
|
||||
|
||||
### 3.4 三个约束(旧逻辑缺失,必须补)
|
||||
|
||||
```
|
||||
plan = min(plan, 2M − daily_usage) # 采集上限:超出不返还,白烧
|
||||
plan = min(plan, 余额快照 − 本次会话已烧 − BUFFER) # 防扣费:脚本不烧穿余额
|
||||
```
|
||||
|
||||
**执行停止条件(每轮请求后检查,3 条闭环)**:
|
||||
```
|
||||
daily_usage ≥ 2M → 停(采集上限,再烧不返还)
|
||||
余额快照 − 本次已烧 ≤ 0 → 停(防扣费)
|
||||
本次已烧 ≥ plan → 停(任务完成)
|
||||
```
|
||||
|
||||
### 3.5 消耗量估算
|
||||
|
||||
- 单次请求:输入 3–5.5 万字符 ≈ 5–11 万 token(中文 1 字 ≈ 1–2 token)+ 输出 ~1000
|
||||
- 极值 2M / 8 万 ≈ 25 次请求 → 15–40s 间隔 ≈ 10–17 分钟,1 小时窗口绰绰有余
|
||||
- 均分模式每天 3–5 次请求,更宽松
|
||||
|
||||
### 3.6 触发调度
|
||||
|
||||
- 北京时间 **23:00 ± 随机抖动**(防固定时间被风控识别),Redis `consume:last_run:{日期}` 去重,当天未跑过才触发
|
||||
- 守护线程(`active_monitor_loop` 已有主循环)检测时点 → spawn daemon 线程执行,不阻塞主循环
|
||||
- 23:00 的时点语义:距 00:00 采集结算 1 小时,此时窗口内包状态已定(真实用户当天用量已发生),洗包目标最准
|
||||
|
||||
---
|
||||
|
||||
## 四、限额设计(防扣费)——v2 简化逻辑(v4 修订见 §4.5,2026-08-12 已实施)
|
||||
|
||||
### 4.1 为什么废弃 Peak-Buffer 峰值状态机(存量场景产物)
|
||||
|
||||
旧逻辑 `stable_limit = PeakPool × 0.85`(PeakPool = usage + balance,首锁/门控重锁/DROP 状态机维护)有两个问题:
|
||||
|
||||
1. **比例 margin 在 usage 接近 balance 时数学失效**(致命):
|
||||
- `u=0.5b` 时限额允许再烧 `0.775b`,实际余额只剩 `0.5b`(超 55%)
|
||||
- `u=0.8b` 时允许再烧 `0.73b`,实际只剩 `0.2b`(超 3.6 倍)→ **烧穿扣费**
|
||||
- 存量场景没爆是因为 usage 远小于 balance;新模型"返还 = 消耗"使 usage 天天逼近 balance → 必炸
|
||||
2. **峰值状态机防的是旧 bug**(存量包余额只减不增时,滞后虚高把 PeakPool 漂移、限额越算越多 399,836 → 599,511)。新模型 balance 每天真实跳涨(返还),锁峰值会把真实发放当异常压住——**跟随现值即可,状态机删除**
|
||||
|
||||
### 4.1b 1.7M/1.8M 保守值的来历:post-hoc 滞后时代的遗产
|
||||
|
||||
旧拦截机制是"请求成功后记账 + 踢模型"(`log_success_event`),**超限请求本身已放行**,存在"滞后一个请求"的漏网量。因此限额必须打折留安全垫:`HARD_DAILY_LIMITS` 默认 1.8M(2M 打 9 折)、`stable_limit` 打 8.5 折(≈1.7M 效应)。
|
||||
|
||||
**现状已改为请求前闸门**:`async_pre_call_hook` → `_check_request_block`(429 拒绝,不进 router,不触发 retries/fallback)——主拦截不再滞后,"滞后一个请求"的折扣失去存在必要。新公式精确到 2M;BUFFER 30 万保留,但它防的是**余额统计滞后**(实测 20 万内不动,固定量非比例),与请求滞后无关。
|
||||
|
||||
### 4.2 新公式
|
||||
|
||||
> ⚠️ 2026-08-12 修订:本节 v2 公式「池恒定」假设(usage ↑ / balance ↓ 同步)失效,
|
||||
> 已由 v4 取代(见 §4.5)。保留原文作历史论证。
|
||||
|
||||
```
|
||||
effective_limit = min(HARD_DAILY_LIMITS, max(0, usage + balance − BUFFER))
|
||||
HARD_DAILY_LIMITS → 统一 2M(采集上限;doubao-seed-code-preview-251028 480 万遗留一并改为 2M)
|
||||
```
|
||||
|
||||
**`usage + balance` = 当日初始可用池,天然稳定**:
|
||||
- 消耗中:usage ↑ / balance ↓ → 池恒定 → 限额恒定 ✓
|
||||
- 返还:balance 跳涨 → 池涨 → 限额次日自动解锁(无需 RECHARGE)✓
|
||||
- 过期:balance 跳跌 → 池跌 → 限额收紧(无需 DROP)✓
|
||||
- 余额滞后(实测 20 万内不动):池虚高 ≤ 20 万 → BUFFER 30 万覆盖 ✓
|
||||
- 消耗任务 23:00 烧(usage 计入):池不变 → 滞后窗口内限额偏松 ≤ BUFFER ✓
|
||||
- 余额耗尽:池 − 30 万 ≤ 0 → 限额 0 → 全拦,扣费结构性杜绝 ✓
|
||||
|
||||
**fallback(stable_limit 缺失窗口)**:免费模型余额常态 < 2M(昨日采多少返多少),若 dynamic 缺失(首次同步前/新增模型/TTL 过期)时裸奔到静态 2M,余额不足的模型会被烧穿扣费。故 fallback 用保守值 `FALLBACK_LIMIT = min(static, 90万)`(env 可调),宁可短时收紧不可裸奔。
|
||||
|
||||
**与消耗任务同构**:限额放行条件 = `balance − BUFFER ≥ 0`;消耗任务停止条件 = `余额快照 − 已烧 ≤ 0`——同一把尺子。
|
||||
|
||||
### 4.3 简化后保留/删除清单
|
||||
|
||||
| 保留 | 删除(~100 行) |
|
||||
|---|---|
|
||||
| 同步频率控制(驱动改为 balance,余额越低同步越勤) | `volc:peak_pool:*` 状态机 |
|
||||
| `remaining = balance − BUFFER` 写入 | RECHARGE / DROP / 首锁 / 门控重锁分支 |
|
||||
| 软踢 / 恢复 / 审计 / pre-block / usage 记账 | `volc:last_seen_pool` / `volc:last_balance` |
|
||||
| `get_usage_key` 北京时间滚动 | stable_limit 改直接写 `max(0, usage + balance − BUFFER)` |
|
||||
|
||||
### 4.4 空窗期防扣费闭环(消耗量计入 daily_usage 的理由)
|
||||
|
||||
23:00 消耗任务把额度洗掉后余额可能很低,此时真实用户请求必须被拦。拦截依据 = `daily_usage vs effective_limit`:
|
||||
|
||||
- **若消耗量不计入 daily_usage**:限额看到"真实用户才用 100k,未超限" → 放行 → **扣费**
|
||||
- **若计入**:daily_usage ≈ 真实 + 消耗任务 ≈ 2M → 超限拦截 → 空窗期安全
|
||||
|
||||
同步线程检测 balance 归零 → limit 收紧有 2–60min 滞后;消耗量计入让拦截在**滞后窗口内也有效**,双保险。
|
||||
|
||||
### 4.5 v4 修订(2026-08-12 已实施):冻结快照,余额红线
|
||||
|
||||
**v2 失效点**:`usage + balance = 当日初始可用池,消耗中恒定` 依赖余额 API 随消耗**实时递减**。实测余额 API 滞后(2026-08-12:快照 1,558,307 在 usage 1.24M→1.70M 期间纹丝不动,滞后上限未知),balance 不随消耗递减 → 池随 usage 膨胀(2.80M→3.26M)→「余额耗尽 → 限额 0」防线失效 → 放行到扣费区(当日超余额 13.8 万已扣费)。
|
||||
|
||||
**扣费口径(用户确认,2026-08-12)**:是否扣费只取决于**当下免费额度(余额)**;超出免费额度的请求都会被扣费;采集量只决定明日发放,与今日是否扣费无关。→ 余额必须是闸门红线,且不能依赖 API 实时性。
|
||||
|
||||
**v4 公式**:
|
||||
|
||||
```
|
||||
stable_limit = max(0, balance − BUFFER) # 冻结快照,同步时算一次,不随 usage 膨胀
|
||||
effective_limit = min(HARD_DAILY_LIMITS, stable_limit) # 2M 防白烧;余额红线防扣费
|
||||
```
|
||||
|
||||
- 以最近同步快照的余额为「当日免费池上限」,本地 usage 实时累计撞线即拦 → **等价于本地实时扣减余额**,不依赖 API 刷新
|
||||
- 返还:balance 跳涨 → 下次同步红线上升 → 自动解锁 ✓;过期/异常:balance 跳跌 → 红线收紧 ✓
|
||||
- 余额 < BUFFER → 限额 0 → 全拦,扣费结构性杜绝 ✓
|
||||
- 与消耗任务「余额快照 − 已烧 ≤ 0 停」同一把尺子(不变量:放行条件 = `balance − BUFFER ≥ 0`)
|
||||
- 与昨日采集量**无关**:昨日 10K、余额 2M → 今日可用 1.7M(存量余额守恒,v3「昨日锚」因把单日流量当存量已被否决,见 volc-quota-notes.md §7.2)
|
||||
|
||||
**BUFFER 语义变化**:v2 里防「余额统计滞后 ≤ 20 万」;v4 里防「快照滞后窗口内(30min 常规同步 + burst 触发)的消耗 + 返还/过期抖动」。默认 30 万(env 可调),滞后上限未知时建议调大(如 50 万)。
|
||||
|
||||
### 4.3 限额与消耗共用数据、方向相反
|
||||
|
||||
| 数据 | 限额(拦真实用户) | 消耗任务(洗过期额度) |
|
||||
|---|---|---|
|
||||
| `daily_usage` | 超限踢出 | 停止条件(≥2M 停) |
|
||||
| `remaining`(余额−缓冲) | 限额收紧 | 防烧穿上限 |
|
||||
| `stable_limit` | effective_limit 组成 | 不适用(直连旁路) |
|
||||
|
||||
---
|
||||
|
||||
## 五、架构与实现
|
||||
|
||||
### 5.1 代码形态
|
||||
|
||||
```
|
||||
litellm-config/
|
||||
├── volc_limit_sync.py # 现状:签名/拉取/归一化/限额/守护线程
|
||||
│ # 新增:23:00 调度钩子(检测时点 + spawn 消耗线程 + 去重)
|
||||
├── volc_consume.py # 新增:消耗任务(从 auto_consume 移植 + 改造)
|
||||
│ ├── load_base_logs / generate_dynamic_payload / _sanitize_line # 原样
|
||||
│ ├── calculate_plan # 保留 + 两个 clamp
|
||||
│ ├── run_stealthy_consume # 保留(停止条件 + 手动 INCR)
|
||||
│ └── consume_once(model) # 新封装:查缺口 → 执行 → 记账
|
||||
├── config.yaml # 模型入口不变(消耗任务读同一份监控发现)
|
||||
└── .env # 密钥统一走 env(HUOSHAN_API_KEY 即 Ark key)
|
||||
```
|
||||
|
||||
请求直连 Ark(`HUOSHAN_API_KEY`),复用 `MONITORED_MODEL_PAIRS` / `MONITORED_MODEL_ALIASES` / `fetch_all_packages` / 签名。`MONITOR_TARGETS` 与 `config.py` 不再需要。
|
||||
|
||||
### 5.2 Redis key 一览
|
||||
|
||||
| key | 读写方 | 说明 |
|
||||
|---|---|---|
|
||||
| `daily_usage:{日期}:{model}:{hash}` | LiteLLM 回调 INCR + 消耗任务 INCR | 当日用量,TTL 2 天 |
|
||||
| `limit:dynamic:volcengine:remaining:{model}` | 守护线程写 | 余额 − BUFFER |
|
||||
| `limit:dynamic:volcengine:stable_limit:{model}` | 守护线程写 | v4 冻结快照:余额 − BUFFER |
|
||||
| `volc:peak_pool:{日期}:{model}` | 守护线程 | 当日峰池 |
|
||||
| `consume:last_run:{日期}` | 消耗任务 | 当日去重,新增 |
|
||||
|
||||
### 5.3 消耗任务执行流程
|
||||
|
||||
```
|
||||
23:00 触发(守护线程)
|
||||
→ consume:last_run:{今天} 已存在? → 跳过
|
||||
→ 遍历 MONITORED_MODELS(config.yaml 发现的 volcengine 模型)
|
||||
→ fetch_all_packages(复用)→ 按模型过滤窗口内包
|
||||
→ plan = calculate_plan 结果,应用两个 clamp
|
||||
→ plan > 阈值(如 5 万)?
|
||||
→ 读 remaining 快照 → spawn 消耗线程
|
||||
→ 循环:生成语料 payload → 直连 Ark(速率伪装/退避/6h 上限)
|
||||
→ 成功 → INCR daily_usage;每轮检查 3 停止条件
|
||||
→ 失败 > 5 次 → 跳过该模型
|
||||
→ 写 consume:last_run:{今天}
|
||||
```
|
||||
|
||||
### 5.4 代码资产迁移清单
|
||||
|
||||
| 资产 | 来源 | 处理 |
|
||||
|---|---|---|
|
||||
| 签名 / `fetch_all_packages` / 归一化 / 模型发现 | volc_limit_sync | 原样复用 |
|
||||
| `calculate_plan`(9 天窗口/均分×1.1/冲刺/相加) | auto_consume | 保留,加 2 个 clamp |
|
||||
| 语料生成/脱敏/合成 | auto_consume | 原样移植 |
|
||||
| 速率伪装/退避/失败上限/6h 上限 | auto_consume | 原样移植 |
|
||||
| `_record_usage` 的 key 计算(`get_usage_key`) | volc_limit_sync | 消耗任务复用做 INCR |
|
||||
| 调度钩子 | 新增 | 守护线程 23:00 检测 + 去重 |
|
||||
|
||||
---
|
||||
|
||||
## 六、风险与边界(已确认/已知)
|
||||
|
||||
| 项 | 结论 |
|
||||
|---|---|
|
||||
| 空窗期(0:00–次日发放不定时) | **用户确认可接受**;均分策略使空窗概率最小化(余额平缓下降) |
|
||||
| 烧满 2M 的精确性 | 放弃该策略,改为过期风险驱动,结构性规避 |
|
||||
| 物理上限 | 窗口内快过期额度 > 2M 时每天最多洗 2M,极少数过期蒸发不可避(返还上限硬约束) |
|
||||
| 触发连续性 | 依赖每天 23:00 成功;连续失败 ~8 天耗尽窗口缓冲(冲刺兜底),守护线程常驻风险低 |
|
||||
| 风控 | 23:00 ± 抖动 + 15–40s 随机间隔 + 每天 3–5 次请求,接近真实行为 |
|
||||
| 容器语料 | LiteLLM 容器内无 `/var/log` 系统日志 → 合成语料兜底(5000 行够用) |
|
||||
| 消耗任务自身扣费 | 结构性杜绝:plan ≤ 余额快照 − 缓冲,且每轮检查余额 |
|
||||
| 余额 API 滞后(v2 池膨胀根因) | v4 冻结快照:balance − BUFFER 定上限,本地 usage 实时撞线,滞后窗口由 BUFFER 覆盖 |
|
||||
|
||||
---
|
||||
|
||||
## 七、待确认事项
|
||||
|
||||
1. `MONITOR_DAYS` 沿用 9 天(推荐:每天触发 + 冲刺兜底下足够,且与旧逻辑一致)
|
||||
2. 实现落点:拆 `volc_consume.py` 模块(推荐,单文件将超 2000 行)还是严格并入 `volc_limit_sync.py`
|
||||
3. 消耗触发阈值(plan 小于多少跳过):建议 5 万 token,避免无谓请求
|
||||
4. 消耗任务与真实用户的额度优先级:均分策略已隐含"真实用户优先",无需额外开关
|
||||
|
||||
---
|
||||
|
||||
## 八、实施步骤(确认后执行)
|
||||
|
||||
1. 移植 `auto_consume.py` 的语料/执行逻辑到 `volc_consume.py`,请求层改为直连 Ark(env key)
|
||||
2. `calculate_plan` 加两个 clamp + 停止条件;执行循环加 `daily_usage` INCR 与余额检查
|
||||
3. `volc_limit_sync.py` 守护线程加 23:00 调度钩子 + `consume:last_run` 去重
|
||||
4. `HARD_DAILY_LIMITS` 统一为 2M;`MONITOR_TARGETS`/`config.py` 退役
|
||||
5. 用 `test_connection.py` 等价方式验证 Ark 连通 → 手动触发一次消耗任务(dry-run 模式验证 plan 计算与 clamp)→ 观察 Redis key 变化
|
||||
6. 全量回归:守护线程限额逻辑不受影响(消耗线程独立 daemon)
|
||||
@@ -0,0 +1,80 @@
|
||||
# 火山引擎免费额度单日限制合入 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 渠道配置映射表)。
|
||||
@@ -0,0 +1,210 @@
|
||||
# 火山免费额度机制速查(VolcEngine Quota Notes)
|
||||
|
||||
> 日期:2026-08-12
|
||||
> 用途:后续限额/防扣费设计、排障、消耗任务的事实基础,避免重复推导
|
||||
> 来源:
|
||||
> - `merge-auto-consume.md` §1(2026-08-11 用户确认的业务细则)
|
||||
> - 2026-08-12 对话澄清(扣费口径、余额 API 滞后、限额方案演进)
|
||||
> - 2026-08-12 生产日志观察(deepseek-v4-flash-ga-260731)
|
||||
>
|
||||
> 代码现状:v4 已实施(2026-08-12,见 §7.3);工作区在 HEAD `c6c1224` 之上仅含 v4 改动。
|
||||
> v2/v3 内容保留为历史论证。
|
||||
|
||||
---
|
||||
|
||||
## 1. 业务模型(细则,用户确认)
|
||||
|
||||
### 1.1 日返还循环
|
||||
|
||||
```
|
||||
每天任意时刻 真实用户 + 消耗任务 的消耗 = 当日采集量(火山实时统计,每模型单独计算)
|
||||
每天 23:00 CST 消耗任务补齐缺口(可选)
|
||||
次日 00:00 起 火山按昨日采集量发放资源包(不定时,可能延迟到上午)
|
||||
```
|
||||
|
||||
### 1.2 硬规则
|
||||
|
||||
| 规则 | 值 | 影响 |
|
||||
|---|---|---|
|
||||
| 采集上限 | 2M token/模型/天 | 消耗超过 2M 的部分**不返还**(白烧);所有模型统一 |
|
||||
| 发放额度 | = 昨日采集量(≤2M) | 消耗 X → 次日返还 X,用多少补多少,延迟一天到账 |
|
||||
| 发放时间 | 不定时 | 次日 0:00 至发放时点存在余额空窗 |
|
||||
| 资源包有效期 | 30 天 | 过期额度实打实消失,失去"启动资金" |
|
||||
| 消耗顺序 | FIFO(先消耗获得时间早的包) | 队列头部 = 最早获得 = 最早过期 |
|
||||
| 余额耗尽 | 请求不失败,按真实价格扣费 | 全部防护的终极目标:避免扣费 |
|
||||
| 首次开通 | 每模型赠送 2M | 启动资金的种子(无对应前日采集的免费午餐) |
|
||||
|
||||
### 1.3 账目守恒
|
||||
|
||||
```
|
||||
余额_{N+1} = 余额_N − 消耗_N + 返还_{N+1}
|
||||
= 余额_N − 消耗_N + 采集_N
|
||||
= 余额_N (消耗即采集,≤2M 内全额返还)
|
||||
```
|
||||
|
||||
推论:
|
||||
- **使用不损失额度**(用多少补多少,仅延迟一天)
|
||||
- **过期才损失额度**(额度蒸发 = 启动资金永久减少)
|
||||
- 消耗任务的价值 = 防止"启动资金"被 30 天过期侵蚀
|
||||
- 真实用量 ≥ 66.7k/天(= 2M/30 天)时,赠送包被自然消耗;低于此值必须靠消耗任务在最后 9 天窗口洗掉
|
||||
|
||||
---
|
||||
|
||||
## 2. 扣费判定(2026-08-12 用户确认口径,最重要)
|
||||
|
||||
> 用户原话:**是否扣费只取决于当下有多少免费额度。超出免费额度的请求,都会被扣费。采集量只决定明日发放,与今日是否扣费无关。**
|
||||
|
||||
```
|
||||
扣费判定:消耗 ≤ 当下余额(免费额度)→ 免费,余额扣减
|
||||
消耗 > 当下余额 → 超出部分按真实价格扣费(无返还、不退)
|
||||
今日采集量(≤2M)→ 只决定明日返还的包大小,与今日是否扣费无关
|
||||
```
|
||||
|
||||
推论(2026-08-12 案例):
|
||||
- 当日余额 1,558,307,今日已采 1,696,135 → 超出 **137,828 tokens 已扣费**,不可挽回
|
||||
- 明日返还 = 今日采集量 1,696,135 的包(免费额度回正),但今天扣的钱不退
|
||||
- 因此「先采后返」**不构成透支许可**:消耗超余额的部分当场计费
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键概念区分(反复踩坑处)
|
||||
|
||||
| 概念 | 含义 | 量级特征 | 与扣费的关系 |
|
||||
|---|---|---|---|
|
||||
| 采集量(今日) | 今日实时消耗统计(火山网页"今日采集量") | 单日流量,0~2M | 只决定明日返还 |
|
||||
| 余额(免费额度) | 资源包 AvailableAmount 存量之和 | 累计存量,守恒 ≈2M(种子) | **扣费唯一依据** |
|
||||
| 采集配额 2M | 今日最大采集量 | 每模型固定 | 超了白烧(不返还),未必当场扣费 |
|
||||
| 返还(T+1) | 昨日采集量 → 新资源包 | 单日流量延迟一天 | 补回余额,不抵今日扣费 |
|
||||
|
||||
**核心:余额是累计存量(守恒),采集量是单日流量。限额设计必须用存量(余额),
|
||||
绝不能用单日流量(昨日采集量)当上限——那会把可用性锁死在水位以下(见 §7.2 v3)。**
|
||||
|
||||
---
|
||||
|
||||
## 4. 余额 API 的滞后问题(2026-08-12)
|
||||
|
||||
- **非实时,延迟未知**(用户明确)。实测:2026-08-12 13:36:01 同步快照 Bal=1,558,307,
|
||||
此后 usage 从 1,241,821 涨到 1,696,135(13:36→13:38)期间 Bal 纹丝不动。
|
||||
- 观察结论:AvailableAmount 不随今日消耗实时递减(至少滞后 20 万级,上限未知)。
|
||||
- **后果**:一切「余额随消耗实时递减」的公式(v2 的 `usage + balance` 池恒定假设)在
|
||||
滞后下失效——usage 涨而 balance 不动 → 池膨胀 → 限额虚高 → 放行到扣费区。
|
||||
- 对策方向:以**最近同步快照**的余额为当日免费池上限(本地 usage 实时累计撞线),
|
||||
快照频率由同步间隔(30min 常规 + 低余额 5/10min + 突发阈值触发)保障;
|
||||
滞后窗口内的消耗用 BUFFER 覆盖(见 §7.3)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 症状对照:Today > Rem(2026-08-12 案例)
|
||||
|
||||
日志示例(13:36-13:38,deepseek-v4-flash-ga-260731):
|
||||
|
||||
```
|
||||
DEBUG: Checking deepseek-v4-flash-ga-260731 (Bal: 1,558,307, Rem: 1,258,307) (余额/剩余)
|
||||
DEBUG: deepseek-v4-flash-ga-260731 Pool=2,800,128 | Usage=1,241,821 | StaticLimit=2,000,000 | StableLimit=2,500,128 | EffectiveLimit=2,000,000
|
||||
Set Redis limit:dynamic:volcengine:remaining:deepseek-v4-flash-ga-260731 = 1,258,307
|
||||
📊 [Usage] deepseek-v4-flash-ga-260731:ba0383d3: +38083 (Today: 1,279,904 | Rem: 1,558,307 | Limit: 2,000,000)
|
||||
```
|
||||
|
||||
字段口径:
|
||||
|
||||
| 字段 | 含义 | 计算 |
|
||||
|---|---|---|
|
||||
| Bal | 火山 API 余额(ListResourcePackages AvailableAmount 之和) | 同步快照 |
|
||||
| Rem(Checking 行) | 入 Redis 的剩余 | Bal − BUFFER |
|
||||
| Rem(Usage 行) | 展示用余额 | Redis remaining + BUFFER = Bal(展示与 API 一致) |
|
||||
| Today | 本地今日采集量 | Redis `daily_usage` 逐请求累加 |
|
||||
| Limit | 生效限额 | min(2M, stable_limit) |
|
||||
| StableLimit(v2) | 动态限额 | usage + balance − BUFFER(随 usage 膨胀,见 §7.1) |
|
||||
|
||||
**Today > Rem 的原因**:Today 是单日流量,Rem 是存量余额快照,两套口径本就不该比较。
|
||||
今日消耗可以暂时大于余额显示值(先采后返),但按 §2 口径,**超出部分实际已扣费**。
|
||||
火山网页「今日采集量 1,696,135/2,000,000」对应 Today 与 2M 配额,与余额无关。
|
||||
|
||||
---
|
||||
|
||||
## 6. Redis key 与日志口径(速查)
|
||||
|
||||
### 6.1 Redis key
|
||||
|
||||
| key | 读写方 | 说明 |
|
||||
|---|---|---|
|
||||
| `daily_usage:{北京日期}:{model}:{hash}` | LiteLLM 成功回调 INCR + 消耗任务 INCR | 当日采集量,TTL 48h,北京 00:00 滚动 |
|
||||
| `limit:dynamic:volcengine:remaining:{model}` | 守护线程写 | 余额 − BUFFER(同步频率驱动、展示) |
|
||||
| `limit:dynamic:volcengine:stable_limit:{model}` | 守护线程写 | 稳定限额(v2:usage+balance−BUFFER) |
|
||||
| `consume:last_run:{日期}` | 消耗任务 | 23:00 当日去重,TTL 48h |
|
||||
|
||||
### 6.2 关键日志
|
||||
|
||||
- `🚫 [PRE-BLOCK] {model} daily limit reached ({usage}+{reserve}/{limit})`:请求前闸门 429
|
||||
- `📊 [Usage] {model}:{hash}: +{tokens} (Today: X | Rem: Bal | Limit: Y)`:成功记账
|
||||
- `📅 [Quota] ...`:每日额度汇总(v4 设计,未实施)
|
||||
|
||||
---
|
||||
|
||||
## 7. 限额方案演进
|
||||
|
||||
### 7.1 v2(现状,HEAD c6c1224):`stable_limit = usage + balance − BUFFER`
|
||||
|
||||
- 意图:`usage + balance` = 当日初始可用池,消耗中恒定 → 限额恒定
|
||||
- **失效点**:该式假设 balance 随消耗实时递减;实测滞后 → usage 涨 balance 不动 →
|
||||
池膨胀(2026-08-12:2.80M → 3.26M)→ 「余额耗尽 → 限额 0」防线失效 → 放行到扣费区
|
||||
- 案例:余额 1,558,307 时 EffectiveLimit 仍取 2M,今日采到 1,696,135(超余额 13.8 万)未被拦
|
||||
|
||||
### 7.2 v3(尝试后否决,已回滚):昨日锚 `effective = min(2M, 昨日采集量 × 系数)`
|
||||
|
||||
- 思路:把「返还 = 昨日采集量」当作「今日可采上限」
|
||||
- **否决理由(用户反例)**:余额 2M、昨日用量 10K → 今日最多 10K?荒谬。
|
||||
错误在于把**单日流量(昨日采集量)当存量(余额)** 用;昨日少用不损余额(守恒),
|
||||
今日可用 = min(2M, 余额) ≈ 2M,与昨日无关
|
||||
- 附:系数 <1.0 还会引发「今日少采 → 明日返还更少」的日复一日递减陷阱(0.9 系数 5 日降至 66%)
|
||||
|
||||
### 7.3 v4(2026-08-12 已实施):冻结快照
|
||||
|
||||
```
|
||||
stable_limit = max(0, balance − BUFFER) # 冻结快照,同步时算一次
|
||||
effective_limit = min(2M 采集配额, stable_limit) # 2M 防白烧;余额红线防扣费
|
||||
```
|
||||
|
||||
- 唯一改动点:同步循环 stable_limit 从 `usage + balance − BUFFER` 改为 `balance − BUFFER`
|
||||
(`volc_limit_sync.py` active_monitor_loop 同步段;debug 行 Pool 字段移除,改 Bal 口径)
|
||||
- 数学等价于「本地实时扣减余额」:以最近快照余额为当日免费池上限,
|
||||
usage 本地逐请求实时累计,撞线即拦,不再等 API
|
||||
- 行为测试(2026-08-12,fake Redis 全过):冻结不膨胀 / 今日 1.696M 撞 1.258M 即拦 /
|
||||
余额 2M 可用 1.7M(与昨日无关)/ stable 缺失兜底 900K / 次日返还解锁 / 余额 < BUFFER 全拦
|
||||
- 效果对照(与 v2 现状):
|
||||
|
||||
| 场景 | v2 现状 | v4 |
|
||||
|---|---|---|
|
||||
| 余额 2M,昨日 10K | 可用 ≈2M | min(2M, 1.7M)=1.7M,不锁死 ✓ |
|
||||
| 余额 1.558M,今日已采 1.696M | 不拦(膨胀到 3.26M) | 立即拦(1.696 ≥ 1.258)→ 止损 |
|
||||
| 明日余额跳涨(返还 1.696M) | — | 红线升 1.4M,自动解锁 |
|
||||
|
||||
- 与消耗任务防线「余额快照 − 已烧 ≤ 0 停」同一把尺子(merge-auto-consume.md §4.4)
|
||||
- 连带改动:`get_effective_limit` 兜底链不变(stable → FALLBACK_LIMIT 90 万);
|
||||
文档同步(merge-auto-consume.md §4.2、AGENTS.md 限额描述)
|
||||
|
||||
### 7.4 实施后待观察项(2026-08-12)
|
||||
|
||||
- **BUFFER(现 30 万,未调大)**:防「余额 API 滞后窗口内的消耗」。实测 20 万内不动 + 10 万冗余;
|
||||
滞后上限未知,若观察同步间隔内(burst 阈值 50 万)烧穿,调大(如 50 万,env 可调)
|
||||
- **部署**:代码已改(工作区),需重启 litellm 容器加载(挂载文件);重启在生产主机由用户执行
|
||||
|
||||
---
|
||||
|
||||
## 8. 排障速查(今日结论汇总)
|
||||
|
||||
1. 看到 Today > Rem 不是异常,先分清口径(§5);但**按 §2 口径,超余额部分已在扣费**,应立即收闸
|
||||
2. 余额显示不动 ≠ 余额充足,是 API 滞后;以最近快照 + BUFFER 为准
|
||||
3. 判断「还能免费采多少」:`min(2M, 最近余额快照 − BUFFER) − 今日已采`(v4 已落地)
|
||||
4. 消耗任务 23:00 直连 Ark 绕过 LiteLLM 闸门(防自杀),其自身 clamp:`plan ≤ 2M − usage`、
|
||||
`plan ≤ 余额快照 − 已烧 − BUFFER`,停止条件三闭环
|
||||
5. 扣费既成事实不可挽回(如 08-12 的 137,828);能做的只有止损(收闸)与次日恢复(返还)
|
||||
|
||||
---
|
||||
|
||||
## 9. 待确认/风险清单
|
||||
|
||||
- [ ] 余额 API 滞后上限实测(决定 BUFFER 是否调大,现 30 万)
|
||||
- [x] v4 方案批准与实施(2026-08-12,§7.3;待生产主机重启生效)
|
||||
- [ ] 消耗任务口径是否同步收紧(可选:其停止条件与闸门同一把尺子已满足)
|
||||
- [ ] 空窗期(0:00–返还到账)行为观察:v4 下余额红线自动收紧,确认无异常误杀
|
||||
Reference in New Issue
Block a user