refactor: move native OpenRouter check to get_llm_provider before strip

The previous check in _get_openai_compatible_provider_info() ran after
the model name was already split, so it never caught the second
get_llm_provider() call from the anthropic_messages bridge.

Moved the check to get_llm_provider() before the provider-list
stripping, using a pattern-based approach (custom_llm_provider ==
"openrouter" and model.startswith("openrouter/")) instead of a
hardcoded set. This covers all current and future native OpenRouter
models.

Updated tests to verify the bridge double-call scenario with
custom_llm_provider passed through.
This commit is contained in:
Chesars
2026-03-02 16:55:34 -03:00
parent 73de1acdc7
commit 09ef5e67e5
2 changed files with 29 additions and 24 deletions
@@ -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
@@ -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/<model> to
openrouter/<model>. The second call (completion) further strips the
remaining openrouter/ provider prefix and returns <model> — the bare
model ID that should be sent to the OpenRouter API.
The first call (bridge) strips the outer prefix:
openrouter/openrouter/<model> → openrouter/<model>
The second call (completion) receives custom_llm_provider="openrouter"
from the bridge, detects the native model, and preserves it:
openrouter/<model> → openrouter/<model> (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/<model>
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",