feat: 优化火山引擎配额同步策略
- 添加硬限制: doubao-seed-code 480w/日, 其他模型 180w/日 - 强制北京时间(UTC+8)计算日期,确保 Docker 容器时区无关 - 移除'忽略小跌'逻辑,Pool 下跌时立即接受(更保守的风控) - 优化轮询间隔适配小时级 API 延迟(减少无效请求)
This commit is contained in:
+43
-28
@@ -120,11 +120,10 @@ VOLC_MANAGED_PAIRS = set()
|
|||||||
# Format: "clean-model-name": limit_in_tokens
|
# Format: "clean-model-name": limit_in_tokens
|
||||||
# Or with hash: "clean-model-name:hash": limit_in_tokens
|
# Or with hash: "clean-model-name:hash": limit_in_tokens
|
||||||
HARD_DAILY_LIMITS = {
|
HARD_DAILY_LIMITS = {
|
||||||
# 示例 Examples:
|
# Specific Limit for Doubao Code
|
||||||
# "doubao-pro-32k": 10_000_000, # Limit Doubao Pro to 10M tokens/day
|
"doubao-seed-code-preview-251028": 4_800_000,
|
||||||
# "deepseek-v3": 5_000_000, # Limit DeepSeek V3 to 5M tokens/day
|
# Default Limit for all other models (Universal Fallback)
|
||||||
# Specific endpoint limit / 特定端点限额
|
"__DEFAULT__": 1_800_000,
|
||||||
# "deepseek-v3-2-251201:ba0383d3": 1_600_000,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -245,9 +244,12 @@ def get_signed_headers(action, version, body_dict, service="billing"):
|
|||||||
# 🎯 Limit Logic (Unified)
|
# 🎯 Limit Logic (Unified)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
def get_usage_key(clean_model, api_base=None):
|
def get_usage_key(clean_model, api_base=None):
|
||||||
|
# Enforce Beijing Time (UTC+8) for daily key rotation at 00:00 CST
|
||||||
|
# Beijing 00:00 = UTC 16:00 (Previous Day) -> simple offset: UTC_timestamp + 8 hours
|
||||||
now = time.time()
|
now = time.time()
|
||||||
adjusted_time = now - 3600 # UTC+1 split
|
beijing_time_ts = now + (8 * 3600)
|
||||||
date_str = time.strftime("%Y-%m-%d", time.gmtime(adjusted_time))
|
date_str = time.strftime("%Y-%m-%d", time.gmtime(beijing_time_ts))
|
||||||
|
|
||||||
suffix = f":{generate_api_base_hash(api_base)}" if api_base else ""
|
suffix = f":{generate_api_base_hash(api_base)}" if api_base else ""
|
||||||
return f"daily_usage:{date_str}:{clean_model}{suffix}"
|
return f"daily_usage:{date_str}:{clean_model}{suffix}"
|
||||||
|
|
||||||
@@ -267,8 +269,15 @@ def get_static_limit(clean_model, api_base):
|
|||||||
|
|
||||||
# 3. Fuzzy Match
|
# 3. Fuzzy Match
|
||||||
for quota_model, limit_val in HARD_DAILY_LIMITS.items():
|
for quota_model, limit_val in HARD_DAILY_LIMITS.items():
|
||||||
|
if quota_model == "__DEFAULT__":
|
||||||
|
continue
|
||||||
if ":" not in quota_model and quota_model in clean_model:
|
if ":" not in quota_model and quota_model in clean_model:
|
||||||
return limit_val
|
return limit_val
|
||||||
|
|
||||||
|
# 4. Universal Default Fallback
|
||||||
|
if "__DEFAULT__" in HARD_DAILY_LIMITS:
|
||||||
|
return HARD_DAILY_LIMITS["__DEFAULT__"]
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -834,7 +843,10 @@ def active_monitor_loop():
|
|||||||
try:
|
try:
|
||||||
time.sleep(CHECK_INTERVAL)
|
time.sleep(CHECK_INTERVAL)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
today_str = datetime.datetime.now().strftime("%Y-%m-%d")
|
|
||||||
|
# Enforce Beijing Time for daily operations
|
||||||
|
beijing_time_ts = now + (8 * 3600)
|
||||||
|
today_str = time.strftime("%Y-%m-%d", time.gmtime(beijing_time_ts))
|
||||||
|
|
||||||
# Idle Check
|
# Idle Check
|
||||||
usage_key_global = f"daily_usage:{today_str}:global_total"
|
usage_key_global = f"daily_usage:{today_str}:global_total"
|
||||||
@@ -863,20 +875,22 @@ def active_monitor_loop():
|
|||||||
# < 100w -> 60min (3600s)
|
# < 100w -> 60min (3600s)
|
||||||
# >= 100w -> 60min (3600s)
|
# >= 100w -> 60min (3600s)
|
||||||
|
|
||||||
# Determine Base Sync Interval based on global risk
|
# Determine Base Sync Interval based on global risk (Optimized for Hourly Latency)
|
||||||
# < 100k -> 5 min (Emergency but limit API calls due to latency)
|
# Since API updates are hourly, frequent polling provides no new info and wastes resources.
|
||||||
# < 300k -> 5 min (Safe Sailing)
|
# We trust Redis Usage for real-time gating.
|
||||||
# < 500k -> 10 min (Speed Mode)
|
# < 100k -> 5 min (Emergency check for the update moment)
|
||||||
# < 800k -> 30 min (Normal Mode)
|
# < 300k -> 10 min
|
||||||
# >= 800k -> 60 min (High Tide Mode)
|
# < 500k -> 20 min
|
||||||
|
# < 800k -> 45 min
|
||||||
|
# >= 800k -> 60 min
|
||||||
if global_min_remaining < 100_000:
|
if global_min_remaining < 100_000:
|
||||||
base_interval = 300 # 5 minutes (Increased from 2m)
|
|
||||||
elif global_min_remaining < 300_000:
|
|
||||||
base_interval = 300 # 5 minutes
|
base_interval = 300 # 5 minutes
|
||||||
elif global_min_remaining < 500_000:
|
elif global_min_remaining < 300_000:
|
||||||
base_interval = 600 # 10 minutes
|
base_interval = 600 # 10 minutes
|
||||||
|
elif global_min_remaining < 500_000:
|
||||||
|
base_interval = 1200 # 20 minutes
|
||||||
elif global_min_remaining < 800_000:
|
elif global_min_remaining < 800_000:
|
||||||
base_interval = 1800 # 30 minutes
|
base_interval = 2700 # 45 minutes
|
||||||
else:
|
else:
|
||||||
base_interval = 3600 # 60 minutes
|
base_interval = 3600 # 60 minutes
|
||||||
|
|
||||||
@@ -986,12 +1000,11 @@ def active_monitor_loop():
|
|||||||
|
|
||||||
# Sum usage across all configured endpoints for this model (usually just one)
|
# Sum usage across all configured endpoints for this model (usually just one)
|
||||||
for h in target_hashes:
|
for h in target_hashes:
|
||||||
# Replicate get_usage_key logic
|
# Replicate get_usage_key logic (Beijing Time UTC+8)
|
||||||
# UTC+1 split logic from get_usage_key
|
|
||||||
now_ts = time.time()
|
now_ts = time.time()
|
||||||
adjusted_time = now_ts - 3600
|
beijing_time_ts = now_ts + (8 * 3600)
|
||||||
usage_date = time.strftime(
|
usage_date = time.strftime(
|
||||||
"%Y-%m-%d", time.gmtime(adjusted_time)
|
"%Y-%m-%d", time.gmtime(beijing_time_ts)
|
||||||
)
|
)
|
||||||
u_key = f"daily_usage:{usage_date}:{target}:{h}"
|
u_key = f"daily_usage:{usage_date}:{target}:{h}"
|
||||||
val = redis_client.get(u_key)
|
val = redis_client.get(u_key)
|
||||||
@@ -1037,14 +1050,16 @@ def active_monitor_loop():
|
|||||||
redis_client.setex(peak_key, 172800, int(current_peak))
|
redis_client.setex(peak_key, 172800, int(current_peak))
|
||||||
|
|
||||||
# 2. DROP DETECTION (Resource Expiry)
|
# 2. DROP DETECTION (Resource Expiry)
|
||||||
# If pool drops MASSIVELY (>20%) below historical peak, it's likely an expiry.
|
# If pool drops (even small amounts), we accept it.
|
||||||
# We accept this drop. If it was just a glitch, Rule #1 will fix it next time it bounces back.
|
# Why? Because keeping a phantom high peak is dangerous.
|
||||||
elif (
|
# If it was a glitch, Rule #1 will fix it next time it bounces back.
|
||||||
total_pool < (historical_peak * 0.8) and historical_peak > 0
|
elif total_pool < historical_peak and historical_peak > 0:
|
||||||
):
|
drop_ratio = (
|
||||||
|
historical_peak - total_pool
|
||||||
|
) / historical_peak
|
||||||
if DEBUG_MODE:
|
if DEBUG_MODE:
|
||||||
print(
|
print(
|
||||||
f"DEBUG: 📉 Expiry/Drop Detected ({int(historical_peak):,} -> {int(total_pool):,}) - Adjusting Limit Down.",
|
f"DEBUG: 📉 Pool Drop Detected ({int(historical_peak):,} -> {int(total_pool):,}) - Ratio: {drop_ratio:.1%} - Adjusting Limit Down.",
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
current_peak = total_pool
|
current_peak = total_pool
|
||||||
|
|||||||
Reference in New Issue
Block a user