fix: usage collection error no longer kills keys; 401/403 treated as key failure; key3 (renewal cancelled) skipped via accounts.json
This commit is contained in:
@@ -95,6 +95,8 @@ def load_accounts():
|
||||
"label": a.get("label", kid),
|
||||
"api_key": a.get("key", ""),
|
||||
"workspace_id": a.get("workspace_id", ""),
|
||||
# 续订取消/显式标记 → 视为不可用(采集失败时也能正确跳过)
|
||||
"subscribed": a.get("subscribed", a.get("renewal") != "cancelled"),
|
||||
})
|
||||
log.info("loaded %d OCG keys from accounts.json", len(keys))
|
||||
except Exception as e:
|
||||
@@ -137,6 +139,12 @@ def refresh_state():
|
||||
global _keys, _key_usage
|
||||
_keys = load_accounts()
|
||||
_key_usage = load_usage()
|
||||
# 合并 accounts.json 的订阅标记:采集失败时 usage 缺失该信息,
|
||||
# 以 accounts.json 的 renewal/subscribed 为准
|
||||
for k in _keys:
|
||||
kid = k["key_id"]
|
||||
if not k.get("subscribed", True):
|
||||
_key_usage.setdefault(kid, {})["subscribed"] = False
|
||||
log.info("state refreshed: %d keys, %d with usage data", len(_keys), len(_key_usage))
|
||||
|
||||
|
||||
@@ -147,16 +155,21 @@ def refresh_state():
|
||||
def _key_health_score(kid):
|
||||
"""
|
||||
计算 key 健康度(越低越好):
|
||||
- 有 error 或 unsubscribe 的 key 排最末(score = 999)
|
||||
- 已取消订阅的 key 排最末(score = 999)
|
||||
- 在冷却期内的 key 排倒数第二(score = 888)
|
||||
- 用量采集失败(error)仅降权(score = 500),key 仍可用
|
||||
- 否则按 rolling% → weekly% → monthly% 加权
|
||||
"""
|
||||
usage = _key_usage.get(kid, {})
|
||||
|
||||
# 订阅取消或采集错误 → 不可用
|
||||
if not usage.get("subscribed", True) or usage.get("error"):
|
||||
# 订阅取消 → 不可用
|
||||
if not usage.get("subscribed", True):
|
||||
return 999
|
||||
|
||||
# 用量采集失败 → 降权但可用(不因采集问题误杀 key)
|
||||
if usage.get("error"):
|
||||
return 500
|
||||
|
||||
# 故障冷却期内 → 低优先级
|
||||
now = time.time()
|
||||
fail_until = _key_failures.get(kid, 0)
|
||||
@@ -264,7 +277,7 @@ class RouterHandler(BaseHTTPRequestHandler):
|
||||
"rolling_pct": usage.get("rolling_pct", None),
|
||||
"weekly_pct": usage.get("weekly_pct", None),
|
||||
"monthly_pct": usage.get("monthly_pct", None),
|
||||
"healthy": usage.get("subscribed", True) and not usage.get("error") and (time.time() >= fail_until),
|
||||
"healthy": usage.get("subscribed", True) and (time.time() >= fail_until),
|
||||
"in_cooldown": time.time() < fail_until,
|
||||
"error": usage.get("error", False),
|
||||
"hits": _route_stats.get(kid, 0),
|
||||
@@ -295,9 +308,9 @@ class RouterHandler(BaseHTTPRequestHandler):
|
||||
api_key = k.get("api_key", "")
|
||||
if not api_key:
|
||||
continue
|
||||
# 跳过不可用 key
|
||||
# 跳过不可用 key(采集 error 不跳过,仅降权)
|
||||
usage = _key_usage.get(kid, {})
|
||||
if not usage.get("subscribed", True) or usage.get("error"):
|
||||
if not usage.get("subscribed", True):
|
||||
continue
|
||||
if time.time() < _key_failures.get(kid, 0):
|
||||
continue
|
||||
@@ -370,9 +383,9 @@ class RouterHandler(BaseHTTPRequestHandler):
|
||||
if not api_key:
|
||||
continue
|
||||
|
||||
# 检查 key 是否可用
|
||||
# 检查 key 是否可用(采集 error 不跳过,仅降权)
|
||||
usage = usage_snapshot.get(kid, {})
|
||||
if not usage.get("subscribed", True) or usage.get("error"):
|
||||
if not usage.get("subscribed", True):
|
||||
continue
|
||||
if time.time() < failures_snapshot.get(kid, 0):
|
||||
continue
|
||||
@@ -390,9 +403,9 @@ class RouterHandler(BaseHTTPRequestHandler):
|
||||
else:
|
||||
last_error = result.get("error", "unknown")
|
||||
log.warning("⚠ %s via %s FAILED: %s", model_name, kid, last_error)
|
||||
# 402/429/5xx → 标记 key 故障
|
||||
# 401(未授权/CreditsError)/402/403/429/5xx → 标记 key 故障
|
||||
status = result.get("status", 0)
|
||||
if status in (402, 429) or status >= 500:
|
||||
if status in (401, 402, 403, 429) or status >= 500:
|
||||
mark_key_failed(kid)
|
||||
except Exception as e:
|
||||
last_error = str(e)
|
||||
|
||||
@@ -77,7 +77,7 @@ DEFAULT_POLL_INTERVAL_SEC = 300 # 5 minutes
|
||||
CDP_PROXY_URL = os.environ.get('CDP_PROXY_URL', 'http://localhost:3456')
|
||||
CDP_EVAL_TIMEOUT = 20
|
||||
CDP_TARGETS_TIMEOUT = 5
|
||||
HTTP_TIMEOUT = 15
|
||||
HTTP_TIMEOUT = 45 # opencode.ai 响应慢(2026-08 实测 10-12s),放宽到 45s
|
||||
OPENCODE_BASE_URL = 'https://opencode.ai'
|
||||
|
||||
# ── SSR HTML parsing ──
|
||||
|
||||
Reference in New Issue
Block a user