Compare commits

...

2 Commits

Author SHA1 Message Date
vibe-code d9c7b1406d refactor: 目录整理配套引用同步
README/AGENTS 文件清单与命令更新为新结构(generate_keys -> scripts/、
daily_limit -> archive/、文档 -> docs/);config.yaml 与 volc_consume.py
注释中废弃脚本/文档路径同步;README 过期限额示例更新为 2M 配置。
2026-08-12 06:58:16 +00:00
vibe-code dd349b64cf 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。
2026-08-12 06:58:03 +00:00
13 changed files with 290 additions and 39 deletions
+6 -4
View File
@@ -7,12 +7,14 @@
- **核心逻辑**:通过 `quota_interceptor` 拦截请求,并通过 `DailyLimitHandler` 记录用量到 Redis。后台 `active_monitor_loop` 守护线程定期从火山引擎 API 同步配额到 Redis。
- **架构**:主逻辑在 `volc_limit_sync.py`(限额 + 同步守护 + 每日消耗调度),消耗任务拆分在 `volc_consume.py`(每日北京时间 23:00 由守护线程延迟导入触发;见 `merge-auto-consume.md`)。LiteLLM 通过 `config.yaml` 中 `callbacks: ["volc_limit_sync.proxy_handler_instance"]` 加载回调,模块加载时自动启动后台同步线程。
- **关键文件**:
- `volc_limit_sync.py`: **核心文件**,包含同步守护线程(`active_monitor_loop`)、LiteLLM 回调(`DailyLimitHandler` + `quota_interceptor`)、限额计算(v2 简化:`stable_limit = usage + balance − BUFFER`,dynamic 缺失时 fallback `FALLBACK_LIMIT` 90 万兜底)、模型屏蔽/恢复、以及每日 23:00 消耗任务调度(`_maybe_trigger_daily_consume`,SETNX 去重)。
- `volc_limit_sync.py`: **核心文件**,包含同步守护线程(`active_monitor_loop`)、LiteLLM 回调(`DailyLimitHandler` + `quota_interceptor`)、限额计算(v4 冻结快照:`stable_limit = balance − BUFFER`,`effective_limit = min(2M 采集配额, stable_limit)`;扣费红线 = 余额,与采集量/返还无关,见 `docs/volc-quota-notes.md`)、模型屏蔽/恢复、以及每日 23:00 消耗任务调度(`_maybe_trigger_daily_consume`,SETNX 去重)。
- `volc_consume.py`: 消耗任务模块(每日 23:00 触发;手动 `python volc_consume.py --dry-run` / `--force`)。复用 volc_limit_sync 的拉包/归一化/用量记账设施,直连 Ark API 洗即将过期的免费资源包(防 30 天过期蒸发)。
- `config.yaml`: LiteLLM 配置文件,包含模型定义和回调配置。
- `docker-compose.yml`: 容器编排,仅挂载 `config.yaml` 和 `volc_limit_sync.py`。
- `debug_volc_sync.py` / `debug_volc_sync_manual.py`: 调试脚本,用于手动验证 API 和 Redis 状态。
- `daily_limit.py.deprecated`: 已废弃,功能已全部合并到 `volc_limit_sync.py`,仅保留作历史参考。
- `docker-compose.yml`: 容器编排,仅挂载 `config.yaml`、`volc_limit_sync.py`、`volc_consume.py`。
- `docs/`: 设计文档与额度机制速查(`merge-auto-consume.md` 消耗任务/限额设计、`volc-quota-notes.md` 火山额度机制事实基础、`merge-volc-limit-into-newapi.md` 未实施的迁移方案)。
- `debug/`: 调试脚本(`debug_volc_sync.py` / `debug_volc_sync_manual.py`),手动验证 API 和 Redis 状态。
- `scripts/`: 工具脚本(`generate_keys.py` 监控模型审计入口)。
- `archive/`: 已废弃(`daily_limit.py.deprecated` 历史参考、`recover.py` 一次性恢复工具)。
## 2. 环境与运行 (Build & Run)
+33 -21
View File
@@ -10,7 +10,7 @@ callbacks: ["volc_limit_sync.proxy_handler_instance"]
这意味着当前 Docker 容器里真正生效的是 `volc_limit_sync.py`。
`daily_limit.py` 目前 **未启用**,只保留作历史参考。除非你把 `callbacks` 改回 `daily_limit.py`,否则改它里面的 `MODEL_QUOTAS` 不会影响当前运行行为。
旧脚本 `archive/daily_limit.py.deprecated` 目前 **未启用**,只保留作历史参考。除非你把 `callbacks` 改回旧模块,否则改它里面的 `MODEL_QUOTAS` 不会影响当前运行行为。
---
@@ -62,7 +62,7 @@ glm-4-7-251222 -> glm-4-7
每次改完 `config.yaml` 后,执行:
```bash
python generate_keys.py
python scripts/generate_keys.py
```
这个脚本现在不是单纯“生成 key”,而是正式的 **监控模型审计入口**。它会输出:
@@ -106,7 +106,7 @@ python generate_keys.py
### 新增监控模型
1. 在 `config.yaml` 的 `model_list` 里加入新的火山模型条目。
2. 运行 `python generate_keys.py`。
2. 运行 `python scripts/generate_keys.py`。
3. 先确认脚本输出中:
- 该模型已经出现在“监控模型审计”里。
- “监控别名”正确。
@@ -119,7 +119,7 @@ python generate_keys.py
### 删除监控模型
1. 从 `config.yaml` 里删掉对应模型条目。
2. 运行 `python generate_keys.py`,确认它已经不在“监控模型审计”里。
2. 运行 `python scripts/generate_keys.py`,确认它已经不在“监控模型审计”里。
3. 如果该模型曾有单独的 `HARD_DAILY_LIMITS` 项,可以按需清理。
4. 如果该模型对应的是特殊手工别名,且已经没有任何上游模型再用它,可以再清理 `MANUAL_MONITORED_MODEL_ALIASES`。
5. 重启 LiteLLM 容器。
@@ -160,14 +160,14 @@ python generate_keys.py
3. 模糊规则
4. `__DEFAULT__`
例如:
例如(当前值,2026-08):
```python
HARD_DAILY_LIMITS = {
"doubao-seed-code-preview-251028": 4_800_000,
"doubao-seed-2-0-pro-260215:ba0383d3": 1_900_000,
"doubao-seed-2-0-lite-260215:ba0383d3": 1_900_000,
"__DEFAULT__": 1_800_000,
# 火山免费额度规则:每模型每日采集上限 2M,次日按采集量发放资源包。
# 采集上限封顶;真正决定今日可采的是 v4 动态层:
# effective = min(2M, stable_limit),stable_limit = 余额快照 − BUFFER(见 docs/volc-quota-notes.md)
"__DEFAULT__": 2_000_000,
}
```
@@ -175,13 +175,13 @@ HARD_DAILY_LIMITS = {
## 常见坑
### 1. 改了 `daily_limit.py` 但不生效
### 1. 改了旧脚本(`archive/daily_limit.py.deprecated`)但不生效
这是最容易踩的坑。
当前 `config.yaml` 没有启用 `daily_limit.py`,所以:
当前 `config.yaml` 没有启用旧模块 `daily_limit`,所以:
- 改 `daily_limit.py`
- 改 `archive/daily_limit.py.deprecated`
- 改 `MODEL_QUOTAS`
都不会影响当前 LiteLLM 容器的启动和监控行为。
@@ -204,7 +204,7 @@ xxx-251201 -> xxx
xxx-260215 -> xxx
```
所以普通版本升级时,通常只需要改 `config.yaml`,再跑一遍 `python generate_keys.py` 审计确认。
所以普通版本升级时,通常只需要改 `config.yaml`,再跑一遍 `python scripts/generate_keys.py` 审计确认。
---
@@ -213,7 +213,7 @@ xxx-260215 -> xxx
### 审计当前纳入监控模型
```bash
python generate_keys.py
python scripts/generate_keys.py
```
### 直接运行同步监控
@@ -235,18 +235,30 @@ DAILY_LIMIT_LOG_LEVEL=DEBUG python volc_limit_sync.py
```text
litellm-config/
├── config.yaml # 纳入监控模型的主入口
├── volc_limit_sync.py # 当前生效的监控、同步、限额脚本
├── generate_keys.py # 监控模型审计入口
├── daily_limit.py # 旧脚本,当前 config 未启用
├── debug_volc_sync.py # 资源包调试脚本
└── debug_volc_sync_manual.py # 手动调试脚本
├── docker-compose.yml # 容器编排(挂载 config.yaml / volc_limit_sync.py / volc_consume.py)
├── volc_limit_sync.py # 当前生效的监控、同步、限额脚本(v4 冻结快照限额)
├── volc_consume.py # 每日 23:00 自动消耗任务(防免费资源包过期蒸发)
├── AGENTS.md # 仓库 Agent 协议(代码风格/运行/提交规范)
├── README.md
├── docs/
│ ├── merge-auto-consume.md # 消耗任务 + 限额设计(v2 历史 / v4 修订)
│ ├── volc-quota-notes.md # 火山额度机制速查(扣费口径/方案演进/排障)
│ └── merge-volc-limit-into-newapi.md # 未实施的迁移方案(历史)
├── scripts/
│ └── generate_keys.py # 监控模型审计入口
├── debug/
│ ├── debug_volc_sync.py # 资源包调试脚本
│ └── debug_volc_sync_manual.py # 手动调试脚本
└── archive/
├── daily_limit.py.deprecated # 旧脚本,当前 config 未启用(历史参考)
└── recover.py # 一次性恢复工具(历史)
```
---
## 维护原则
- 先改 `config.yaml`,不要先改 `daily_limit.py`
- 先跑 `python generate_keys.py` 审计,再决定要不要补 `HARD_DAILY_LIMITS`
- 先改 `config.yaml`,不要先改旧脚本 `archive/daily_limit.py.deprecated`
- 先跑 `python scripts/generate_keys.py` 审计,再决定要不要补 `HARD_DAILY_LIMITS`
- 普通版本号模型优先依赖自动推导,不要手工补一堆重复映射
- 只有特殊别名或一对多场景,才改 `MANUAL_MONITORED_MODEL_ALIASES`
View File
+1 -1
View File
@@ -473,7 +473,7 @@ litellm_settings:
allowed_fails: 3 # cooldown model if it fails > 1 call in a minute.
# 当前生效入口:volc_limit_sync.py
# 监控模型发现、监控别名推导、资源包同步、HARD_DAILY_LIMITS 都在这个脚本里。
# daily_limit.py 当前未启用,仅保留作历史参考。
# 旧脚本 archive/daily_limit.py.deprecated 当前未启用,仅保留作历史参考。
callbacks: ["volc_limit_sync.proxy_handler_instance"]
router_settings: # router_settings are optional
@@ -155,7 +155,7 @@ daily_usage ≥ 2M → 停(采集上限,再烧不返还
---
## 四、限额设计(防扣费)——v2 简化逻辑
## 四、限额设计(防扣费)——v2 简化逻辑(v4 修订见 §4.5,2026-08-12 已实施)
### 4.1 为什么废弃 Peak-Buffer 峰值状态机(存量场景产物)
@@ -175,6 +175,9 @@ daily_usage ≥ 2M → 停(采集上限,再烧不返还
### 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)
@@ -210,6 +213,27 @@ HARD_DAILY_LIMITS → 统一 2M(采集上限;doubao-seed-code-preview-251028
同步线程检测 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 限额与消耗共用数据、方向相反
| 数据 | 限额(拦真实用户) | 消耗任务(洗过期额度) |
@@ -245,7 +269,7 @@ litellm-config/
|---|---|---|
| `daily_usage:{日期}:{model}:{hash}` | LiteLLM 回调 INCR + 消耗任务 INCR | 当日用量,TTL 2 天 |
| `limit:dynamic:volcengine:remaining:{model}` | 守护线程写 | 余额 − BUFFER |
| `limit:dynamic:volcengine:stable_limit:{model}` | 守护线程写 | 稳定限额 |
| `limit:dynamic:volcengine:stable_limit:{model}` | 守护线程写 | v4 冻结快照:余额 − BUFFER |
| `volc:peak_pool:{日期}:{model}` | 守护线程 | 当日峰池 |
| `consume:last_run:{日期}` | 消耗任务 | 当日去重,新增 |
@@ -289,6 +313,7 @@ litellm-config/
| 风控 | 23:00 ± 抖动 + 15–40s 随机间隔 + 每天 3–5 次请求,接近真实行为 |
| 容器语料 | LiteLLM 容器内无 `/var/log` 系统日志 → 合成语料兜底(5000 行够用) |
| 消耗任务自身扣费 | 结构性杜绝:plan ≤ 余额快照 − 缓冲,且每轮检查余额 |
| 余额 API 滞后(v2 池膨胀根因) | v4 冻结快照:balance − BUFFER 定上限,本地 usage 实时撞线,滞后窗口由 BUFFER 覆盖 |
---
+210
View File
@@ -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 下余额红线自动收紧,确认无异常误杀
+1 -1
View File
@@ -1,7 +1,7 @@
# ==========================================
# 🚀 VolcConsume — 火山免费额度自动消耗任务
# ==========================================
# 与 volc_limit_sync.py 合并方案的一部分(见 merge-auto-consume.md):
# 与 volc_limit_sync.py 合并方案的一部分(见 docs/merge-auto-consume.md):
# - 每日北京时间 23:00 由 volc_limit_sync 守护线程触发(可手动 --force)
# - 洗掉 CONSUME_MONITOR_DAYS 天内过期的资源包额度(FIFO 下即队列头部),
# 防止 30 天过期蒸发导致"启动资金"缩水(免费额度规则:消耗 X → 次日返还 X)
+12 -10
View File
@@ -1473,17 +1473,19 @@ def active_monitor_loop():
dynamic_key = f"limit:dynamic:volcengine:remaining:{target}"
# ==========================================
# 📏 v2 简化限额:stable_limit = usage + balance − BUFFER
# 📏 v4 冻结快照:stable_limit = max(0, balance − BUFFER)
# ==========================================
# 旧 Peak-Buffer(峰值首锁/门控重锁/DROP,~90 行状态机)为存量
# 资源包场景设计;日返还模型下 balance 每天真实跳涨,锁峰值会把
# 真实发放当异常压住,且比例 margin 在 usage 逼近 balance 时
# 数学失效(允许再烧量 > 剩余余额 → 烧穿扣费)。
# usage + balance = 当日初始可用池,消耗中恒定;返还/过期自然增减。
# BUFFER 覆盖余额统计滞后(实测 20 万内不动)。见 merge-auto-consume.md §4。
# v2 的 usage + balance − BUFFER 假设「消耗中池恒定」(usage ↑ /
# balance ↓ 同步),但余额 API 实测滞后(2026-08-12 观察:快照
# 1,558,307 在 usage 1.24M→1.70M 期间不动,滞后上限未知),
# balance 不随消耗实时递减 → 池随 usage 膨胀、限额虚高,
# 「余额耗尽 → 限额 0」防线失效 → 放行到扣费区(当日超余额 13.8 万)。
# v4:以最近同步快照的余额为「当日免费池上限」,本地 usage 实时
# 累计撞线即拦(等价于本地实时扣减余额),不随 usage 膨胀;
# 扣费判定 = 消耗 > 当下免费额度即扣费(与采集量/返还无关)。
# BUFFER 覆盖快照滞后窗口内的消耗。见 docs/volc-quota-notes.md §7.3。
total_pool = current_usage + balance
stable_limit = int(max(0, total_pool - BUFFER))
stable_limit = int(max(0, balance - BUFFER))
stable_limit_key = (
f"limit:dynamic:volcengine:stable_limit:{target}"
@@ -1500,7 +1502,7 @@ def active_monitor_loop():
static_limit, stable_limit
)
print(
f"DEBUG: {target} Pool={int(total_pool):,} | Usage={int(current_usage):,} | {format_limit_debug_fields(static_limit=static_limit, stable_limit=stable_limit, effective_limit=effective_limit)}",
f"DEBUG: {target} Bal={int(balance):,} | Usage={int(current_usage):,} | {format_limit_debug_fields(static_limit=static_limit, stable_limit=stable_limit, effective_limit=effective_limit)}",
flush=True,
)