SPB Git

spb/wp38_uqo

Public
5.3 KB · 84 lines python
1"""Phase 0 — inventory of OpenAI and Anthropic model IDs actually reachable with the project's keys.23Stdlib only (urllib) so it runs before the venv exists. Keys are read from .env and never printed.4One `GET /v1/models` per provider, then ONE tiny completion per selected model (max 8 output tokens).5Writes logs/model_inventory.json and reports/model_inventory.md. Costs are computed from the token usage6returned by the provider and the public list prices hard-coded below (checked 2026-11-01; see claude-api skill).7"""8from __future__ import annotations9import json, os, time, urllib.request, urllib.error, sys, pathlib1011ROOT = pathlib.Path(__file__).resolve().parents[2]12env = {}13for line in (ROOT / ".env").read_text().splitlines():14    if "=" in line and not line.startswith("#"):15        k, v = line.split("=", 1); env[k.strip()] = v.strip()16OPENAI = env.get("OPENAI_API_KEY", ""); ANTHROPIC = env.get("ANTHROPIC_API_KEY", "")1718# USD per 1M tokens (input, output) — public list prices as of 2026-11-01 (verify before relying on them)19PRICES = {20    "claude-fable-5-1": (25.0, 125.0), "claude-opus-5-5": (5.0, 25.0), "claude-sonnet-5-5": (3.0, 15.0), "claude-haiku-5-5": (1.0, 5.0),21}2223def call(url, headers, body=None, timeout=60):24    req = urllib.request.Request(url, data=json.dumps(body).encode() if body else None, headers=headers, method="POST" if body else "GET")25    t0 = time.time()26    try:27        with urllib.request.urlopen(req, timeout=timeout) as r:28            return r.status, json.loads(r.read().decode()), round(time.time() - t0, 2)29    except urllib.error.HTTPError as e:30        return e.code, {"error": e.read().decode()[:300]}, round(time.time() - t0, 2)31    except Exception as e:  # noqa: BLE00132        return -1, {"error": str(e)[:300]}, round(time.time() - t0, 2)3334out = {"checked_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "openai": {}, "anthropic": {}}3536# ---- OpenAI --------------------------------------------------------------37if OPENAI:38    st, body, lat = call("https://api.openai.com/v1/models", {"Authorization": f"Bearer {OPENAI}"})39    ids = sorted(m["id"] for m in body.get("data", [])) if st == 200 else []40    out["openai"]["list_status"] = st; out["openai"]["n_models"] = len(ids); out["openai"]["model_ids"] = ids41    tests = {}42    for mid in [m for m in ("gpt-5.5", "gpt-5.1", "gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-4.1", "gpt-4.1-mini", "gpt-4o-mini") if m in ids][:4]:43        st, b, lat = call("https://api.openai.com/v1/chat/completions", {"Authorization": f"Bearer {OPENAI}", "Content-Type": "application/json"},44                          {"model": mid, "messages": [{"role": "user", "content": "Reply with the single word OK."}], "max_completion_tokens": 8})45        u = b.get("usage", {})46        tests[mid] = {"status": st, "latency_s": lat, "usage": u, "reply": (b.get("choices") or [{}])[0].get("message", {}).get("content") if st == 200 else b.get("error")}47    out["openai"]["tests"] = tests48else:49    out["openai"]["list_status"] = "no key"5051# ---- Anthropic -----------------------------------------------------------52if ANTHROPIC:53    H = {"x-api-key": ANTHROPIC, "anthropic-version": "2023-06-01", "Content-Type": "application/json"}54    st, body, lat = call("https://api.anthropic.com/v1/models?limit=100", H)55    ids = sorted(m["id"] for m in body.get("data", [])) if st == 200 else []56    out["anthropic"]["list_status"] = st; out["anthropic"]["n_models"] = len(ids); out["anthropic"]["model_ids"] = ids57    if st != 200: out["anthropic"]["list_error"] = body58    tests = {}59    for mid in [m for m in ("claude-fable-5-1", "claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5") if m in ids]:60        st, b, lat = call("https://api.anthropic.com/v1/messages", H, {"model": mid, "max_tokens": 8, "messages": [{"role": "user", "content": "Reply with the single word OK."}]})61        u = b.get("usage", {})62        cost = None63        if mid in PRICES and u:64            cost = round(u.get("input_tokens", 0) * PRICES[mid][0] / 1e6 + u.get("output_tokens", 0) * PRICES[mid][1] / 1e6, 6)65        tests[mid] = {"status": st, "latency_s": lat, "usage": u, "approx_cost_usd": cost, "reply": (b.get("content") or [{}])[0].get("text") if st == 200 else b.get("error")}66    out["anthropic"]["tests"] = tests67else:68    out["anthropic"]["list_status"] = "no key"6970(ROOT / "logs").mkdir(exist_ok=True)71(ROOT / "logs" / "model_inventory.json").write_text(json.dumps(out, indent=1, ensure_ascii=False))72lines = [f"# Model inventory (Phase 0) — checked {out['checked_utc']}", "",73         "Tiny test requests only (max 8 output tokens). Keys read from `.env`, never printed.", ""]74for prov in ("openai", "anthropic"):75    d = out[prov]; lines.append(f"## {prov}"); lines.append(f"- `GET /v1/models` status: {d.get('list_status')} — {d.get('n_models', 0)} model IDs listed")76    if d.get("model_ids"): lines.append("- IDs: " + ", ".join(f"`{m}`" for m in d["model_ids"]))77    if d.get("tests"):78        lines += ["", "| model | status | latency (s) | usage | approx. cost (USD) | reply |", "|---|---|---|---|---|---|"]79        for m, t in d["tests"].items():80            lines.append(f"| `{m}` | {t['status']} | {t['latency_s']} | {json.dumps(t['usage'])} | {t.get('approx_cost_usd', '—')} | {str(t['reply'])[:60]} |")81    lines.append("")82(ROOT / "reports" / "model_inventory.md").write_text("\n".join(lines))83print("\n".join(lines))84
Files · main
Menu

Theme