diff --git a/litellm/router_utils/ptu_shares.py b/litellm/router_utils/ptu_shares.py index 64d9cf1dc8a..858e5260b54 100644 --- a/litellm/router_utils/ptu_shares.py +++ b/litellm/router_utils/ptu_shares.py @@ -10,7 +10,7 @@ from collections.abc import Mapping, Sequence from dataclasses import dataclass from typing import Final, Generic, TypeVar -from litellm.litellm_core_utils.ptu_pricing import is_model_info_mapping, parsed_ptu_shares, ptu_terms +from litellm.litellm_core_utils.ptu_pricing import is_model_info_mapping, ptu_terms from litellm.llms.azure.ptu_capacity import PTUCapacity, deployment_ptu_capacity, is_azure_deployment from litellm.router_utils.common_utils import team_may_use_deployment @@ -36,10 +36,17 @@ class PTUShareFilterResult(Generic[_DeploymentT]): def _deployment_shares(deployment: Mapping[str, object]) -> Mapping[str, int] | None: + """The teams a deployment's capacity is split across, else None. + + Only a map registration would accept counts, read through the same terms flat cost accrues + under, so a row that reached the table without its count, rate and start reserves nothing + instead of refusing every other team while charging nobody. + """ model_info: Final = deployment.get("model_info") - if not is_model_info_mapping(model_info): + if not is_model_info_mapping(model_info) or model_info.get("ptu_shares") is None: return None - return parsed_ptu_shares(model_info.get("ptu_shares")) + terms: Final = ptu_terms(model_info) + return None if terms is None else terms.shares def filter_ptu_shared_deployments( diff --git a/tests/unit/router_utils/test_ptu_shares.py b/tests/unit/router_utils/test_ptu_shares.py index 0e1fd165dee..ae4e26bac74 100644 --- a/tests/unit/router_utils/test_ptu_shares.py +++ b/tests/unit/router_utils/test_ptu_shares.py @@ -19,13 +19,15 @@ _GPT6SOL: Final = AZURE_PTU_CAPACITY["gpt-6-sol"] _SHARES: Final = {"team-a": 30, "team-b": 20} -def _shared(model: str = "azure/gpt-4.1", shares: object = _SHARES, deployment_id: str = "shared") -> dict: +def _shared( + model: str = "azure/gpt-4.1", shares: object = _SHARES, deployment_id: str = "shared", ptu_count: int | None = None +) -> dict: return { "model_name": "gpt-4.1-ptu", "litellm_params": {"model": model}, "model_info": { "id": deployment_id, - "ptu_count": 50, + "ptu_count": ptu_count or (sum(shares.values()) if isinstance(shares, dict) else 50), "cost_per_ptu_per_hour": 1.0, "ptu_effective_from": "2026-01-01T00:00:00Z", "ptu_shares": shares, @@ -81,6 +83,23 @@ def test_a_single_team_deployment_and_a_malformed_share_map_are_not_filtered_her assert result.withheld is False +_TERMLESS_SHARED: Final = { + "model_name": "gpt-4.1-ptu", + "litellm_params": {"model": "azure/gpt-4.1"}, + "model_info": {"id": "shared", "ptu_shares": _SHARES}, +} + + +def test_a_share_map_registration_would_refuse_reserves_nothing(): + """Registration refuses ``ptu_shares`` without the count, rate and start they split, and a map + that does not add up to the count, so a row carrying either shape holds nobody's capacity: + every team is served from it and no ceiling applies, the same as before the map was read.""" + short: Final = _shared(shares={"team-a": 30}, ptu_count=50) + for deployment in (_TERMLESS_SHARED, short): + assert filter_ptu_shared_deployments([deployment], "team-z").withheld is False + assert _unaliased_ceiling([deployment], "team-a", "gpt-4.1-ptu") is None + + def test_a_team_is_served_from_its_shared_deployment_first_and_never_from_another_teams_reservation(): """team-b holds a share on the shared deployment and the open one serves everyone, while the deployment reserved for team-a alone is withheld from it the way the router withholds it."""