diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index cf78ec4615..82ae5a9ff0 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -158,6 +158,14 @@ def get_llm_provider( # noqa: PLR0915 ): # handle scenario where model="azure/*" and custom_llm_provider="azure" model = custom_llm_provider + "/" + model + # Native OpenRouter models have IDs like "openrouter/free" where the + # "openrouter/" prefix is part of the actual model name on the API. + # When called from a bridge (e.g. anthropic_messages adapter), + # custom_llm_provider is already resolved, so return early to prevent + # the provider-list stripping below from removing the prefix. + if custom_llm_provider == "openrouter" and model.startswith("openrouter/"): + return model, custom_llm_provider, dynamic_api_key, api_base + if api_key and api_key.startswith("os.environ/"): dynamic_api_key = get_secret_str(api_key) @@ -504,15 +512,6 @@ def _get_openai_compatible_provider_info( # noqa: PLR0915 custom_llm_provider = model.split("/", 1)[0] model = model.split("/", 1)[1] - # If the provider is openrouter and the remaining model name still starts - # with "openrouter/", that inner prefix is part of the actual model ID on - # the OpenRouter API (e.g. openrouter/openrouter/aurora-alpha → - # model="openrouter/aurora-alpha"). Return immediately so the prefix is - # not stripped a second time. - if custom_llm_provider == "openrouter" and model.startswith("openrouter/"): - dynamic_api_key = api_key or get_secret_str("OPENROUTER_API_KEY") - return model, custom_llm_provider, dynamic_api_key, api_base - # Check JSON providers FIRST (before hardcoded ones) from litellm.llms.openai_like.dynamic_config import create_config_class from litellm.llms.openai_like.json_loader import JSONProviderRegistry diff --git a/tests/test_litellm/llms/openrouter/test_openrouter_provider_routing.py b/tests/test_litellm/llms/openrouter/test_openrouter_provider_routing.py index 64fd77954a..72cf2eec37 100644 --- a/tests/test_litellm/llms/openrouter/test_openrouter_provider_routing.py +++ b/tests/test_litellm/llms/openrouter/test_openrouter_provider_routing.py @@ -44,31 +44,37 @@ class TestOpenRouterNativeModelRouting: assert result_model == expected_model @pytest.mark.parametrize( - "input_model,expected_first,expected_second", + "input_model", [ - # After the first call strips outer prefix: openrouter/openrouter/aurora-alpha - # → openrouter/aurora-alpha. A second call on that result splits at the - # first "/" giving provider=openrouter, model=aurora-alpha — which is the - # correct model ID to send to the OpenRouter API. - ("openrouter/openrouter/aurora-alpha", "openrouter/aurora-alpha", "aurora-alpha"), - ("openrouter/openrouter/auto", "openrouter/auto", "auto"), + "openrouter/openrouter/aurora-alpha", + "openrouter/openrouter/auto", + "openrouter/openrouter/free", + "openrouter/openrouter/some-future-model", ], ) - def test_no_double_strip_on_second_call(self, input_model, expected_first, expected_second): + def test_bridge_double_call_preserves_native_model(self, input_model): """Simulates two consecutive get_llm_provider calls (bridge → completion). - The first call (bridge) converts openrouter/openrouter/ to - openrouter/. The second call (completion) further strips the - remaining openrouter/ provider prefix and returns — the bare - model ID that should be sent to the OpenRouter API. + The first call (bridge) strips the outer prefix: + openrouter/openrouter/ → openrouter/ + + The second call (completion) receives custom_llm_provider="openrouter" + from the bridge, detects the native model, and preserves it: + openrouter/ → openrouter/ (no further stripping) """ + # First call: bridge resolves provider model_first, provider, _, _ = litellm.get_llm_provider(model=input_model) assert provider == "openrouter" - assert model_first == expected_first + expected_model = input_model.split("/", 1)[1] # openrouter/ + assert model_first == expected_model - model_second, provider2, _, _ = litellm.get_llm_provider(model=model_first) + # Second call: completion receives model + custom_llm_provider from bridge + model_second, provider2, _, _ = litellm.get_llm_provider( + model=model_first, + custom_llm_provider="openrouter", + ) assert provider2 == "openrouter" - assert model_second == expected_second + assert model_second == expected_model # preserved, not stripped further @pytest.mark.parametrize( "input_model,expected_model",