Skip to content

Commit c52d561

Browse files
committed
feat(llm): add shared model and run registry
Move canonical LLM model metadata and benchmarkable model-run declarations into utils so downstream repos can select shared runs by stable model_run_key. Add Models.dev and Artificial Analysis metadata snapshots and loaders. Resolve release dates from Models.dev with manual fallbacks, separate canonical model_key values from provider_model_id routing strings, and validate model declarations during registry construction. Require every ModelRun to declare an explicit, filename-safe model_run_key. Keep build_model_run_key as a naming helper for option coverage, validate duplicate keys, and expose MODEL_RUNS_BY_KEY/select_model_runs for benchmark selection. Add Model.active and ACTIVE_MODEL_RUNS so historical runs remain in MODEL_RUNS while runs depending on inactive provider routes are excluded from current live-callable benchmark sweeps. Mark the Together deepseek-v3.1 route inactive and replace live smoke tests with the active MiniMax M2.7 route. Add Artificial Analysis-backed model-run declarations as benchmark-selectable runs that are automatically included in MODEL_RUNS, with display names resolved from a minimized checked-in AA snapshot containing only stable IDs and display names. Add third-party notices for Models.dev's MIT license and Artificial Analysis attribution, and include those notices in built wheel license metadata. Move shared LLM provider dependencies into pyproject metadata, make requirements.txt delegate to .[dev], configure the package for Python 3.14, and preserve pytest-xdist for parallel integration tests. Document registry conventions, local dev setup, validation commands, and Claude/agent handoff files. Add unit and integration coverage for metadata snapshots, registry validation, provider routing, explicit model-run keys, active model-run filtering, third-party notices, and selectable shared model runs. As a byproduct of using Models.dev, the following model release dates have changed: mistral-large-2411: 2024-11-18 -> 2024-11-01 deepseek-r1: 2025-01-20 -> 2024-12-26 deepseek-v3: 2024-12-25 -> 2025-01-20 glm-4.6: 2025-11-13 -> 2025-09-30 kimi-k2-thinking: 2025-11-05 -> 2025-11-06 kimi-k2.5: 2026-01-30 -> 2026-01-27 glm-5: 2026-02-12 -> 2026-02-11 glm-5.1: 2026-04-07 -> 2026-03-27 kimi-k2.6: 2026-04-20 -> 2026-04-21 claude-3-7-sonnet-20250219: 2025-02-24 -> 2025-02-19 claude-haiku-4-5-20251001: 2025-10-01 -> 2025-10-15 claude-opus-4-5-20251101: 2025-11-24 -> 2025-11-01 grok-4.3: 2026-05-01 -> 2026-04-17 gemini-2.5-flash: 2025-06-17 -> 2025-03-20 gemini-2.5-pro: 2025-06-17 -> 2025-03-20 gemini-3.1-flash-lite: 2026-05-08 -> 2026-05-07
1 parent e660e35 commit c52d561

31 files changed

Lines changed: 5917 additions & 303 deletions

