Capability graph — provider → api_family → endpoint → model → capability / tool (4 providers)
Status: DOCUMENTED · exporter run on the rebuilt merged files on 2026-09-19 (python3 scripts/build_generated.py && python3 scripts/export_capability_graph.py) — 4 provider nodes (anthropic, gemini, openai, xai) · offline-tested against tests/shared/fixtures/{models,endpoints,tools}.json (tests/shared/test_capability_graph.py, 6 tests, fixtures now span all four providers incl. xAI redirects and Gemini dict aliases/tools)
Sources: CLAUDE.md record schemas (model, endpoint, tool); generated/fragments/models/{openai,anthropic,xai,gemini}-models.json conventions (record_kind, kind, dict aliases, dict tools, verification.request_note redirects); Mermaid graph LR; Graphviz DOT
Last verified: 2026-09-19
Script: scripts/export_capability_graph.py (stdlib, provider-agnostic — every provider value found in the records becomes a node). Outputs: generated/capability-graph.json, generated/capability-graph.mmd, generated/capability-graph.dot.
python3 scripts/build_generated.py # merge generated/fragments/** → generated/*.json
python3 scripts/export_capability_graph.py # defaults: generated/{models,endpoints,tools}.json → generated/
python3 scripts/export_capability_graph.py --models generated/models.json --out-dir /tmp/g --max-mermaid-nodes 200Node types
| type | id pattern | label | attributes carried |
|---|---|---|---|
provider |
provider:xai |
provider | — |
api_family |
api_family:gemini/generate_content |
family | provider |
endpoint |
endpoint:gemini/POST /v1beta/models/{model}:generateContent |
METHOD /path (templated paths kept verbatim) |
provider, api_family, method, path, status[], streaming, idempotency, placeholder? |
model |
model:xai/grok-4.3 |
id | provider, status[], family, record_kind (model | snapshot | id_only | alias | redirect), context_window, max_output, placeholder? |
capability |
capability:structured_output |
key as emitted by the provider agent (no synonym folding — structured_outputs and structured_output are two nodes; the registry folds them) |
shared across providers |
tool |
tool:gemini/google_search |
exact type string | provider, name, category, status[], beta_header, security |
record_kind is derived per provider: OpenAI emits it (model, snapshot, id_only, alias); xAI/Gemini use kind — retired_redirect → redirect, alias → alias, everything else → model; Anthropic's kind: "snapshot" denotes a real dated model record and stays model (a bug fixed on 2026-09-19 had excluded them).
Edge types
| edge | from → to | meaning |
|---|---|---|
HAS_FAMILY |
provider → api_family | from endpoints.json |
HAS_ENDPOINT |
api_family → endpoint | |
OFFERS |
provider → model | every model record |
AVAILABLE_ON |
model → endpoint | model.endpoints[] (placeholder endpoint node when endpoints.json lacks it) |
SUPPORTS |
model → capability | capabilities[k] === true |
SUPPORTS_UNKNOWN |
model → capability | capabilities[k] === "unknown" (e.g. xAI web_search on grok-4.3, Gemini computer_use on Flash) |
| (no edge) | false, or non-boolean metadata values (lists, notes, Gemini thinking{} objects) |
|
SUPPORTS_TOOL |
model → tool | model.tools[] — strings, or Gemini dicts {type, category, support} where support: false yields no edge and "Supported (Preview)" counts as supported |
PROVIDES_TOOL |
provider → tool | every tool record |
USABLE_ON |
tool → endpoint | tool.compatible_endpoints[] |
COMPATIBLE_WITH |
tool → model | tool.compatible_models[] (placeholder model nodes for unknown ids — 57 of them on Gemini today, mostly retired 2.0/2.5 ids named by tool records) |
ALIAS_OF |
model(alias) → model | model.aliases[] strings (parenthetical notes stripped: grok-voice-latest (routes here since …) → grok-voice-latest), and Gemini -latest records whose dict alias {alias: <self>, resolves_to_live: <target>} names an existing id (gemini-flash-latest → gemini-3.8-flash); gemini-pro-latest stays unlinked because its live target gemini-3.1-pro (quota dimension) is not a record id |
SNAPSHOT_OF |
model(snapshot) → model | model.snapshots[] and canonical_model |
REDIRECTS_TO |
model(redirect) → model | xAI kind: "retired_redirect" with a live target in verification.request_note (grok-3, grok-4-0709, grok-4-fast-reasoning… → grok-4.3; 6 edges) |
Edges are de-duplicated; node attributes are merged (first writer wins, later writers fill gaps). meta.providers lists the providers present and meta.per_provider counts real models (not alias/snapshot/redirect/placeholder), endpoints and tools per provider.
Graceful degradation
meta.inputs.<name>.status is one of loaded | empty | missing | invalid. Missing endpoints.json → endpoint nodes are synthesised from model.endpoints[] (flagged placeholder). Missing tools.json → tool nodes are synthesised from model.tools[] without attributes. Missing models.json → graph contains only providers/families/endpoints/tools. All inputs missing → empty but valid files (graph LR, digraph capability_graph {}), exit code 0.
Output formats
- JSON —
{meta{generated_at, inputs, providers, per_provider, node_counts, edge_counts, semantics}, nodes[], edges[]}; the registry, the comparator and RAG loaders should read this file, not the Mermaid. - Mermaid (
graph LR) — alias/snapshot/redirect model nodes are omitted, then the list is truncated at--max-mermaid-nodes(default 400) with a%% N node(s) omittedcomment. Shapes: provider[[ ]], family( ), endpoint[ ], model([ ]), capability{{ }}, tool> ]. - DOT — complete graph,
rankdir=LR, oneshapeper node type and aclassattribute. Render:dot -Tsvg generated/capability-graph.dot -o graph.svg(orsfdpfor the full graph).
Example queries on the JSON
- "Which models support structured outputs on all four providers?" →
SUPPORTSedges tocapability:structured_outputsandcapability:structured_outputgrouped by nodeprovider(or useRegistry.models_with("structured_outputs"), which folds the synonym). - "Which endpoints can use MCP?" →
tool:openai/mcp,tool:anthropic/mcp_toolset,tool:xai/mcp,tool:gemini/mcpServers→USABLE_ON. - "Which capabilities are still
unknownfor model X?" →SUPPORTS_UNKNOWNout-edges — the verification backlog (2 025 edges today, most of them xAI server-tool flags and Gemini per-model feature matrices). - "What does
grok-3resolve to?" → followREDIRECTS_TO; "what isgemini-flash-latesttoday?" → followALIAS_OF(re-run the exporter after each atlas refresh — the alias moved three times in 2026).
Full run (2026-09-19, after build_generated.py)
Inputs: models 396 records, endpoints 870, tools 69 (all loaded).
| anthropic | gemini | openai | xai | total | |
|---|---|---|---|---|---|
| model nodes (real records) | 33 | 93 | 160 | 25 | 311 (+ 191 alias/snapshot/redirect/placeholder nodes = 502) |
| endpoint nodes | 283 | 143 | 372 | 121 | 919 (870 from endpoints.json + 49 placeholders) |
| tool nodes | 27 | 18 | 23 | 15 | 83 (69 records + 14 synthesised from model.tools[]) |
Other node counts: provider 4, api_family 84, capability 191. Edge counts: HAS_FAMILY 84, HAS_ENDPOINT 870, OFFERS 396, AVAILABLE_ON 165, SUPPORTS 3 665, SUPPORTS_UNKNOWN 2 025, SUPPORTS_TOOL 964, PROVIDES_TOOL 69, USABLE_ON 147, COMPATIBLE_WITH 1 065, ALIAS_OF 67, SNAPSHOT_OF 75, REDIRECTS_TO 6.
Fixture run
tests/shared/fixtures: 11 model records (openai 3, anthropic 2, xai 3 incl. a retired_redirect and a parenthetical alias, gemini 3 incl. a -latest alias record and dict-shaped tools), 11 endpoints, 9 tools → 4 providers, REDIRECTS_TO 1, Gemini support: false tool produces no edge; edge/node assertions live in tests/shared/test_capability_graph.py.