From f791d8dee78d4ee112db74a92350b056373f071f Mon Sep 17 00:00:00 2001 From: hmo Date: Sun, 19 Jul 2026 14:41:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20API=20key=20availability=20monitoring?= =?UTF-8?q?=20=E2=80=94=20auto-select=20best=20key=20from=20AgentsMeeting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.py | 11 ++++++++ xmpp_logger.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 1b4c86fb..baa15f4a 100644 --- a/server.py +++ b/server.py @@ -1329,6 +1329,17 @@ def api_xmpp_autoheal(): return jsonify({"error": "xmpp_logger not available"}), 500 +@app.route("/api/xmpp/keys") +def api_xmpp_keys(): + """API Key 可用性:从 AgentsMeeting 获取并选择最佳 key""" + try: + from xmpp_logger import best_key + bk = best_key() + return jsonify({"best_key": bk} if bk else {"error": "no keys available"}) + except ImportError: + return jsonify({"error": "xmpp_logger not available"}), 500 + + # 注册提示词管理路由 register_routes(app) diff --git a/xmpp_logger.py b/xmpp_logger.py index 977d2683..8113b8a6 100644 --- a/xmpp_logger.py +++ b/xmpp_logger.py @@ -10,6 +10,7 @@ import json import time as _time import subprocess as _sp +import urllib.request as _ur from pathlib import Path from datetime import datetime, timedelta @@ -230,9 +231,75 @@ def health(): elif er > 50: result["status"] = "degraded" elif la > 0: result["status"] = "ok" + # 5. API Key 可用性 + bk = best_key() + if bk: + result["best_key"] = bk + if bk["issues"]: + result["status"] = "degraded" + return result +def _fetch_keys(): + """从 AgentsMeeting Dashboard 获取可用 API Key 列表""" + try: + req = _ur.Request("http://127.0.0.1:5803/api/keys") + resp = _ur.urlopen(req, timeout=5) + data = json.loads(resp.read()) + return data.get("keys", []) if data.get("ok") else [] + except Exception: + return [] + + +def best_key(): + """选择最佳可用 API Key。 + + 优先级: rolling ok > weekly ok > monthly ok > 最低 usage + 返回: {"key_id": "key6", "label": "...", "usage": {...}, "reason": "..."} + """ + keys = _fetch_keys() + if not keys: + return None + + def score(k): + """分数越低越好""" + s = 0 + r = k.get("rolling", {}) + w = k.get("weekly", {}) + m = k.get("monthly", {}) + # rate-limited 惩罚 + if r.get("status") != "ok": s += 1000 + if w.get("status") != "ok": s += 100 + if m.get("status") != "ok": s += 10 + # usage 越高越差 + s += r.get("usage_percent", 0) * 0.01 + s += w.get("usage_percent", 0) * 0.001 + s += m.get("usage_percent", 0) * 0.0001 + # session_expired 惩罚 + if k.get("session_expired"): s += 500 + return s + + best = min(keys, key=score) + reasons = [] + if best["rolling"]["status"] != "ok": reasons.append(f"rolling {best['rolling']['usage_percent']}%") + if best["weekly"]["status"] != "ok": reasons.append(f"weekly {best['weekly']['usage_percent']}%") + if best["monthly"]["status"] != "ok": reasons.append(f"monthly {best['monthly']['usage_percent']}%") + if best.get("session_expired"): reasons.append("session_expired") + + return { + "key_id": best["key_id"], + "label": best["label"], + "masked": best.get("key_masked", ""), + "rolling": best["rolling"], + "weekly": best["weekly"], + "monthly": best["monthly"], + "session_expired": best.get("session_expired", False), + "issues": reasons, + "total_keys": len(keys), + } + + def auto_heal(): """自愈:检测并尝试修复 XMPP 通信问题。""" h = health() @@ -256,7 +323,7 @@ def auto_heal(): ba = h.get("bot_activity", {}) if ba.get("inbound", 0) > 0 and ba.get("outbound", 0) == 0 and ba.get("errors", 0) > 0: try: - r = _sp.run(["sudo", "-n", "systemctl", "restart", "xmpp-zhiwei"], capture_output=True, timeout=30, text=True) + r = _sp.run(["sudo", "-n", "systemctl", "restart", "xmpp-zhiwei"], capture_output=True, timeout=30, text=True) actions.append({"action": "restart_zhiwei_bot", "target": "xmpp-zhiwei", "success": r.returncode == 0, "detail": "restarted" if r.returncode == 0 else r.stderr[:100]}) except Exception as e: @@ -266,7 +333,7 @@ def auto_heal(): # 3. ejabberd → 重启容器 if h.get("ejabberd", "") and "Up" not in str(h["ejabberd"]): try: - r = _sp.run(["sudo", "-n", "docker", "restart", "ejabberd"], capture_output=True, timeout=30, text=True) + r = _sp.run(["sudo", "-n", "docker", "restart", "ejabberd"], capture_output=True, timeout=30, text=True) actions.append({"action": "restart_ejabberd", "target": "ejabberd", "success": r.returncode == 0, "detail": "restarted" if r.returncode == 0 else r.stderr[:100]}) except Exception as e: