From 2ac98ab4cab795658e473ff4d4b0c4c7cf6f2db1 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Fri, 11 Sep 2026 14:26:43 -0700 Subject: [PATCH] fix(router): satisfy calibration lint and schema checks --- litellm/proxy/_lazy_openapi_snapshot.json | 2 +- .../capability_classifier.py | 2 +- .../complexity_router/complexity_router.py | 27 ++++++++++++------- .../add_model/ComplexityRouterConfig.tsx | 5 +--- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/litellm/proxy/_lazy_openapi_snapshot.json b/litellm/proxy/_lazy_openapi_snapshot.json index 53af85baac6..681bbad1dd1 100644 --- a/litellm/proxy/_lazy_openapi_snapshot.json +++ b/litellm/proxy/_lazy_openapi_snapshot.json @@ -18914,7 +18914,7 @@ } } }, - "description": "\n Unified rate-limit error.\n\n Every rate-limit condition surfaced by litellm \u2014 whether it originated from\n an upstream LLM provider, a vendor batch endpoint, or one of litellm's own\n proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,\n max-iterations, etc.) \u2014 is raised as an instance of this class.\n\n The :attr:`category` attribute lets callers distinguish the source. See\n :class:`RateLimitErrorCategory` for the available values.\n " + "description": "\nUnified rate-limit error.\n\nEvery rate-limit condition surfaced by litellm \u2014 whether it originated from\nan upstream LLM provider, a vendor batch endpoint, or one of litellm's own\nproxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,\nmax-iterations, etc.) \u2014 is raised as an instance of this class.\n\nThe :attr:`category` attribute lets callers distinguish the source. See\n:class:`RateLimitErrorCategory` for the available values.\n" }, "500": { "content": { diff --git a/litellm/router_strategy/complexity_router/capability_classifier.py b/litellm/router_strategy/complexity_router/capability_classifier.py index 5adf15cfc19..66ed9c36ed8 100644 --- a/litellm/router_strategy/complexity_router/capability_classifier.py +++ b/litellm/router_strategy/complexity_router/capability_classifier.py @@ -185,7 +185,7 @@ def capability_classifier_response_format( ) -> Mapping[str, object]: """Fresh copy of Switchyard's packaged strict JSON Schema wrapper.""" return ( - {"type": "json_object"} + _RESPONSE_FORMAT_ADAPTER.validate_json('{"type": "json_object"}') if mode == "json_object" else _RESPONSE_FORMAT_ADAPTER.validate_json(_CAPABILITY_CLASSIFIER_RESPONSE_FORMAT_JSON) ) diff --git a/litellm/router_strategy/complexity_router/complexity_router.py b/litellm/router_strategy/complexity_router/complexity_router.py index ed523fd6019..26769e1d3da 100644 --- a/litellm/router_strategy/complexity_router/complexity_router.py +++ b/litellm/router_strategy/complexity_router/complexity_router.py @@ -1032,11 +1032,12 @@ def _with_capability_forecast( } if forecast.calibration_version is None: return enriched - return { + calibrated: Final[StandardLoggingRoutingDecision] = { **enriched, "classifier_calibrated_p_solve": forecast.p_solve, "classifier_calibration_version": forecast.calibration_version, } + return calibrated class _ClassifierCircuitBreaker: @@ -2265,7 +2266,9 @@ class ComplexityRouter(CustomLogger): INTERNAL_CALL_ORIGIN_METADATA_KEY: AUTOROUTER_CLASSIFIER_CALL_ORIGIN, } classifier_call_params: Final = ( - {"reasoning_effort": llm_config.reasoning_effort} if llm_config.reasoning_effort is not None else {} + MappingProxyType({"reasoning_effort": llm_config.reasoning_effort}) + if llm_config.reasoning_effort is not None + else EMPTY_MAPPING ) classifier_payload: Final = ( self._native_classifier_payload(messages_for_call, response_format, encrypted_task) @@ -2274,14 +2277,18 @@ class ComplexityRouter(CustomLogger): {"messages": messages_for_call, "response_format": response_format, **classifier_call_params} ) ) - payload: Final = { - **classifier_payload, - **( - {"max_output_tokens" if encrypted_task is not None else "max_tokens": max_output_tokens} - if max_output_tokens is not None - else {} - ), - } + payload: Final = MappingProxyType( + { + **classifier_payload, + **( + MappingProxyType( + {"max_output_tokens" if encrypted_task is not None else "max_tokens": max_output_tokens} + ) + if max_output_tokens is not None + else EMPTY_MAPPING + ), + } + ) proxy_server_request: Final = { "originating_request_masked": masked_originating_request(request_kwargs), "body": {"model": llm_config.model, **payload}, diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index c005030b68b..299e052cf1a 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -151,10 +151,7 @@ export type ClassifierType = "heuristic" | "heuristic_v2" | "llm" | "heuristic_f * control and payload key, so a new chaining type cannot strip knobs the operator set. */ export const usesLlmClassifier = (classifierType: ClassifierType): boolean => - classifierType === "llm" || - classifierType === "heuristic_first" || - classifierType === "hybrid" || - classifierType === "capability"; + (["llm", "heuristic_first", "hybrid", "capability"] as const).some((type) => type === classifierType); export type ClassifierFallback = "heuristic" | "default_model";