mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-07 02:25:34 +00:00
feat(anthropic): support ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL env vars
Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Devin Petersohn <devin.petersohn@gmail.com>
This commit is contained in:
committed by
Devin Petersohn
co-authored by
Claude
parent
e5baa2232f
commit
f415b72bcf
@@ -524,6 +524,7 @@ def _handle_retrieve_batch_providers_without_provider_config(
|
||||
optional_params.api_base
|
||||
or litellm.api_base
|
||||
or get_secret_str("ANTHROPIC_API_BASE")
|
||||
or get_secret_str("ANTHROPIC_BASE_URL")
|
||||
)
|
||||
api_key = (
|
||||
optional_params.api_key
|
||||
|
||||
@@ -1444,6 +1444,7 @@ SENTRY_DENYLIST = [
|
||||
"credential",
|
||||
"OPENAI_API_KEY",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"AZURE_API_KEY",
|
||||
"COHERE_API_KEY",
|
||||
"REPLICATE_API_KEY",
|
||||
|
||||
@@ -42,9 +42,8 @@ class AnthropicBatchesConfig(BaseBatchesConfig):
|
||||
api_base: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""Validate and prepare environment-specific headers and parameters."""
|
||||
# Resolve api_key from environment if not provided
|
||||
api_key = api_key or self.anthropic_model_info.get_api_key()
|
||||
if api_key is None:
|
||||
auth_header = self.anthropic_model_info.get_auth_header(api_key)
|
||||
if auth_header is None:
|
||||
raise ValueError(
|
||||
"Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params"
|
||||
)
|
||||
@@ -52,8 +51,8 @@ class AnthropicBatchesConfig(BaseBatchesConfig):
|
||||
"accept": "application/json",
|
||||
"anthropic-version": "2023-06-01",
|
||||
"content-type": "application/json",
|
||||
"x-api-key": api_key,
|
||||
}
|
||||
_headers.update(auth_header)
|
||||
# Add beta header for message batches
|
||||
if "anthropic-beta" not in headers:
|
||||
headers["anthropic-beta"] = "message-batches-2024-09-24"
|
||||
|
||||
@@ -359,9 +359,7 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
Returns:
|
||||
List of beta header strings
|
||||
"""
|
||||
from litellm.types.llms.anthropic import (
|
||||
ANTHROPIC_EFFORT_BETA_HEADER,
|
||||
)
|
||||
from litellm.types.llms.anthropic import ANTHROPIC_EFFORT_BETA_HEADER
|
||||
|
||||
betas = []
|
||||
|
||||
@@ -390,7 +388,8 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
|
||||
def get_anthropic_headers(
|
||||
self,
|
||||
api_key: str,
|
||||
api_key: Optional[str] = None,
|
||||
auth_token: Optional[str] = None,
|
||||
anthropic_version: Optional[str] = None,
|
||||
computer_tool_used: Optional[str] = None,
|
||||
prompt_caching_set: bool = False,
|
||||
@@ -451,6 +450,8 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
headers["authorization"] = f"Bearer {api_key}"
|
||||
headers["anthropic-dangerous-direct-browser-access"] = "true"
|
||||
betas.add(ANTHROPIC_OAUTH_BETA_HEADER)
|
||||
elif auth_token and not api_key:
|
||||
headers["authorization"] = f"Bearer {auth_token}"
|
||||
else:
|
||||
headers["x-api-key"] = api_key
|
||||
|
||||
@@ -485,9 +486,13 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
headers, api_key = optionally_handle_anthropic_oauth(
|
||||
headers=headers, api_key=api_key
|
||||
)
|
||||
# Resolve auth_token from ANTHROPIC_AUTH_TOKEN if api_key is not set
|
||||
auth_token: Optional[str] = None
|
||||
if api_key is None:
|
||||
auth_token = AnthropicModelInfo.get_auth_token()
|
||||
if api_key is None and auth_token is None:
|
||||
raise litellm.AuthenticationError(
|
||||
message="Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params. Please set `ANTHROPIC_API_KEY` in your environment vars",
|
||||
message="Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params. Please set `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` in your environment vars",
|
||||
llm_provider="anthropic",
|
||||
model=model,
|
||||
)
|
||||
@@ -519,6 +524,7 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
prompt_caching_set=prompt_caching_set,
|
||||
pdf_used=pdf_used,
|
||||
api_key=api_key,
|
||||
auth_token=auth_token,
|
||||
file_id_used=file_id_used,
|
||||
web_search_tool_used=web_search_tool_used,
|
||||
is_vertex_request=optional_params.get("is_vertex_request", False),
|
||||
@@ -543,6 +549,7 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
return (
|
||||
api_base
|
||||
or get_secret_str("ANTHROPIC_API_BASE")
|
||||
or get_secret_str("ANTHROPIC_BASE_URL")
|
||||
or "https://api.anthropic.com"
|
||||
)
|
||||
|
||||
@@ -552,6 +559,33 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
|
||||
return api_key or get_secret_str("ANTHROPIC_API_KEY")
|
||||
|
||||
@staticmethod
|
||||
def get_auth_token(auth_token: Optional[str] = None) -> Optional[str]:
|
||||
"""Get auth token from ANTHROPIC_AUTH_TOKEN env var.
|
||||
|
||||
Unlike api_key (which uses X-Api-Key header), auth_token uses
|
||||
Authorization: Bearer header, matching the official Anthropic SDK behavior.
|
||||
"""
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
return auth_token or get_secret_str("ANTHROPIC_AUTH_TOKEN")
|
||||
|
||||
@staticmethod
|
||||
def get_auth_header(api_key: Optional[str] = None) -> Optional[dict]:
|
||||
"""Resolve Anthropic credentials and return the appropriate auth header dict.
|
||||
|
||||
Checks ANTHROPIC_API_KEY first (-> x-api-key), then
|
||||
ANTHROPIC_AUTH_TOKEN (-> Authorization: Bearer).
|
||||
Returns None if neither is available.
|
||||
"""
|
||||
resolved_key = AnthropicModelInfo.get_api_key(api_key)
|
||||
if resolved_key is not None:
|
||||
return {"x-api-key": resolved_key}
|
||||
auth_token = AnthropicModelInfo.get_auth_token()
|
||||
if auth_token is not None:
|
||||
return {"authorization": f"Bearer {auth_token}"}
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_base_model(model: Optional[str] = None) -> Optional[str]:
|
||||
return model.replace("anthropic/", "") if model else None
|
||||
@@ -560,14 +594,16 @@ class AnthropicModelInfo(BaseLLMModelInfo):
|
||||
self, api_key: Optional[str] = None, api_base: Optional[str] = None
|
||||
) -> List[str]:
|
||||
api_base = AnthropicModelInfo.get_api_base(api_base)
|
||||
api_key = AnthropicModelInfo.get_api_key(api_key)
|
||||
if api_base is None or api_key is None:
|
||||
auth_header = AnthropicModelInfo.get_auth_header(api_key)
|
||||
if api_base is None or auth_header is None:
|
||||
raise ValueError(
|
||||
"ANTHROPIC_API_BASE or ANTHROPIC_API_KEY is not set. Please set the environment variable, to query Anthropic's `/models` endpoint."
|
||||
"ANTHROPIC_API_BASE/ANTHROPIC_BASE_URL or ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN is not set. Please set the environment variable, to query Anthropic's `/models` endpoint."
|
||||
)
|
||||
headers = {"anthropic-version": "2023-06-01"}
|
||||
headers.update(auth_header)
|
||||
response = litellm.module_level_client.get(
|
||||
url=f"{api_base}/v1/models",
|
||||
headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -142,17 +142,15 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> Tuple[dict, Optional[str]]:
|
||||
import os
|
||||
|
||||
# Check for Anthropic OAuth token in Authorization header
|
||||
headers, api_key = optionally_handle_anthropic_oauth(
|
||||
headers=headers, api_key=api_key
|
||||
)
|
||||
if api_key is None:
|
||||
api_key = os.getenv("ANTHROPIC_API_KEY")
|
||||
|
||||
if "x-api-key" not in headers and "authorization" not in headers and api_key:
|
||||
headers["x-api-key"] = api_key
|
||||
if "x-api-key" not in headers and "authorization" not in headers:
|
||||
auth_header = AnthropicModelInfo.get_auth_header(api_key)
|
||||
if auth_header is not None:
|
||||
headers.update(auth_header)
|
||||
if "anthropic-version" not in headers:
|
||||
headers["anthropic-version"] = DEFAULT_ANTHROPIC_API_VERSION
|
||||
if "content-type" not in headers:
|
||||
|
||||
@@ -8,10 +8,8 @@ import httpx
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm._uuid import uuid
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
get_async_httpx_client,
|
||||
)
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
|
||||
from litellm.types.llms.openai import (
|
||||
FileContentRequest,
|
||||
HttpxBinaryResponseContent,
|
||||
@@ -85,9 +83,9 @@ class AnthropicFilesHandler:
|
||||
|
||||
# Get Anthropic API credentials
|
||||
api_base = self.anthropic_model_info.get_api_base(api_base)
|
||||
api_key = api_key or self.anthropic_model_info.get_api_key()
|
||||
auth_header = self.anthropic_model_info.get_auth_header(api_key)
|
||||
|
||||
if not api_key:
|
||||
if auth_header is None:
|
||||
raise ValueError("Missing Anthropic API Key")
|
||||
|
||||
# Construct the Anthropic batch results URL
|
||||
@@ -97,8 +95,8 @@ class AnthropicFilesHandler:
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"anthropic-version": "2023-06-01",
|
||||
"x-api-key": api_key,
|
||||
}
|
||||
headers.update(auth_header)
|
||||
|
||||
# Make the request to Anthropic
|
||||
async_client = get_async_httpx_client(llm_provider=LlmProviders.ANTHROPIC)
|
||||
|
||||
@@ -94,14 +94,14 @@ class AnthropicFilesConfig(BaseFilesConfig):
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> dict:
|
||||
api_key = AnthropicModelInfo.get_api_key(api_key)
|
||||
if not api_key:
|
||||
auth_header = AnthropicModelInfo.get_auth_header(api_key)
|
||||
if auth_header is None:
|
||||
raise ValueError(
|
||||
"Anthropic API key is required. Set ANTHROPIC_API_KEY environment variable or pass api_key parameter."
|
||||
"Anthropic API key is required. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable or pass api_key parameter."
|
||||
)
|
||||
headers.update(
|
||||
{
|
||||
"x-api-key": api_key,
|
||||
**auth_header,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"anthropic-beta": ANTHROPIC_FILES_BETA_HEADER,
|
||||
}
|
||||
|
||||
@@ -35,17 +35,16 @@ class AnthropicSkillsConfig(BaseSkillsAPIConfig):
|
||||
"""Add Anthropic-specific headers"""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
# Get API key
|
||||
# Get API key from litellm_params if available
|
||||
api_key = None
|
||||
if litellm_params:
|
||||
if litellm_params is not None:
|
||||
api_key = litellm_params.api_key
|
||||
api_key = AnthropicModelInfo.get_api_key(api_key)
|
||||
|
||||
if not api_key:
|
||||
raise ValueError("ANTHROPIC_API_KEY is required for Skills API")
|
||||
auth_header = AnthropicModelInfo.get_auth_header(api_key)
|
||||
if auth_header is None:
|
||||
raise ValueError("ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN is required for Skills API")
|
||||
|
||||
# Add required headers
|
||||
headers["x-api-key"] = api_key
|
||||
headers.update(auth_header)
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
|
||||
# Add beta header for skills API
|
||||
|
||||
@@ -585,7 +585,7 @@ async def anthropic_proxy_route(
|
||||
"""
|
||||
[Docs](https://docs.litellm.ai/docs/pass_through/anthropic_completion)
|
||||
"""
|
||||
base_target_url = os.getenv("ANTHROPIC_API_BASE") or "https://api.anthropic.com"
|
||||
base_target_url = os.getenv("ANTHROPIC_API_BASE") or os.getenv("ANTHROPIC_BASE_URL") or "https://api.anthropic.com"
|
||||
encoded_endpoint = httpx.URL(endpoint).path
|
||||
|
||||
# Ensure endpoint starts with '/' for proper URL construction
|
||||
@@ -609,7 +609,7 @@ async def anthropic_proxy_route(
|
||||
endpoint_func = create_pass_through_route(
|
||||
endpoint=endpoint,
|
||||
target=str(updated_url),
|
||||
custom_headers={"x-api-key": "{}".format(anthropic_api_key)},
|
||||
custom_headers={"x-api-key": "{}".format(anthropic_api_key)} if anthropic_api_key else {},
|
||||
_forward_headers=True,
|
||||
is_streaming_request=is_streaming_request,
|
||||
) # dynamically construct pass-through endpoint based on incoming path
|
||||
|
||||
+3
-5
@@ -6160,7 +6160,7 @@ def validate_environment( # noqa: PLR0915
|
||||
["AZURE_API_BASE", "AZURE_API_VERSION", "AZURE_API_KEY"]
|
||||
)
|
||||
elif custom_llm_provider == "anthropic":
|
||||
if "ANTHROPIC_API_KEY" in os.environ:
|
||||
if "ANTHROPIC_API_KEY" in os.environ or "ANTHROPIC_AUTH_TOKEN" in os.environ:
|
||||
keys_in_environment = True
|
||||
else:
|
||||
missing_keys.append("ANTHROPIC_API_KEY")
|
||||
@@ -6399,7 +6399,7 @@ def validate_environment( # noqa: PLR0915
|
||||
missing_keys.append("OPENAI_API_KEY")
|
||||
## anthropic
|
||||
elif model in litellm.anthropic_models:
|
||||
if "ANTHROPIC_API_KEY" in os.environ:
|
||||
if "ANTHROPIC_API_KEY" in os.environ or "ANTHROPIC_AUTH_TOKEN" in os.environ:
|
||||
keys_in_environment = True
|
||||
else:
|
||||
missing_keys.append("ANTHROPIC_API_KEY")
|
||||
@@ -8593,9 +8593,7 @@ class ProviderConfigManager:
|
||||
|
||||
return ManusFilesConfig()
|
||||
elif LlmProviders.ANTHROPIC == provider:
|
||||
from litellm.llms.anthropic.files.transformation import (
|
||||
AnthropicFilesConfig,
|
||||
)
|
||||
from litellm.llms.anthropic.files.transformation import AnthropicFilesConfig
|
||||
|
||||
return AnthropicFilesConfig()
|
||||
return None
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
"""
|
||||
Tests for Anthropic OAuth token handling in common_utils.
|
||||
Tests for Anthropic authentication and environment variable handling in common_utils.
|
||||
|
||||
Verifies that OAuth tokens (sk-ant-oat*) are sent via Authorization: Bearer
|
||||
instead of x-api-key, per Anthropic's OAuth specification.
|
||||
Verifies that:
|
||||
- OAuth tokens (sk-ant-oat*) produce Authorization: Bearer headers with OAuth beta flags.
|
||||
- Regular API keys produce x-api-key headers.
|
||||
- ANTHROPIC_AUTH_TOKEN produces Authorization: Bearer headers,
|
||||
matching the official Anthropic SDK behavior.
|
||||
- ANTHROPIC_BASE_URL is used as a fallback for base URL resolution.
|
||||
- ANTHROPIC_API_KEY / ANTHROPIC_API_BASE take precedence over their aliases.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../.."))
|
||||
)
|
||||
|
||||
# Fake OAuth token for testing (not a real secret)
|
||||
# Fake tokens for testing (not real secrets)
|
||||
FAKE_OAUTH_TOKEN = "sk-ant-oat01-fake-token-for-testing-123456789abcdef"
|
||||
FAKE_REGULAR_KEY = "sk-ant-api03-regular-key-for-testing-123456789"
|
||||
FAKE_AUTH_TOKEN = "sk-ant-aut01-fake-auth-token-for-testing-123456789"
|
||||
|
||||
|
||||
class TestOptionallyHandleAnthropicOAuth:
|
||||
@@ -697,3 +704,360 @@ class TestProxyOAuthHeaderForwarding:
|
||||
assert cleaned["authorization"] == oauth_token
|
||||
# Proxy key must be stripped
|
||||
assert "x-litellm-api-key" not in cleaned
|
||||
|
||||
|
||||
class TestGetAnthropicHeadersWithAuthToken:
|
||||
"""Tests for get_anthropic_headers with auth_token parameter."""
|
||||
|
||||
def test_auth_token_uses_bearer_header(self):
|
||||
"""auth_token should produce Authorization: Bearer header."""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
config = AnthropicModelInfo()
|
||||
headers = config.get_anthropic_headers(
|
||||
api_key=None,
|
||||
auth_token=FAKE_AUTH_TOKEN,
|
||||
computer_tool_used=False,
|
||||
prompt_caching_set=False,
|
||||
pdf_used=False,
|
||||
is_vertex_request=False,
|
||||
)
|
||||
|
||||
assert headers["authorization"] == f"Bearer {FAKE_AUTH_TOKEN}"
|
||||
assert "x-api-key" not in headers
|
||||
# auth_token should NOT set OAuth-specific flags
|
||||
assert "anthropic-dangerous-direct-browser-access" not in headers
|
||||
|
||||
def test_auth_token_includes_standard_headers(self):
|
||||
"""auth_token path should include standard Anthropic headers."""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
config = AnthropicModelInfo()
|
||||
headers = config.get_anthropic_headers(
|
||||
api_key=None,
|
||||
auth_token=FAKE_AUTH_TOKEN,
|
||||
computer_tool_used=False,
|
||||
prompt_caching_set=False,
|
||||
pdf_used=False,
|
||||
is_vertex_request=False,
|
||||
)
|
||||
|
||||
assert headers["anthropic-version"] == "2023-06-01"
|
||||
assert headers["accept"] == "application/json"
|
||||
assert headers["content-type"] == "application/json"
|
||||
|
||||
def test_api_key_takes_precedence_over_auth_token(self):
|
||||
"""When both api_key and auth_token are provided, api_key wins."""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
config = AnthropicModelInfo()
|
||||
headers = config.get_anthropic_headers(
|
||||
api_key=FAKE_REGULAR_KEY,
|
||||
auth_token=FAKE_AUTH_TOKEN,
|
||||
computer_tool_used=False,
|
||||
prompt_caching_set=False,
|
||||
pdf_used=False,
|
||||
is_vertex_request=False,
|
||||
)
|
||||
|
||||
assert headers["x-api-key"] == FAKE_REGULAR_KEY
|
||||
assert "authorization" not in headers
|
||||
|
||||
|
||||
class TestValidateEnvironmentAuthToken:
|
||||
"""Tests for validate_environment with auth_token resolution."""
|
||||
|
||||
def test_auth_token_env_var_produces_bearer_header(self):
|
||||
"""validate_environment should use Bearer auth when only ANTHROPIC_AUTH_TOKEN is set."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
config = AnthropicModelInfo()
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN},
|
||||
clear=True,
|
||||
):
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["authorization"] == f"Bearer {FAKE_AUTH_TOKEN}"
|
||||
assert "x-api-key" not in headers
|
||||
assert "anthropic-dangerous-direct-browser-access" not in headers
|
||||
|
||||
def test_api_key_param_takes_precedence_over_auth_token_env_var(self):
|
||||
"""validate_environment should prefer explicit api_key over ANTHROPIC_AUTH_TOKEN."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
config = AnthropicModelInfo()
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN},
|
||||
clear=True,
|
||||
):
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=FAKE_REGULAR_KEY,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert headers["x-api-key"] == FAKE_REGULAR_KEY
|
||||
assert "authorization" not in headers
|
||||
|
||||
def test_raises_when_no_credentials(self):
|
||||
"""validate_environment should raise when neither API key nor auth token is available."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
config = AnthropicModelInfo()
|
||||
with mock_patch.dict("os.environ", {}, clear=True):
|
||||
with pytest.raises(
|
||||
Exception, match="ANTHROPIC_API_KEY.*ANTHROPIC_AUTH_TOKEN"
|
||||
):
|
||||
config.validate_environment(
|
||||
headers={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
class TestGetAuthToken:
|
||||
"""Tests for AnthropicModelInfo.get_auth_token() static method."""
|
||||
|
||||
def test_returns_env_var_value(self):
|
||||
"""get_auth_token returns the ANTHROPIC_AUTH_TOKEN env var value."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ", {"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN}, clear=True
|
||||
):
|
||||
assert AnthropicModelInfo.get_auth_token() == FAKE_AUTH_TOKEN
|
||||
|
||||
def test_returns_none_when_not_set(self):
|
||||
"""get_auth_token returns None when ANTHROPIC_AUTH_TOKEN is not set."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict("os.environ", {}, clear=True):
|
||||
assert AnthropicModelInfo.get_auth_token() is None
|
||||
|
||||
def test_explicit_param_takes_precedence(self):
|
||||
"""Explicit auth_token param takes precedence over env var."""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
explicit_token = "sk-ant-aut01-explicit-token-override-123456789"
|
||||
assert AnthropicModelInfo.get_auth_token(explicit_token) == explicit_token
|
||||
|
||||
|
||||
class TestGetAuthHeader:
|
||||
"""Tests for AnthropicModelInfo.get_auth_header() centralized helper."""
|
||||
|
||||
def test_returns_x_api_key_when_api_key_provided(self):
|
||||
"""Explicit api_key param should return x-api-key header."""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
result = AnthropicModelInfo.get_auth_header(api_key=FAKE_REGULAR_KEY)
|
||||
assert result == {"x-api-key": FAKE_REGULAR_KEY}
|
||||
|
||||
def test_returns_x_api_key_from_env(self):
|
||||
"""ANTHROPIC_API_KEY env var should return x-api-key header."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_API_KEY": FAKE_REGULAR_KEY},
|
||||
clear=True,
|
||||
):
|
||||
result = AnthropicModelInfo.get_auth_header()
|
||||
assert result == {"x-api-key": FAKE_REGULAR_KEY}
|
||||
|
||||
def test_returns_bearer_from_auth_token_env(self):
|
||||
"""ANTHROPIC_AUTH_TOKEN env var should return Authorization: Bearer header."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN},
|
||||
clear=True,
|
||||
):
|
||||
result = AnthropicModelInfo.get_auth_header()
|
||||
assert result == {"authorization": f"Bearer {FAKE_AUTH_TOKEN}"}
|
||||
|
||||
def test_api_key_takes_precedence_over_auth_token(self):
|
||||
"""ANTHROPIC_API_KEY should take precedence over ANTHROPIC_AUTH_TOKEN."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{
|
||||
"ANTHROPIC_API_KEY": FAKE_REGULAR_KEY,
|
||||
"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN,
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
result = AnthropicModelInfo.get_auth_header()
|
||||
assert result == {"x-api-key": FAKE_REGULAR_KEY}
|
||||
|
||||
def test_explicit_api_key_overrides_env_auth_token(self):
|
||||
"""Explicit api_key param should override ANTHROPIC_AUTH_TOKEN env var."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN},
|
||||
clear=True,
|
||||
):
|
||||
result = AnthropicModelInfo.get_auth_header(api_key=FAKE_REGULAR_KEY)
|
||||
assert result == {"x-api-key": FAKE_REGULAR_KEY}
|
||||
|
||||
def test_returns_none_when_no_credentials(self):
|
||||
"""Should return None when neither api_key nor auth_token is available."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict("os.environ", {}, clear=True):
|
||||
result = AnthropicModelInfo.get_auth_header()
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestGetApiBaseFallbackChain:
|
||||
"""Tests for AnthropicModelInfo.get_api_base() fallback to ANTHROPIC_BASE_URL."""
|
||||
|
||||
def test_explicit_param_takes_precedence(self):
|
||||
"""Explicit api_base param takes precedence over all env vars."""
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
assert (
|
||||
AnthropicModelInfo.get_api_base("https://explicit.example.com")
|
||||
== "https://explicit.example.com"
|
||||
)
|
||||
|
||||
def test_defaults_to_anthropic_api(self):
|
||||
"""get_api_base returns the default Anthropic API base when no env vars are set."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict("os.environ", {}, clear=True):
|
||||
assert AnthropicModelInfo.get_api_base() == "https://api.anthropic.com"
|
||||
|
||||
def test_api_base_env_preferred_over_base_url_env(self):
|
||||
"""ANTHROPIC_API_BASE takes precedence over ANTHROPIC_BASE_URL."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{
|
||||
"ANTHROPIC_API_BASE": "https://api-base.example.com",
|
||||
"ANTHROPIC_BASE_URL": "https://base-url.example.com",
|
||||
},
|
||||
clear=True,
|
||||
):
|
||||
assert AnthropicModelInfo.get_api_base() == "https://api-base.example.com"
|
||||
|
||||
def test_falls_back_to_base_url_env(self):
|
||||
"""get_api_base falls back to ANTHROPIC_BASE_URL when ANTHROPIC_API_BASE is not set."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_BASE_URL": "https://base-url.example.com"},
|
||||
clear=True,
|
||||
):
|
||||
assert AnthropicModelInfo.get_api_base() == "https://base-url.example.com"
|
||||
|
||||
|
||||
class TestPassthroughAuthToken:
|
||||
"""Tests for passthrough messages endpoint with ANTHROPIC_AUTH_TOKEN."""
|
||||
|
||||
def test_passthrough_auth_token_uses_bearer_header(self):
|
||||
"""Passthrough endpoint should use Bearer auth when only ANTHROPIC_AUTH_TOKEN is set."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
config = AnthropicMessagesConfig()
|
||||
with mock_patch.dict(
|
||||
"os.environ", {"ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN}, clear=True
|
||||
):
|
||||
updated_headers, _ = config.validate_anthropic_messages_environment(
|
||||
headers={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert updated_headers["authorization"] == f"Bearer {FAKE_AUTH_TOKEN}"
|
||||
assert "x-api-key" not in updated_headers
|
||||
assert "anthropic-dangerous-direct-browser-access" not in updated_headers
|
||||
|
||||
def test_passthrough_api_key_takes_precedence(self):
|
||||
"""Passthrough endpoint should prefer ANTHROPIC_API_KEY over ANTHROPIC_AUTH_TOKEN."""
|
||||
from unittest.mock import patch as mock_patch
|
||||
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
config = AnthropicMessagesConfig()
|
||||
with mock_patch.dict(
|
||||
"os.environ",
|
||||
{"ANTHROPIC_API_KEY": FAKE_REGULAR_KEY, "ANTHROPIC_AUTH_TOKEN": FAKE_AUTH_TOKEN},
|
||||
clear=True,
|
||||
):
|
||||
updated_headers, _ = config.validate_anthropic_messages_environment(
|
||||
headers={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
assert updated_headers["x-api-key"] == FAKE_REGULAR_KEY
|
||||
assert "authorization" not in updated_headers
|
||||
|
||||
Reference in New Issue
Block a user