From bee423818000811bce130958a31afa346ffaba67 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Sat, 22 Aug 2026 08:06:44 -0400 Subject: [PATCH] docs(complexity-router): state why equal tier boundaries are accepted The ascending check uses <=, so a zero-width band passes. The test claimed that takes the tier out of rotation without saying how: the strict < below it claims every lower score, and everything from that value up falls through to the tier above, so nothing routes there. The validator allows it because a non-decreasing set is coherent where a decreasing one asks for a tier starting above the tier above it. Also asserts the whole resolved triple rather than one key, so a later normalization of equal boundaries would fail here. --- .../router_strategy/complexity_router/config.py | 9 +++++---- .../router_strategy/test_complexity_router.py | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index 6caa9e519f8..1e0f6dc9769 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -1115,10 +1115,11 @@ class ComplexityRouterConfig(BaseModel): @model_validator(mode="after") def _validate_tier_boundaries_ascend(self) -> "ComplexityRouterConfig": - # The score-to-tier mapping is a sequential comparison chain, so a boundary that sits below the one - # under it makes the tier between them unreachable and silently sends its traffic to a costlier tier. - # Resolved, not raw: omitting a key is the common way to arrive here, since the omitted key is filled - # from a shipped default that knows nothing about the boundary the operator did set. + # The score-to-tier mapping is a sequential comparison chain, so a boundary below the one under it + # asks for a tier starting above the tier above it, which no score satisfies, and the stranded tier's + # traffic silently lands on a costlier one. Equal boundaries pass: an empty band is coherent. + # Resolved, not raw, because filling an omitted key from a shipped default is the usual way an + # operator arrives here without having written anything out of order. resolved: Final = resolve_tier_boundaries(self.tier_boundaries) simple_medium, medium_complex, complex_reasoning = ( resolved["simple_medium"], diff --git a/tests/test_litellm/router_strategy/test_complexity_router.py b/tests/test_litellm/router_strategy/test_complexity_router.py index 1a73b31b9d4..28ecccb0dc7 100644 --- a/tests/test_litellm/router_strategy/test_complexity_router.py +++ b/tests/test_litellm/router_strategy/test_complexity_router.py @@ -661,8 +661,15 @@ class TestEffectiveTierBoundaries: }, ) - def test_equal_boundaries_are_allowed(self, mock_router_instance): - """Collapsing a tier to an empty band is a deliberate way to take it out of rotation.""" + def test_equal_boundaries_are_accepted_and_close_the_band(self, mock_router_instance): + """Equal boundaries are accepted, and they leave the tier between them unreachable. + + With simple_medium == medium_complex, MEDIUM's band is zero width: the strict `<` below it + already claims every lower score for SIMPLE, and every score from that value up falls + through to COMPLEX. The validator still allows it because a non-decreasing set is coherent, + it just describes an empty band, where a decreasing pair asks for a tier that starts above + the tier above it and no score can satisfy that. + """ router = ComplexityRouter( model_name="test-complexity-router", litellm_router_instance=mock_router_instance, @@ -671,7 +678,11 @@ class TestEffectiveTierBoundaries: "tier_boundaries": {"simple_medium": 0.25, "medium_complex": 0.25, "complex_reasoning": 0.50}, }, ) - assert router._effective_tier_boundaries()["medium_complex"] == 0.25 + assert dict(router._effective_tier_boundaries()) == { + "simple_medium": 0.25, + "medium_complex": 0.25, + "complex_reasoning": 0.50, + } def test_defaults_stay_ordered_and_within_the_scoring_range(self): """The tiers only all remain reachable while the boundaries ascend."""