165 lines
5.4 KiB
Python
165 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import datetime as dt
|
|
import json
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DEFAULT_HEALTH_URL = "http://192.168.220.91:8765/api/health"
|
|
DEFAULT_OUTPUT = ROOT / "reports" / "model-chat" / "status.md"
|
|
|
|
|
|
def fetch_json(url: str, timeout: int) -> dict[str, Any]:
|
|
request = urllib.request.Request(url, headers={"Accept": "application/json"})
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
payload = response.read().decode("utf-8")
|
|
except urllib.error.URLError as exc:
|
|
raise RuntimeError(f"failed to fetch {url}: {exc}") from exc
|
|
data = json.loads(payload)
|
|
if not isinstance(data, dict):
|
|
raise RuntimeError(f"health endpoint returned {type(data).__name__}, expected object")
|
|
return data
|
|
|
|
|
|
def table(headers: list[str], rows: list[list[Any]]) -> list[str]:
|
|
lines = [
|
|
"| " + " | ".join(headers) + " |",
|
|
"| " + " | ".join("---" for _ in headers) + " |",
|
|
]
|
|
for row in rows:
|
|
lines.append("| " + " | ".join(str(value) if value is not None else "" for value in row) + " |")
|
|
return lines
|
|
|
|
|
|
def summarize_routes(health: dict[str, Any]) -> list[list[Any]]:
|
|
rows = []
|
|
for route in health.get("routes") or []:
|
|
model = route.get("model") or {}
|
|
readiness = route.get("readiness") or {}
|
|
rows.append(
|
|
[
|
|
route.get("plugin"),
|
|
model.get("id"),
|
|
route.get("served_model_name"),
|
|
readiness.get("status") or ("online" if route.get("available") else "offline"),
|
|
route.get("base_url"),
|
|
]
|
|
)
|
|
return rows
|
|
|
|
|
|
def summarize_endpoints(health: dict[str, Any]) -> list[list[Any]]:
|
|
rows = []
|
|
for endpoint in health.get("endpoints") or []:
|
|
models = ", ".join(endpoint.get("models") or [])
|
|
rows.append(
|
|
[
|
|
endpoint.get("id"),
|
|
endpoint.get("status"),
|
|
endpoint.get("base_url"),
|
|
models,
|
|
endpoint.get("latency_ms"),
|
|
]
|
|
)
|
|
return rows
|
|
|
|
|
|
def summarize_profiles(health: dict[str, Any]) -> list[list[Any]]:
|
|
rows = []
|
|
for profile_id, profile in (health.get("gpu_profile_status") or {}).items():
|
|
rows.append(
|
|
[
|
|
profile_id,
|
|
"yes" if profile.get("ready") else "no",
|
|
", ".join(profile.get("missing") or []),
|
|
", ".join(profile.get("conflicts") or []),
|
|
]
|
|
)
|
|
return rows
|
|
|
|
|
|
def summarize_model_services(health: dict[str, Any]) -> list[list[Any]]:
|
|
rows = []
|
|
for service in health.get("model_services") or []:
|
|
storage = service.get("storage") or {}
|
|
endpoint_models = ", ".join(service.get("endpoint_models") or [])
|
|
rows.append(
|
|
[
|
|
service.get("id"),
|
|
", ".join(service.get("plugins") or []),
|
|
service.get("runtime"),
|
|
service.get("served_model_name"),
|
|
service.get("status"),
|
|
"yes" if service.get("online") else "no",
|
|
storage.get("status") or ("ok" if storage.get("exists") else "missing"),
|
|
endpoint_models,
|
|
]
|
|
)
|
|
return rows
|
|
|
|
|
|
def build_markdown(health: dict[str, Any], source_url: str) -> str:
|
|
generated_at = dt.datetime.now(dt.UTC).replace(microsecond=0).isoformat()
|
|
gpu = health.get("gpu") or {}
|
|
lines = [
|
|
"# Model Chat Status",
|
|
"",
|
|
f"- Generated at: `{generated_at}`",
|
|
f"- Source: `{source_url}`",
|
|
f"- Health status: `{health.get('status')}`",
|
|
f"- Host: `{health.get('host')}`",
|
|
f"- Registry models: `{health.get('registry_models')}`",
|
|
f"- GPU: `{gpu.get('summary') or 'unknown'}`",
|
|
"",
|
|
"## Plugin Routes",
|
|
"",
|
|
*table(["plugin", "model", "served model", "status", "base URL"], summarize_routes(health)),
|
|
"",
|
|
"## GPU Profiles",
|
|
"",
|
|
*table(["profile", "ready", "missing", "conflicts"], summarize_profiles(health)),
|
|
"",
|
|
"## Endpoints",
|
|
"",
|
|
*table(["endpoint", "status", "base URL", "models", "latency ms"], summarize_endpoints(health)),
|
|
"",
|
|
"## Model Services",
|
|
"",
|
|
*table(
|
|
["model", "plugins", "runtime", "served model", "status", "online", "storage", "endpoint models"],
|
|
summarize_model_services(health),
|
|
),
|
|
"",
|
|
]
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Generate a Markdown status report from Model Chat /api/health.")
|
|
parser.add_argument("--url", default=DEFAULT_HEALTH_URL)
|
|
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
|
|
parser.add_argument("--timeout", type=int, default=60)
|
|
parser.add_argument("--print", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
health = fetch_json(args.url, args.timeout)
|
|
markdown = build_markdown(health, args.url)
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(markdown + "\n", encoding="utf-8")
|
|
if args.print:
|
|
sys.stdout.write(markdown + "\n")
|
|
else:
|
|
print(f"Wrote {args.output}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|