#!/usr/bin/env python3 """ NewAPI 模型健康巡检(快速版) 每 6h 运行,测试关键模型的响应状态 输出: ~/.hermes/model-health.json """ import json import time import urllib.request import urllib.error import os import sys from datetime import datetime, timezone API = "http://127.0.0.1:3000/v1" KEY = "0ExNiLblJvIWBDpkS50fwOBw4MmqLyKdHJK5iQtlw9dOMWBP" OUTPUT = os.path.expanduser("~/.hermes/model-health.json") # 测试模型列表(按优先级排列) # 第1批: 已知候选(先测快的) BATCH_1 = [ "minimaxai/minimax-m3", "minimaxai/minimax-m2.7", "stepfun-ai/step-3.5-flash", "deepseek-ai/deepseek-v3.2", "microsoft/phi-4-mini-instruct", "meta/llama-4-maverick-17b-128e-instruct", ] # 第2批: 大型模型 BATCH_2 = [ "mistralai/mistral-medium-3.5-128b", "meta/llama-3.3-70b-instruct", "qwen/qwen3.5-122b-a10b", "z-ai/glm4.7", "z-ai/glm5", "bytedance/seed-oss-36b-instruct", ] # 第3批: 超大/专用 BATCH_3 = [ "mistralai/mistral-large-3-675b-instruct-2512", "qwen/qwen3-coder-480b-a35b-instruct", "qwen/qwen2.5-coder-32b-instruct", "moonshotai/kimi-k2-instruct", ] ALL_MODELS = BATCH_1 + BATCH_2 + BATCH_3 HEADERS = { "Authorization": f"Bearer {KEY}", "Content-Type": "application/json", } PROMPT = "你好" def test_model(model: str) -> dict: """测试单个模型 2 次,返回汇总""" trials = [] for t in range(2): payload = json.dumps({ "model": model, "messages": [{"role": "user", "content": PROMPT}], "max_tokens": 20, }).encode() req = urllib.request.Request( f"{API}/chat/completions", data=payload, headers=HEADERS, method="POST", ) start = time.time() try: with urllib.request.urlopen(req, timeout=15) as resp: body = json.loads(resp.read()) except urllib.error.HTTPError as e: trials.append({"status": "fail", "error": f"HTTP_{e.code}", "latency_ms": round((time.time() - start) * 1000)}) continue except Exception as e: trials.append({"status": "fail", "error": str(e)[:60], "latency_ms": round((time.time() - start) * 1000)}) continue elapsed = round((time.time() - start) * 1000) try: choice = body["choices"][0] msg = choice.get("message", {}) content = msg.get("content", "") or "" finish = choice.get("finish_reason", "") usage = body.get("usage", {}) # ttft 从 nvext 取,没有就估计 ttft = body.get("nvext", {}).get("timing", {}).get("ttft_ms", -1) if ttft < 0: ttft = round(elapsed * 0.3) trials.append({ "status": "ok", "latency_ms": elapsed, "ttft_ms": ttft, "has_content": 1 if content.strip() else 0, "completion_tokens": usage.get("completion_tokens", 0), "finish_reason": finish, }) except (KeyError, IndexError, json.JSONDecodeError) as e: trials.append({"status": "fail", "error": f"parse: {e}", "latency_ms": elapsed}) # 汇总 ok_count = sum(1 for t in trials if t["status"] == "ok") fail_count = 2 - ok_count if ok_count == 2: stability = "stable" elif ok_count == 1: stability = "unstable" else: stability = "dead" ok_trials = [t for t in trials if t["status"] == "ok"] avg_latency = round(sum(t["latency_ms"] for t in ok_trials) / len(ok_trials)) if ok_trials else 0 avg_ttft = round(sum(t.get("ttft_ms", 0) for t in ok_trials) / len(ok_trials)) if ok_trials else -1 last_ok = ok_trials[-1] if ok_trials else trials[-1] last_finish = last_ok.get("finish_reason", "error") return { "model": model, "tests": 2, "success": ok_count, "failure": fail_count, "avg_latency_ms": avg_latency, "avg_ttft_ms": avg_ttft, "stability": stability, "last_status": "ok" if ok_count > 0 else "fail", "last_finish": last_finish, } def main(): timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") results = [] deadline = time.time() + 240 # 4分钟全局超时 for model in ALL_MODELS: if time.time() > deadline: print(f"⏰ 全局超时,跳过剩余模型") break entry = test_model(model) results.append(entry) icon = "✅" if entry["stability"] == "stable" else ("⚠️" if entry["stability"] == "unstable" else "❌") print(f"{icon} {model:45s} {entry['avg_latency_ms']:>6}ms | {entry['success']}/2 ok | {entry['stability']}") sys.stdout.flush() # 汇总 healthy = sum(1 for r in results if r["stability"] == "stable") flaky = sum(1 for r in results if r["stability"] == "unstable") dead = sum(1 for r in results if r["stability"] == "dead") stable_models = [r for r in results if r["stability"] == "stable"] stable_sorted = sorted(stable_models, key=lambda x: x["avg_latency_ms"]) fastest = stable_sorted[:5] if stable_sorted else [] summary = { "timestamp": timestamp, "total_models": len(results), "stable": healthy, "unstable": flaky, "dead": dead, "fastest_stable": [m["model"] for m in fastest], "recommendations": { "fast": [m["model"] for m in fastest], "fastest3": [m["model"] for m in fastest[:3]], "priorities": { "日常快速": fastest[:3] if len(fastest) >= 3 else fastest, "复杂推理": [m["model"] for m in sorted(stable_models, key=lambda x: -x.get("completion_tokens", 0) if hasattr(x, "get") else 0)[:2]], }, }, "models": results, } os.makedirs(os.path.dirname(OUTPUT), exist_ok=True) with open(OUTPUT + ".new", "w") as f: json.dump(summary, f, indent=2, ensure_ascii=False) os.replace(OUTPUT + ".new", OUTPUT) print(f"\n{'='*50}") print(f"巡检完成: {healthy}个稳定 / {flaky}个不稳定 / {dead}个死 (共{len(results)}个)") if fastest: print(f"推荐: {', '.join(summary['recommendations']['fast'])}") if __name__ == "__main__": main()