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:
Devin Petersohn
2026-03-19 10:16:30 -07:00
committed by Devin Petersohn
co-authored by Claude
parent e5baa2232f
commit f415b72bcf
11 changed files with 441 additions and 47 deletions
+1
View File
@@ -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
+1
View File
@@ -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"
+45 -9
View File
@@ -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:
+4 -6
View File
@@ -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
View File
@@ -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