AGENTS.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Repository Instructions
2+
3+
## Shared LLM Registry
4+
5+
This package targets Python 3.14. Black is configured with
6+
`target-version = ["py314"]`; do not broaden `requires-python` without first
7+
checking that formatted code remains valid for the older target.
8+
9+
## Local Development Setup
10+
11+
Use Python 3.14 for local development:
12+
13+
```bash
14+
python3.14 -m venv .venv
15+
source .venv/bin/activate
16+
python -m pip install --upgrade pip
17+
python -m pip install -r requirements.txt
18+
```
19+
20+
`requirements.txt` delegates to `.[dev]`; it installs this package and the dev
21+
tools from `pyproject.toml` without editable mode.
22+
23+
When another repo needs local utils changes during development, use that repo's
24+
virtual environment and install utils explicitly in editable mode, for example:
25+
26+
```bash
27+
python -m pip install -e ../utils
28+
```
29+
30+
Do not add local relative paths to another repo's requirements files. Those
31+
files should use the deployed git pin when ready to deploy.
32+
33+
The shared LLM registry has two layers:
34+
35+
- `utils.llm.model_registry.MODELS` contains canonical provider-callable base models.
36+
- `utils.llm.model_runs.MODEL_RUNS` contains exact benchmarkable model-plus-options runs.
37+
38+
Benchmarks should choose from `MODEL_RUNS` by `model_run_key`; forecast files should store that exact key.
39+
40+
When adding a base model:
41+
42+
- Add provider/lab registry entries first only if the provider or lab is missing.
43+
- Look up the model in Models.dev. Prefer a `ModelsDevReference` when Models.dev
44+
has the provider/model entry.
45+
- In Models.dev source paths, `provider_id` is the folder under `providers/`,
46+
and `model_id` is the TOML filename stem under `models/`, for example
47+
`providers/anthropic/models/claude-opus-4-8.toml` maps to `anthropic` /
48+
`claude-opus-4-8`.
49+
- The checked-in Models.dev snapshot is not a catalog; it contains only
50+
registry-referenced models and only `id`, `name`, and `release_date`.
51+
- Use exact Models.dev `provider_id`/`model_id` values. If a reference is wrong,
52+
refreshing the snapshot should fail and suggest nearby Models.dev entries.
53+
- Use `manual_release_date` when the model is missing from Models.dev, when the
54+
Models.dev entry lacks a usable full release date, or for deliberate
55+
historical/manual entries.
56+
- Put the model in the provider-specific list in `utils/llm/model_registry.py` (`OPENAI_MODELS`, `TOGETHER_MODELS`, `ANTHROPIC_MODELS`, `XAI_MODELS`, or `GOOGLE_MODELS`).
57+
- Insert the model where `(release_date, model_key)` stays ascending within its
58+
provider-specific list.
59+
- Use `provider_model_id` for the exact string sent to the provider API. It may differ from `model_key`, especially for routed providers like Together.
60+
- Set `active=False` only when a provider route should remain in registry history
61+
but should be excluded from current live-callable benchmark runs.
62+
- Do not add duplicate `model_key`s. `MODELS = create_models_list(...)` validates uniqueness.
63+
64+
After changing `ModelsDevReference` values, refresh the Models.dev snapshot from the utils repo:
65+
```bash
66+
python - <<'PY'
67+
from scripts.refresh_models_dev_metadata import write_models_dev_snapshot
68+
69+
write_models_dev_snapshot()
70+
PY
71+
```
72+
73+
When adding a model run:
74+
75+
- Add it to `utils/llm/model_runs.py` with
76+
`_model_run(model_run_key=..., model_key=..., options=...)`.
77+
- Write `model_run_key` explicitly as the stable benchmark identifier. Do not
78+
rely on implicit generation from model/options.
79+
- Put every runtime call option in the `ModelRun` declaration; do not add hidden defaults elsewhere.
80+
- Use exact provider option names and values as they are passed to `get_response`.
81+
- If an option affects performance and should appear in filenames/forecast keys, add or update a naming rule in `NAME_COMPONENT_RULES`.
82+
- If an option is intentionally name-neutral, add it to `NAME_NEUTRAL_OPTION_PATHS`.
83+
- Unknown option paths should fail loudly rather than silently producing ambiguous model-run keys.
84+
- `build_model_run_key(...)` is a suggested-key helper for consistency checks and
85+
new naming rules; the declared `model_run_key` remains the durable identity.
86+
- Do not add duplicate `model_run_key`s. `MODEL_RUNS = create_model_runs_list(...)` validates uniqueness.
87+
- `MODEL_RUNS` is the historical registry. `ACTIVE_MODEL_RUNS` is derived from
88+
it by dropping runs whose base `Model` has `active=False`.
89+
- Add unit tests for new naming behavior, registry inclusion, and routed provider options when relevant.
90+
91+
## Artificial Analysis Model Runs
92+
93+
When adding an Artificial Analysis-backed model run:
94+
95+
- Use the checked-in Artificial Analysis snapshot as the source for the stable AA model ID and displayed AA name.
96+
- Refresh the snapshot from the AA endpoint; do not hand-edit individual AA models into the JSON file.
97+
- The official AA API key is `API_KEY_ARTIFICIAL_ANALYSIS` in GCP Secret Manager.
98+
- Do not hard-code an AA display name in a `ModelRun`; set `artificial_analysis_id` and let the run read the display name from the snapshot.
99+
- Do not add an `artificial_analysis_model` flag. A non-null `artificial_analysis_id` is the marker that a run is AA-backed.
100+
- Add or update the canonical base `Model` only if the provider-callable model is missing from `utils.llm.model_registry`.
101+
- Add the callable model-plus-options declaration to
102+
`ARTIFICIAL_ANALYSIS_MODEL_RUN_DECLARATIONS` in
103+
`utils/llm/artificial_analysis_model_runs.py`. Every declaration there is
104+
automatically included in `utils.llm.model_runs.MODEL_RUNS`; do not add the
105+
same AA run manually to `MODEL_RUNS`.
106+
- Use the exact provider option names that are passed at runtime. Token suffixes in model-run keys must reflect the actual token cap option used for the call.
107+
108+
Artificial Analysis token caps should be encoded in the run options this way:
109+
110+
- Non-reasoning models: use `16_384` output tokens, adjusted downward if the model has a smaller context window or a lower maximum output-token cap.
111+
- Reasoning models: use the maximum output tokens allowed by the model creator for that reasoning configuration.
112+
- If the correct cap is not clear from provider/model documentation or the AA metadata, stop and confirm rather than guessing.
113+
114+
After adding an AA model run:
115+
116+
- Add or update unit tests that prove the AA ID resolves from the snapshot and that `display_name` matches the AA leaderboard name.
117+
- Add or update shared registry coverage tests for the new selectable model-run key.
118+
- Run the focused model-run and AA metadata tests, then run the full lint/test suite before committing.
119+
120+
## Validation
121+
122+
- Run `make lint` before committing. It runs `isort .`, `black .`, `flake8 .`,
123+
and `pydocstyle .`.
124+
- Run `make test` before committing code changes. Use `PYTEST_ARGS=...` for a
125+
focused test pass while iterating.
126+
- Run `make test-integration` or `make test-integration-parallel` only when the
127+
relevant provider/GCP credentials are available.
128+
129+
## Live Model-Run Smoke Tests
130+
131+
Integration tests that hit real LLM APIs require provider API keys.
132+
133+
- `tests/conftest.py` loads `.env`, then `configure_api_keys(from_gcp=True)` when pytest is run with `--integration`.
134+
- `configure_api_keys(from_gcp=True)` reads provider keys from GCP Secret Manager using the secret names in `utils/helpers/constants.py`.
135+
- The standard LLM secret names are `API_KEY_OPENAI`, `API_KEY_ANTHROPIC`, `API_KEY_GEMINI`, `API_KEY_XAI`, and `API_KEY_TOGETHERAI`.
136+
- To test a specific shared model run, set `LLM_MODEL_RUN_KEYS` to one or more comma-separated `model_run_key`s and run `pytest --integration tests/integration/llm/test_model_runs.py`.
137+
- The model-run integration test calls `model_run.get_response`, so it uses the run's declared provider route, provider model ID, and options.
138+
- For a newly added model run, prefer running its exact smoke test before assuming the provider accepts the declared options.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

