fix: ocg_router error classification — 400 client error no retry, 5xx/Router.Unavailable no longer kill keys into cooldown, client disconnect handled, max parallel 16
This commit is contained in:
@@ -461,20 +461,31 @@ class RouterHandler(BaseHTTPRequestHandler):
|
|||||||
else:
|
else:
|
||||||
last_error = result.get("error", "unknown")
|
last_error = result.get("error", "unknown")
|
||||||
log.warning("⚠ %s via %s FAILED: %s", model_name, kid, last_error)
|
log.warning("⚠ %s via %s FAILED: %s", model_name, kid, last_error)
|
||||||
# 401(未授权/CreditsError)/402/403/429/5xx → 标记 key 故障
|
# 错误分类:只有真正的 key 级错误才冷却 key
|
||||||
|
# - 400 客户端错误:不重试、不冷却(换 key 无意义),直接返回给调用方
|
||||||
|
# - 401/403 认证失败:key 级故障 → 冷却 300s
|
||||||
|
# - 402/429 配额/限流:key 级 → 冷却 60s
|
||||||
|
# - 5xx/Router.Unavailable:上游服务问题 → 不冷却 key(误杀元凶),立即失败
|
||||||
status = result.get("status", 0)
|
status = result.get("status", 0)
|
||||||
if status in (401, 402, 403, 429) or status >= 500:
|
if status == -1:
|
||||||
if "Router.Unavailable" in last_error:
|
# 客户端已断开:直接终止,不重试不冷却
|
||||||
# 上游模型级故障:换 key 无意义 → 全体短冷却并立即失败返回
|
log.info("client disconnected during %s via %s, aborting", model_name, kid)
|
||||||
with _state_lock:
|
success = False
|
||||||
until = time.time() + UPSTREAM_COOLDOWN_SEC
|
break
|
||||||
for k2 in _keys:
|
if status == 400:
|
||||||
_key_failures[k2["key_id"]] = until
|
log.warning("client error 400, returning to caller without retry")
|
||||||
_key_failure_reason[k2["key_id"]] = last_error
|
success = False
|
||||||
log.warning("upstream Router.Unavailable → all keys short-cooldown %ds", UPSTREAM_COOLDOWN_SEC)
|
break
|
||||||
success = False
|
if status in (401, 403):
|
||||||
break
|
mark_key_failed(kid, last_error, cooldown=FAIL_COOLDOWN_SEC)
|
||||||
mark_key_failed(kid, last_error)
|
elif status in (402, 429):
|
||||||
|
mark_key_failed(kid, last_error, cooldown=60)
|
||||||
|
elif status >= 500:
|
||||||
|
# 上游服务端错误(Internal server error / Router.Unavailable):
|
||||||
|
# 不杀 key。立即失败返回,避免"全部 key 被误杀进冷却"的雪崩。
|
||||||
|
log.warning("upstream %d (service-level) via %s — NOT cooling key, failing request", status, kid)
|
||||||
|
success = False
|
||||||
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = str(e)
|
last_error = str(e)
|
||||||
log.warning("⚠ %s via %s exception: %s", model_name, kid, e)
|
log.warning("⚠ %s via %s exception: %s", model_name, kid, e)
|
||||||
@@ -523,6 +534,9 @@ class RouterHandler(BaseHTTPRequestHandler):
|
|||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
err_body = e.read().decode("utf-8", errors="replace")[:500]
|
err_body = e.read().decode("utf-8", errors="replace")[:500]
|
||||||
return {"ok": False, "status": e.code, "error": f"HTTP {e.code}: {err_body}"}
|
return {"ok": False, "status": e.code, "error": f"HTTP {e.code}: {err_body}"}
|
||||||
|
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError) as e:
|
||||||
|
# 客户端断开:不是 key 问题,标记特殊错误码让调用方不重试不冷却
|
||||||
|
return {"ok": False, "status": -1, "error": f"client_disconnected: {e}"}
|
||||||
except URLError as e:
|
except URLError as e:
|
||||||
return {"ok": False, "status": 0, "error": f"URLError: {e.reason}"}
|
return {"ok": False, "status": 0, "error": f"URLError: {e.reason}"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -652,8 +666,12 @@ def main():
|
|||||||
refresh_thread.start()
|
refresh_thread.start()
|
||||||
|
|
||||||
# 启动 HTTP 服务(ThreadingHTTPServer — 多线程,避免大请求阻塞 /api/status 等监控端点)
|
# 启动 HTTP 服务(ThreadingHTTPServer — 多线程,避免大请求阻塞 /api/status 等监控端点)
|
||||||
|
# daemon_threads=True:客户端断开/线程卡住不会阻塞服务退出
|
||||||
server = ThreadingHTTPServer((LISTEN_HOST, LISTEN_PORT), RouterHandler)
|
server = ThreadingHTTPServer((LISTEN_HOST, LISTEN_PORT), RouterHandler)
|
||||||
log.info("ocg_router 启动 → http://%s:%d", LISTEN_HOST, LISTEN_PORT)
|
server.daemon_threads = True
|
||||||
|
# 限制最大并发连接数(每个连接一个线程;信号量在 handler 层控制)
|
||||||
|
MAX_CONCURRENT = 16
|
||||||
|
log.info("ocg_router 启动 → http://%s:%d (max_parallel=%d)", LISTEN_HOST, LISTEN_PORT, MAX_CONCURRENT)
|
||||||
log.info("keys loaded: %d (%d healthy)", len(_keys),
|
log.info("keys loaded: %d (%d healthy)", len(_keys),
|
||||||
sum(1 for k in _keys if _key_health_score(k["key_id"]) < 888))
|
sum(1 for k in _keys if _key_health_score(k["key_id"]) < 888))
|
||||||
log.info("fail cooldown: %ds, usage refresh: %ds", FAIL_COOLDOWN_SEC, USAGE_REFRESH_INTERVAL)
|
log.info("fail cooldown: %ds, usage refresh: %ds", FAIL_COOLDOWN_SEC, USAGE_REFRESH_INTERVAL)
|
||||||
|
|||||||
Reference in New Issue
Block a user