Skip to content

Commit 8faf569

Browse files
Fix azure tenant id check from env var + response_format check on api_version 2025+ (#9993)
* fix(azure/common_utils.py): check for azure tenant id, client id, client secret in env var Fixes #9598 (comment) * fix(azure/gpt_transformation.py): fix passing response_format to azure when api year = 2025 Fixes #9703 * test: monkeypatch azure api version in test * test: update testing * test: fix test * test: update test * docs(config_settings.md): document env vars
1 parent ce2595f commit 8faf569

File tree

5 files changed

+84
-15
lines changed

5 files changed

+84
-15
lines changed

docs/my-website/docs/proxy/config_settings.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,9 @@ router_settings:
323323
| AZURE_AUTHORITY_HOST | Azure authority host URL
324324
| AZURE_CLIENT_ID | Client ID for Azure services
325325
| AZURE_CLIENT_SECRET | Client secret for Azure services
326+
| AZURE_TENANT_ID | Tenant ID for Azure Active Directory
327+
| AZURE_USERNAME | Username for Azure services, use in conjunction with AZURE_PASSWORD for azure ad token with basic username/password workflow
328+
| AZURE_PASSWORD | Password for Azure services, use in conjunction with AZURE_USERNAME for azure ad token with basic username/password workflow
326329
| AZURE_FEDERATED_TOKEN_FILE | File path to Azure federated token
327330
| AZURE_KEY_VAULT_URI | URI for Azure Key Vault
328331
| AZURE_STORAGE_ACCOUNT_KEY | The Azure Storage Account Key to use for Authentication to Azure Blob Storage logging
@@ -331,7 +334,7 @@ router_settings:
331334
| AZURE_STORAGE_TENANT_ID | The Application Tenant ID to use for Authentication to Azure Blob Storage logging
332335
| AZURE_STORAGE_CLIENT_ID | The Application Client ID to use for Authentication to Azure Blob Storage logging
333336
| AZURE_STORAGE_CLIENT_SECRET | The Application Client Secret to use for Authentication to Azure Blob Storage logging
334-
| AZURE_TENANT_ID | Tenant ID for Azure Active Directory
337+
335338
| BERRISPEND_ACCOUNT_ID | Account ID for BerriSpend service
336339
| BRAINTRUST_API_KEY | API key for Braintrust integration
337340
| CIRCLE_OIDC_TOKEN | OpenID Connect token for CircleCI

litellm/llms/azure/chat/gpt_transformation.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,22 @@ def _is_response_format_supported_api_version(
125125
) -> bool:
126126
"""
127127
- check if api_version is supported for response_format
128+
- returns True if the API version is equal to or newer than the supported version
128129
"""
130+
api_year = int(api_version_year)
131+
api_month = int(api_version_month)
132+
supported_year = int(API_VERSION_YEAR_SUPPORTED_RESPONSE_FORMAT)
133+
supported_month = int(API_VERSION_MONTH_SUPPORTED_RESPONSE_FORMAT)
129134

130-
is_supported = (
131-
int(api_version_year) <= API_VERSION_YEAR_SUPPORTED_RESPONSE_FORMAT
132-
and int(api_version_month) >= API_VERSION_MONTH_SUPPORTED_RESPONSE_FORMAT
133-
)
134-
135-
return is_supported
135+
# If the year is greater than supported year, it's definitely supported
136+
if api_year > supported_year:
137+
return True
138+
# If the year is less than supported year, it's not supported
139+
elif api_year < supported_year:
140+
return False
141+
# If same year, check if month is >= supported month
142+
else:
143+
return api_month >= supported_month
136144

137145
def map_openai_params(
138146
self,
@@ -202,6 +210,7 @@ def map_openai_params(
202210
is_response_format_supported_api_version
203211
and _is_response_format_supported_model
204212
)
213+
205214
optional_params = self._add_response_format_to_tools(
206215
optional_params=optional_params,
207216
value=value,

litellm/llms/azure/common_utils.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,34 +309,47 @@ def initialize_azure_sdk_client(
309309
azure_ad_token_provider: Optional[Callable[[], str]] = None
310310
# If we have api_key, then we have higher priority
311311
azure_ad_token = litellm_params.get("azure_ad_token")
312-
tenant_id = litellm_params.get("tenant_id")
313-
client_id = litellm_params.get("client_id")
314-
client_secret = litellm_params.get("client_secret")
315-
azure_username = litellm_params.get("azure_username")
316-
azure_password = litellm_params.get("azure_password")
312+
tenant_id = litellm_params.get("tenant_id", os.getenv("AZURE_TENANT_ID"))
313+
client_id = litellm_params.get("client_id", os.getenv("AZURE_CLIENT_ID"))
314+
client_secret = litellm_params.get(
315+
"client_secret", os.getenv("AZURE_CLIENT_SECRET")
316+
)
317+
azure_username = litellm_params.get(
318+
"azure_username", os.getenv("AZURE_USERNAME")
319+
)
320+
azure_password = litellm_params.get(
321+
"azure_password", os.getenv("AZURE_PASSWORD")
322+
)
317323
max_retries = litellm_params.get("max_retries")
318324
timeout = litellm_params.get("timeout")
319325
if not api_key and tenant_id and client_id and client_secret:
320-
verbose_logger.debug("Using Azure AD Token Provider for Azure Auth")
326+
verbose_logger.debug(
327+
"Using Azure AD Token Provider from Entrata ID for Azure Auth"
328+
)
321329
azure_ad_token_provider = get_azure_ad_token_from_entrata_id(
322330
tenant_id=tenant_id,
323331
client_id=client_id,
324332
client_secret=client_secret,
325333
)
326334
if azure_username and azure_password and client_id:
335+
verbose_logger.debug("Using Azure Username and Password for Azure Auth")
327336
azure_ad_token_provider = get_azure_ad_token_from_username_password(
328337
azure_username=azure_username,
329338
azure_password=azure_password,
330339
client_id=client_id,
331340
)
332341

333342
if azure_ad_token is not None and azure_ad_token.startswith("oidc/"):
343+
verbose_logger.debug("Using Azure OIDC Token for Azure Auth")
334344
azure_ad_token = get_azure_ad_token_from_oidc(azure_ad_token)
335345
elif (
336346
not api_key
337347
and azure_ad_token_provider is None
338348
and litellm.enable_azure_ad_token_refresh is True
339349
):
350+
verbose_logger.debug(
351+
"Using Azure AD token provider based on Service Principal with Secret workflow for Azure Auth"
352+
)
340353
try:
341354
azure_ad_token_provider = get_azure_ad_token_provider()
342355
except ValueError:

tests/litellm/llms/azure/test_azure_common_utils.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,33 @@ def test_initialize_with_api_key(setup_mocks):
7878
assert result["azure_ad_token"] is None
7979

8080

81+
def test_initialize_with_tenant_credentials_env_var(setup_mocks, monkeypatch):
82+
monkeypatch.setenv("AZURE_TENANT_ID", "test-tenant-id")
83+
monkeypatch.setenv("AZURE_CLIENT_ID", "test-client-id")
84+
monkeypatch.setenv("AZURE_CLIENT_SECRET", "test-client-secret")
85+
86+
result = BaseAzureLLM().initialize_azure_sdk_client(
87+
litellm_params={},
88+
api_key=None,
89+
api_base="https://test.openai.azure.com",
90+
model_name="gpt-4",
91+
api_version=None,
92+
is_async=False,
93+
)
94+
95+
# Verify that get_azure_ad_token_from_entrata_id was called
96+
setup_mocks["entrata_token"].assert_called_once_with(
97+
tenant_id="test-tenant-id",
98+
client_id="test-client-id",
99+
client_secret="test-client-secret",
100+
)
101+
102+
# Verify expected result
103+
assert result["api_key"] is None
104+
assert result["azure_endpoint"] == "https://test.openai.azure.com"
105+
assert "azure_ad_token_provider" in result
106+
107+
81108
def test_initialize_with_tenant_credentials(setup_mocks):
82109
# Test with tenant_id, client_id, and client_secret provided
83110
result = BaseAzureLLM().initialize_azure_sdk_client(
@@ -150,8 +177,12 @@ def test_initialize_with_oidc_token(setup_mocks):
150177
assert result["azure_ad_token"] == "mock-oidc-token"
151178

152179

153-
def test_initialize_with_enable_token_refresh(setup_mocks):
180+
def test_initialize_with_enable_token_refresh(setup_mocks, monkeypatch):
181+
litellm._turn_on_debug()
154182
# Enable token refresh
183+
monkeypatch.delenv("AZURE_CLIENT_ID", raising=False)
184+
monkeypatch.delenv("AZURE_CLIENT_SECRET", raising=False)
185+
monkeypatch.delenv("AZURE_TENANT_ID", raising=False)
155186
setup_mocks["litellm"].enable_azure_ad_token_refresh = True
156187

157188
# Test with token refresh enabled
@@ -171,8 +202,11 @@ def test_initialize_with_enable_token_refresh(setup_mocks):
171202
assert "azure_ad_token_provider" in result
172203

173204

174-
def test_initialize_with_token_refresh_error(setup_mocks):
205+
def test_initialize_with_token_refresh_error(setup_mocks, monkeypatch):
175206
# Enable token refresh but make it raise an error
207+
monkeypatch.delenv("AZURE_CLIENT_ID", raising=False)
208+
monkeypatch.delenv("AZURE_CLIENT_SECRET", raising=False)
209+
monkeypatch.delenv("AZURE_TENANT_ID", raising=False)
176210
setup_mocks["litellm"].enable_azure_ad_token_refresh = True
177211
setup_mocks["token_provider"].side_effect = ValueError("Token provider error")
178212

tests/llm_translation/test_optional_params.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,3 +1449,13 @@ def test_anthropic_unified_reasoning_content(model, provider):
14491449
)
14501450
assert optional_params["thinking"] == {"type": "enabled", "budget_tokens": 4096}
14511451

1452+
1453+
1454+
def test_azure_response_format(monkeypatch):
1455+
monkeypatch.setenv("AZURE_API_VERSION", "2025-02-01")
1456+
optional_params = get_optional_params(
1457+
model="azure/gpt-4o-mini",
1458+
custom_llm_provider="azure",
1459+
response_format={"type": "json_object"},
1460+
)
1461+
assert optional_params["response_format"] == {"type": "json_object"}

0 commit comments

Comments
 (0)