Makefile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
PYTEST_ARGS ?=
2+
13
lint: pyproject.toml setup.cfg
24
isort .
35
black .
@@ -8,13 +10,13 @@ clean:
810
find . -type f -name "*~" -exec rm -f {} +
911

1012
test:
11-
pytest
13+
pytest $(PYTEST_ARGS)
1214

1315
test-integration:
14-
pytest --integration
16+
pytest --integration $(PYTEST_ARGS)
1517

1618
test-integration-parallel:
17-
pytest --integration -n auto
19+
pytest --integration -n auto $(PYTEST_ARGS)
1820

1921
coverage:
20-
pytest --cov=utils --cov-report=term-missing --cov-report=html
22+
pytest --cov=utils --cov-report=term-missing --cov-report=html $(PYTEST_ARGS)

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ uv add fri-utils
2626

2727

2828
```
29-
from utils.llm.model_registry import configure_api_keys, MODELS
29+
from utils.llm.model_registry import configure_api_keys, MODELS_BY_KEY
3030
3131
# Input the API key for any model provider you like!
3232
configure_api_keys(
@@ -40,7 +40,7 @@ configure_api_keys(
4040
4141
# Call any model we support!
4242
# See the full list of supported models in `utils/llm/model_registry.py`
43-
model = next(m for m in MODELS if m.id == "gemini-2.5-flash")
43+
model = MODELS_BY_KEY["gemini-2.5-pro"]
4444
model.get_response("Hello")
4545
# > "Hello! How can I help you?"
4646
```
@@ -62,6 +62,12 @@ Use option names supported by the respective provider (`utils/llm/providers`).
6262

6363
If you don’t see an option you need, feel free to open a GitHub issue!
6464

65+
### Third-party metadata
66+
67+
The shared LLM registry includes normalized metadata from Models.dev and
68+
Artificial Analysis. See `THIRD_PARTY_NOTICES.md` for Models.dev license terms
69+
and Artificial Analysis attribution.
70+
6571

6672
### Configuring keys from GCP Secret Manager
6773

@@ -71,7 +77,7 @@ If so, you can use the `from_gcp=True` shortcut to set your keys for all model p
7177

