diff --git a/litellm/proxy/common_utils/model_deprecation.py b/litellm/proxy/common_utils/model_deprecation.py index eab58300848..ae807ea14c7 100644 --- a/litellm/proxy/common_utils/model_deprecation.py +++ b/litellm/proxy/common_utils/model_deprecation.py @@ -75,23 +75,11 @@ def _mapping_field(deployment: Mapping[str, object], key: str) -> Mapping[str, o def _resolve_deployment_deprecation( deployment: Mapping[str, object], ) -> _ResolvedDeprecation | None: - """Resolve a deployment's deprecation date and successor, preferring its explicit overrides""" + """Resolve a deployment's deprecation date and successor, preferring its explicit overrides field by field""" model_info: Final = _mapping_field(deployment, "model_info") raw_model: Final = _mapping_field(deployment, "litellm_params").get("model") - explicit_successor: Final = _successor_model(model_info.get("successor_model")) - - override: Final = _parse_deprecation_date(model_info.get("deprecation_date")) - if override is not None: - provider: Final = model_info.get("litellm_provider") - return _ResolvedDeprecation( - deprecation_date=override, - litellm_model=raw_model if isinstance(raw_model, str) else None, - litellm_provider=provider if isinstance(provider, str) else None, - successor_model=explicit_successor, - ) - unprefixed: Final = raw_model.split("/", 1)[1] if isinstance(raw_model, str) and "/" in raw_model else None - resolved: Final = next( + from_cost_map: Final = next( ( candidate for candidate in ( @@ -103,9 +91,20 @@ def _resolve_deployment_deprecation( ), None, ) - if resolved is None or explicit_successor is None: - return resolved - return replace(resolved, successor_model=explicit_successor) + successor: Final = _successor_model(model_info.get("successor_model")) or ( + from_cost_map.successor_model if from_cost_map is not None else None + ) + + override: Final = _parse_deprecation_date(model_info.get("deprecation_date")) + if override is not None: + provider: Final = model_info.get("litellm_provider") + return _ResolvedDeprecation( + deprecation_date=override, + litellm_model=raw_model if isinstance(raw_model, str) else None, + litellm_provider=provider if isinstance(provider, str) else None, + successor_model=successor, + ) + return None if from_cost_map is None else replace(from_cost_map, successor_model=successor) def _classify(days_until: int, warn_within_days: int) -> DeprecationStatus: diff --git a/tests/test_litellm/proxy/common_utils/test_model_deprecation.py b/tests/test_litellm/proxy/common_utils/test_model_deprecation.py index 3d97450f3f4..68cd9918faf 100644 --- a/tests/test_litellm/proxy/common_utils/test_model_deprecation.py +++ b/tests/test_litellm/proxy/common_utils/test_model_deprecation.py @@ -480,3 +480,24 @@ class TestSuccessorModel: assert "migrate to `<https://evil.example|new>`" in message assert "" not in message assert message.count("migrate to") == 1 + + def test_should_take_the_cost_map_successor_when_the_deployment_overrides_only_the_date(self, monkeypatch): + monkeypatch.setattr( + litellm, + "model_cost", + {"old-model": {"deprecation_date": "2030-01-01", "successor_model": "new-model"}}, + ) + router = _make_router( + [ + { + "model_name": "old", + "litellm_params": {"model": "old-model"}, + "model_info": {"id": "1", "deprecation_date": "2026-06-10"}, + } + ] + ) + + snapshot = collect_model_deprecations(llm_router=router, warn_within_days=30, today=date(2026, 6, 1)) + + assert snapshot.imminent[0].deprecation_date == date(2026, 6, 10) + assert snapshot.imminent[0].successor_model == "new-model"