fix(proxy): resolve credential routing provider from deployment config

When the user-facing model name has no provider prefix (e.g.
"claude-sonnet-4.6" instead of "bedrock/claude-sonnet-4.6"),
_apply_credential_overrides_from_model_config sets provider to None.
The fallback in _extract_credential_from_entry then picks the first
entry in defaultconfig dict order instead of the correct provider.

Resolve the provider from the deployment's litellm_params.model
(e.g. "bedrock/us.anthropic.claude-sonnet-4-6" -> "bedrock") when
the request model name has no "/" prefix.

Fixes #27516
This commit is contained in:
Jonathan Wrede 2026-05-10 08:35:48 +00:00
parent 0af33fbe70
commit aecf410e93
2 changed files with 93 additions and 0 deletions

View file

@ -1860,6 +1860,24 @@ def _update_model_if_key_alias_exists(
return
def _resolve_provider_from_deployment(model_name: str) -> Optional[str]:
"""
Look up the provider prefix from the router's deployment config for a
given user-facing model name (e.g. "claude-sonnet-4.6" ->
"bedrock" via litellm_params.model "bedrock/us.anthropic...").
"""
from litellm.proxy.proxy_server import llm_router
if llm_router is None:
return None
for deployment in llm_router.model_list:
if deployment.get("model_name") == model_name:
litellm_model = (deployment.get("litellm_params") or {}).get("model", "")
if "/" in litellm_model:
return litellm_model.split("/", 1)[0]
return None
def _apply_credential_overrides_from_model_config(
data: dict,
user_api_key_dict: UserAPIKeyAuth,
@ -1900,9 +1918,13 @@ def _apply_credential_overrides_from_model_config(
return
# Extract provider hint from model name (e.g. "azure/gpt-4" -> "azure")
# When the user-facing model name has no "/" prefix, resolve from the
# deployment's litellm_params.model instead (e.g. "bedrock/us.anthropic...")
provider: Optional[str] = None
if "/" in model_name:
provider = model_name.split("/", 1)[0]
else:
provider = _resolve_provider_from_deployment(model_name)
credential_name = _resolve_credential_from_model_config(
model_name=model_name,

View file

@ -21,6 +21,7 @@ from litellm.proxy.litellm_pre_call_utils import (
_get_enforced_params,
_get_metadata_variable_name,
_resolve_credential_from_model_config,
_resolve_provider_from_deployment,
_update_model_if_key_alias_exists,
add_guardrails_from_policy_engine,
add_litellm_data_to_request,
@ -3821,6 +3822,76 @@ def test_resolve_provider_hint_from_model_name():
assert result == "azure-cred"
def test_resolve_provider_from_deployment():
"""
Regression test for https://github.com/BerriAI/litellm/issues/27516
When the user-facing model name has no "/" prefix (e.g. "claude-sonnet-4.6"),
the provider should be resolved from the deployment's litellm_params.model
(e.g. "bedrock/us.anthropic.claude-sonnet-4-6" -> "bedrock").
"""
mock_router = MagicMock()
mock_router.model_list = [
{
"model_name": "claude-sonnet-4.6",
"litellm_params": {"model": "bedrock/us.anthropic.claude-sonnet-4-6"},
},
{
"model_name": "gemini-2.5-pro",
"litellm_params": {"model": "gemini/gemini-2.5-pro-preview"},
},
]
with patch(
"litellm.proxy.proxy_server.llm_router",
mock_router,
):
assert _resolve_provider_from_deployment("claude-sonnet-4.6") == "bedrock"
assert _resolve_provider_from_deployment("gemini-2.5-pro") == "gemini"
assert _resolve_provider_from_deployment("unknown-model") is None
def test_resolve_provider_from_deployment_no_router():
"""When llm_router is None, _resolve_provider_from_deployment returns None."""
with patch(
"litellm.proxy.proxy_server.llm_router",
None,
):
assert _resolve_provider_from_deployment("claude-sonnet-4.6") is None
def test_credential_routing_uses_deployment_provider():
"""
End-to-end: defaultconfig with multiple providers should resolve the
correct credential when the user model name has no "/" prefix.
"""
mock_router = MagicMock()
mock_router.model_list = [
{
"model_name": "claude-sonnet-4.6",
"litellm_params": {"model": "bedrock/us.anthropic.claude-sonnet-4-6"},
},
]
team_model_config = {
"defaultconfig": {
"gemini": {"litellm_credentials": "gemini-team-1"},
"bedrock": {"litellm_credentials": "bedrock-team-1"},
},
}
with patch(
"litellm.proxy.proxy_server.llm_router",
mock_router,
):
result = _resolve_credential_from_model_config(
model_name="claude-sonnet-4.6",
project_model_config=None,
team_model_config=team_model_config,
provider="bedrock",
)
assert result == "bedrock-team-1"
def test_clean_headers_preserves_x_api_key_when_byok_enabled():
"""
Regression test: when forward_llm_provider_auth_headers=True,