chore: revert changes to daily_limit.py as requested
This commit is contained in:
+16
-122
@@ -29,9 +29,7 @@ DEBUG_MODE = CURRENT_LEVEL_VAL <= 10
|
|||||||
def log(msg, level="INFO"):
|
def log(msg, level="INFO"):
|
||||||
msg_val = get_log_level_value(level)
|
msg_val = get_log_level_value(level)
|
||||||
if msg_val >= CURRENT_LEVEL_VAL:
|
if msg_val >= CURRENT_LEVEL_VAL:
|
||||||
icon = {"DEBUG": "🔍", "INFO": "ℹ️", "WARNING": "⚠️", "ERROR": "❌"}.get(
|
icon = {"DEBUG": "🔍", "INFO": "ℹ️", "WARNING": "⚠️", "ERROR": "❌"}.get(level, "📝")
|
||||||
level, "📝"
|
|
||||||
)
|
|
||||||
print(f"{icon} [DailyLimit] {msg}", flush=True)
|
print(f"{icon} [DailyLimit] {msg}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -59,9 +57,7 @@ MODEL_QUOTAS = {
|
|||||||
try:
|
try:
|
||||||
redis_host = os.getenv("REDIS_HOST", "redis")
|
redis_host = os.getenv("REDIS_HOST", "redis")
|
||||||
redis_port = int(os.getenv("REDIS_PORT", 6379))
|
redis_port = int(os.getenv("REDIS_PORT", 6379))
|
||||||
redis_client = redis.Redis(
|
redis_client = redis.Redis(host=redis_host, port=redis_port, db=0, decode_responses=True)
|
||||||
host=redis_host, port=redis_port, db=0, decode_responses=True
|
|
||||||
)
|
|
||||||
log(f"✅ Redis connected successfully to {redis_host}:{redis_port}", level="INFO")
|
log(f"✅ Redis connected successfully to {redis_host}:{redis_port}", level="INFO")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"❌ Redis connection failed: {str(e)}", level="ERROR")
|
log(f"❌ Redis connection failed: {str(e)}", level="ERROR")
|
||||||
@@ -88,10 +84,10 @@ def generate_api_base_hash(api_base):
|
|||||||
try:
|
try:
|
||||||
# 1. 预处理:去空格
|
# 1. 预处理:去空格
|
||||||
s = api_base.strip()
|
s = api_base.strip()
|
||||||
|
|
||||||
# 2. 解析 URL
|
# 2. 解析 URL
|
||||||
parsed = urlparse(s)
|
parsed = urlparse(s)
|
||||||
|
|
||||||
# 3. 提取 Scheme 和 Netloc (域名+端口)
|
# 3. 提取 Scheme 和 Netloc (域名+端口)
|
||||||
# 如果解析成功且包含 scheme 和 netloc,则重组
|
# 如果解析成功且包含 scheme 和 netloc,则重组
|
||||||
if parsed.scheme and parsed.netloc:
|
if parsed.scheme and parsed.netloc:
|
||||||
@@ -99,23 +95,20 @@ def generate_api_base_hash(api_base):
|
|||||||
else:
|
else:
|
||||||
# 兜底:如果不是标准 URL,或者解析失败,尝试简单的去斜杠和小写化
|
# 兜底:如果不是标准 URL,或者解析失败,尝试简单的去斜杠和小写化
|
||||||
# 这种情况比较少见,通常 litellm 会校验 URL 格式
|
# 这种情况比较少见,通常 litellm 会校验 URL 格式
|
||||||
clean_base = s.rstrip("/").lower()
|
clean_base = s.rstrip('/').lower()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# 极端情况下的兜底
|
# 极端情况下的兜底
|
||||||
clean_base = api_base.strip().rstrip("/").lower()
|
clean_base = api_base.strip().rstrip('/').lower()
|
||||||
|
|
||||||
# 4. 计算 Hash
|
# 4. 计算 Hash
|
||||||
h = hashlib.md5(clean_base.encode()).hexdigest()[:8]
|
h = hashlib.md5(clean_base.encode()).hexdigest()[:8]
|
||||||
|
|
||||||
if DEBUG_MODE:
|
if DEBUG_MODE:
|
||||||
# Debugging hash mismatch
|
# Debugging hash mismatch
|
||||||
# 打印原始值和清洗后的值,方便确认
|
# 打印原始值和清洗后的值,方便确认
|
||||||
print(
|
print(f"🔍 [Hash Debug] Base='{api_base}' -> Clean='{clean_base}' -> Hash='{h}'", flush=True)
|
||||||
f"🔍 [Hash Debug] Base='{api_base}' -> Clean='{clean_base}' -> Hash='{h}'",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return h
|
return h
|
||||||
|
|
||||||
|
|
||||||
@@ -139,74 +132,6 @@ def get_redis_key(clean_name, api_base=None):
|
|||||||
# 🎯 限额查找逻辑 (支持精确匹配)
|
# 🎯 限额查找逻辑 (支持精确匹配)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
def get_daily_limit(clean_model, api_base):
|
def get_daily_limit(clean_model, api_base):
|
||||||
# 0. Check Dynamic Limit (Redis Priority) for THIS model
|
|
||||||
# Logic: Limit = Current_Usage + Remaining_Balance_After_Buffer
|
|
||||||
if redis_client:
|
|
||||||
try:
|
|
||||||
# Check specific model limit first (from volc_limit_sync breakdown)
|
|
||||||
# Key: limit:dynamic:volcengine:remaining:{clean_model}
|
|
||||||
# Example: deepseek-v3
|
|
||||||
# Fallback to global if needed (though sync script now sets per-model)
|
|
||||||
|
|
||||||
# Since normalized names in sync script match clean_model (ideally),
|
|
||||||
# we try to find a matching key.
|
|
||||||
# But wait, clean_model in daily_limit might have -251201 suffix,
|
|
||||||
# while sync script key might be just "deepseek-v3".
|
|
||||||
# We need to iterate or check fuzzy?
|
|
||||||
# Actually, sync script sets keys based on mapped names.
|
|
||||||
# Let's rely on the fuzzy match logic we used in syncing?
|
|
||||||
# No, sync script sets specific keys.
|
|
||||||
|
|
||||||
# Let's try to find a key that is a substring of clean_model
|
|
||||||
# Or just check if there is a key.
|
|
||||||
# For simplicity and performance, we can iterate over the keys set by sync script?
|
|
||||||
# No, that's slow.
|
|
||||||
|
|
||||||
# Better approach:
|
|
||||||
# The sync script sets keys like "limit:dynamic:volcengine:remaining:deepseek-v3"
|
|
||||||
# We check if we can construct that key.
|
|
||||||
# clean_model is "deepseek-v3-2-251201"
|
|
||||||
|
|
||||||
# Let's use the scan match approach? No, slow.
|
|
||||||
# Let's try direct lookups for likely prefixes.
|
|
||||||
|
|
||||||
# Actually, if we use the same VOLC_MODEL_MAP logic here? No, code duplication.
|
|
||||||
|
|
||||||
# Pragmatic approach:
|
|
||||||
# Iterate over known dynamic keys?
|
|
||||||
# Or, just check "limit:dynamic:volcengine:remaining:global" if we want a catch-all?
|
|
||||||
# But user wants per-model.
|
|
||||||
|
|
||||||
# Let's try to match the keys that exist.
|
|
||||||
# Sync script sets keys. We can just list them once or use a known pattern.
|
|
||||||
# But we don't know the exact key name here easily without the map.
|
|
||||||
|
|
||||||
# Wait, the user said "ensure alignment".
|
|
||||||
# If VOLC_MODEL_MAP maps "deepseek-v3" -> "deepseek-v3-241226",
|
|
||||||
# then sync script sets key "limit:dynamic:volcengine:remaining:deepseek-v3-241226"
|
|
||||||
# And clean_model IS "deepseek-v3-241226".
|
|
||||||
# So it SHOULD be an exact match!
|
|
||||||
|
|
||||||
remaining_key = f"limit:dynamic:volcengine:remaining:{clean_model}"
|
|
||||||
remaining_balance = redis_client.get(remaining_key)
|
|
||||||
|
|
||||||
# If not found, maybe it's a prefix match case?
|
|
||||||
if remaining_balance is None:
|
|
||||||
# Try finding a key that is a prefix of clean_model
|
|
||||||
# This is tricky without listing all keys.
|
|
||||||
# For now, let's assume the alignment from Task 2 (Mapping) works.
|
|
||||||
pass
|
|
||||||
|
|
||||||
if remaining_balance is not None:
|
|
||||||
usage_key = get_redis_key(clean_model, api_base)
|
|
||||||
current_usage = redis_client.get(usage_key)
|
|
||||||
current_val = float(current_usage) if current_usage else 0.0
|
|
||||||
dynamic_limit = current_val + float(remaining_balance)
|
|
||||||
return dynamic_limit
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 1. 尝试精确匹配 (Model + Hash)
|
# 1. 尝试精确匹配 (Model + Hash)
|
||||||
# 用户可以在 MODEL_QUOTAS 中配置 "model_name:hash": limit
|
# 用户可以在 MODEL_QUOTAS 中配置 "model_name:hash": limit
|
||||||
current_hash = generate_api_base_hash(api_base)
|
current_hash = generate_api_base_hash(api_base)
|
||||||
@@ -272,18 +197,13 @@ def soft_evict_from_router(target_upstream_model, target_api_base=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Backup original state
|
# Backup original state
|
||||||
model_entry["_daily_limit_backup"] = {
|
model_entry["_daily_limit_backup"] = {"model_name": current_alias, "api_base": current_api_base}
|
||||||
"model_name": current_alias,
|
|
||||||
"api_base": current_api_base,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1. 修改别名 - Modify IN PLACE
|
# 1. 修改别名 - Modify IN PLACE
|
||||||
llm_router.model_list[i]["model_name"] = f"{current_alias}-blocked"
|
llm_router.model_list[i]["model_name"] = f"{current_alias}-blocked"
|
||||||
|
|
||||||
# 2. 破坏连接
|
# 2. 破坏连接
|
||||||
llm_router.model_list[i]["litellm_params"]["api_base"] = (
|
llm_router.model_list[i]["litellm_params"]["api_base"] = "http://0.0.0.0"
|
||||||
"http://0.0.0.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
evicted = True
|
evicted = True
|
||||||
|
|
||||||
@@ -348,9 +268,7 @@ def restore_blocked_models():
|
|||||||
|
|
||||||
# Restore IN PLACE
|
# Restore IN PLACE
|
||||||
llm_router.model_list[i]["model_name"] = backup["model_name"]
|
llm_router.model_list[i]["model_name"] = backup["model_name"]
|
||||||
llm_router.model_list[i]["litellm_params"]["api_base"] = (
|
llm_router.model_list[i]["litellm_params"]["api_base"] = original_api_base
|
||||||
original_api_base
|
|
||||||
)
|
|
||||||
del llm_router.model_list[i]["_daily_limit_backup"]
|
del llm_router.model_list[i]["_daily_limit_backup"]
|
||||||
restored_count += 1
|
restored_count += 1
|
||||||
|
|
||||||
@@ -427,22 +345,6 @@ class DailyLimitHandler(CustomLogger):
|
|||||||
current_total = redis_client.incrby(key, int(total_tokens))
|
current_total = redis_client.incrby(key, int(total_tokens))
|
||||||
redis_client.expire(key, 172800)
|
redis_client.expire(key, 172800)
|
||||||
|
|
||||||
# Increment global counter for Volcengine sync
|
|
||||||
try:
|
|
||||||
# Ensure date_str is defined for the global key
|
|
||||||
now_ts_global = time.time()
|
|
||||||
# Adjust for timezone if needed, or consistent with existing date_str logic
|
|
||||||
# Assuming local time is acceptable for sync triggers
|
|
||||||
date_str_global = time.strftime(
|
|
||||||
"%Y-%m-%d", time.localtime(now_ts_global)
|
|
||||||
)
|
|
||||||
|
|
||||||
global_key = f"daily_usage:{date_str_global}:global_total"
|
|
||||||
redis_client.incrby(global_key, int(total_tokens))
|
|
||||||
redis_client.expire(global_key, 172800) # 48h
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Get Limit for display
|
# Get Limit for display
|
||||||
daily_limit = get_daily_limit(clean_model, api_base)
|
daily_limit = get_daily_limit(clean_model, api_base)
|
||||||
display_limit = f"/{int(daily_limit):,}" if daily_limit else ""
|
display_limit = f"/{int(daily_limit):,}" if daily_limit else ""
|
||||||
@@ -456,25 +358,17 @@ class DailyLimitHandler(CustomLogger):
|
|||||||
|
|
||||||
# Enforcement Logic
|
# Enforcement Logic
|
||||||
if daily_limit and current_total >= float(daily_limit):
|
if daily_limit and current_total >= float(daily_limit):
|
||||||
log(
|
log(f"🚫 [BLOCK] {clean_model} Limit: {current_total}/{daily_limit}", level="WARNING")
|
||||||
f"🚫 [BLOCK] {clean_model} Limit: {current_total}/{daily_limit}",
|
|
||||||
level="WARNING",
|
|
||||||
)
|
|
||||||
soft_evict_from_router(raw_model, api_base)
|
soft_evict_from_router(raw_model, api_base)
|
||||||
else:
|
else:
|
||||||
if DEBUG_MODE:
|
if DEBUG_MODE:
|
||||||
if not clean_model:
|
if not clean_model:
|
||||||
log("⚠️ [Usage Skip] No clean model name found", level="DEBUG")
|
log("⚠️ [Usage Skip] No clean model name found", level="DEBUG")
|
||||||
if total_tokens <= 0:
|
if total_tokens <= 0:
|
||||||
log(
|
log(f"⚠️ [Usage Skip] Total tokens is {total_tokens}", level="DEBUG")
|
||||||
f"⚠️ [Usage Skip] Total tokens is {total_tokens}",
|
|
||||||
level="DEBUG",
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(
|
log(f"❌ _record_usage failed: {e}\n{traceback.format_exc()}", level="ERROR")
|
||||||
f"❌ _record_usage failed: {e}\n{traceback.format_exc()}", level="ERROR"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
proxy_handler_instance = DailyLimitHandler()
|
proxy_handler_instance = DailyLimitHandler()
|
||||||
|
|||||||
Reference in New Issue
Block a user