From 1669e239854ea393c8b6138cfb1549a47e6c984e Mon Sep 17 00:00:00 2001 From: Abhimanyu Kapur <38531241+akapur99@users.noreply.github.com> Date: Sat, 8 Aug 2026 09:32:49 -0700 Subject: [PATCH] style(auto-router): satisfy new ruff strict rules picked up from staging Merge from litellm_internal_staging brought in RUF023 (sorted __slots__), UP037 (unquoted forward refs where possible), I001 (import ordering), and PLR0402 (proxy_server import aliasing). Auto-fixed with `ruff check --fix`; no behavior change. Also regenerates schema.d.ts to pick up the endpoint docstring wording from the prior commit. --- litellm/proxy/db/autorouter_quality_signals.py | 12 ++++++------ .../test_auto_router_endpoints.py | 16 ++++++++-------- ui/litellm-dashboard/src/lib/http/schema.d.ts | 13 +++++++++++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/litellm/proxy/db/autorouter_quality_signals.py b/litellm/proxy/db/autorouter_quality_signals.py index 268e72b6a0e..36a31d6aaf8 100644 --- a/litellm/proxy/db/autorouter_quality_signals.py +++ b/litellm/proxy/db/autorouter_quality_signals.py @@ -51,13 +51,13 @@ class Turn: """ __slots__ = ( - "session_id", "api_key", - "model", - "started_at", "client_disconnected", - "router_name", "has_client_session_id", + "model", + "router_name", + "session_id", + "started_at", ) def __init__( @@ -83,7 +83,7 @@ class Turn: class CohortSignals: """What one population evidences, and how much of it there was to look at.""" - __slots__ = ("sessions", "escalation_rate_pct", "abandonment_rate_pct") + __slots__ = ("abandonment_rate_pct", "escalation_rate_pct", "sessions") def __init__( self, @@ -97,7 +97,7 @@ class CohortSignals: self.abandonment_rate_pct = abandonment_rate_pct -def rank_models_by_cost(router: "Router", models: Iterable[str]) -> Mapping[str, int]: +def rank_models_by_cost(router: Router, models: Iterable[str]) -> Mapping[str, int]: """Order models cheapest-first, by what one fixed reference request would cost on each. Rank has to come from a request, not from a rate card: a model dearer per output token diff --git a/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py index ce801030aeb..136c1dc15ba 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_auto_router_endpoints.py @@ -21,10 +21,10 @@ from litellm.proxy.management_endpoints.auto_router_endpoints import ( preview_auto_router_routing, ) from litellm.router import Router -from litellm.types.utils import Choices, Message, ModelResponse from litellm.types.management_endpoints.auto_router_endpoints import ( AutoRouterRoutingTestRequest, ) +from litellm.types.utils import Choices, Message, ModelResponse ADMIN = UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN, api_key="sk-test", user_id="admin") @@ -55,7 +55,7 @@ def _request(prompt: str, **config_overrides: object) -> AutoRouterRoutingTestRe async def _route(prompt: str, monkeypatch: pytest.MonkeyPatch, **config_overrides: object): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server monkeypatch.setattr(proxy_server, "llm_router", _router()) return await preview_auto_router_routing( @@ -119,7 +119,7 @@ async def test_tier_model_missing_from_the_proxy_is_reported(monkeypatch: pytest @pytest.mark.asyncio async def test_llm_classifier_call_is_billed_to_the_calling_key(monkeypatch: pytest.MonkeyPatch): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server router = _router() calls: list[dict] = [] @@ -164,7 +164,7 @@ async def test_llm_classifier_call_is_billed_to_the_calling_key(monkeypatch: pyt async def test_a_key_that_cannot_call_the_classifier_model_is_rejected_before_it_is_called( monkeypatch: pytest.MonkeyPatch, config_overrides: dict ): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server router = _router() calls: list[dict] = [] @@ -194,7 +194,7 @@ async def test_a_key_that_cannot_call_the_classifier_model_is_rejected_before_it @pytest.mark.asyncio async def test_a_key_over_its_budget_cannot_run_a_classifier_config(monkeypatch: pytest.MonkeyPatch): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server router = _router() calls: list[dict] = [] @@ -228,7 +228,7 @@ async def test_a_key_over_its_budget_cannot_run_a_classifier_config(monkeypatch: @pytest.mark.asyncio async def test_a_heuristic_config_does_not_need_a_budget(monkeypatch: pytest.MonkeyPatch): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server monkeypatch.setattr(proxy_server, "llm_router", _router()) @@ -249,7 +249,7 @@ async def test_a_heuristic_config_does_not_need_a_budget(monkeypatch: pytest.Mon @pytest.mark.asyncio async def test_no_llm_router_on_the_proxy_is_a_500(monkeypatch: pytest.MonkeyPatch): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server monkeypatch.setattr(proxy_server, "llm_router", None) @@ -261,7 +261,7 @@ async def test_no_llm_router_on_the_proxy_is_a_500(monkeypatch: pytest.MonkeyPat @pytest.mark.asyncio async def test_non_admin_without_a_team_is_rejected(monkeypatch: pytest.MonkeyPatch): - import litellm.proxy.proxy_server as proxy_server + from litellm.proxy import proxy_server monkeypatch.setattr(proxy_server, "llm_router", _router()) diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 6f6e1fe1aa0..b07a1939bc5 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -820,8 +820,17 @@ export interface paths { * model mid-session, and how often they hung up mid-stream, for auto-routed traffic and for * the same keys' directly-addressed traffic. * - * Reads LiteLLM_SpendLogs rather than the per-session rollup, because escalation is a - * question about turn order and the rollup folds order away. + * Both cohorts come from one scan of LiteLLM_SpendLogs rather than the per-session rollup, + * so they cannot drift apart -- same window, same session grouping, same disconnect test -- + * and because escalation is a question about turn order, which the rollup folds away. + * `router_name` is NULL for a directly-addressed request, which is what separates the two + * populations downstream. Abandonment reads `error_information.error_code` rather than + * `status`, because a client disconnect still bills its partial streamed spend as a success + * and so does not show up in `status`. `session_turn_count` counts, per api_key, how many + * rows in the window share a session_id; a fallback uuid minted by the spend writer + * (`_get_session_id_for_spend_log`) is always unique to its one request, so a repeating + * session_id can only have come from the caller, which needs nothing beyond columns every + * deployment already writes, prompt storage on or off. * * The two cohorts self-select, so this is directional evidence and not an experiment: a * deployment that pins its hardest prompts to one model and routes only the easy ones will