mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
Merge d535120c92 into 0318b4acdc
This commit is contained in:
commit
0b0905dd4d
2 changed files with 60 additions and 9 deletions
|
|
@ -2630,21 +2630,23 @@ def _extract_credential_from_entry(entry: dict, provider: str | None = None) ->
|
|||
|
||||
Entry structure: {"azure": {"litellm_credentials": "name"}, ...}
|
||||
|
||||
When provider is given (e.g. "azure"), tries an exact provider match first.
|
||||
Falls back to the first credential found across all provider keys.
|
||||
When provider is given (e.g. "azure") and the entry has a key for it, that
|
||||
provider's credential is used. When provider is given but the entry has no
|
||||
key for it, the entry does not apply to this request and None is returned,
|
||||
so the deployment keeps its own credentials. Only when no provider could be
|
||||
resolved at all does this fall back to the first credential in the entry.
|
||||
"""
|
||||
if not isinstance(entry, dict):
|
||||
return None
|
||||
|
||||
# Prefer exact provider match when provider hint is available
|
||||
if provider and provider in entry:
|
||||
provider_config = entry[provider]
|
||||
if provider:
|
||||
provider_config = entry.get(provider)
|
||||
if isinstance(provider_config, dict):
|
||||
credential_name = provider_config.get("litellm_credentials")
|
||||
if credential_name:
|
||||
return credential_name
|
||||
return None
|
||||
|
||||
# Fall back to first available provider
|
||||
for provider_config in entry.values():
|
||||
if isinstance(provider_config, dict):
|
||||
credential_name = provider_config.get("litellm_credentials")
|
||||
|
|
|
|||
|
|
@ -4814,9 +4814,9 @@ def test_extract_credential_provider_hint_prefers_exact_match():
|
|||
result = _extract_credential_from_entry(entry)
|
||||
assert result in ("openai-cred", "azure-cred")
|
||||
|
||||
# Unknown provider falls back to first available
|
||||
result = _extract_credential_from_entry(entry, provider="bedrock")
|
||||
assert result in ("openai-cred", "azure-cred")
|
||||
# A resolved provider that the entry does not configure gets no credential,
|
||||
# otherwise a bedrock request would be sent with an openai or azure key
|
||||
assert _extract_credential_from_entry(entry, provider="bedrock") is None
|
||||
|
||||
|
||||
def test_resolve_provider_hint_from_model_name():
|
||||
|
|
@ -5764,6 +5764,55 @@ def test_apply_overrides_provider_prefix_in_model_skips_router_lookup(
|
|||
router.get_deployment_by_model_group_name.assert_not_called()
|
||||
|
||||
|
||||
def test_apply_overrides_single_provider_default_does_not_leak_to_other_providers(
|
||||
setup_test_credentials,
|
||||
):
|
||||
"""
|
||||
A team whose defaultconfig only names anthropic must not have that key
|
||||
injected into requests the router resolves to a different provider. Before
|
||||
this fix an unprefixed gpt-4o-mini request on such a team was sent to OpenAI
|
||||
with the team's sk-ant key and got a 401 back.
|
||||
"""
|
||||
litellm.credential_list.append(
|
||||
CredentialItem(
|
||||
credential_name="anthropic-team-1",
|
||||
credential_info={},
|
||||
credential_values={"api_key": "sk-ant-key-for-team-1"},
|
||||
)
|
||||
)
|
||||
team_metadata = {
|
||||
"model_config": {"defaultconfig": {"anthropic": {"litellm_credentials": "anthropic-team-1"}}}
|
||||
}
|
||||
|
||||
router = MagicMock()
|
||||
openai_deployment = MagicMock()
|
||||
openai_deployment.litellm_params.model = "openai/gpt-4o-mini"
|
||||
openai_deployment.litellm_params.custom_llm_provider = "openai"
|
||||
anthropic_deployment = MagicMock()
|
||||
anthropic_deployment.litellm_params.model = "anthropic/claude-sonnet-4-6"
|
||||
anthropic_deployment.litellm_params.custom_llm_provider = "anthropic"
|
||||
router.get_deployment_by_model_group_name.side_effect = lambda model_group_name: {
|
||||
"gpt-4o-mini": openai_deployment,
|
||||
"claude-sonnet-4.6": anthropic_deployment,
|
||||
}.get(model_group_name)
|
||||
|
||||
openai_data = {"model": "gpt-4o-mini"}
|
||||
_apply_credential_overrides_from_model_config(
|
||||
data=openai_data,
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="test-key", team_metadata=team_metadata),
|
||||
llm_router=router,
|
||||
)
|
||||
assert "api_key" not in openai_data
|
||||
|
||||
anthropic_data = {"model": "claude-sonnet-4.6"}
|
||||
_apply_credential_overrides_from_model_config(
|
||||
data=anthropic_data,
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="test-key", team_metadata=team_metadata),
|
||||
llm_router=router,
|
||||
)
|
||||
assert anthropic_data["api_key"] == "sk-ant-key-for-team-1"
|
||||
|
||||
|
||||
def _make_request_mock(path: str, headers: dict) -> MagicMock:
|
||||
request_mock = MagicMock(spec=Request)
|
||||
request_mock.url = MagicMock()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue