mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
refactor(auth): build normalized headers immutably
This commit is contained in:
parent
209531ac17
commit
7dc8df7eda
7 changed files with 48 additions and 41 deletions
|
|
@ -718,11 +718,11 @@ class BaseAzureLLM(BaseOpenAILLM):
|
|||
(value for name, value in headers.items() if name.lower() == "api-key" and str(value).strip()),
|
||||
None,
|
||||
)
|
||||
non_auth_headers: Final = {
|
||||
name: value for name, value in headers.items() if name.lower() not in {"api-key", "authorization"}
|
||||
}
|
||||
non_auth_headers: Final = MappingProxyType(
|
||||
{name: value for name, value in headers.items() if name.lower() not in ("api-key", "authorization")}
|
||||
)
|
||||
if forwarded_api_key is not None:
|
||||
return {**non_auth_headers, "api-key": forwarded_api_key}
|
||||
return MappingProxyType({**non_auth_headers, "api-key": forwarded_api_key}).copy()
|
||||
|
||||
api_key: Final = (
|
||||
litellm_params.api_key
|
||||
|
|
@ -733,15 +733,15 @@ class BaseAzureLLM(BaseOpenAILLM):
|
|||
)
|
||||
|
||||
if api_key:
|
||||
return {**non_auth_headers, "api-key": api_key}
|
||||
return MappingProxyType({**non_auth_headers, "api-key": api_key}).copy()
|
||||
|
||||
### Fallback to Azure AD token-based authentication if no API key is available
|
||||
### Retrieves Azure AD token and adds it to the Authorization header
|
||||
azure_ad_token: Final = get_azure_ad_token(litellm_params)
|
||||
if azure_ad_token:
|
||||
return {**non_auth_headers, "Authorization": f"Bearer {azure_ad_token}"}
|
||||
return MappingProxyType({**non_auth_headers, "Authorization": f"Bearer {azure_ad_token}"}).copy()
|
||||
|
||||
return non_auth_headers
|
||||
return non_auth_headers.copy()
|
||||
|
||||
@staticmethod
|
||||
def _get_base_azure_url(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Azure Anthropic messages transformation config - extends AnthropicMessagesConfig with Azure authentication
|
||||
"""
|
||||
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Final
|
||||
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
|
|
@ -57,16 +58,18 @@ class AzureAnthropicMessagesConfig(AnthropicMessagesConfig):
|
|||
None,
|
||||
)
|
||||
if azure_api_key is not None:
|
||||
headers = {
|
||||
**{
|
||||
non_auth_headers: Final = MappingProxyType(
|
||||
{
|
||||
name: value
|
||||
for name, value in headers.items()
|
||||
if name.lower() not in {"api-key", "x-api-key", "authorization"}
|
||||
},
|
||||
"x-api-key": azure_api_key,
|
||||
}
|
||||
if name.lower() not in ("api-key", "x-api-key", "authorization")
|
||||
}
|
||||
)
|
||||
headers = MappingProxyType({**non_auth_headers, "x-api-key": azure_api_key}).copy()
|
||||
elif any(name.lower() == "authorization" for name in headers):
|
||||
headers = {name: value for name, value in headers.items() if name.lower() != "x-api-key"}
|
||||
headers = MappingProxyType(
|
||||
{name: value for name, value in headers.items() if name.lower() != "x-api-key"}
|
||||
).copy()
|
||||
|
||||
# Set anthropic-version header
|
||||
if "anthropic-version" not in headers:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
Azure Anthropic transformation config - extends AnthropicConfig with Azure authentication
|
||||
"""
|
||||
|
||||
from types import MappingProxyType
|
||||
from typing import Final
|
||||
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
|
|
@ -80,14 +81,14 @@ class AzureAnthropicConfig(AnthropicConfig):
|
|||
None,
|
||||
)
|
||||
if azure_api_key is not None:
|
||||
headers = {
|
||||
**{
|
||||
non_auth_headers: Final = MappingProxyType(
|
||||
{
|
||||
name: value
|
||||
for name, value in headers.items()
|
||||
if name.lower() not in {"api-key", "x-api-key", "authorization"}
|
||||
},
|
||||
"x-api-key": azure_api_key,
|
||||
}
|
||||
if name.lower() not in ("api-key", "x-api-key", "authorization")
|
||||
}
|
||||
)
|
||||
headers = MappingProxyType({**non_auth_headers, "x-api-key": azure_api_key}).copy()
|
||||
|
||||
# Get tools and other anthropic-specific setup
|
||||
tools: Final = optional_params.get("tools")
|
||||
|
|
@ -114,7 +115,9 @@ class AzureAnthropicConfig(AnthropicConfig):
|
|||
# Merge headers - Azure auth (api-key or Authorization) takes precedence
|
||||
headers = {**anthropic_headers, **headers}
|
||||
if "Authorization" in headers:
|
||||
headers = {name: value for name, value in headers.items() if name.lower() != "x-api-key"}
|
||||
headers = MappingProxyType(
|
||||
{name: value for name, value in headers.items() if name.lower() != "x-api-key"}
|
||||
).copy()
|
||||
|
||||
# Ensure anthropic-version header is set
|
||||
if "anthropic-version" not in headers:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
import enum
|
||||
import re
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Final, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
|
@ -81,14 +82,14 @@ class AzureAIStudioConfig(OpenAIConfig):
|
|||
api_key: str | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> dict:
|
||||
non_auth_headers: Final = {
|
||||
name: value for name, value in headers.items() if name.lower() not in {"api-key", "authorization"}
|
||||
}
|
||||
non_auth_headers: Final = MappingProxyType(
|
||||
{name: value for name, value in headers.items() if name.lower() not in ("api-key", "authorization")}
|
||||
)
|
||||
if api_key:
|
||||
if api_base and self._should_use_api_key_header(api_base):
|
||||
headers = {**non_auth_headers, "api-key": api_key}
|
||||
headers = MappingProxyType({**non_auth_headers, "api-key": api_key}).copy()
|
||||
else:
|
||||
headers = {**non_auth_headers, "Authorization": f"Bearer {api_key}"}
|
||||
headers = MappingProxyType({**non_auth_headers, "Authorization": f"Bearer {api_key}"}).copy()
|
||||
else:
|
||||
# No api_key provided — fall back to Azure AD token-based auth
|
||||
litellm_params_obj = GenericLiteLLMParams(**(litellm_params if isinstance(litellm_params, dict) else {}))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Support for Mistral Voxtral audio transcription via ``/v1/audio/transcriptions``
|
|||
API reference: https://docs.mistral.ai/api/#tag/audio/operation/audio_transcriptions_v1_audio_transcriptions_post
|
||||
"""
|
||||
|
||||
from types import MappingProxyType
|
||||
from typing import Final
|
||||
|
||||
import httpx
|
||||
|
|
@ -86,10 +87,10 @@ class MistralAudioTranscriptionConfig(BaseAudioTranscriptionConfig):
|
|||
"Authorization": f"Bearer {api_key}",
|
||||
"accept": "application/json",
|
||||
}
|
||||
non_auth_headers: Final = {
|
||||
name: value for name, value in (headers or {}).items() if name.lower() != "authorization"
|
||||
}
|
||||
return {**non_auth_headers, **default_headers}
|
||||
non_auth_headers: Final = MappingProxyType(
|
||||
{name: value for name, value in headers.items() if name.lower() != "authorization"}
|
||||
)
|
||||
return MappingProxyType({**non_auth_headers, **default_headers}).copy()
|
||||
|
||||
def transform_audio_transcription_request(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
|
||||
import httpx
|
||||
|
|
@ -55,7 +56,9 @@ class _BaseReductoOCRConfig(BaseOCRConfig):
|
|||
"Missing REDUCTO_API_KEY - set it in the environment or pass api_key to litellm.ocr()/litellm.aocr()"
|
||||
)
|
||||
|
||||
non_auth_headers: Final = {name: value for name, value in headers.items() if name.lower() != "authorization"}
|
||||
non_auth_headers: Final = MappingProxyType(
|
||||
{name: value for name, value in headers.items() if name.lower() != "authorization"}
|
||||
)
|
||||
return {
|
||||
**non_auth_headers,
|
||||
"Authorization": f"Bearer {resolved_key}",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ sys.path.insert(
|
|||
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../.."))
|
||||
)
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -118,8 +118,7 @@ class TestAzureAnthropicConfig:
|
|||
call_args = mock_validate.call_args
|
||||
assert call_args[1]["litellm_params"].api_key == "provided-api-key"
|
||||
|
||||
def test_validate_environment_preserves_api_key_header(self):
|
||||
"""Test that api-key header is preserved as-is (Azure handles the header internally)"""
|
||||
def test_validate_environment_converts_api_key_header(self):
|
||||
config = AzureAnthropicConfig()
|
||||
headers = {}
|
||||
model = "claude-sonnet-4-5"
|
||||
|
|
@ -140,9 +139,8 @@ class TestAzureAnthropicConfig:
|
|||
litellm_params=litellm_params,
|
||||
)
|
||||
|
||||
# Verify api-key header is preserved as-is
|
||||
assert "api-key" in result
|
||||
assert result["api-key"] == "test-api-key"
|
||||
assert result["x-api-key"] == "test-api-key"
|
||||
assert "api-key" not in result
|
||||
|
||||
def test_validate_environment_sets_anthropic_version(self):
|
||||
"""Test that anthropic-version header is set"""
|
||||
|
|
@ -217,8 +215,8 @@ class TestAzureAnthropicConfig:
|
|||
See: https://github.com/BerriAI/litellm/issues/XXXX
|
||||
"""
|
||||
config = AzureAnthropicConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
optional_params = {
|
||||
"max_tokens": 100,
|
||||
}
|
||||
|
|
@ -260,8 +258,8 @@ class TestAzureAnthropicConfig:
|
|||
def test_context_management_compact_beta_header(self):
|
||||
"""Test that context_management with compact adds the correct beta header for Azure AI"""
|
||||
config = AzureAnthropicConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
optional_params = {
|
||||
"context_management": {"edits": [{"type": "compact_20260112"}]},
|
||||
"max_tokens": 100,
|
||||
|
|
@ -289,7 +287,6 @@ class TestAzureAnthropicConfig:
|
|||
"""Test that compact beta header is added to headers for Azure AI"""
|
||||
config = AzureAnthropicConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
optional_params = {
|
||||
"context_management": {"edits": [{"type": "compact_20260112"}]},
|
||||
"max_tokens": 100,
|
||||
|
|
@ -428,7 +425,6 @@ class TestAzureAnthropicConfig:
|
|||
"""Test that context_management with both compact and other edits adds both beta headers"""
|
||||
config = AzureAnthropicConfig()
|
||||
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
optional_params = {
|
||||
"context_management": {
|
||||
"edits": [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue