mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-14 04:09:09 +00:00
fix(complexity-router): route on Nadir's routing_tier, not the raw bucket
/v1/bucket returns two tiers. `bucket` is the classifier's prediction; `routing_tier` is the tier Nadir itself routes on, after its deterministic adjustments: a one-tier bump when a low-confidence prediction carries real probability mass above it, the step-down for a complex-gate over-catch, and an on-prem calibrated confidence floor. The API documents `routing_tier` as the authoritative tier, so reading `bucket` skipped exactly the rules that guard against under-routing. The plugin now reads `routing_tier` and falls back to `bucket` only for a server that predates it, such as an older self-hosted image. An unknown `routing_tier` still declines rather than falling through to `bucket`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
651bd044e3
commit
df0714e1f7
2 changed files with 34 additions and 7 deletions
|
|
@ -6,11 +6,12 @@ endpoint, and everything else stays here. The tier's model pool, the provider ca
|
|||
operator's own provider keys, fallbacks, spend tracking and the response path are
|
||||
untouched, so Nadir sees the messages it is asked to classify and never the response.
|
||||
|
||||
``/v1/bucket`` grades the request without generating an answer and replies ``simple`` /
|
||||
``medium`` / ``complex``, which map to the router's default SIMPLE / MEDIUM / COMPLEX
|
||||
tiers. Renaming the tiers with ``tier_labels`` needs nothing here, since the router still
|
||||
accepts the default names. A router built on ``tier_definitions`` names its own tiers, so
|
||||
it constructs its own instance with a ``tier_map`` onto them.
|
||||
``/v1/bucket`` grades the request without generating an answer and replies with a
|
||||
``routing_tier`` of ``simple`` / ``medium`` / ``complex``, which map to the router's
|
||||
default SIMPLE / MEDIUM / COMPLEX tiers. Renaming the tiers with ``tier_labels`` needs
|
||||
nothing here, since the router still accepts the default names. A router built on
|
||||
``tier_definitions`` names its own tiers, so it constructs its own instance with a
|
||||
``tier_map`` onto them.
|
||||
|
||||
Configure it in the proxy by pointing ``classifier_plugin`` at the module-level
|
||||
instance::
|
||||
|
|
@ -138,10 +139,13 @@ class NadirComplexityClassifier:
|
|||
body: Final = {"messages": list(messages), "source": "litellm"}
|
||||
response: Final = await client.post(url=url, json=body, headers=self._headers(url))
|
||||
try:
|
||||
bucket: Final = _VERDICT.validate_python(response.json()).get("bucket")
|
||||
verdict: Final = _VERDICT.validate_python(response.json())
|
||||
except ValidationError:
|
||||
return None
|
||||
return self._tier_map.get(bucket.strip().lower()) if isinstance(bucket, str) else None
|
||||
# ``routing_tier`` is the tier Nadir itself routes on: the classifier's bucket after its
|
||||
# deterministic confidence and gate adjustments. A server that predates it sends only ``bucket``.
|
||||
tier: Final = verdict.get("routing_tier") or verdict.get("bucket")
|
||||
return self._tier_map.get(tier.strip().lower()) if isinstance(tier, str) else None
|
||||
|
||||
|
||||
nadir_classifier: Final = NadirComplexityClassifier()
|
||||
|
|
|
|||
|
|
@ -154,6 +154,29 @@ class TestClassify:
|
|||
"""Anything outside tier_map is declined, so classifier_fallback decides rather than this guessing."""
|
||||
assert await NadirComplexityClassifier(client=_FakeClient(payload=payload)).classify(_context()) is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("payload", "expected_tier"),
|
||||
[
|
||||
({"bucket": "simple", "routing_tier": "medium"}, "MEDIUM"),
|
||||
({"bucket": "complex", "routing_tier": "medium"}, "MEDIUM"),
|
||||
({"bucket": "complex"}, "COMPLEX"),
|
||||
],
|
||||
)
|
||||
async def test_routing_tier_wins_over_the_raw_bucket(self, payload, expected_tier):
|
||||
"""routing_tier carries Nadir's own confidence and gate adjustments; bucket is the pre-adjustment
|
||||
prediction, read only from a server that predates routing_tier."""
|
||||
assert (
|
||||
await NadirComplexityClassifier(client=_FakeClient(payload=payload)).classify(_context()) == expected_tier
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unknown_routing_tier_declines_rather_than_reading_the_bucket(self):
|
||||
classifier = NadirComplexityClassifier(
|
||||
client=_FakeClient(payload={"bucket": "simple", "routing_tier": "reasoning"})
|
||||
)
|
||||
assert await classifier.classify(_context()) is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bucket_name_is_matched_case_insensitively(self):
|
||||
classifier = NadirComplexityClassifier(client=_FakeClient(payload={"bucket": " Complex "}))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue