feat: dynamically refresh Anthropic model list#46
Conversation
Code Review — PR #46: Dynamically refresh Anthropic model listOverview: This PR replaces the static 🐛 Critical bug: validation/discovery mismatch
# parameter_validator.py
from src.constants import CLAUDE_MODELS
class ParameterValidator:
SUPPORTED_MODELS = set(CLAUDE_MODELS)When the live API returns models not in the static list (which is the whole point of this PR), every subsequent chat request for those models will be rejected at lines 534 and 796 of Fix options (pick one):
|
| Severity | Issue |
|---|---|
| 🐛 Bug | Live-discovered models fail validate_model → chat requests rejected |
| Thundering-herd race condition on cache expiry | |
| Error fallback cached for full TTL (1 h default) | |
| 💡 Polish | Missing created field in OpenAI model objects |
| 💡 Polish | No test for cache hit / HTTP fetch / concurrency |
The overall design is sound — live fetch, graceful degradation, override escape hatch. Fixing the validation mismatch is required before this is safe to merge. The race condition and stale-error-cache issues are worth addressing in the same PR given how straightforward they are.
- Lock + double-check refresh path so concurrent requests at TTL expiry don't stampede the Anthropic Models API. - Use a short MODEL_LIST_ERROR_TTL_SECONDS (default 60s) for the fallback cache so transient outages don't suppress live discovery for a full hour. - Populate `created` (unix timestamp) on both live and fallback /v1/models entries to match OpenAI's model object schema. - Resolve DEFAULT_MODEL at startup by picking the latest Sonnet from the live Models API; honor explicit DEFAULT_MODEL env override.
Code Review – PR #46: Dynamically refresh Anthropic model listOverviewThis PR upgrades Bugs / Correctness1. Model validation is stale after live fetch (medium)
The fix is to validate against the live cache rather than the static constant: # parameter_validator.py – validate against the live model cache
async def validate_model(cls, model: str) -> bool:
from src.main import get_available_models
live_ids = {m["id"] for m in await get_available_models()}
if model not in live_ids:
logger.warning(...)
return TrueOr, minimally, add live model IDs to 2. Env-var integer/float parsing can crash at import time (medium) # src/constants.py
MODEL_LIST_CACHE_TTL_SECONDS = int(os.getenv("MODEL_LIST_CACHE_TTL_SECONDS", "3600"))
MODEL_LIST_ERROR_TTL_SECONDS = int(os.getenv("MODEL_LIST_ERROR_TTL_SECONDS", "60"))
MODEL_LIST_REQUEST_TIMEOUT_SECONDS = float(os.getenv("MODEL_LIST_REQUEST_TIMEOUT_SECONDS", "5"))If an operator sets any of these to a non-numeric string (a common misconfiguration), the app crashes on import with an unhandled def _env_int(key: str, default: int) -> int:
try:
return int(os.getenv(key, str(default)))
except ValueError:
logger.warning("Invalid value for %s, using default %d", key, default)
return defaultDesign / Style3.
# at module level
_using_model_override = bool(os.getenv("CLAUDE_MODELS_OVERRIDE", "").strip())
async def get_available_models():
if _using_model_override:
return _fallback_model_payload()
...4.
Tests5. Module-global mutation without cleanup fixtures (low) Several tests directly assign to main._model_list_cache = {"expires_at": 0.0, "models": None}
constants.RESOLVED_DEFAULT_MODEL = NoneThese mutations are not cleaned up via @pytest.fixture(autouse=True)
def reset_model_cache():
main._model_list_cache.update({"expires_at": 0.0, "models": None})
yield
main._model_list_cache.update({"expires_at": 0.0, "models": None})6. Import inside test body (trivial) # test_dynamic_models.py – test_failed_fetch_uses_short_error_ttl
import time as _timeMove this to the top of the file with the other imports. 7. No HTTP-level integration test for The new helper functions are well-tested in isolation, but there's no test using FastAPI's Security8. Allowing this via env var is a useful escape hatch, but document that setting it to an untrusted URL would allow an attacker (who controls env vars) to supply arbitrary model lists. Not a blocker given that env-var access already implies privileged access in this deployment model. Positives
SummaryThe two medium-priority issues (stale model validation set, uncaught |
- README: expand env vars table with ANTHROPIC_API_KEY (optional), DEFAULT_MODEL, FAST_MODEL, CLAUDE_MODELS_OVERRIDE, and the model list cache/timeout knobs. Rewrite the Supported Models section to explain the live-vs-static behavior and refresh the catalog around Claude 4.6 family. Bump model examples to claude-sonnet-4-6. - .env.example: add a Model Discovery (optional) block documenting ANTHROPIC_API_KEY, CLAUDE_MODELS_OVERRIDE, and the cache TTLs; comment out DEFAULT_MODEL so live resolution drives it by default. - main.py: log a single explicit info line at startup when live discovery is disabled (no ANTHROPIC_API_KEY) so operators see whether the dynamic path activated. - tests: cover the new disabled-path log and update the env-key gate in the existing resolve_default_model test.
## New Features (PR #46) - Live `/v1/models` discovery from Anthropic's Models API when `ANTHROPIC_API_KEY` is set, cached for 1 hour with thundering-herd protection and a short retry TTL on failure - Dynamic default model resolution: `DEFAULT_MODEL` resolves to the latest Sonnet at startup when `ANTHROPIC_API_KEY` is configured; falls back to `claude-sonnet-4-6` otherwise - New env vars: `CLAUDE_MODELS_OVERRIDE`, `FAST_MODEL`, `MODEL_LIST_CACHE_TTL_SECONDS`, `MODEL_LIST_ERROR_TTL_SECONDS`, `MODEL_LIST_REQUEST_TIMEOUT_SECONDS` - `created` (unix timestamp) populated on `/v1/models` entries to match OpenAI's model object schema - Claude 4.6 family added to the static fallback list ## Documentation - README and `.env.example` document `ANTHROPIC_API_KEY` as optional for live discovery; wrapper continues to work with Bedrock, Vertex, and Claude CLI subscription auth using the static fallback list
|
test |
|
hello world |
|
line1 line2 |
Code Review: PR #46 — Dynamic Anthropic Model Discovery |
Code Review: PR #46 — Dynamic Anthropic Model DiscoveryOverviewThis PR adds live model discovery via Anthropic's Models API, makes Positives
Issues1. Env var parse errors crash the app at startup MODEL_LIST_CACHE_TTL_SECONDS = int(os.getenv("MODEL_LIST_CACHE_TTL_SECONDS", "3600"))
MODEL_LIST_ERROR_TTL_SECONDS = int(os.getenv("MODEL_LIST_ERROR_TTL_SECONDS", "60"))
MODEL_LIST_REQUEST_TIMEOUT_SECONDS = float(os.getenv("MODEL_LIST_REQUEST_TIMEOUT_SECONDS", "5"))A misconfigured value (e.g. 2.
3. Inconsistency in the override path
monkeypatch.setenv("CLAUDE_MODELS_OVERRIDE", "custom-a,custom-b")
monkeypatch.setattr(main, "CLAUDE_MODELS", ["custom-a", "custom-b"])This could be simplified if 4. ID-sort fallback in When 5. Minor: inline import in test
6. ANTHROPIC_MODELS_URL = os.getenv("ANTHROPIC_MODELS_URL", "https://api.anthropic.com/v1/models")Redirecting this to an arbitrary server causes Test Observations
Summary
The only blocker before merging is issue 1 (bare |
…tighten supported-models intro - Version 2.9.3 -> 2.9.6 in header and docker pin example - Test count 650 -> 664 in Status and Testing sections - Add 2.9.6 highlight bullet covering SDK 0.1.81, urllib3/python-multipart sec fixes, upstream PR RichardAtCT#46 dynamic-models sync, and check-sdk-version auto-PR - Add ANTHROPIC_MODELS_URL, ANTHROPIC_VERSION, ANTHROPIC_BETA/ANTHROPIC_BETA_HEADER rows to the env var table (advanced overrides for the new live-discovery path) - Tighten the Supported Models intro paragraph (was 3 dense sentences)
…models from upstream, SDK-drift auto-PR (#17) * feat: dynamically refresh Anthropic model list (RichardAtCT#46) * feat: dynamically refresh Anthropic model list * fix: harden /v1/models cache and resolve default model live - Lock + double-check refresh path so concurrent requests at TTL expiry don't stampede the Anthropic Models API. - Use a short MODEL_LIST_ERROR_TTL_SECONDS (default 60s) for the fallback cache so transient outages don't suppress live discovery for a full hour. - Populate `created` (unix timestamp) on both live and fallback /v1/models entries to match OpenAI's model object schema. - Resolve DEFAULT_MODEL at startup by picking the latest Sonnet from the live Models API; honor explicit DEFAULT_MODEL env override. * docs: clarify ANTHROPIC_API_KEY is optional for live model discovery - README: expand env vars table with ANTHROPIC_API_KEY (optional), DEFAULT_MODEL, FAST_MODEL, CLAUDE_MODELS_OVERRIDE, and the model list cache/timeout knobs. Rewrite the Supported Models section to explain the live-vs-static behavior and refresh the catalog around Claude 4.6 family. Bump model examples to claude-sonnet-4-6. - .env.example: add a Model Discovery (optional) block documenting ANTHROPIC_API_KEY, CLAUDE_MODELS_OVERRIDE, and the cache TTLs; comment out DEFAULT_MODEL so live resolution drives it by default. - main.py: log a single explicit info line at startup when live discovery is disabled (no ANTHROPIC_API_KEY) so operators see whether the dynamic path activated. - tests: cover the new disabled-path log and update the env-key gate in the existing resolve_default_model test. * chore(v2.9.6): SDK 0.1.81 bump, urllib3/python-multipart sec fixes, SDK-drift workflow auto-PR - claude-agent-sdk 0.1.68 -> 0.1.81 (13 patch releases since v2.9.5). - python-multipart ^0.0.26 -> ^0.0.27 (GHSA-pp6c-gr5w-3c5g, supersedes Dependabot PR #16). - urllib3 security floor >=2.6.3 -> >=2.7.0 (GHSA-qccp-gfcp-xxvc, GHSA-mf9v-mfxr-j63j). - check-sdk-version.yml opens a draft chore/sdk-bump-<latest> PR on drift instead of only writing to the run summary. Permissions widened to contents: write + pull-requests: write; idempotent by head branch; fallback summary still fires. Lockfile regenerated locally with Poetry 2.3.4. Full suite at 664 passed, 31 skipped (+14 from upstream test_dynamic_models.py picked up in the prior cherry-pick). * docs(readme): bump to v2.9.6, document new model-discovery env vars, tighten supported-models intro - Version 2.9.3 -> 2.9.6 in header and docker pin example - Test count 650 -> 664 in Status and Testing sections - Add 2.9.6 highlight bullet covering SDK 0.1.81, urllib3/python-multipart sec fixes, upstream PR RichardAtCT#46 dynamic-models sync, and check-sdk-version auto-PR - Add ANTHROPIC_MODELS_URL, ANTHROPIC_VERSION, ANTHROPIC_BETA/ANTHROPIC_BETA_HEADER rows to the env var table (advanced overrides for the new live-discovery path) - Tighten the Supported Models intro paragraph (was 3 dense sentences) --------- Co-authored-by: Richard A <richardatk01@gmail.com>
Summary
claude-sonnet-4-6the default model/v1/modelsto prefer Anthropic's live Models API whenANTHROPIC_API_KEYis configuredCLAUDE_MODELS_OVERRIDEavailable for deployments that need a pinned/curated listTest plan
poetry run black --check src/constants.py src/main.py tests/test_sdk_migration.py tests/test_dynamic_models.pypoetry run pytest tests/test_parameter_validator_unit.py tests/test_property_based.py tests/test_models_unit.py tests/test_dynamic_models.py tests/test_sdk_migration.py