From 8b518490025ebb1fc1e7b2bc9da5d5a22d79e3d7 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:20:40 -0700 Subject: [PATCH] fix(lite): round Codex's default level down and describe a team's shared-name model from its own deployments Move the reasoning strength helper to litellm_core_utils so lite and the Codex catalog import it without the SDK or the router, and let make lint-format-check-changed diff the working tree so a staged rename passes. --- Makefile | 2 +- .../reasoning_effort_strength.py | 0 .../proxy/common_utils/codex_model_catalog.py | 7 +- litellm/proxy/proxy_server.py | 2 +- litellm/router.py | 15 ++- .../reasoning_effort_capability.py | 6 +- tests/unit/proxy/client/cli/test_agents.py | 2 +- .../common_utils/test_codex_model_catalog.py | 15 +-- .../proxy_server/test_routes_model_info.py | 103 +++++++++++++----- .../proxy/proxy_server/test_routes_models.py | 52 ++++++--- .../public_v1/test_model_hub.py | 2 +- .../test_reasoning_effort_capability.py | 2 +- tests/unit/test_default_branch.py | 11 ++ 13 files changed, 146 insertions(+), 73 deletions(-) rename litellm/{router_utils => litellm_core_utils}/reasoning_effort_strength.py (100%) diff --git a/Makefile b/Makefile index 937873f8835..48df3c8e215 100644 --- a/Makefile +++ b/Makefile @@ -153,7 +153,7 @@ lint-install: # the same set CI's ':(glob)litellm/**/*.py' selects. lint-format-check-changed: $(LINT_DEP_INSTALL) $(LINT_DEP_BASE) @base_ref=$$($(RESOLVE_BASE)) && \ - changed=$$(git diff --name-only --diff-filter=ACMR "$$base_ref...HEAD" -- 'litellm/*.py') && \ + changed=$$(git diff --name-only --diff-filter=ACMR --merge-base "$$base_ref" -- 'litellm/*.py') && \ files=$$(printf '%s\n' "$$changed" | grep -v '^litellm/enterprise/' || true) || exit $$?; \ if [ -z "$$files" ]; then \ echo "No changed litellm Python files to format-check."; \ diff --git a/litellm/router_utils/reasoning_effort_strength.py b/litellm/litellm_core_utils/reasoning_effort_strength.py similarity index 100% rename from litellm/router_utils/reasoning_effort_strength.py rename to litellm/litellm_core_utils/reasoning_effort_strength.py diff --git a/litellm/proxy/common_utils/codex_model_catalog.py b/litellm/proxy/common_utils/codex_model_catalog.py index 3da9ca19e42..1c58670c3a9 100644 --- a/litellm/proxy/common_utils/codex_model_catalog.py +++ b/litellm/proxy/common_utils/codex_model_catalog.py @@ -31,7 +31,7 @@ from packaging.version import InvalidVersion, Version from pydantic import ConfigDict, Field, StringConstraints, TypeAdapter, ValidationError from litellm._logging import verbose_proxy_logger -from litellm.router_utils.reasoning_effort_strength import nearest_declared_reasoning_effort +from litellm.litellm_core_utils.reasoning_effort_strength import nearest_declared_reasoning_effort from litellm.types.llms.base_model import LiteLLMBaseModel if TYPE_CHECKING: @@ -282,12 +282,13 @@ def codex_reasoning_levels( a version that does not parse, only the efforts every release knows are offered. The default is set whenever a level is offered, because Codex reads a missing one as `none`: `none` itself when the group accepts it, so a user who picks no level sends what Codex sent before, else medium, else the - nearest stronger declared level, else the first one.""" + nearest weaker declared level, so picking no level never costs more reasoning than Codex's usual medium, + else the weakest declared level, else the first one.""" parses_any: Final = _parses_custom_efforts(client_version) offered: Final = tuple(effort for effort in efforts if parses_any or effort in _EFFORTS_EVERY_CODEX_PARSES) if not offered: return (), None - nearest: Final = nearest_declared_reasoning_effort(_PREFERRED_DEFAULT_EFFORT, offered) + nearest: Final = nearest_declared_reasoning_effort(_PREFERRED_DEFAULT_EFFORT, offered, "down") levels: Final = tuple( CodexReasoningLevel(effort=effort, description=f"Sends reasoning_effort={effort} upstream") for effort in offered diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index bd1d1cca606..f2d254ddec1 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -17986,7 +17986,7 @@ def get_model_group_info( **_model_group_info.model_dump(), **model_group_custom_fields( llm_router.get_model_list_of_routed_group( - resolve_model_group_alias(llm_router.model_group_alias, model) or model + resolve_model_group_alias(llm_router.model_group_alias, model) or model, team_id ) ), } diff --git a/litellm/router.py b/litellm/router.py index 522464986ee..d51073b57e8 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -12635,11 +12635,7 @@ class Router: serves_team_deployments: Final = team_id is not None and bool( self._team_deployments_named(team_id, model_group) ) - model_list: Final = ( - self.get_all_deployments(model_name=model_group, team_id=team_id) - if serves_team_deployments - else self.get_model_list_of_routed_group(model_group) - ) + model_list: Final = self.get_model_list_of_routed_group(model_group, team_id) model_group_description: Final[str | None] = next( (d for d in (_configured_model_info(m).get("description") for m in model_list) if isinstance(d, str)), None, @@ -13997,17 +13993,20 @@ class Router: lambda name: self.wildcard_route_deployments(name, team_id), ) - def get_model_list_of_routed_group(self, model_group: str) -> list[DeploymentTypedDict]: + def get_model_list_of_routed_group(self, model_group: str, team_id: str | None = None) -> list[DeploymentTypedDict]: """ The deployments a request the router has already resolved to model_group is - served from: the ones named model_group, the routing group of that name, or - the wildcard route matching it when neither exists. + served from: team_id's own deployments under that public name when the team has + any, else the ones named model_group, the routing group of that name, or the + wildcard route matching it when neither exists. Unlike get_model_list, model_group's own model_group_alias entry is not followed. The router resolves an alias exactly once, so a group reached as an alias target is served by its own deployments, never by a second hop: in the chain X -> T -> U a request to X is served from T's deployments. """ + if team_id is not None and self._team_deployments_named(team_id, model_group): + return self.get_all_deployments(model_name=model_group, team_id=team_id) named: Final = [ *self._get_all_deployments(model_name=model_group), *self.get_model_list_from_routing_groups(model_name=model_group), diff --git a/litellm/router_utils/reasoning_effort_capability.py b/litellm/router_utils/reasoning_effort_capability.py index c802c7b37ec..2bd4d82b1ff 100644 --- a/litellm/router_utils/reasoning_effort_capability.py +++ b/litellm/router_utils/reasoning_effort_capability.py @@ -33,13 +33,13 @@ import litellm from litellm._logging import verbose_logger from litellm.litellm_core_utils.core_helpers import as_str_mapping from litellm.litellm_core_utils.get_supported_openai_params import get_supported_openai_params -from litellm.litellm_core_utils.reasoning_effort_utils import reasoning_effort_from_thinking -from litellm.llms.anthropic.common_utils import AnthropicModelInfo, validated_object_mapping -from litellm.router_utils.reasoning_effort_strength import ( +from litellm.litellm_core_utils.reasoning_effort_strength import ( REASONING_EFFORT_STRENGTH_RANK, ReasoningEffortRounding, nearest_declared_reasoning_effort, ) +from litellm.litellm_core_utils.reasoning_effort_utils import reasoning_effort_from_thinking +from litellm.llms.anthropic.common_utils import AnthropicModelInfo, validated_object_mapping from litellm.types.llms.anthropic import AnthropicThinkingParam from litellm.types.llms.openai import REASONING_EFFORT diff --git a/tests/unit/proxy/client/cli/test_agents.py b/tests/unit/proxy/client/cli/test_agents.py index 8df08da1228..1abb66f8396 100644 --- a/tests/unit/proxy/client/cli/test_agents.py +++ b/tests/unit/proxy/client/cli/test_agents.py @@ -868,7 +868,7 @@ class TestCodexModelSync: "effort": "low", "description": "Sends reasoning_effort=low upstream", } - assert entry["default_reasoning_level"] == "high" + assert entry["default_reasoning_level"] == "low" def test_known_models_and_unknown_or_empty_groups_keep_their_entries(self, tmp_path): listing = self._listing(self._row("gpt-5.5"), self._row("unknown"), self._row("empty")) diff --git a/tests/unit/proxy/common_utils/test_codex_model_catalog.py b/tests/unit/proxy/common_utils/test_codex_model_catalog.py index c6376990ab9..3819eb38f55 100644 --- a/tests/unit/proxy/common_utils/test_codex_model_catalog.py +++ b/tests/unit/proxy/common_utils/test_codex_model_catalog.py @@ -139,7 +139,8 @@ _ABSENT = object() @pytest.mark.parametrize( ("efforts", "client_version", "levels", "default"), [ - (("low", "high", "max"), "0.165.0", ["low", "high", "max"], "high"), + (("low", "high", "max"), "0.165.0", ["low", "high", "max"], "low"), + (("high", "xhigh", "max"), "0.165.0", ["high", "xhigh", "max"], "high"), ( ("none", "low", "medium", "high", "xhigh", "max"), "0.165.0", @@ -150,9 +151,9 @@ _ABSENT = object() (("none", "low", "max"), "0.137.0", ["none", "low"], "none"), (("none",), "0.165.0", ["none"], "none"), (("low", "turbo"), "0.165.0", ["low", "turbo"], "low"), - (("low", "high", "max"), "0.138.0", ["low", "high", "max"], "high"), - (("low", "high", "max"), "0.137.9", ["low", "high"], "high"), - (("low", "high", "max"), "0.138.0-alpha.2", ["low", "high"], "high"), + (("low", "high", "max"), "0.138.0", ["low", "high", "max"], "low"), + (("low", "high", "max"), "0.137.9", ["low", "high"], "low"), + (("low", "high", "max"), "0.138.0-alpha.2", ["low", "high"], "low"), (("minimal", "max"), "not-a-version", ["minimal"], "minimal"), (("low", "turbo"), None, ["low"], "low"), (("max",), "0.137.0", [], _ABSENT), @@ -652,12 +653,12 @@ def test_model_list_body_offers_what_model_group_info_reports_for_each_listed_mo assert router.get_model_group_info("declared").supported_reasoning_efforts == ("low", "high", "max") assert offered("0.165.0") == { - "declared": (["low", "high", "max"], "high"), + "declared": (["low", "high", "max"], "low"), "unknown": ([], None), "non-reasoning": ([], None), - "hidden-declared": (["low", "high", "max"], "high"), + "hidden-declared": (["low", "high", "max"], "low"), } - assert offered("0.137.0")["declared"] == (["low", "high"], "high") + assert offered("0.137.0")["declared"] == (["low", "high"], "low") def test_catalog_rows_without_a_router_carry_only_the_listing(): diff --git a/tests/unit/proxy/proxy_server/test_routes_model_info.py b/tests/unit/proxy/proxy_server/test_routes_model_info.py index 71ea394a4fd..b2b93fcf6ef 100644 --- a/tests/unit/proxy/proxy_server/test_routes_model_info.py +++ b/tests/unit/proxy/proxy_server/test_routes_model_info.py @@ -221,57 +221,39 @@ def test_deleted_model_params_stay_absent_after_reload_and_another_save( from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo, updateDeployment, updateLiteLLMParams configured: Final = { - "model": "openai/test-model", - "api_key": "synthetic-runtime-secret", - "use_litellm_proxy": True, - "use_in_pass_through": True, - "use_chat_completions_api": False, - "litellm_image_enabled": True, - "timeout": 30, - "input_cost_per_token": 0.003, - "team_id": "param-only-team", - "temperature": 0, - "metadata": {}, + "model": "openai/test-model", "api_key": "synthetic-runtime-secret", + "use_litellm_proxy": True, "use_in_pass_through": True, "use_chat_completions_api": False, + "litellm_image_enabled": True, "timeout": 30, "input_cost_per_token": 0.003, + "team_id": "param-only-team", "temperature": 0, "metadata": {}, } original: Final = Deployment( - model_name="editor-model", - litellm_params=LiteLLM_Params.model_validate(configured), + model_name="editor-model", litellm_params=LiteLLM_Params.model_validate(configured), model_info=ModelInfo(id="editor-model", team_id="owner-team", access_groups=["retained-group"]), ) removals: Final = ("litellm_image_enabled", "use_litellm_proxy", "input_cost_per_token", "team_id") deleted: Final = update_db_model( db_model=original, updated_patch=PatchModelRequest( - litellm_params=updateLiteLLMParams(max_retries=0), - litellm_params_to_remove=removals, + litellm_params=updateLiteLLMParams(max_retries=0), litellm_params_to_remove=removals, model_info=ModelInfo(id="editor-model", input_cost_per_token=0.003), ), ) saved_params: Final = json.loads(deleted["litellm_params"]) saved_info: Final = json.loads(deleted["model_info"]) - assert saved_params == { - **{key: value for key, value in configured.items() if key not in removals}, - "max_retries": 0, - } + assert saved_params == {**{key: value for key, value in configured.items() if key not in removals}, "max_retries": 0} assert "input_cost_per_token" not in saved_info assert saved_info["team_id"] == "owner-team" assert saved_info["access_groups"] == ["retained-group"] reloaded: Final = Deployment.model_validate( {"model_name": original.model_name, "litellm_params": saved_params, "model_info": saved_info} ) - router: Final = litellm.Router( - model_list=[ - { - "model_name": "editor-model", - "litellm_params": saved_params, - "model_info": {"id": "editor-model", "db_model": True}, - } - ] - ) + router: Final = litellm.Router(model_list=[{ + "model_name": "editor-model", "litellm_params": saved_params, + "model_info": {"id": "editor-model", "db_model": True}, + }]) stored_params: Final = {**saved_params, "api_key": "synthetic-stored-secret"} mock_prisma.db.litellm_proxymodeltable.find_unique.return_value = LiteLLM_ProxyModelTable( - model_id="editor-model", - model_name="editor-model", + model_id="editor-model", model_name="editor-model", litellm_params=json.dumps(stored_params) if encoded else stored_params, model_info={"id": "editor-model"}, ) @@ -1051,6 +1033,67 @@ def test_model_group_info_describes_a_team_model_under_the_public_name_its_team_ assert groups[_BYOK_PUBLIC_NAME]["supported_reasoning_efforts"] == efforts +@pytest.mark.parametrize( + ("team_id", "providers", "efforts", "custom_tags"), + [ + (_BYOK_TEAM_ID, ["fireworks_ai"], ["low", "high", "max"], ["team-tag"]), + ("team-other", ["openai"], [], ["global-tag"]), + ], +) +def test_model_group_info_describes_the_deployments_a_shared_name_routes_the_team_to( + client, auth_as, monkeypatch, team_id: str, providers: list[str], efforts: list[str], custom_tags: list[str] +): + """A team model whose public name is also a global model's name routes the team's requests to the + team's deployment, so the team's row must describe that deployment in every field, while another + team still sees the global model.""" + from litellm.proxy._types import LitellmUserRoles + from litellm.proxy.agent_endpoints import model_list_helpers + + router: Final = litellm.Router( + model_list=[ + { + "model_name": _BYOK_PUBLIC_NAME, + "litellm_params": {"model": "openai/gpt-4.1-mini"}, + "model_info": {"supports_reasoning": False, "custom_tags": ["global-tag"]}, + }, + { + "model_name": _BYOK_INTERNAL_NAME, + "litellm_params": {"model": "fireworks_ai/accounts/fireworks/models/kimi-k3"}, + "model_info": { + "team_id": _BYOK_TEAM_ID, + "team_public_model_name": _BYOK_PUBLIC_NAME, + "supports_reasoning": True, + "reasoning_effort_levels": ["low", "high", "max"], + "custom_tags": ["team-tag"], + }, + }, + ] + ) + + async def append_agents_to_model_group(*, model_groups, user_api_key_dict): + return model_groups + + monkeypatch.setattr(proxy_server, "llm_router", router) + monkeypatch.setattr(proxy_server, "llm_model_list", router.model_list) + monkeypatch.setattr(proxy_server, "user_model", None) + monkeypatch.setattr(proxy_server, "general_settings", {}) + monkeypatch.setattr(proxy_server, "prisma_client", None) + monkeypatch.setattr( + model_list_helpers, "append_agents_to_model_group", AsyncMock(side_effect=append_agents_to_model_group) + ) + + with auth_as(LitellmUserRoles.INTERNAL_USER, user_id=None, team_id=team_id, team_models=[_BYOK_PUBLIC_NAME]): + response: Final = client.get("/model_group/info") + + assert response.status_code == 200 + group: Final = {group["model_group"]: group for group in response.json()["data"]}[_BYOK_PUBLIC_NAME] + assert (group["providers"], group["supported_reasoning_efforts"], group["custom_tags"]) == ( + providers, + efforts, + custom_tags, + ) + + # --------------------------------------------------------------------------- # GET /v2/model/info?exclude_auto_routers # --------------------------------------------------------------------------- diff --git a/tests/unit/proxy/proxy_server/test_routes_models.py b/tests/unit/proxy/proxy_server/test_routes_models.py index 249fcbf16bd..40a9c71f15f 100644 --- a/tests/unit/proxy/proxy_server/test_routes_models.py +++ b/tests/unit/proxy/proxy_server/test_routes_models.py @@ -22,7 +22,9 @@ from litellm.types.router import DeploymentModelListingInfo from .conftest import normalize # type: ignore[import-not-found] -def _stub_model_info_response(model_id: str = "gpt-4", provider: str = "openai") -> dict: +def _stub_model_info_response( + model_id: str = "gpt-4", provider: str = "openai" +) -> dict: return { "id": model_id, "object": "model", @@ -64,7 +66,9 @@ def patched_models(monkeypatch): def _fake_create_model_info_response(model_id, provider="openai", **kwargs): return _stub_model_info_response(model_id=model_id, provider=provider) - monkeypatch.setattr(proxy_utils, "create_model_info_response", _fake_create_model_info_response) + monkeypatch.setattr( + proxy_utils, "create_model_info_response", _fake_create_model_info_response + ) monkeypatch.setattr(proxy_utils, "validate_model_access", lambda **kwargs: None) @@ -103,7 +107,9 @@ def test_get_models_happy_path(client, auth_as, patched_models, path): @pytest.mark.parametrize("path", ["/v1/models", "/models"]) -def test_get_models_anthropic_format_when_header_present(client, auth_as, patched_models, path): +def test_get_models_anthropic_format_when_header_present( + client, auth_as, patched_models, path +): """Pins: ``GET /v1/models`` returns the Anthropic-native models shape when the caller sends an ``anthropic-version`` header (Claude Code gateway discovery), while the default OpenAI shape is unchanged without it.""" @@ -123,7 +129,9 @@ def test_get_models_anthropic_format_when_header_present(client, auth_as, patche @pytest.mark.parametrize("path", ["/v1/models", "/models"]) -def test_anthropic_format_exposes_token_limits(client, auth_as, patched_models, monkeypatch, path): +def test_anthropic_format_exposes_token_limits( + client, auth_as, patched_models, monkeypatch, path +): """Claude Code sizes requests off the listing, so the Anthropic-native entries carry the same token limits the OpenAI listing resolves, with the output budget named max_tokens as the Messages API names it.""" @@ -138,7 +146,9 @@ def test_anthropic_format_exposes_token_limits(client, auth_as, patched_models, "max_output_tokens": 64000, } - monkeypatch.setattr(proxy_utils, "create_model_info_response", _create_model_info_response) + monkeypatch.setattr( + proxy_utils, "create_model_info_response", _create_model_info_response + ) with auth_as(): response = client.get(path, headers={"anthropic-version": "2023-06-01"}) @@ -213,7 +223,9 @@ def test_anthropic_format_uses_configured_display_name(client, auth_as, patched_ @pytest.mark.parametrize("params", [{}, {"scope": "expand"}]) -def test_anthropic_display_name_resolved_via_internal_team_key(client, auth_as, patched_models, monkeypatch, params): +def test_anthropic_display_name_resolved_via_internal_team_key( + client, auth_as, patched_models, monkeypatch, params +): """For a team-scoped row the configured display name must be looked up by the internal routing key while the entry itself is keyed by the public name, so the clean name lands on the id the client actually sees.""" @@ -246,10 +258,14 @@ def test_anthropic_display_name_resolved_via_internal_team_key(client, auth_as, "get_available_models_for_user", _fake_get_available_models_for_user, ) - monkeypatch.setattr(model_checks, "get_complete_model_list", lambda **kwargs: [internal_name]) + monkeypatch.setattr( + model_checks, "get_complete_model_list", lambda **kwargs: [internal_name] + ) with auth_as(): - response = client.get("/v1/models", params=params, headers={"anthropic-version": "2023-06-01"}) + response = client.get( + "/v1/models", params=params, headers={"anthropic-version": "2023-06-01"} + ) assert response.status_code == 200 (entry,) = response.json()["data"] @@ -290,7 +306,9 @@ def test_get_model_by_id_not_found(client, auth_as, patched_models, path): @pytest.mark.parametrize("params", [{}, {"scope": "expand"}]) -def test_anthropic_format_returns_public_team_model_name(client, auth_as, patched_models, monkeypatch, params): +def test_anthropic_format_returns_public_team_model_name( + client, auth_as, patched_models, monkeypatch, params +): """Regression: the Anthropic-native listing must go through the same team name translation as the OpenAI listing, so a caller never sees the internal ``model_name_{team_id}_{uuid}`` routing key.""" @@ -320,10 +338,14 @@ def test_anthropic_format_returns_public_team_model_name(client, auth_as, patche "get_available_models_for_user", _fake_get_available_models_for_user, ) - monkeypatch.setattr(model_checks, "get_complete_model_list", lambda **kwargs: [internal_name]) + monkeypatch.setattr( + model_checks, "get_complete_model_list", lambda **kwargs: [internal_name] + ) with auth_as(): - response = client.get("/v1/models", params=params, headers={"anthropic-version": "2023-06-01"}) + response = client.get( + "/v1/models", params=params, headers={"anthropic-version": "2023-06-01"} + ) assert response.status_code == 200 assert [m["id"] for m in response.json()["data"]] == ["gpt-4-team"] @@ -354,9 +376,7 @@ def test_anthropic_format_lists_claude_code_view_ids_for_claude_code( patched_models.model_group_alias = {} patched_models.has_model_id.return_value = False - patched_models.get_candidate_model_ids_for_route.side_effect = lambda name, team_id=None: ( - frozenset({name}) if name in ("gpt-4", "claude-sonnet") else frozenset() - ) + patched_models.get_candidate_model_ids_for_route.side_effect = lambda name, team_id=None: frozenset({name}) if name in ("gpt-4", "claude-sonnet") else frozenset() monkeypatch.setattr(proxy_utils, "create_model_info_response", _create_model_info_response) with auth_as(): @@ -376,9 +396,7 @@ def test_anthropic_format_lists_claude_code_view_ids_for_claude_code( def test_anthropic_format_keeps_served_ids_for_other_anthropic_clients(client, auth_as, patched_models, path): """An Anthropic SDK asking for the vendor shape gets the served ids: the view is Claude Code's alone.""" with auth_as(): - response = client.get( - path, headers={"anthropic-version": "2023-06-01", "user-agent": "anthropic-sdk-python/0.40"} - ) + response = client.get(path, headers={"anthropic-version": "2023-06-01", "user-agent": "anthropic-sdk-python/0.40"}) assert response.status_code == 200 assert [m["id"] for m in response.json()["data"]] == ["gpt-4", "claude-sonnet"] diff --git a/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py b/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py index ddb4b08ee9e..9db5175741b 100644 --- a/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py +++ b/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py @@ -29,7 +29,7 @@ class _FakeRouter: def get_model_group_info(self, model_group: str, team_id: str | None = None) -> ModelGroupInfo | None: return self.infos.get(model_group) - def get_model_list_of_routed_group(self, model_group: str) -> list[DeploymentTypedDict]: + def get_model_list_of_routed_group(self, model_group: str, team_id: str | None = None) -> list[DeploymentTypedDict]: return [] diff --git a/tests/unit/router_utils/test_reasoning_effort_capability.py b/tests/unit/router_utils/test_reasoning_effort_capability.py index f36943f6d1a..eaa0cfed561 100644 --- a/tests/unit/router_utils/test_reasoning_effort_capability.py +++ b/tests/unit/router_utils/test_reasoning_effort_capability.py @@ -16,7 +16,7 @@ from litellm.router_utils.reasoning_effort_capability import ( resolve_supported_reasoning_efforts, thinking_budget_effort, ) -from litellm.router_utils.reasoning_effort_strength import REASONING_EFFORT_STRENGTH_ORDER +from litellm.litellm_core_utils.reasoning_effort_strength import REASONING_EFFORT_STRENGTH_ORDER class TestDeploymentIsCatalogMapped: diff --git a/tests/unit/test_default_branch.py b/tests/unit/test_default_branch.py index bb04ea2a0fa..577b1126976 100644 --- a/tests/unit/test_default_branch.py +++ b/tests/unit/test_default_branch.py @@ -119,6 +119,17 @@ def test_explicit_base_works_without_remote_access( assert "No changed litellm Python files" in checked.stdout +def test_format_check_reads_staged_renames(remote_and_clone: tuple[Path, Path]) -> None: + _, repo = remote_and_clone + (repo / "litellm" / "before_move.py").write_text("value = 2\n") + _commit(repo, "add module") + _git(repo, "mv", "litellm/before_move.py", "litellm/after_move.py") + checked: Final = _make(repo, "lint-format-check-changed", "BASE_REF=origin/release_branch", "UV_RUN=echo") + assert checked.returncode == 0, checked.stdout + checked.stderr + assert "litellm/after_move.py" in checked.stdout + assert "before_move.py" not in checked.stdout + + def _freshness(repo: Path, *args: str) -> subprocess.CompletedProcess[str]: return subprocess.run( [