Files
OBLITERATUS/obliteratus/models_client.py
T

175 lines
6.6 KiB
Python

"""
models_client.py — drop-in BESTIARY catalog reader for any PlinyOS app.
Copy this single file into any repo that needs the model list. It reads the
catalog BESTIARY produces and gives you filtered views, with zero third-party
deps. No need to import the full bestiary engine.
Resolution order for the catalog:
1. $BESTIARY_CATALOG (explicit path or http(s) URL)
2. a local catalog.json next to this file
3. the canonical monorepo path (…/03-PLINYOS/organs/bestiary/state/catalog.json)
Usage
─────
from models_client import models, model_ids, newest
# frontier / hosted apps (godmode, tempest, libertarium):
api_models = models(channel="api") # OpenRouter-reachable
ids = model_ids(channel="api", vendor="anthropic") # ["anthropic/claude-opus-4.8", …]
# open-weight apps (obliteratus, incanta, pandora):
weights = models(open_weight=True) # downloadable, with hf_id
coders = models(open_weight=True, capability="tools")
# newest drops across everything:
for m in newest(days=7):
print(m["id"], m["released"])
"""
from __future__ import annotations
import json
import os
import urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
from .service_contracts import (
normalized_text_list,
validate_catalog,
validate_day_window,
)
_HERE = Path(__file__).resolve().parent
_CANONICAL = _HERE.parent / "bestiary" / "state" / "catalog.json"
_LOCAL = _HERE / "catalog.json"
_CHANNEL_ALIASES = {"api": "openrouter", "hosted": "openrouter",
"frontier": "openrouter", "hf": "huggingface"}
_CATALOG_TIMEOUT_SECONDS = 15
_MAX_CATALOG_BYTES = 8 * 1024 * 1024
def _utc_now() -> datetime:
return datetime.now(timezone.utc)
def _decode_catalog(payload: bytes, source: str) -> dict[str, Any]:
if len(payload) > _MAX_CATALOG_BYTES:
raise ValueError(f"BESTIARY catalog exceeds size limit: {source}")
try:
decoded = payload.decode("utf-8")
parsed = json.loads(decoded)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ValueError(f"BESTIARY catalog is not valid UTF-8 JSON: {source}") from exc
return validate_catalog(parsed)
def _read_catalog_path(path: Path) -> dict[str, Any]:
try:
size = path.stat().st_size
except FileNotFoundError:
raise FileNotFoundError(f"BESTIARY catalog not found: {path}") from None
if size > _MAX_CATALOG_BYTES:
raise ValueError(f"BESTIARY catalog exceeds size limit: {path}")
return _decode_catalog(path.read_bytes(), str(path))
def _load() -> dict[str, Any]:
src = os.environ.get("BESTIARY_CATALOG", "").strip()
if src.startswith("http://") or src.startswith("https://"):
with urllib.request.urlopen(src, timeout=_CATALOG_TIMEOUT_SECONDS) as response:
return _decode_catalog(response.read(_MAX_CATALOG_BYTES + 1), src)
if src:
return _read_catalog_path(Path(src))
for path in (_LOCAL, _CANONICAL, _HERE / "state" / "catalog.json"):
if path.exists():
return _read_catalog_path(path)
raise FileNotFoundError(
"BESTIARY catalog not found. Run `python3 bestiary.py update`, or set "
"$BESTIARY_CATALOG to a catalog.json path or URL."
)
def _normalize_filter(value: str | None, name: str) -> str | None:
if value is None:
return None
if not isinstance(value, str):
raise TypeError(f"{name} must be a string")
normalized = value.strip().casefold()
return normalized or None
def models(channel: str | None = None, vendor: str | None = None,
open_weight: bool | None = None, capability: str | None = None) -> list[dict]:
"""Return catalog records filtered by channel / vendor / open_weight / capability."""
channel = _normalize_filter(channel, "channel")
vendor = _normalize_filter(vendor, "vendor")
capability = _normalize_filter(capability, "capability")
if open_weight is not None and not isinstance(open_weight, bool):
raise TypeError("open_weight must be a boolean")
if channel in ("open-weight", "openweight", "weights"):
open_weight, channel = True, None
if channel:
channel = _CHANNEL_ALIASES.get(channel, channel)
out = []
for m in _load().get("models", []):
channels = normalized_text_list(m, "channels")
capabilities = normalized_text_list(m, "capabilities")
record_vendor = m.get("vendor")
if record_vendor is not None and not isinstance(record_vendor, str):
raise ValueError("BESTIARY catalog vendor must be a string")
record_open_weight = m.get("open_weight", False)
if not isinstance(record_open_weight, bool):
raise ValueError("BESTIARY catalog open_weight must be a boolean")
if channel and channel not in channels:
continue
if vendor and (record_vendor or "").strip().casefold() != vendor:
continue
if open_weight is not None and record_open_weight != open_weight:
continue
if capability and capability not in capabilities:
continue
out.append(m)
return out
def model_ids(**kw) -> list[str]:
"""Just the ids — the common case for populating a dropdown or a config."""
ids = []
for record in models(**kw):
model_id = record.get("id")
if not isinstance(model_id, str) or not model_id.strip():
raise ValueError("BESTIARY catalog model requires a non-empty string id")
ids.append(model_id)
return ids
def newest(days: int = 7, **kw) -> list[dict]:
"""Models first-seen (≈ released) within the last `days`, newest first."""
cutoff = _utc_now() - timedelta(days=validate_day_window(days))
out: list[tuple[datetime, dict]] = []
for m in models(**kw):
fs = m.get("first_seen") or m.get("released")
if not fs:
continue
if not isinstance(fs, str):
raise ValueError("BESTIARY catalog dates must be strings")
try:
t = datetime.strptime(fs, "%Y-%m-%d").replace(tzinfo=timezone.utc)
except ValueError:
continue
if t >= cutoff:
out.append((t, m))
out.sort(key=lambda item: item[0], reverse=True)
return [record for _, record in out]
if __name__ == "__main__":
cat = _load()
print(f"catalog generated_at={cat.get('generated_at')} count={cat.get('count')}")
print(f" api models : {len(models(channel='api'))}")
print(f" open-weight : {len(models(open_weight=True))}")
print(f" new (7d) : {len(newest(7))}")