diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index 70989cf71a6..aa39dff8c53 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -697,6 +697,13 @@ class JevClassifierConfig(BaseModel): raise ValueError("jev_classifier_config.instructions must be non-empty; omit it to use the default") return value + @field_validator("api_key") + @classmethod + def _reject_blank_api_key(cls, value: str | None) -> str | None: + if value is not None and not value.strip(): + raise ValueError("jev_classifier_config.api_key must be non-empty; omit it to use TYPESAFE_API_KEY") + return value + @model_validator(mode="after") def _keep_the_environment_key_on_the_environment_base(self) -> "JevClassifierConfig": if self.api_base is not None and self.api_key is None: diff --git a/tests/test_litellm/proxy/management_helpers/test_auto_router_permissions.py b/tests/test_litellm/proxy/management_helpers/test_auto_router_permissions.py index c75cb791448..2884efb0825 100644 --- a/tests/test_litellm/proxy/management_helpers/test_auto_router_permissions.py +++ b/tests/test_litellm/proxy/management_helpers/test_auto_router_permissions.py @@ -137,6 +137,7 @@ def test_tier_config_is_normalized_and_unknown_router_extras_are_rejected() -> N ({"api_base": "https://collector.invalid"}, "jev_classifier_config"), ({"api_key": "sk-member"}, "api_key"), ({"api_base": "https://collector.invalid", "api_key": "sk-member"}, "api_key"), + ({"api_base": "https://collector.invalid", "api_key": ""}, "jev_classifier_config.api_key"), ], ) def test_members_cannot_move_the_jev_classifier_off_the_proxys_typesafe_account( diff --git a/tests/test_litellm/router_strategy/complexity_router/test_jev_classifier.py b/tests/test_litellm/router_strategy/complexity_router/test_jev_classifier.py index c0fac132704..78eaf26cb4d 100644 --- a/tests/test_litellm/router_strategy/complexity_router/test_jev_classifier.py +++ b/tests/test_litellm/router_strategy/complexity_router/test_jev_classifier.py @@ -47,10 +47,23 @@ def test_jev_instructions_reject_blank_values() -> None: JevClassifierConfig(instructions=" \t") -def test_jev_api_base_without_its_own_key_is_rejected_so_the_environment_key_stays_home() -> None: - with pytest.raises(ValueError, match=r"api_base requires jev_classifier_config\.api_key"): +@pytest.mark.parametrize( + ("missing_key", "rejection"), + [ + ({}, r"api_base requires jev_classifier_config\.api_key"), + ({"api_key": ""}, r"api_key must be non-empty"), + ({"api_key": " "}, r"api_key must be non-empty"), + ], +) +def test_jev_api_base_without_its_own_key_is_rejected_so_the_environment_key_stays_home( + missing_key: Mapping[str, str], rejection: str +) -> None: + with pytest.raises(ValueError, match=rejection): ComplexityRouterConfig.model_validate( - {"classifier_type": "jev", "jev_classifier_config": {"api_base": "https://collector.invalid"}} + { + "classifier_type": "jev", + "jev_classifier_config": {"api_base": "https://collector.invalid", **missing_key}, + } ) paired: Final = JevClassifierConfig(api_base="https://eu.typesafe.invalid", api_key="sk-own") assert (paired.api_base, paired.api_key) == ("https://eu.typesafe.invalid", "sk-own")