mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(complexity_router): stop default_model rescuing an unservable default_tier under plugins
The validator accepted default_model as evidence that an explicitly configured default_tier could be served. The plugin path never consults it: candidates come from the tier pool alone, and a pool that is empty raises rather than falling through to a model the plugins never vetted. So a config with plugins, an explicit default_tier absent from tiers, and a default_model passed validation and then failed on its first no-signal request, which is exactly what validating at load is meant to prevent default_model now counts only when no plugins are configured, and the error names which of the two remedies applies
This commit is contained in:
parent
2d25d23566
commit
9879baeabf
2 changed files with 40 additions and 5 deletions
|
|
@ -476,14 +476,28 @@ class ComplexityRouterConfig(BaseModel):
|
|||
def _validate_default_tier_is_servable(self) -> "ComplexityRouterConfig":
|
||||
if "default_tier" not in self.model_fields_set:
|
||||
return self
|
||||
if self.tiers.get(self.default_tier.value) or self.default_model:
|
||||
if self.tiers.get(self.default_tier.value):
|
||||
return self
|
||||
# default_model rescues this only without plugins. The plugin path never falls back
|
||||
# to it, since a model the plugins did not vet must not serve, so accepting it here
|
||||
# would validate a config whose every no-signal request fails at routing time.
|
||||
if self.default_model and not self.plugins:
|
||||
return self
|
||||
remedy = (
|
||||
"Add it to tiers, or name a tier that is configured"
|
||||
if self.plugins
|
||||
else "Add it to tiers, name a tier that is configured, or set default_model in complexity_router_config"
|
||||
)
|
||||
because = (
|
||||
"routing plugins are configured, so default_model is not consulted: a model the plugins "
|
||||
"never vetted must not serve"
|
||||
if self.plugins
|
||||
else "the deployment-level complexity_router_default_model does not count here, because "
|
||||
"falling through to it would serve every no-signal request from a model this tier never names"
|
||||
)
|
||||
raise ValueError(
|
||||
f"default_tier {self.default_tier.value} is not a non-empty entry in tiers "
|
||||
f"({sorted(self.tiers)}). Add it to tiers, name a tier that is configured, or set "
|
||||
f"default_model in complexity_router_config; the deployment-level "
|
||||
f"complexity_router_default_model does not count here, because falling through to it "
|
||||
f"would serve every no-signal request from a model this tier never names"
|
||||
f"({sorted(self.tiers)}). {remedy}; {because}"
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
|
|
|
|||
|
|
@ -4893,6 +4893,27 @@ class TestNoSignalDefaultTier:
|
|||
},
|
||||
)
|
||||
|
||||
def test_default_model_does_not_rescue_the_default_tier_when_plugins_are_configured(self, mock_router_instance):
|
||||
"""The plugin path builds candidates from the tier pool alone and never consults
|
||||
default_model, since a model the plugins did not vet must not serve. Accepting
|
||||
default_model here would validate a config whose every no-signal request raises."""
|
||||
|
||||
class NoOpPlugin:
|
||||
async def run(self, context):
|
||||
return context
|
||||
|
||||
with pytest.raises(ValidationError, match="routing plugins are configured"):
|
||||
ComplexityRouter(
|
||||
model_name="test-router",
|
||||
litellm_router_instance=mock_router_instance,
|
||||
complexity_router_config={
|
||||
"tiers": {"SIMPLE": "simple-model", "MEDIUM": "medium-model"},
|
||||
"default_tier": "COMPLEX",
|
||||
"default_model": "fallback-model",
|
||||
"plugins": [NoOpPlugin()],
|
||||
},
|
||||
)
|
||||
|
||||
def test_default_tier_outside_tiers_is_allowed_with_default_model(self, mock_router_instance):
|
||||
router = ComplexityRouter(
|
||||
model_name="test-router",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue