mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(speech): keep Entra ID from env vars and token refresh ahead of the provider-scoped key
This commit is contained in:
parent
19123dd9f4
commit
33d3544ea6
4 changed files with 118 additions and 6 deletions
|
|
@ -145,6 +145,34 @@ def cached_azure_ad_token_refresh_provider(scope: str) -> Callable[[], str]:
|
|||
return _cached_azure_ad_token_refresh_provider(scope)
|
||||
|
||||
|
||||
def _param_or_env_set(
|
||||
litellm_params: Mapping[str, object], environ: Mapping[str, str], param_key: str, env_var_key: str
|
||||
) -> bool:
|
||||
param_value: Final = litellm_params.get(param_key)
|
||||
return bool(param_value if param_value is not None else environ.get(env_var_key))
|
||||
|
||||
|
||||
def uses_azure_entra_id_auth(
|
||||
litellm_params: Mapping[str, object], environ: Mapping[str, str], token_refresh_enabled: bool
|
||||
) -> bool:
|
||||
client_id_set: Final = _param_or_env_set(litellm_params, environ, "client_id", "AZURE_CLIENT_ID")
|
||||
return (
|
||||
litellm_params.get("azure_ad_token_provider") is not None
|
||||
or bool(litellm_params.get("azure_ad_token"))
|
||||
or (
|
||||
client_id_set
|
||||
and _param_or_env_set(litellm_params, environ, "tenant_id", "AZURE_TENANT_ID")
|
||||
and _param_or_env_set(litellm_params, environ, "client_secret", "AZURE_CLIENT_SECRET")
|
||||
)
|
||||
or (
|
||||
client_id_set
|
||||
and _param_or_env_set(litellm_params, environ, "azure_username", "AZURE_USERNAME")
|
||||
and _param_or_env_set(litellm_params, environ, "azure_password", "AZURE_PASSWORD")
|
||||
)
|
||||
or token_refresh_enabled
|
||||
)
|
||||
|
||||
|
||||
def _async_azure_ad_token_provider(
|
||||
provider: Callable[[], str | Awaitable[str]],
|
||||
) -> Callable[[], Awaitable[str]]:
|
||||
|
|
|
|||
|
|
@ -239,10 +239,10 @@ from .llms.anthropic.chat import AnthropicChatCompletion
|
|||
from .llms.azure.audio_transcriptions import AzureAudioTranscription
|
||||
from .llms.azure.azure import AzureChatCompletion, check_dynamic_azure_params
|
||||
from .llms.azure.chat.o_series_handler import AzureOpenAIO1ChatCompletion
|
||||
from .llms.azure.common_utils import uses_azure_entra_id_auth
|
||||
from .llms.azure.completion.handler import AzureTextCompletion
|
||||
from .llms.azure_ai.anthropic.handler import AzureAnthropicChatCompletion
|
||||
from .llms.azure_ai.anthropic.transformation import azure_anthropic_messages_url
|
||||
from .llms.azure_ai.common_utils import has_azure_entra_params
|
||||
from .llms.azure_ai.embed import AzureAIEmbedding
|
||||
from .llms.bedrock.chat import BedrockConverseLLM
|
||||
from .llms.bedrock.chat.converse_transformation import AmazonConverseConfig
|
||||
|
|
@ -8843,15 +8843,13 @@ def speech(
|
|||
optional_params.get("extra_body", {}).pop("azure_ad_token", None)
|
||||
)
|
||||
request_kwargs: Final = _REQUEST_KWARGS_ADAPTER.validate_python(kwargs)
|
||||
deployment_uses_entra: Final = (
|
||||
request_azure_ad_token is not None
|
||||
or request_kwargs.get("azure_ad_token_provider") is not None
|
||||
or has_azure_entra_params(request_kwargs)
|
||||
uses_entra_id: Final = request_azure_ad_token is not None or uses_azure_entra_id_auth(
|
||||
request_kwargs, os.environ, litellm.enable_azure_ad_token_refresh is True
|
||||
)
|
||||
|
||||
api_key = (
|
||||
api_key
|
||||
or (None if deployment_uses_entra else provider_api_key)
|
||||
or (None if uses_entra_id else provider_api_key)
|
||||
or litellm.api_key
|
||||
or litellm.azure_key
|
||||
or get_secret("AZURE_OPENAI_API_KEY")
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from litellm.llms.azure.common_utils import (
|
|||
get_azure_ad_token_from_entra_id,
|
||||
get_azure_ad_token_from_oidc,
|
||||
get_azure_ad_token_from_username_password,
|
||||
uses_azure_entra_id_auth,
|
||||
)
|
||||
from litellm.secret_managers.get_azure_ad_token_provider import (
|
||||
get_azure_ad_token_provider,
|
||||
|
|
@ -2492,3 +2493,42 @@ def test_an_azure_client_litellm_built_its_own_http_client_for_is_still_closed(m
|
|||
closer.reap()
|
||||
|
||||
assert wrapper.is_closed() is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("litellm_params", "environ", "token_refresh_enabled", "expected"),
|
||||
[
|
||||
({"azure_ad_token": "deployment-token"}, {}, False, True),
|
||||
({"azure_ad_token_provider": lambda: "token"}, {}, False, True),
|
||||
({"tenant_id": "t", "client_id": "c", "client_secret": "s"}, {}, False, True),
|
||||
({}, {"AZURE_TENANT_ID": "t", "AZURE_CLIENT_ID": "c", "AZURE_CLIENT_SECRET": "s"}, False, True),
|
||||
({"tenant_id": "t", "client_id": "c"}, {"AZURE_CLIENT_SECRET": "s"}, False, True),
|
||||
({"client_id": "c", "azure_username": "u"}, {"AZURE_PASSWORD": "p"}, False, True),
|
||||
({}, {}, True, True),
|
||||
({"tenant_id": "t", "client_id": "c"}, {}, False, False),
|
||||
({"client_secret": "s"}, {}, False, False),
|
||||
({"azure_username": "u", "azure_password": "p"}, {}, False, False),
|
||||
({"tenant_id": None, "client_id": "c", "client_secret": "s"}, {"AZURE_TENANT_ID": "t"}, False, True),
|
||||
({"tenant_id": "", "client_id": "c", "client_secret": "s"}, {"AZURE_TENANT_ID": "t"}, False, False),
|
||||
({}, {}, False, False),
|
||||
],
|
||||
ids=[
|
||||
"ad-token",
|
||||
"ad-token-provider",
|
||||
"service-principal-in-params",
|
||||
"service-principal-in-env",
|
||||
"ids-in-params-secret-in-env",
|
||||
"username-password-split",
|
||||
"token-refresh",
|
||||
"service-principal-missing-secret",
|
||||
"secret-alone",
|
||||
"username-password-missing-client-id",
|
||||
"none-param-falls-back-to-env",
|
||||
"empty-param-does-not-fall-back-to-env",
|
||||
"nothing-set",
|
||||
],
|
||||
)
|
||||
def test_uses_azure_entra_id_auth_matches_the_sources_the_azure_client_builds_entra_auth_from(
|
||||
litellm_params, environ, token_refresh_enabled, expected
|
||||
):
|
||||
assert uses_azure_entra_id_auth(litellm_params, environ, token_refresh_enabled) is expected
|
||||
|
|
|
|||
|
|
@ -5599,6 +5599,52 @@ def test_speech_azure_ai_keeps_the_deployments_entra_token_over_the_provider_sco
|
|||
assert "key-for-another-resource" not in sent_headers.get("api-key", "")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entra_env", "deployment_params", "token_refresh"),
|
||||
[
|
||||
({"AZURE_TENANT_ID": "t", "AZURE_CLIENT_ID": "c", "AZURE_CLIENT_SECRET": "s"}, {}, False),
|
||||
({"AZURE_CLIENT_SECRET": "s"}, {"tenant_id": "t", "client_id": "c"}, False),
|
||||
({}, {}, True),
|
||||
],
|
||||
ids=["service-principal-in-env", "ids-in-config-secret-in-env", "token-refresh"],
|
||||
)
|
||||
def test_speech_azure_ai_entra_id_setups_keep_the_key_chain_they_had_before_the_provider_key(
|
||||
respx_mock: respx.MockRouter,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
entra_env: dict[str, str],
|
||||
deployment_params: dict[str, str],
|
||||
token_refresh: bool,
|
||||
):
|
||||
_clear_global_tts_keys(monkeypatch)
|
||||
monkeypatch.setattr(litellm, "enable_azure_ad_token_refresh", token_refresh)
|
||||
_set_or_clear_env(
|
||||
monkeypatch,
|
||||
{
|
||||
"AZURE_TENANT_ID": None,
|
||||
"AZURE_CLIENT_ID": None,
|
||||
"AZURE_CLIENT_SECRET": None,
|
||||
"AZURE_OPENAI_API_KEY": None,
|
||||
"AZURE_AI_API_KEY": "key-for-another-resource",
|
||||
"AZURE_API_KEY": "generic-azure-key",
|
||||
**entra_env,
|
||||
},
|
||||
)
|
||||
route: Final = respx_mock.post(url__regex=r"https://foundry\.internal/openai/v1/audio/speech.*").mock(
|
||||
return_value=httpx.Response(200, content=TTS_AUDIO, headers={"content-type": "audio/mpeg"})
|
||||
)
|
||||
|
||||
litellm.speech(
|
||||
model="azure_ai/tts-1",
|
||||
input="which key goes out",
|
||||
voice="alloy",
|
||||
api_base="https://foundry.internal",
|
||||
api_version="preview",
|
||||
**deployment_params,
|
||||
)
|
||||
|
||||
assert route.calls.last.request.headers["authorization"] == "Bearer generic-azure-key"
|
||||
|
||||
|
||||
def test_speech_docker_model_runner_placeholder_does_not_beat_a_real_key(
|
||||
respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue