From ad07d7faadcbba9fc44d26b0db5b97688387e279 Mon Sep 17 00:00:00 2001 From: Imgyu Kim Date: Sat, 21 Mar 2026 18:06:44 +0900 Subject: [PATCH 1/3] fix: strip 'openrouter/' prefix from model names (#24234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove early return in get_llm_provider_logic.py that prevented the 'openrouter/' prefix from being stripped. The early return was intended for 'native OpenRouter models' like 'openrouter/free', but no such models exist in the model registry — all OpenRouter models are multi-segment (e.g. 'openrouter/anthropic/claude-3.5-sonnet') and need the prefix stripped before being sent to the OpenRouter API. This regression was introduced in v1.82.3 and caused 400 Bad Request errors for all OpenRouter models. --- .../litellm_core_utils/get_llm_provider_logic.py | 8 -------- tests/local_testing/test_get_llm_provider.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 36218417377..2870a92aabc 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -158,14 +158,6 @@ 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) diff --git a/tests/local_testing/test_get_llm_provider.py b/tests/local_testing/test_get_llm_provider.py index 9b07111ea54..019b938842c 100644 --- a/tests/local_testing/test_get_llm_provider.py +++ b/tests/local_testing/test_get_llm_provider.py @@ -420,3 +420,19 @@ def test_get_llm_provider_use_proxy_arg_true_with_direct_args(): assert provider == "litellm_proxy" assert key == arg_api_key # Should use the argument key assert base == arg_api_base # Should use the argument base + + +def test_openrouter_model_prefix_stripped(): + """ + GH#24234: OpenRouter models like "openrouter/anthropic/claude-3.5-sonnet" + should have the "openrouter/" prefix stripped before being sent to the API. + The API expects "anthropic/claude-3.5-sonnet", not the full prefixed name. + """ + model, provider, _, _ = litellm.get_llm_provider( + model="openrouter/anthropic/claude-3.5-sonnet" + ) + assert provider == "openrouter" + assert model == "anthropic/claude-3.5-sonnet", ( + f"Expected 'anthropic/claude-3.5-sonnet' but got '{model}'. " + "The 'openrouter/' prefix was not stripped." + ) From 302e33ba9226f2a99e4b0412086cd4d37ec7915d Mon Sep 17 00:00:00 2001 From: Imgyu Kim Date: Sat, 21 Mar 2026 18:24:59 +0900 Subject: [PATCH 2/3] fix: preserve prefix only for native OpenRouter models (single-segment) Native models like openrouter/auto, openrouter/free have no extra '/' after the prefix. Provider-routed models like openrouter/anthropic/claude have extra segments and need prefix stripping. Condition: only early-return when model after 'openrouter/' has no '/'. --- litellm/litellm_core_utils/get_llm_provider_logic.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 2870a92aabc..f96c2e47e6a 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -158,6 +158,17 @@ 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/auto" where the + # "openrouter/" prefix is part of the actual model name on the API. + # Only preserve prefix for single-segment native models (no extra '/'), + # not for provider-routed models like "openrouter/anthropic/claude-3.5-sonnet". + if ( + custom_llm_provider == "openrouter" + and model.startswith("openrouter/") + and "/" not in model[len("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) From 64eb6954226ed695ff003623bc4dddb8171594b0 Mon Sep 17 00:00:00 2001 From: Imgyu Kim Date: Sat, 21 Mar 2026 18:32:59 +0900 Subject: [PATCH 3/3] style: apply black formatting --- litellm/litellm_core_utils/get_llm_provider_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index f96c2e47e6a..a79dcfe98c6 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -165,7 +165,7 @@ def get_llm_provider( # noqa: PLR0915 if ( custom_llm_provider == "openrouter" and model.startswith("openrouter/") - and "/" not in model[len("openrouter/"):] + and "/" not in model[len("openrouter/") :] ): return model, custom_llm_provider, dynamic_api_key, api_base