166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify that all models in hf_models.json exist on HuggingFace and all
|
|
Ollama mappings in src/providers.rs point to valid Ollama registry entries.
|
|
|
|
Usage:
|
|
python3 scripts/verify_models.py # check both
|
|
python3 scripts/verify_models.py --hf # HuggingFace only
|
|
python3 scripts/verify_models.py --ollama # Ollama only
|
|
|
|
Exits with code 1 if any model is missing. Suitable for CI.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
import urllib.error
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
HF_MODELS_PATH = REPO_ROOT / "data" / "hf_models.json"
|
|
PROVIDERS_RS_PATH = REPO_ROOT / "src" / "providers.rs"
|
|
|
|
HEADERS = {"User-Agent": "llmfit-verify/1.0"}
|
|
REQUEST_DELAY = 0.3 # seconds between requests to avoid rate limiting
|
|
|
|
|
|
def check_url(url: str) -> int:
|
|
"""GET a URL and return the HTTP status code, or -1 on error."""
|
|
try:
|
|
req = urllib.request.Request(url, headers=HEADERS)
|
|
resp = urllib.request.urlopen(req, timeout=10)
|
|
return resp.status
|
|
except urllib.error.HTTPError as e:
|
|
return e.code
|
|
except Exception:
|
|
return -1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HuggingFace verification
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def load_hf_models() -> list[str]:
|
|
"""Return list of HF repo names from hf_models.json."""
|
|
with open(HF_MODELS_PATH) as f:
|
|
data = json.load(f)
|
|
return [m["name"] for m in data]
|
|
|
|
|
|
def verify_hf(models: list[str]) -> list[str]:
|
|
"""Check each HF model exists. Returns list of missing model names."""
|
|
missing = []
|
|
total = len(models)
|
|
for i, name in enumerate(models, 1):
|
|
url = f"https://huggingface.co/api/models/{name}"
|
|
status = check_url(url)
|
|
if status == 200:
|
|
print(f" [{i}/{total}] ✓ {name}")
|
|
else:
|
|
print(f" [{i}/{total}] ✗ {name} (HTTP {status})")
|
|
missing.append(name)
|
|
time.sleep(REQUEST_DELAY)
|
|
return missing
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ollama verification
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def parse_ollama_tags() -> list[str]:
|
|
"""Extract unique Ollama tags from OLLAMA_MAPPINGS in providers.rs."""
|
|
src = PROVIDERS_RS_PATH.read_text()
|
|
|
|
# Find the OLLAMA_MAPPINGS block
|
|
match = re.search(
|
|
r"const OLLAMA_MAPPINGS:.*?=.*?\[(.+?)\];",
|
|
src,
|
|
re.DOTALL,
|
|
)
|
|
if not match:
|
|
print("ERROR: Could not find OLLAMA_MAPPINGS in providers.rs")
|
|
sys.exit(2)
|
|
|
|
block = match.group(1)
|
|
# Extract the second element of each tuple: ("hf_name", "ollama_tag")
|
|
tags = re.findall(r'\(\s*"[^"]+"\s*,\s*"([^"]+)"\s*\)', block)
|
|
# Deduplicate while preserving order
|
|
seen = set()
|
|
unique = []
|
|
for tag in tags:
|
|
if tag not in seen:
|
|
seen.add(tag)
|
|
unique.append(tag)
|
|
return unique
|
|
|
|
|
|
def verify_ollama(tags: list[str]) -> list[str]:
|
|
"""Check each Ollama tag exists. Returns list of missing tags."""
|
|
missing = []
|
|
total = len(tags)
|
|
for i, tag in enumerate(tags, 1):
|
|
url = f"https://ollama.com/library/{tag}"
|
|
status = check_url(url)
|
|
if status == 200:
|
|
print(f" [{i}/{total}] ✓ {tag}")
|
|
else:
|
|
print(f" [{i}/{total}] ✗ {tag} (HTTP {status})")
|
|
missing.append(tag)
|
|
time.sleep(REQUEST_DELAY)
|
|
return missing
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Verify model availability")
|
|
parser.add_argument("--hf", action="store_true", help="Check HuggingFace only")
|
|
parser.add_argument("--ollama", action="store_true", help="Check Ollama only")
|
|
args = parser.parse_args()
|
|
|
|
# Default: check both
|
|
check_hf = args.hf or not args.ollama
|
|
check_ollama = args.ollama or not args.hf
|
|
|
|
failures = False
|
|
|
|
if check_hf:
|
|
models = load_hf_models()
|
|
print(f"\n=== HuggingFace: checking {len(models)} models ===\n")
|
|
missing = verify_hf(models)
|
|
if missing:
|
|
failures = True
|
|
print(f"\n ⚠ {len(missing)} HuggingFace model(s) not found:")
|
|
for m in missing:
|
|
print(f" - {m}")
|
|
else:
|
|
print(f"\n All {len(models)} HuggingFace models verified ✓")
|
|
|
|
if check_ollama:
|
|
tags = parse_ollama_tags()
|
|
print(f"\n=== Ollama: checking {len(tags)} tags ===\n")
|
|
missing = verify_ollama(tags)
|
|
if missing:
|
|
failures = True
|
|
print(f"\n ⚠ {len(missing)} Ollama tag(s) not found:")
|
|
for t in missing:
|
|
print(f" - {t}")
|
|
else:
|
|
print(f"\n All {len(tags)} Ollama tags verified ✓")
|
|
|
|
print()
|
|
if failures:
|
|
print("FAIL: Some models are unavailable. Fix mappings or remove entries.")
|
|
sys.exit(1)
|
|
else:
|
|
print("PASS: All models verified.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|