From aecf410e93a2489a8b3d74a6fb84501b3424175c Mon Sep 17 00:00:00 2001 From: Jonathan Wrede Date: Sun, 10 May 2026 08:35:48 +0000 Subject: [PATCH] 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 --- litellm/proxy/litellm_pre_call_utils.py | 22 ++++++ .../proxy/test_litellm_pre_call_utils.py | 71 +++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index a63613c5836..ce326d7d91d 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -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, diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index 92611431a15..f1e7dc0bb4c 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -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,