Skip to content

Provider data pipeline: registry tiers, generated model catalogue, maintenance jobs, RFC-0033 - #176

Closed
cunninghamcard-bit wants to merge 13 commits into
arcships:masterfrom
cunninghamcard-bit:provider-data-pipeline
Closed

cunninghamcard-bit wants to merge 13 commits into
arcships:masterfrom
cunninghamcard-bit:provider-data-pipeline

Conversation

@cunninghamcard-bit

Copy link
Copy Markdown
Contributor

One branch for the whole provider data pipeline so it can be reviewed as a system. Companion to #168/#169 (which delete the one-shot generators); tracks #170#173 and #166.

The model (RFC-0033 §4.1, included in this PR)

Layer What Who changes it Gate
L0 protocol implementations aimux-providers/src/<protocol>/ people, Rust cassette conformance
L1 provider identity provider_registry.json, one row per provider, now with tier / note / status people, JSON; never regenerated Rust validation tests; weekly diff report + monthly probe (non-blocking)
L2 model knowledge aimux-providers/data/models/<provider>.json + .manifest.json, from models.dev (primary) and anya2a (secondary), merged with hand-written models.overrides.json machine, scripts/gen_models.py; weekly refresh PR gen_models.py --check (hash, no network)
L3 derived artifacts ProviderName ×8, docs/api/providers.md, ts-rs types machine each generator's --check

pi-ai is the reference (packages/ai: src/api, src/providers/<id>.ts, scripts/generate-models.ts + check-model-data.ts). Two deliberate differences: L1 is data rather than TypeScript factories (251 providers vs 39), and L2 is committed and refreshed by a scheduled PR rather than hydrated at build time, so the crate and the seven binding CIs build offline.

Tiers — making "251 providers" honest

tier definition today
verified real recorded cassette under tests/cassettes/<name>/ 19
listed identity confirmed by models.dev or anya2a, no real recording 124
unverified only the July LiteLLM/mastra scan as a source 108

status: "unreachable" is reserved for the monthly probe; nothing is deleted by machines. Rows with a note are treated as intentional deviations by the diff report.

