feat: 合入火山免费额度自动消耗任务(每日23点洗过期包防扣费)
- 新增 volc_consume.py:消耗任务模块,直连 Ark 洗 9 天内过期资源包 (旧 calculate_plan 均分×1.1/24h冲刺保留 + 双 clamp:2M 采集上限、余额−BUFFER 防扣费) - volc_limit_sync.py:限额 v2 简化(stable_limit = usage+balance−BUFFER,删除峰值状态机 ~90 行), HARD_DAILY_LIMITS 统一 2M(pre-block 不滞后,1.8M 折扣作废), 新增 _maybe_trigger_daily_consume(北京时间 23:00 ± 抖动,SETNX 去重,延迟导入) - docker-compose 挂载 volc_consume.py;AGENTS.md/文档同步
This commit is contained in:
+65
-86
@@ -3,6 +3,8 @@ import sys
|
||||
import time
|
||||
import json
|
||||
import hmac
|
||||
import math
|
||||
import random
|
||||
import hashlib
|
||||
import datetime
|
||||
import requests
|
||||
@@ -146,13 +148,10 @@ MONITORED_MODEL_ALIASES = {}
|
||||
# Format: "clean-model-name": limit_in_tokens
|
||||
# Or with hash: "clean-model-name:hash": limit_in_tokens
|
||||
HARD_DAILY_LIMITS = {
|
||||
# Specific Limit for Doubao Code
|
||||
"doubao-seed-code-preview-251028": 4_800_000,
|
||||
# Specific Limits for Doubao Seed 2.0 models on HUOSHAN_BASE_URL
|
||||
"doubao-seed-2-0-pro-260215:ba0383d3": 1_900_000,
|
||||
"doubao-seed-2-0-lite-260215:ba0383d3": 1_900_000,
|
||||
# Default Limit for all other models (Universal Fallback)
|
||||
"__DEFAULT__": 1_800_000,
|
||||
# 火山免费额度规则(2026-08):每模型每日采集上限 2M,次日按采集量发放资源包。
|
||||
# 请求前闸门(async_pre_call_hook)已不滞后 → 无需打折(旧 1.8M/480 万为 post-hoc 时代遗产)。
|
||||
# 统一 2M,余额相关的动态限额由 stable_limit(usage+balance−BUFFER)表达。
|
||||
"__DEFAULT__": 2_000_000,
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
@@ -973,8 +972,12 @@ def process_packages_breakdown(packages):
|
||||
return model_counts
|
||||
|
||||
|
||||
def active_monitor_loop():
|
||||
# Config Discovery
|
||||
def discover_monitored_models():
|
||||
"""
|
||||
🔎 从 config.yaml 自动发现纳入监控的火山模型,填充 MONITORED_MODEL_PAIRS /
|
||||
MONITORED_MODEL_ALIASES。返回监控模型名 set。
|
||||
独立函数化:volc_consume.py 手动运行(无守护线程)时也可复用。
|
||||
"""
|
||||
global MONITORED_MODEL_PAIRS, MONITORED_MODEL_ALIASES
|
||||
MONITORED_MODEL_PAIRS.clear()
|
||||
MONITORED_MODEL_ALIASES.clear()
|
||||
@@ -1107,6 +1110,50 @@ def active_monitor_loop():
|
||||
except Exception as e:
|
||||
print(f"Config Auto-discovery failed / 配置自动发现失败: {e}", flush=True)
|
||||
|
||||
return MONITORED_MODELS
|
||||
|
||||
|
||||
def _maybe_trigger_daily_consume():
|
||||
"""
|
||||
🕰️ 每日北京时间 23:00 触发免费额度消耗任务(洗过期资源包)。
|
||||
- consume:last_run:{北京日期} 去重(SETNX,当天只触发一次)
|
||||
- daemon 线程执行,不阻塞主循环;线程内 0-10 分钟随机抖动,避免固定 23:00 被风控识别
|
||||
- 延迟 import volc_consume,避免模块级循环 import
|
||||
"""
|
||||
if not redis_client:
|
||||
return
|
||||
try:
|
||||
now = time.time()
|
||||
beijing_ts = now + (8 * 3600)
|
||||
beijing_hour = time.gmtime(beijing_ts).tm_hour
|
||||
if beijing_hour != 23:
|
||||
return
|
||||
date_str = time.strftime("%Y-%m-%d", time.gmtime(beijing_ts))
|
||||
lock_key = f"consume:last_run:{date_str}"
|
||||
if redis_client.set(lock_key, "1", nx=True, ex=172800) is None:
|
||||
return # 今天已触发过
|
||||
|
||||
log("🚀 触发每日消耗任务(洗过期资源包防退费)...", level="INFO")
|
||||
|
||||
def _run():
|
||||
try:
|
||||
time.sleep(random.randint(0, 600))
|
||||
import volc_consume # 延迟导入,避免模块级循环 import
|
||||
|
||||
volc_consume.run_consume_all()
|
||||
except Exception as e:
|
||||
log(f"❌ 消耗任务异常: {e}\n{traceback.format_exc()}", level="ERROR")
|
||||
|
||||
t = threading.Thread(target=_run, daemon=True)
|
||||
t.start()
|
||||
except Exception as e:
|
||||
log(f"❌ 消耗任务调度失败: {e}", level="ERROR")
|
||||
|
||||
|
||||
def active_monitor_loop():
|
||||
# Config Discovery
|
||||
MONITORED_MODELS = discover_monitored_models()
|
||||
|
||||
if not redis_client:
|
||||
print(
|
||||
"❌ Redis not connected. Exiting monitor loop / Redis 未连接,退出监控循环.",
|
||||
@@ -1142,6 +1189,7 @@ def active_monitor_loop():
|
||||
while True:
|
||||
try:
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
_maybe_trigger_daily_consume()
|
||||
now = time.time()
|
||||
|
||||
# Enforce Beijing Time for daily operations
|
||||
@@ -1336,86 +1384,17 @@ def active_monitor_loop():
|
||||
dynamic_key = f"limit:dynamic:volcengine:remaining:{target}"
|
||||
|
||||
# ==========================================
|
||||
# 🏔️ Daily Peak Lock (First-Sync + Balance-Gated Re-Lock)
|
||||
# 📏 v2 简化限额:stable_limit = usage + balance − BUFFER
|
||||
# ==========================================
|
||||
# 背景:AvailableAmount 是各资源包实例的实时剩余,但火山统计有
|
||||
# 更新滞后(高用量时段会短暂冻结,实测 20 万 token 消耗内余额不动)。
|
||||
# 旧逻辑把每次用量增长误判为"余额恢复"(Jump)锁更高峰值,
|
||||
# 导致 stable_limit 越算越多(当天 399,836 -> 599,511 一路抬升)。
|
||||
# 修正策略(日内限额单调可控):
|
||||
# 1. 首次同步锁定当日峰值(= usage + balance 的总配额估计);
|
||||
# 2. 仅当 balance 自身净上涨(新资源包发放/对账回补,≥50k)
|
||||
# 时,按新增额度抬高峰值 —— 幅度 = 历史峰值 + 发放增量,
|
||||
# 绝不把滞后虚高的 usage 一起算进去;
|
||||
# 3. balance 回落(额度扣减追上用量)时 Drop 收紧;
|
||||
# 4. usage 增长驱动的 pool 上涨一律不得抬高限额。
|
||||
# 实测:协作奖励计划包每自然日发放、偶发一天 2 个,故必须有第 2 条;
|
||||
# 次日 00:00 (CST) 自动重建当日峰值。
|
||||
# 旧 Peak-Buffer(峰值首锁/门控重锁/DROP,~90 行状态机)为存量
|
||||
# 资源包场景设计;日返还模型下 balance 每天真实跳涨,锁峰值会把
|
||||
# 真实发放当异常压住,且比例 margin 在 usage 逼近 balance 时
|
||||
# 数学失效(允许再烧量 > 剩余余额 → 烧穿扣费)。
|
||||
# usage + balance = 当日初始可用池,消耗中恒定;返还/过期自然增减。
|
||||
# BUFFER 覆盖余额统计滞后(实测 20 万内不动)。见 merge-auto-consume.md §4。
|
||||
|
||||
total_pool = current_usage + balance
|
||||
peak_key = f"volc:peak_pool:{today_str}:{target}"
|
||||
|
||||
# Short-term memory (Last seen value) - 仅用于日志对比/增量计算
|
||||
last_pool_key = f"volc:last_seen_pool:{target}"
|
||||
last_pool_val = redis_client.get(last_pool_key)
|
||||
last_pool = float(last_pool_val) if last_pool_val else 0.0
|
||||
last_balance_key = f"volc:last_balance:{target}"
|
||||
last_balance_val = redis_client.get(last_balance_key)
|
||||
last_balance = float(last_balance_val) if last_balance_val else 0.0
|
||||
|
||||
# Long-term memory (Day's High Water Mark)
|
||||
old_peak_val = redis_client.get(peak_key)
|
||||
historical_peak = float(old_peak_val) if old_peak_val else 0.0
|
||||
|
||||
current_peak = historical_peak
|
||||
|
||||
# 1. INITIALIZATION (First sync of the day): lock once
|
||||
if historical_peak == 0:
|
||||
if DEBUG_MODE and last_pool > 0:
|
||||
print(
|
||||
f"DEBUG: 📌 Locking Daily Peak ({int(last_pool):,} -> {int(total_pool):,}) - First Sync Today.",
|
||||
flush=True,
|
||||
)
|
||||
current_peak = total_pool
|
||||
redis_client.setex(peak_key, 172800, int(current_peak))
|
||||
|
||||
# 2. RECHARGE (Balance-driven upward re-lock)
|
||||
# 新资源包发放 / API 对账回补 → balance 自身净上涨 ≥50k。
|
||||
# 只把"发放增量"加进已锁定的峰值,不重算整个滞后 pool。
|
||||
elif last_balance > 0 and (balance - last_balance) >= 50000:
|
||||
grant_delta = int(balance - last_balance)
|
||||
current_peak = historical_peak + grant_delta
|
||||
if DEBUG_MODE:
|
||||
print(
|
||||
f"DEBUG: 🆕 New Quota Detected (balance {int(last_balance):,} -> {int(balance):,}, +{grant_delta:,}) - Re-locking Daily Peak to {int(current_peak):,}.",
|
||||
flush=True,
|
||||
)
|
||||
redis_client.setex(peak_key, 172800, int(current_peak))
|
||||
|
||||
# 3. DROP DETECTION (Resource Expiry / latency catch-up)
|
||||
# pool 回落时收紧限额;回落之后保持不变,绝不回升。
|
||||
elif total_pool < historical_peak and historical_peak > 0:
|
||||
drop_ratio = (
|
||||
historical_peak - total_pool
|
||||
) / historical_peak
|
||||
if DEBUG_MODE:
|
||||
print(
|
||||
f"DEBUG: 📉 Pool Drop Detected ({int(historical_peak):,} -> {int(total_pool):,}) - Ratio: {drop_ratio:.1%} - Adjusting Limit Down.",
|
||||
flush=True,
|
||||
)
|
||||
current_peak = total_pool
|
||||
redis_client.setex(peak_key, 172800, int(current_peak))
|
||||
|
||||
# 4. NO USAGE-DRIVEN RE-LOCK: 用量增长(balance 不变)不得抬高限额。
|
||||
# (旧逻辑的 Bug 就在这里:把滞后虚高当余额恢复。)
|
||||
|
||||
# Always update "Last Seen" for next comparison
|
||||
redis_client.setex(last_pool_key, 86400, int(total_pool))
|
||||
redis_client.setex(last_balance_key, 86400, int(balance))
|
||||
|
||||
# Calculate Stable Limit
|
||||
safety_margin = max(current_peak * 0.15, 250000)
|
||||
stable_limit = int(max(0, current_peak - safety_margin))
|
||||
stable_limit = int(max(0, total_pool - BUFFER))
|
||||
|
||||
stable_limit_key = (
|
||||
f"limit:dynamic:volcengine:stable_limit:{target}"
|
||||
|
||||
Reference in New Issue
Block a user