7278
```
7379
configure_api_keys(from_gcp=True) # Configure all provider keys from GCP.
74-
model = next(m for m in MODELS if m.id == "gpt-4.1-mini")
80+
model = MODELS_BY_KEY["gpt-5-mini-2025-08-07"]
7581
response = model.get_response("Hello")
7682
```
7783

@@ -82,6 +88,7 @@ If you're setting up a Google Cloud Project, the API keys must be stored in Secr
8288
- `API_KEY_OPENAI` for OpenAI
8389
- `API_KEY_XAI` for xAI
8490
- `API_KEY_TOGETHERAI` for Together AI
91+
- `API_KEY_ARTIFICIAL_ANALYSIS` for refreshing the Artificial Analysis metadata snapshot
8592

8693
You can also check `utils/helpers/constants.py` for the complete list of secret names.
8794

THIRD_PARTY_NOTICES.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Third-Party Notices
2+
3+
This repository includes normalized metadata derived from third-party sources.
4+
5+
## Models.dev
6+
7+
The checked-in Models.dev snapshot is derived from https://models.dev/api.json
8+
and the upstream repository https://github.com/anomalyco/models.dev
9+
10+
Models.dev is licensed under the MIT License:
11+
12+
```text
13+
MIT License
14+
15+
Copyright (c) 2025 models.dev
16+
17+
Permission is hereby granted, free of charge, to any person obtaining a copy
18+
of this software and associated documentation files (the "Software"), to deal
19+
in the Software without restriction, including without limitation the rights
20+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21+
copies of the Software, and to permit persons to whom the Software is
22+
furnished to do so, subject to the following conditions:
23+
24+
The above copyright notice and this permission notice shall be included in all
25+
copies or substantial portions of the Software.
26+
27+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33+
SOFTWARE.
34+
```
35+
36+
## Artificial Analysis
37+
38+
The checked-in Artificial Analysis snapshot is derived from the Artificial
39+
Analysis free API and is minimized to the stable model IDs and display names
40+
used by this package.
41+
42+
Attribution: Artificial Analysis, https://artificialanalysis.ai/.

pyproject.toml

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,32 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "fri-utils"
7-
version = "0.1.0"
7+
version = "0.2.0"
88
description = "Utilities for the Forecasting Research Institute codebase."
99
readme = "README.md"
10-
requires-python = ">=3.10"
10+
requires-python = ">=3.14"
1111
license = { file = "LICENSE" }
1212
authors = [{ name = "Forecasting Research Institute" }]
1313
dependencies = [
14-
"google-genai==1.73.1",
15-
"anthropic==0.97.0",
16-
"together==2.11.0",
17-
"openai==2.33.0",
14+
"google-genai==2.7.0",
15+
"anthropic==0.105.2",
16+
"together==2.16.0",
17+
"openai==2.40.0",
1818
"google-cloud-secret-manager>=2.20.0",
1919
"google-cloud-storage>=2.14.0",
2020
"python-dotenv>=1.0.0",
2121
]
2222

2323
[project.optional-dependencies]
2424
dev = [
25-
"black",
26-
"flake8",
27-
"flake8-bugbear",
28-
"isort",
29-
"pydocstyle",
30-
"pytest",
31-
"pytest-cov",
25+
"black==26.5.1",
26+
"flake8==7.3.0",
27+
"flake8-bugbear==25.11.29",
28+
"isort==8.0.1",
29+
"pydocstyle==6.3.0",
30+
"pytest==9.0.3",
31+
"pytest-cov==7.1.0",
32+
"pytest-xdist==3.8.0",
3233
]
3334

3435
[tool.setuptools.packages.find]
@@ -38,8 +39,15 @@ include = [
3839
]
3940
exclude = ["tests*", "htmlcov*", "venv*"]
4041

42+
[tool.setuptools]
43+
license-files = ["LICENSE", "THIRD_PARTY_NOTICES.md"]
44+
45+
[tool.setuptools.package-data]
46+
"utils.llm.metadata" = ["*.json"]
47+
4148
[tool.black]
4249
line-length = 100
50+
target-version = ["py314"]
4351

4452
[tool.pytest.ini_options]
4553
markers = [

requirements.txt

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1 @@
1-
google-genai==1.73.1
2-
anthropic==0.97.0
3-
together==2.11.0
4-
openai==2.33.0
5-
google-cloud-secret-manager>=2.20.0
6-
google-cloud-storage>=2.14.0
7-
python-dotenv>=1.0.0
8-
isort
9-
black
10-
flake8
11-
flake8-bugbear
12-
pydocstyle
13-
pytest
14-
pytest-cov
15-
pytest-xdist
1+
.[dev]

0 commit comments

Comments
 (0)