From a5cfe625e395c4edb401cf0d2daec3ccb686ed82 Mon Sep 17 00:00:00 2001 From: moe-berri Date: Tue, 8 Sep 2026 13:14:52 -0700 Subject: [PATCH] refactor: trim the comments this PR added Cuts the explanatory comments and docstrings added here down to one line each, or removes them where the code already says it. Restores the four pre-existing docstrings this PR had reworded to their original text; the one remaining edit to existing text is TierDefinition.description, whose hardcoded tier list would otherwise misstate that a tier named NON_REASONING may also omit its description. --- .../complexity_router/complexity_router.py | 7 +- .../complexity_router/config.py | 20 +--- .../router_strategy/test_complexity_router.py | 91 ++++++------------- .../add_model/ClassificationMethodConfig.tsx | 7 +- .../add_model/ComplexityRouterConfig.tsx | 15 +-- .../add_model/NonReasoningTierToggle.tsx | 6 -- .../build_complexity_router_config.ts | 3 +- .../src/components/add_model/tier_rows.ts | 6 +- 8 files changed, 41 insertions(+), 114 deletions(-) diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index c40405ecf0f..66c328c49ea 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -100,12 +100,7 @@ else: class TierClassification(BaseModel): - """Structured response schema for the LLM-based complexity classifier. - - The four-tier ladder, which is what a router that did not opt into NON_REASONING sends. The - enum actually put on the wire is rebuilt per router from `classifier_wire_labels`, so a - five-tier or renamed ladder widens it there rather than here. - """ + """Structured response schema for the LLM-based complexity classifier.""" tier: Literal["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] diff --git a/litellm/router_strategy/complexity_router/config.py b/litellm/router_strategy/complexity_router/config.py index 1d03c56050a..9cd4ce1d2fa 100644 --- a/litellm/router_strategy/complexity_router/config.py +++ b/litellm/router_strategy/complexity_router/config.py @@ -56,9 +56,6 @@ DEFAULT_CLASSIFICATION_RUBRIC: Final[ClassificationRubric] = ClassificationRubri LLM_CLASSIFIER_TYPES: Final[frozenset[str]] = frozenset({"llm", "heuristic_first", "hybrid"}) -# Excludes NON_REASONING so an existing router keeps the ladder, rubric and wire labels it already -# has, and so heuristic_v2 keeps mapping onto the four classes its artifact is trained on. Anywhere -# `enable_non_reasoning_tier` can reach, read the ladder off the config instead. TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( ComplexityTier.SIMPLE, ComplexityTier.MEDIUM, @@ -73,7 +70,6 @@ NON_REASONING_TIER_SEVERITY_ORDER: Final[tuple[ComplexityTier, ...]] = ( def tier_severity_order(non_reasoning_enabled: bool) -> tuple[ComplexityTier, ...]: - """The built-in ladder for one router, tier 0 included only when it opted in.""" return NON_REASONING_TIER_SEVERITY_ORDER if non_reasoning_enabled else TIER_SEVERITY_ORDER @@ -1524,8 +1520,7 @@ class ComplexityRouterConfig(BaseModel): return self.classifier_type in LLM_CLASSIFIER_TYPES def active_tier_severity_order(self) -> tuple[ComplexityTier, ...]: - """This router's built-in ladder, ascending. Meaningless for a custom tier set, whose - severity order is tier_definitions list order over names that are not enum members.""" + """This router's built-in ladder, ascending; not meaningful for a custom tier set.""" return tier_severity_order(self.enable_non_reasoning_tier) def tier_names(self) -> tuple[str, ...]: @@ -1626,12 +1621,7 @@ class ComplexityRouterConfig(BaseModel): @model_validator(mode="after") def _validate_non_reasoning_tier(self) -> "ComplexityRouterConfig": - """Gate the opt-in fifth tier on the two things that make it reachable and routable. - - The heuristic scorers cannot emit it (the v1 score ladder has no rung below simple_medium - and the v2 artifact is trained on four classes), so a router whose classifier can never - return the tier would pay for a rubric bullet and a configured pool that no request reaches. - """ + """Require a classifier that can emit the opt-in tier and a model to route it to.""" non_reasoning_key: Final = ComplexityTier.NON_REASONING.value if not self.enable_non_reasoning_tier: if not self.has_custom_tiers and non_reasoning_key in self.tiers: @@ -1834,13 +1824,11 @@ class ComplexityRouterConfig(BaseModel): return self.tier_labels.get(tier, "").strip() or tier.value def labeled_tiers(self) -> tuple[tuple[ComplexityTier, str], ...]: - """Every active tier paired with its display name, in ascending severity order.""" + """Every tier paired with its display name, in ascending severity order.""" return tuple((tier, self.tier_label(tier)) for tier in self.active_tier_severity_order()) def tier_for_label(self, label: str) -> ComplexityTier | None: - """Resolve a display name back to its active tier, case-insensitively, then canonical - names. A tier this router did not opt into resolves to None, so a classifier naming - NON_REASONING on a four-tier router is an unparseable reply rather than a fifth rung.""" + """Resolve a display name back to its tier, case-insensitively, then canonical names.""" folded: Final = label.strip().casefold() labeled: Final = self.labeled_tiers() return next( diff --git a/tests/test_litellm/router_strategy/test_complexity_router.py b/tests/test_litellm/router_strategy/test_complexity_router.py index ce9cd5d3b7d..2732694ab7e 100644 --- a/tests/test_litellm/router_strategy/test_complexity_router.py +++ b/tests/test_litellm/router_strategy/test_complexity_router.py @@ -1512,6 +1512,7 @@ class TestRouterComplexityDeploymentMethods: def test_the_shipped_rubric_and_default_prompt_stay_free(self) -> None: """Only an operator-written prompt is gated: picking a shipped rubric preset, or writing no prompt at all, leaves a router unmetered, so several of them register under a ceiling of one.""" + def rubric(model_name: str, model_id: str, preset: str | None) -> dict[str, object]: llm_config: dict[str, object] = {"model": "gpt-4o-mini"} if preset is not None: @@ -1648,6 +1649,7 @@ class TestRouterComplexityDeploymentMethods: def test_renaming_built_in_tiers_is_not_a_custom_tier_set(self) -> None: """tier_labels renames the built-in ladder without defining one, so it stays ungated: two such routers register under a ceiling of one.""" + def labeled(model_name: str, model_id: str) -> dict[str, object]: row = self._router_row(model_name, model_id, "heuristic") row["litellm_params"]["complexity_router_config"]["tier_labels"] = {"SIMPLE": "Cheap", "MEDIUM": "Standard"} @@ -2521,9 +2523,7 @@ class TestLLMClassifier: assert outcome.classifier_cost == pytest.approx(1.35e-05) @pytest.mark.asyncio - async def test_aclassify_timeout_does_not_inherit_router_retries_or_fallbacks( - self, llm_classifier_config - ): + async def test_aclassify_timeout_does_not_inherit_router_retries_or_fallbacks(self, llm_classifier_config): real_router = Router( model_list=[ { @@ -2566,9 +2566,7 @@ class TestLLMClassifier: assert real_router.total_calls["openai/mock-backup-classifier"] == 0 @pytest.mark.asyncio - async def test_aclassify_enforces_total_classifier_deadline( - self, mock_router_instance, llm_classifier_config - ): + async def test_aclassify_enforces_total_classifier_deadline(self, mock_router_instance, llm_classifier_config): cancelled = asyncio.Event() async def slow_classifier(**_kwargs: object) -> None: @@ -12415,9 +12413,7 @@ class TestTierHealthFailover: llm_provider="", ) filtered = (*cooling, *blocked, *excluded) - healthy = [ - {"model_name": model, "model_info": {"id": i}} for i in ids_by_model[model] if i not in filtered - ] + healthy = [{"model_name": model, "model_info": {"id": i}} for i in ids_by_model[model] if i not in filtered] if not healthy: raise RouterRateLimitError( model=model, cooldown_time=60.0, enable_pre_call_checks=False, cooldown_list=[] @@ -12846,9 +12842,7 @@ class TestTierHealthFailover: assert all(probed is not request_kwargs for probed in router.litellm_router_instance.probed_kwargs) @pytest.mark.asyncio - async def test_a_peer_whose_every_deployment_is_over_its_rpm_is_not_a_failover_target( - self, mock_router_instance - ): + async def test_a_peer_whose_every_deployment_is_over_its_rpm_is_not_a_failover_target(self, mock_router_instance): """RPM exhaustion is its own verdict from the owner (RouterRateLimitErrorBasic). A peer in that state would be rejected downstream, so it cannot be the substitute.""" from litellm.types.router import RouterRateLimitErrorBasic @@ -12881,9 +12875,7 @@ class TestTierHealthFailover: assert {r.model for r in results} == {"live-c"} @pytest.mark.asyncio - async def test_the_probe_forwards_input_so_window_checks_run_on_input_only_surfaces( - self, mock_router_instance - ): + async def test_the_probe_forwards_input_so_window_checks_run_on_input_only_surfaces(self, mock_router_instance): """The Responses API carries its prompt as `input`, never as messages. The owner only runs its context-window pre-call check when one of them is present, so dropping `input` would silently skip window filtering on that whole surface.""" @@ -12909,9 +12901,7 @@ class TestTierHealthFailover: ), "the eligibility probe must forward `input` to the owner" @pytest.mark.asyncio - async def test_a_group_the_router_has_no_deployment_for_is_not_a_failover_target( - self, mock_router_instance - ): + async def test_a_group_the_router_has_no_deployment_for_is_not_a_failover_target(self, mock_router_instance): """The owner answers an unconfigured group with BadRequestError. Reading that as live would both skip failover off it and let it be chosen as a substitute.""" router = self._router( @@ -13100,9 +13090,7 @@ class TestClassifierVision: routed as default_fallback on text the request never contained. """ router = self._router(mock_router_instance, vision={"enabled": True}) - response = await router.async_pre_routing_hook( - model="m", request_kwargs={}, messages=self._turn(IMG_PART) - ) + response = await router.async_pre_routing_hook(model="m", request_kwargs={}, messages=self._turn(IMG_PART)) assert response.routing_decision["cause"] == "llm_classifier" assert response.model == "t-complex" assert [block["type"] for block in self._classifier_user_content(mock_router_instance)] == [ @@ -13113,9 +13101,7 @@ class TestClassifierVision: @pytest.mark.asyncio async def test_image_only_turn_still_falls_back_when_vision_is_off(self, mock_router_instance): router = self._router(mock_router_instance, vision={"enabled": False}) - response = await router.async_pre_routing_hook( - model="m", request_kwargs={}, messages=self._turn(IMG_PART) - ) + response = await router.async_pre_routing_hook(model="m", request_kwargs={}, messages=self._turn(IMG_PART)) assert response.routing_decision["cause"] == "default_fallback" mock_router_instance.acompletion.assert_not_awaited() @@ -13185,9 +13171,7 @@ class TestClassifierVision: makes the image the only variable; a margin loose enough to leave the score undecided would pass whether or not the guard exists. """ - router = self._router( - mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra - ) + router = self._router(mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra) response = await router.async_pre_routing_hook( model="m", request_kwargs={}, messages=self._turn({"type": "text", "text": "what is this"}, IMG_PART) ) @@ -13201,9 +13185,7 @@ class TestClassifierVision: self, mock_router_instance, classifier_type, extra, short_circuit_cause ): """The negative class: same router, same text, no image, and the scorer still decides.""" - router = self._router( - mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra - ) + router = self._router(mock_router_instance, vision={"enabled": True}, classifier_type=classifier_type, **extra) response = await router.async_pre_routing_hook( model="m", request_kwargs={}, messages=[{"role": "user", "content": "what is this"}] ) @@ -13225,13 +13207,7 @@ NON_REASONING_TIERS: Final = { class TestNonReasoningTier: - """The opt-in fifth built-in tier below SIMPLE. - - Two properties carry the feature. A router that did not opt in must be byte-identical to one - built before the tier existed, because the tier set feeds the classifier rubric, the wire enum, - and the savings baseline, all of which move live routing decisions and spend. A router that did - opt in must be able to actually reach the tier and escalate off it. - """ + """The opt-in fifth built-in tier below SIMPLE: inert unless enabled, reachable when it is.""" @staticmethod def _router(mock_router_instance, **overrides) -> ComplexityRouter: @@ -13249,8 +13225,7 @@ class TestNonReasoningTier: ) def test_ladder_gains_a_rung_below_simple_only_when_enabled(self): - """Tier 0 sits at the bottom. Anywhere else and escalation, the savings baseline, and - heuristic_first's 'highest tier' check would all read a different ladder.""" + """Tier 0 sits at the bottom; anywhere else and escalation and the baseline shift.""" enabled: Final = ComplexityRouterConfig( tiers=dict(NON_REASONING_TIERS), enable_non_reasoning_tier=True, @@ -13261,8 +13236,7 @@ class TestNonReasoningTier: assert ComplexityRouterConfig().tier_names() == ("SIMPLE", "MEDIUM", "COMPLEX", "REASONING") def test_default_router_is_unchanged_by_the_tier_existing(self): - """The regression that matters for every already-deployed router: the enum grew a member, - and nothing a four-tier router sends or resolves may change because of it.""" + """The enum grew a member, and nothing a four-tier router sends or resolves may change.""" default: Final = ComplexityRouterConfig() assert default.enable_non_reasoning_tier is False assert "NON_REASONING" not in DEFAULT_COMPLEXITY_CONFIG.tiers @@ -13272,8 +13246,7 @@ class TestNonReasoningTier: @pytest.mark.parametrize("preset", tuple(ClassificationRubric)) def test_rubric_gains_the_bullet_only_when_enabled(self, preset): - """Every preset renders one bullet per active tier, so an unset toggle must leave all four - shipped rubrics byte-identical while an enabled one must actually describe the new tier.""" + """An unset toggle leaves every shipped rubric byte-identical; an enabled one adds a bullet.""" enabled: Final = ComplexityRouterConfig( tiers=dict(NON_REASONING_TIERS), enable_non_reasoning_tier=True, @@ -13286,18 +13259,18 @@ class TestNonReasoningTier: assert "- NON_REASONING" not in off def test_enabled_router_puts_the_tier_on_the_classifier_wire(self, mock_router_instance): - """The response schema's enum is what the classifier may return; without the new label the - tier would be unreachable no matter what the rubric says.""" + """The schema enum bounds what the classifier may return, whatever the rubric says.""" router: Final = self._router(mock_router_instance) enum: Final = router._classifier_response_format["json_schema"]["schema"]["properties"]["tier"]["enum"] assert enum == ["NON_REASONING", "SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] @pytest.mark.asyncio async def test_classifier_verdict_routes_to_the_tier_model(self, mock_router_instance): - """End to end on the LLM path: the classifier names the tier and the request lands on that - tier's model with the decision recording it.""" + """The classifier names the tier and the request lands on that tier's model.""" mock_router_instance.acompletion = AsyncMock(return_value=_llm_response('{"tier": "NON_REASONING"}')) - router: Final = self._router(mock_router_instance, tiers={**NON_REASONING_TIERS, "NON_REASONING": "cheap-relay"}) + router: Final = self._router( + mock_router_instance, tiers={**NON_REASONING_TIERS, "NON_REASONING": "cheap-relay"} + ) response = await router.async_pre_routing_hook( model="test-non-reasoning-router", request_kwargs={}, @@ -13308,9 +13281,10 @@ class TestNonReasoningTier: assert response.routing_decision["cause"] == "llm_classifier" @pytest.mark.asyncio - async def test_a_four_tier_router_ignores_a_non_reasoning_verdict(self, llm_complexity_router, mock_router_instance): - """A classifier that names the tier at a router which never opted in must be an unparseable - reply that falls back, not a silent route to a tier the operator did not configure.""" + async def test_a_four_tier_router_ignores_a_non_reasoning_verdict( + self, llm_complexity_router, mock_router_instance + ): + """Naming the tier at a router that never opted in falls back instead of routing there.""" mock_router_instance.acompletion = AsyncMock(return_value=_llm_response('{"tier": "NON_REASONING"}')) outcome = await llm_complexity_router.aclassify("relay this") assert outcome.tier != ComplexityTier.NON_REASONING @@ -13323,8 +13297,7 @@ class TestNonReasoningTier: assert router._escalate_tier(ComplexityTier.REASONING) == ComplexityTier.REASONING def test_escalation_skips_the_tier_when_unconfigured(self, mock_router_instance): - """SIMPLE must still escalate to MEDIUM rather than to the cheaper new rung, or escalation - would route below the model the caller would otherwise have received.""" + """SIMPLE still escalates to MEDIUM, so escalation never routes below the caller's model.""" router: Final = self._router( mock_router_instance, tiers={"NON_REASONING": "cheap-relay", "SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o"}, @@ -13332,8 +13305,7 @@ class TestNonReasoningTier: assert router._escalate_tier(ComplexityTier.SIMPLE) == ComplexityTier.MEDIUM def test_tier_zero_is_never_the_savings_baseline(self, mock_router_instance): - """Savings are measured against the hardest configured tier. If tier 0 could win that pick, - every enabled router's reported savings would invert.""" + """Savings use the hardest configured tier; tier 0 winning would invert every figure.""" assert self._router(mock_router_instance)._hardest_tier_models() == ("o1-preview",) cheap_only: Final = self._router( mock_router_instance, tiers={"NON_REASONING": "cheap-relay", "SIMPLE": "gpt-4o-mini"} @@ -13356,8 +13328,7 @@ class TestNonReasoningTier: ids=["heuristic", "heuristic_v2", "no_model"], ) def test_unreachable_or_unroutable_configs_are_rejected(self, overrides, expected): - """The toggle is refused wherever it could not do anything: the heuristic scorers cannot - emit the tier, and an unconfigured tier would fall through to the default model.""" + """Refused where it could do nothing: no scorer emits the tier, no pool routes it.""" config: Final = { "tiers": dict(NON_REASONING_TIERS), "enable_non_reasoning_tier": True, @@ -13386,9 +13357,7 @@ class TestNonReasoningTier: ) def test_heuristic_v2_predictions_never_reach_the_new_tier(self, mock_router_instance): - """The bundled artifact is trained on four classes, so its 1-based tier index must keep - mapping onto SIMPLE..REASONING. Reading the enabled ladder here would shift every - prediction down a rung and make REASONING unreachable.""" + """The four-class artifact's 1-based index must keep mapping onto SIMPLE..REASONING.""" router: Final = ComplexityRouter( model_name="v2-router", litellm_router_instance=mock_router_instance, @@ -13399,8 +13368,6 @@ class TestNonReasoningTier: ) outcome = router._classify_with_heuristic_v2("implement a distributed rate limiter under concurrency") assert outcome.tier in TIER_SEVERITY_ORDER - # One probability signal per trained class, named for the tier that class means. A ladder - # shifted by the new rung would relabel all four and lose REASONING off the end. assert tuple(signal.split(":")[1].split("=")[0] for signal in outcome.signals[1:]) == ( "simple", "medium", diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index 5ec9f72c2a1..33bb7283a7e 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -236,11 +236,8 @@ const ClassifierTypeRadios: React.FC<{ ); }; -/** - * The NON_REASONING keys a classifier switch carries forward, or clears. Only the LLM classifier - * can emit the tier, and the switch is disabled elsewhere, so a flag left set under another - * classifier would be an unsaveable config the operator could not undo. - */ +/** The NON_REASONING keys a classifier switch carries forward, or clears for a classifier that + * cannot emit the tier. Leaving them set there is a config the backend refuses on save. */ export const nonReasoningTierFields = ( classifierType: ClassifierType, value: ComplexityRouterConfigValue, diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index c89c130af86..38c2f13b3f9 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -78,10 +78,7 @@ export const DEFAULT_CLASSIFICATION_MODE: ClassificationMode = "every_request"; */ export type ClassificationFrequency = ClassificationMode | "session"; -/** - * NON_REASONING is optional because it is the opt-in fifth tier: a router that never enabled it - * stores no such key, and hydrating one in would send an empty pool the backend rejects. - */ +/** NON_REASONING is optional: a router that never enabled it stores no such key. */ export type ComplexityTiers = { SIMPLE: string[]; MEDIUM: string[]; @@ -362,10 +359,7 @@ export type ComplexityTierLabels = Partial export interface ComplexityRouterConfigValue { tiers: ComplexityTiers; - /** - * Opt into the NON_REASONING tier below SIMPLE. Off means the router keeps the four-tier ladder - * it has always had, so an existing router's rubric and tier decisions cannot move under it. - */ + /** Opt into the NON_REASONING tier below SIMPLE; off keeps the four-tier ladder. */ enable_non_reasoning_tier?: boolean; custom_tier_set?: CustomTierSet; tier_labels?: ComplexityTierLabels; @@ -510,7 +504,6 @@ export const TIER_DESCRIPTIONS: Record< }, }; -/** Every built-in tier name, including the opt-in one, for label and membership checks. */ export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array; export const effectiveTierLabel = (tier: keyof ComplexityTiers, tierLabels: ComplexityTierLabels | undefined): string => @@ -523,9 +516,7 @@ export const DEFAULT_HYBRID_BOUNDARY_MARGIN = 0.03; /** * Tiers the heuristic_first threshold may name. The top tier is excluded because it would short - * circuit every request and leave the classifier unreachable, which the backend rejects. So is - * NON_REASONING, which the backend refuses alongside heuristic_first because the local scorer - * cannot produce it. + * circuit every request and leave the classifier unreachable, which the backend rejects. */ export const HEURISTIC_FIRST_MAX_TIER_KEYS = TIER_ORDER.slice(0, -1); diff --git a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx index 622a460fb79..caadd79039d 100644 --- a/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx +++ b/ui/litellm-dashboard/src/components/add_model/NonReasoningTierToggle.tsx @@ -5,17 +5,11 @@ import { Switch } from "@/components/ui/switch"; import type { ComplexityRouterConfigValue } from "./ComplexityRouterConfig"; -/** - * The opt-in fifth tier. Only offered on the LLM classification method, matching the backend: the - * heuristic scorers cannot produce the tier, so enabling it there would buy a rubric bullet and a - * model pool that no request ever reaches. - */ const NonReasoningTierToggle: React.FC<{ value: ComplexityRouterConfigValue; onChange: (value: ComplexityRouterConfigValue) => void; available: boolean; }> = ({ value, onChange, available }) => { - // Off drops the tier's key rather than leaving the empty pool the backend rejects. const handleToggle = (enabled: boolean): void => { const { NON_REASONING: existingPool, ...keptTiers } = value.tiers; const next: ComplexityRouterConfigValue = { diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts index 2f36ec2f43d..b1edae6b78e 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts @@ -538,8 +538,7 @@ export const buildComplexityRouterConfig = ({ const payload: ComplexityRouterConfigPayload = { tiers, - // Only written when on, and never beside a custom tier set: the backend rejects the two - // together, and an explicit false on a four-tier router would be a key it never carried. + // The backend rejects the flag beside a custom tier set. ...(!customTierSet && enableNonReasoningTier && { enable_non_reasoning_tier: true }), ...(serializedTierModelConfigs && { tier_model_configs: serializedTierModelConfigs }), ...(defaultModel?.trim() && { default_model: defaultModel }), diff --git a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts index 289a5645b51..cd3082b9456 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts @@ -4,13 +4,9 @@ import type { TierModelParams, TierModelParamsByTier } from "./complexity_router export const TIER_ORDER: ComplexityTier[] = ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"]; -/** Every built-in tier name, so a stored NON_REASONING row is recognized as built-in either way. */ export const ALL_BUILT_IN_TIERS: ComplexityTier[] = ["NON_REASONING", ...TIER_ORDER]; -/** - * The ladder one router renders, ascending. NON_REASONING is tier 0 and appears only when enabled, - * which is what keeps an existing four-tier router's form, payload, and rubric unchanged. - */ +/** The ladder one router renders, ascending; NON_REASONING appears only when enabled. */ export const tierOrderFor = (enableNonReasoningTier: boolean | undefined): ComplexityTier[] => enableNonReasoningTier ? ALL_BUILT_IN_TIERS : TIER_ORDER;