What is in the diff

  • L1: every row gets an explicit tier; RegistryEntry gains tier / note / status with serde defaults so RFC-0020 external entries keep loading; registry_entries_are_valid insists built-in rows state their tier. docs/api/providers.md shows a Tier column and legend.
  • L2: scripts/gen_models.py (fetch / --offline / --check), 137 provider files, 4,872 models, 2.7 MB, .manifest.json with sources, counts and sha256. data/ is excluded from the published crate until a Rust consumer reads it (runtime wiring into RFC-0027's catalogue is a follow-up).
  • Maintenance: scripts/sync_registry.py --report (absorbs extract_litellm_bases.py and scan_litellm_urls.py, which are removed) and scripts/probe_registry.py; .github/workflows/registry-maintenance.yml runs the weekly diff → comment on Registry maintenance: weekly models.dev diff report and monthly reachability probe #170, the weekly model-data refresh → PR on bot/refresh-model-data, the monthly probe → comment on Registry maintenance: weekly models.dev diff report and monthly reachability probe #170. models.dev rejects urllib's default User-Agent, so both fetchers send one.
  • Gates: gen_providers_doc.py --check and gen_models.py --check added to CI next to gen_provider_names.py --check, and to scripts/local-ci.sh.
  • Docs: docs/contributing/adding-a-provider.md (four layers, three checklists, the generator rule), linked from README; RFC-0033 (code convergence plan) added with §4.1 rewritten to this design.

Verification

cargo test -p aimux-providers --lib provider:: 23 passed; --test conformance_test 53 passed; clippy and fmt clean; all three --check generators green; sync_registry.py --report produces identical output from the snapshot and live (150 missing / 84 base_url / 19 env_var disagreements — the backlog for #171); probe_registry.py smoke-tested on five rows.

Line count is dominated by the generated data/models/*.json (≈122k of the 122.6k insertions).

Planning document for the code reduction effort tracked in arcships#166 and arcships#167:
measured baseline, tracks A-E, design-level changes (flat cross-language
error model after OpenDAL, executor pattern for single-modality providers
after LiteLLM, transport-level replay), defect list, docs reorganization,
dependency order and ledger.
RFC-0033 L1: the registry is a hand-maintained identity table, but nothing
in it said how much a row could be trusted. Each of the 251 rows now carries
a `tier` — verified (a cassette replays it), listed (an upstream catalogue
knows the name), unverified (a lead from vendor docs) — plus optional `note`
(why this row deviates from upstream; exempts it from the sync report) and
`status` (only "unreachable").

Initial split: 19 verified / 124 listed / 108 unverified. `listed` matches
models.dev and anya2a ids through catalogue.rs's normalize_provider_name,
since both upstreams use hyphens where the registry uses underscores.

`tier` defaults to Unverified on deserialize so RFC-0020 external entries
keep loading; registry_entries_are_valid re-reads the raw JSON to insist
every built-in row states its tier explicitly.
The generated provider table now has a Tier column with verified rows first
and a legend defining the three tiers. `--check` mirrors the
gen_provider_names.py UX so CI can catch a stale docs/api/providers.md
instead of trusting people to rerun the generator.
RFC-0033 L2: model knowledge (context windows, pricing, modalities) is
upstream data, not something to hand-maintain. scripts/gen_models.py
normalises models.dev (primary) and anya2a (fallback for providers
models.dev lacks) into one flat per-model schema and writes one file per
registry provider: 137 providers / 4871 models, 2.7M on disk.

Hand corrections go in data/models.overrides.json (partial field patches or
"_drop": true), applied after normalisation, so a fix survives the next
refresh. data/models/.manifest.json records counts and a sha256 over the
provider files; `--check` recomputes it offline in ~0.15s and also rejects a
file missing its `_generated` header or naming a provider the registry does
not have.
…d-hoc scanners

Two maintenance jobs the registry needs on a cadence, neither of which may
edit the JSON — a human decides what to take from upstream.

sync_registry.py --report diffs the registry against models.dev, anya2a and
(with --litellm) litellm's api_base constants, printing three Markdown
tables: ids missing from the registry, base_url disagreements, env_var
disagreements. A row with a `note` is a deliberate deviation and is exempt
from the last two. On the snapshots: 150 missing rows / 84 base_url / 19
env_var disagreements.

probe_registry.py does an unauthenticated GET {base_url}/models over a thread
pool and reports only what fails: 5xx as server-error, DNS/connect/timeout as
dead, anything that answers (401/403/404 included) as alive, templated URLs
skipped.

extract_litellm_bases.py and scan_litellm_urls.py are deleted — sync_registry
does the same api_base scan behind --litellm, with the result cross-checked
against the registry instead of dumped as raw JSON.
The generator rule (do-not-edit header + a CI --check) now covers all three
provider generators: gen_providers_doc.py --check and gen_models.py --check
join gen_provider_names.py --check in the contract-tests job and in
scripts/local-ci.sh, so a stale providers.md or model manifest fails the PR
rather than rotting.

registry-maintenance.yml runs the jobs that need a clock rather than a PR:
weekly upstream diff and monthly reachability probe comment on arcships#170, weekly
gen_models.py opens or updates bot/refresh-model-data. It never writes to
provider_registry.json.
docs/contributing/adding-a-provider.md states the layer table (L0 protocol /
L1 identity / L2 model data / L3 derived artifacts, each with its owner and
its gate), the generator rule, the tier definitions, and one checklist per
case: OpenAI-compatible vendor, new wire protocol, single-modality vendor
(RFC-0033 §8.2 executor pattern rather than another hand-written polling
loop). Linked from README's docs table; reference.md's Providers section now
names gen_providers_doc.py --check and gen_models.py.
models.dev answers 403 to urllib's default User-Agent, so the live (non
--offline) path of gen_models.py and sync_registry.py failed outright — the
weekly Action would never have worked. Model data refreshed from the live
fetch that verified it (llmgateway gained a model since the snapshot).
@cunninghamcard-bit

Copy link
Copy Markdown
Contributor Author

Closing: opened prematurely, the author wants to review this branch locally first. Branch kept.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant