SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
5.1 KB · 119 lines python
Raw Blame History
1#!/usr/bin/env python32"""Re-runnable Gemini model discovery (Gemini Developer API).34GET /v1beta/models (paginated: pageSize + pageToken) -> sanitized sources/gemini/models-api-raw.json5GET /v1/models      (stable surface)                 -> sources/gemini/models-v1-raw.json6Diff against the previous v1beta file -> sources/gemini/models-diff.json, prints NEW / REMOVED / CHANGED7(limits, supportedGenerationMethods, thinking, version, defaults) and the v1-vs-v1beta id difference.89Usage:  python3 scripts/discover_gemini_models.py [--page-size N] [--dry-run] [--skip-v1]10Auth comes from .env via scripts/live.py (x-goog-api-key header, never in the URL, never printed).11Each call is logged to reports/live-requests.jsonl (path is normalised to /v1beta/models).12"""13from __future__ import annotations1415import argparse16import json17import sys18from pathlib import Path1920sys.path.insert(0, str(Path(__file__).resolve().parent))21import live  # noqa: E402  (scripts/live.py)2223ROOT = Path(__file__).resolve().parents[1]24RAW = ROOT / "sources" / "gemini" / "models-api-raw.json"25RAW_V1 = ROOT / "sources" / "gemini" / "models-v1-raw.json"26DIFF = ROOT / "sources" / "gemini" / "models-diff.json"2728# Fields whose change is worth flagging as CHANGED (order irrelevant for lists).29WATCH = ("inputTokenLimit", "outputTokenLimit", "supportedGenerationMethods", "thinking", "version",30         "temperature", "topP", "topK", "maxTemperature", "displayName", "description")313233def fetch_all(version: str, page_size: int) -> dict:34    models: list[dict] = []35    token: str | None = None36    pages = 037    while True:38        q = f"?pageSize={page_size}" + (f"&pageToken={token}" if token else "")39        st, body, _ = live.gemini_request("GET", f"/{version}/models{q}", note=f"discover models {version} page {pages + 1}")40        if st != 200:41            raise SystemExit(f"GET /{version}/models -> HTTP {st}: {json.dumps(body)[:300]}")42        models.extend(body.get("models", []))43        pages += 144        token = body.get("nextPageToken")45        if not token:46            break47    return {"models": models, "_pages": pages, "_retrieved_at": live.now(), "_version": version}484950def index(doc: dict) -> dict[str, dict]:51    return {m["name"].removeprefix("models/"): m for m in doc.get("models", [])}525354def changed_fields(a: dict, b: dict) -> list[str]:55    out = []56    for k in WATCH:57        va, vb = a.get(k), b.get(k)58        if isinstance(va, list) and isinstance(vb, list):59            if sorted(va) != sorted(vb):60                out.append(k)61        elif va != vb:62            out.append(k)63    return out646566def main() -> None:67    ap = argparse.ArgumentParser()68    ap.add_argument("--page-size", type=int, default=50, help="pageSize query param (docs: default 50, max 1000)")69    ap.add_argument("--dry-run", action="store_true", help="fetch and diff, do not write")70    ap.add_argument("--skip-v1", action="store_true", help="do not call GET /v1/models")71    args = ap.parse_args()7273    prev = json.loads(RAW.read_text()) if RAW.exists() else {"models": []}74    cur = fetch_all("v1beta", args.page_size)75    pi, ci = index(prev), index(cur)76    new = sorted(set(ci) - set(pi))77    removed = sorted(set(pi) - set(ci))78    changed = [{"id": i, "fields": changed_fields(pi[i], ci[i]), "before": {k: pi[i].get(k) for k in WATCH}, "after": {k: ci[i].get(k) for k in WATCH}}79               for i in sorted(set(ci) & set(pi)) if changed_fields(pi[i], ci[i])]8081    v1_ids: list[str] = []82    only_v1beta: list[str] = []83    only_v1: list[str] = []84    cur_v1 = None85    if not args.skip_v1:86        cur_v1 = fetch_all("v1", args.page_size)87        vi = index(cur_v1)88        v1_ids = sorted(vi)89        only_v1beta = sorted(set(ci) - set(vi))90        only_v1 = sorted(set(vi) - set(ci))9192    diff = {"retrieved_at": cur["_retrieved_at"], "previous_count": len(pi), "current_count": len(ci),93            "pages_fetched_v1beta": cur["_pages"], "page_size": args.page_size,94            "new": new, "removed": removed, "changed": changed,95            "v1": {"count": len(v1_ids), "only_in_v1beta": only_v1beta, "only_in_v1": only_v1} if cur_v1 else None}9697    print(f"v1beta models: {len(ci)} (previous {len(pi)}), pages={cur['_pages']} (pageSize={args.page_size})")98    for i in new:99        print(f"NEW      {i}  in={ci[i].get('inputTokenLimit')} out={ci[i].get('outputTokenLimit')} methods={ci[i].get('supportedGenerationMethods')}")100    for i in removed:101        print(f"REMOVED  {i}")102    for c in changed:103        print(f"CHANGED  {c['id']}  fields={c['fields']}")104    if not (new or removed or changed):105        print("no changes vs previous v1beta snapshot")106    if cur_v1:107        print(f"v1 models: {len(v1_ids)}; only in v1beta: {only_v1beta or 'none'}; only in v1: {only_v1 or 'none'}")108    if args.dry_run:109        return110    live.save_sanitized({"models": cur["models"]}, RAW)111    if cur_v1:112        live.save_sanitized({"models": cur_v1["models"]}, RAW_V1)113    live.save_sanitized(diff, DIFF)114    print(f"wrote {RAW.relative_to(ROOT)}" + (f", {RAW_V1.relative_to(ROOT)}" if cur_v1 else "") + f" and {DIFF.relative_to(ROOT)}")115116117if __name__ == "__main__":118    main()119