diff --git a/litellm/utils.py b/litellm/utils.py index ec7bb041fb5..aeeeb989c77 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -11226,7 +11226,7 @@ class ProviderConfigManager: return next( ( item["custom_handler"] - for item in litellm.custom_provider_map + for item in reversed(litellm.custom_provider_map) if item["provider"] == provider and isinstance(item["custom_handler"], CustomLLM) ), None, @@ -11240,7 +11240,7 @@ class ProviderConfigManager: return next( ( item["custom_handler"] - for item in litellm.custom_provider_map + for item in reversed(litellm.custom_provider_map) if item["provider"] == provider and isinstance(item["custom_handler"], BaseVideoConfig) ), None, diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 32063f65765..628aa5b48d1 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1727,6 +1727,33 @@ def test_get_provider_video_config_resolves_a_custom_provider_map_base_video_con assert ProviderConfigManager.get_provider_video_config(model="seedance-1", provider="not-registered") is None +def test_custom_provider_map_lookups_take_the_last_entry_for_a_duplicated_provider( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A provider listed twice in `custom_provider_map` resolves to its last entry, as the per-call loops did + before the lookup was shared.""" + from litellm.llms.custom_llm import CustomLLM + from litellm.llms.openai.videos.transformation import OpenAIVideoConfig + + first_llm: Final = CustomLLM() + last_llm: Final = CustomLLM() + first_video: Final = OpenAIVideoConfig() + last_video: Final = OpenAIVideoConfig() + monkeypatch.setattr( + litellm, + "custom_provider_map", + [ + {"provider": "dup", "custom_handler": first_llm}, + {"provider": "dup", "custom_handler": first_video}, + {"provider": "dup", "custom_handler": last_llm}, + {"provider": "dup", "custom_handler": last_video}, + ], + ) + + assert ProviderConfigManager.get_custom_llm_handler("dup") is last_llm + assert ProviderConfigManager.get_custom_provider_video_config("dup") is last_video + + def test_get_provider_text_to_speech_config_vertex_gemini_skips_cloud_tts(): """Regression for LIT-6501: mapping vertex Gemini TTS params through Google Cloud TTS dropped response_format before the speech_to_completion bridge could honor it."""