feat: add sync_models.py script to auto-update Hermes custom_providers from router model list

This commit is contained in:
root 2026-06-15 21:10:36 +00:00
parent 95c87a764b
commit 903f06c634

161
scripts/sync_models.py Normal file
View File

@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Sync intelligence-router model list into Hermes custom_providers.
Usage:
# One-shot: discover models from the router and update Hermes config
python3 scripts/sync_models.py
# Cron mode (auto): set up via:
# cp scripts/sync_models.py ~/.hermes/scripts/
# hermes cron create --schedule "every 30m" --no-agent --script sync_models.py
Silent exit when nothing changed. Prints a summary + restarts the gateway when
the model list differs.
"""
import json
import os
import subprocess
import sys
import urllib.error
import urllib.request
from pathlib import Path
# ── CONFIGURE THESE ──────────────────────────────────────────────────
ROUTER_BASE_URL = "http://10.0.4.100:9001/v1"
PROVIDER_NAME = "intelligence_router"
GATEWAY_SERVICE = "hermes-gateway"
# ─────────────────────────────────────────────────────────────────────
MODELS_URL = f"{ROUTER_BASE_URL}/models"
CONFIG_PATH = Path(os.path.expanduser("~/.hermes/config.yaml"))
def fetch_models() -> list[str] | None:
try:
req = urllib.request.Request(MODELS_URL, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read().decode())
models = sorted(m["id"] for m in data.get("data", []) if isinstance(m, dict))
return models if models else None
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, OSError) as e:
print(f"ERROR: Failed to fetch models from {MODELS_URL}: {e}", file=sys.stderr)
return None
def read_current_models() -> list[str]:
"""Parse current custom_providers entries for our provider name."""
if not CONFIG_PATH.exists():
return []
models = []
with open(CONFIG_PATH) as f:
content = f.read()
idx = content.find("custom_providers:")
if idx == -1:
return []
section = content[idx:]
lines = section.split("\n")
current_entry = {}
for line in lines:
s = line.strip()
if s.startswith("- base_url:"):
if current_entry.get("name") == PROVIDER_NAME:
m = current_entry.get("model", "")
if m:
models.append(m)
current_entry = {}
elif s.startswith("model:"):
current_entry["model"] = s.split("model:", 1)[1].strip().strip("'\"")
elif s.startswith("name:"):
current_entry["name"] = s.split("name:", 1)[1].strip().strip("'\"")
elif s and not s.startswith(("-", " ")):
break
# Don't forget the last entry
if current_entry.get("name") == PROVIDER_NAME:
m = current_entry.get("model", "")
if m:
models.append(m)
return sorted(models)
def generate_block(models: list[str]) -> str:
lines = ["custom_providers:"]
for m in models:
lines.append(f"- base_url: {ROUTER_BASE_URL}")
lines.append(f" model: {m}")
lines.append(f" name: {PROVIDER_NAME}")
return "\n".join(lines)
def replace_section(models: list[str]) -> bool:
"""Replace the custom_providers section in-place. Returns True if changed."""
if not CONFIG_PATH.exists():
return False
import yaml
content = CONFIG_PATH.read_text()
config = yaml.safe_load(content)
new_entries = [
{"base_url": ROUTER_BASE_URL, "model": m, "name": PROVIDER_NAME}
for m in models
]
if config.get("custom_providers") == new_entries:
return False
config["custom_providers"] = new_entries
CONFIG_PATH.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
return True
def restart_gateway() -> bool:
try:
r = subprocess.run(
["systemctl", "--user", "restart", GATEWAY_SERVICE],
capture_output=True, text=True, timeout=30,
)
return r.returncode == 0
except Exception:
return False
def main():
models = fetch_models()
if models is None:
sys.exit(1)
current = read_current_models()
if current == models:
print("Model list unchanged — nothing to do.")
return
added = set(models) - set(current)
removed = set(current) - set(models)
print(f"Model list changed! {len(current)}{len(models)} models")
if added:
print(f" Added: {sorted(added)}")
if removed:
print(f" Removed: {sorted(removed)}")
if not replace_section(models):
print("ERROR: Config update failed")
return
print("Config updated. Restarting gateway...")
if restart_gateway():
print("Gateway restarted successfully.")
else:
print("WARNING: Gateway restart failed — restart manually.")
if __name__ == "__main__":
main()