diff --git a/gateway/scripts/ocg_router.py b/gateway/scripts/ocg_router.py index 0b6fb03..42d3ad0 100644 --- a/gateway/scripts/ocg_router.py +++ b/gateway/scripts/ocg_router.py @@ -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) diff --git a/gateway/scripts/usage_collector.py b/gateway/scripts/usage_collector.py index e84aa3f..312ab54 100644 --- a/gateway/scripts/usage_collector.py +++ b/gateway/scripts/usage_collector.py @@ -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 ──