-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(providers): honor model registry overrides for CLI providers #463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| from __future__ import annotations | ||
|
|
||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| from langchain_anthropic import ChatAnthropic | ||
|
|
@@ -830,6 +831,62 @@ def test_is_available_reports_not_ready(self) -> None: | |
| assert reason | ||
|
|
||
|
|
||
| class TestAgentCLIProviderMetadata: | ||
| """Shared model-registry behavior for supported agent CLI providers.""" | ||
|
|
||
| @pytest.mark.parametrize( | ||
| "provider_type", | ||
| [ClaudeCLIProvider, CodexCLIProvider, GeminiCLIProvider], | ||
| ) | ||
| def test_honors_model_registry_override( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice coverage for the happy paths. Once the registry lookups are hardened (see my comment in |
||
| self, | ||
| provider_type: type[ClaudeCLIProvider | CodexCLIProvider | GeminiCLIProvider], | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| tmp_path: Path, | ||
| ) -> None: | ||
| registry_path = tmp_path / "model_registry.yaml" | ||
| registry_path.write_text( | ||
| "models:\n test-model:\n context_length: 200000\n max_output_tokens: 32000\n", | ||
| encoding="utf-8", | ||
| ) | ||
| monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) | ||
|
|
||
| provider = provider_type() | ||
| assert provider.get_context_length("test-model") == 200_000 | ||
| assert provider.get_max_output_tokens("test-model") == 32_000 | ||
|
|
||
| @pytest.mark.parametrize("registry_value", [None, " "]) | ||
| def test_returns_none_without_registry( | ||
| self, | ||
| registry_value: str | None, | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| if registry_value is None: | ||
| monkeypatch.delenv("SKILLSPECTOR_MODEL_REGISTRY", raising=False) | ||
| else: | ||
| monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", registry_value) | ||
|
|
||
| provider = ClaudeCLIProvider() | ||
| assert provider.get_context_length("test-model") is None | ||
| assert provider.get_max_output_tokens("test-model") is None | ||
|
|
||
| def test_unknown_model_returns_none( | ||
| self, | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| tmp_path: Path, | ||
| ) -> None: | ||
| registry_path = tmp_path / "model_registry.yaml" | ||
| registry_path.write_text( | ||
| "models:\n known-model:\n context_length: 200000\n max_output_tokens: 32000\n", | ||
| encoding="utf-8", | ||
| ) | ||
| monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) | ||
|
|
||
| provider = ClaudeCLIProvider() | ||
| assert provider.get_context_length("unknown-model") is None | ||
| assert provider.get_max_output_tokens("unknown-model") is None | ||
|
|
||
|
|
||
| class TestClaudeCLIProvider: | ||
| """Claude CLI provider — metadata, availability, and capability detection.""" | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The guard you removed was also (accidentally) the only thing keeping CLI providers away from parsing a user's hand-written registry file. Now that the file is parsed, a small mistake in it crashes the whole CLI instead of falling back to the default budget:
models:written as a list ->AttributeError: 'list' object has no attribute 'get'my-model: 42->AttributeError: 'int' object has no attribute 'get'context_length: lots->ValueErrorAnd because
constants._validate_model_config()runs at import time, the crash happens at startup: withSKILLSPECTOR_PROVIDER=claude_cli,SKILLSPECTOR_MODEL=my-model, and that YAML,skillspectordies with a raw traceback before doing anything. I ran this exact setup on currentmainand it starts fine there (warnings only), so this is a new failure mode from removing the guard.The root cause is in
registry.py:lookup_context_length()/lookup_max_output_tokens()callentry.get(...)andint(...)outside thetry, so only file-level problems (missing/unreadable) are caught — shape and value problems are not. Since this PR is what turns hand-written registries into a real workflow for CLI users, could you harden those two functions in the same change? Treating a non-dict entry or a bad/non-positive value as "not found" (warn + returnNone, same as_loadalready does for unreadable files) covers all three cases in a few lines.Everything else checks out — I built the wheel and confirmed the override works end to end (details in the review summary).