Merge pull request #20516 from Chesars/fix/openrouter-native-model-double-strip

fix(adapter): double-stripping of model names with provider-matching prefixes
This commit is contained in:
Cesar Garcia
2026-02-27 18:58:37 -03:00
committed by GitHub
4 changed files with 92 additions and 0 deletions
@@ -3,6 +3,7 @@ from typing import Optional, Tuple
import litellm
from litellm.constants import REPLICATE_MODEL_NAME_WITH_ID_LENGTH
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
from litellm.llms.openrouter.common_utils import NATIVE_OPENROUTER_MODELS
from litellm.secret_managers.main import get_secret, get_secret_str
from ..types.router import LiteLLM_Params
@@ -171,6 +172,12 @@ def get_llm_provider( # noqa: PLR0915
dynamic_api_key=dynamic_api_key,
)
# Check native OpenRouter models before provider_list stripping.
# These models have IDs like "openrouter/free" which would be
# incorrectly stripped to just "free" by the logic below.
if model in NATIVE_OPENROUTER_MODELS:
return model, "openrouter", dynamic_api_key, api_base
# check if llm provider part of model name
if (
+10
View File
@@ -1,5 +1,15 @@
from litellm.llms.base_llm.chat.transformation import BaseLLMException
# Native OpenRouter models whose IDs start with "openrouter/".
# When used via LiteLLM (openrouter/openrouter/free), get_llm_provider()
# must not strip the inner "openrouter/" prefix on its second invocation.
# See: https://github.com/BerriAI/litellm/issues/16353
NATIVE_OPENROUTER_MODELS = {
"openrouter/auto",
"openrouter/free",
"openrouter/bodybuilder",
}
class OpenRouterException(BaseLLMException):
pass
@@ -0,0 +1,75 @@
"""
Tests for native OpenRouter model name handling in get_llm_provider.
OpenRouter's native models (openrouter/auto, openrouter/free,
openrouter/bodybuilder) should not have their "openrouter/" prefix
stripped when passed to get_llm_provider(), since that prefix is part
of the actual model ID on OpenRouter's API.
"""
import pytest
import litellm
class TestNativeOpenRouterModelsNotStripped:
"""get_llm_provider must preserve native OpenRouter model names."""
@pytest.mark.parametrize(
"model",
[
"openrouter/auto",
"openrouter/free",
"openrouter/bodybuilder",
],
)
def test_native_model_not_stripped(self, model):
"""Native OpenRouter model IDs are returned as-is."""
result_model, provider, _, _ = litellm.get_llm_provider(model=model)
assert result_model == model
assert provider == "openrouter"
@pytest.mark.parametrize(
"model,expected_model",
[
("openrouter/openrouter/free", "openrouter/free"),
("openrouter/openrouter/auto", "openrouter/auto"),
("openrouter/openrouter/bodybuilder", "openrouter/bodybuilder"),
],
)
def test_double_prefixed_model_strips_once_to_native(self, model, expected_model):
"""openrouter/openrouter/free strips to openrouter/free (not further)."""
result_model, provider, _, _ = litellm.get_llm_provider(model=model)
assert result_model == expected_model
assert provider == "openrouter"
@pytest.mark.parametrize(
"model,expected_model",
[
("openrouter/openrouter/free", "openrouter/free"),
("openrouter/openrouter/auto", "openrouter/auto"),
],
)
def test_full_round_trip_no_double_strip(self, model, expected_model):
"""Simulates the bridge flow: two consecutive get_llm_provider calls."""
# First call (in adapter/handler)
model_after_first, provider, _, _ = litellm.get_llm_provider(model=model)
assert model_after_first == expected_model
# Second call (inside litellm.completion)
model_after_second, provider2, _, _ = litellm.get_llm_provider(
model=model_after_first
)
# Should stay as native model, not stripped further
assert model_after_second == expected_model
assert provider2 == "openrouter"
def test_regular_openrouter_model_still_strips_normally(self):
"""Non-native models like openrouter/anthropic/claude-3-haiku still strip normally."""
model, provider, _, _ = litellm.get_llm_provider(
model="openrouter/anthropic/claude-3-haiku"
)
assert provider == "openrouter"
# Should strip the openrouter/ prefix
assert model == "anthropic/claude-3-haiku"