fix(anthropic): raise missing-credential error on /v1/messages passthrough

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-08-25 18:11:42 +00:00
parent 92fe35854b
commit e14f485827
2 changed files with 56 additions and 2 deletions

View file

@ -8,6 +8,7 @@ from litellm.constants import (
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET, DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET,
) )
from litellm.exceptions import AuthenticationError
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
from litellm.litellm_core_utils.litellm_logging import verbose_logger from litellm.litellm_core_utils.litellm_logging import verbose_logger
from litellm.llms.base_llm.anthropic_messages.transformation import ( from litellm.llms.base_llm.anthropic_messages.transformation import (
@ -309,8 +310,17 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
if "x-api-key" not in headers and "authorization" not in headers: if "x-api-key" not in headers and "authorization" not in headers:
auth_header: Final = AnthropicModelInfo.get_auth_header(api_key) auth_header: Final = AnthropicModelInfo.get_auth_header(api_key)
if auth_header is not None: if auth_header is None:
headers.update(auth_header) raise 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` "
"or `ANTHROPIC_AUTH_TOKEN` in your environment vars"
),
llm_provider=self._resolved_provider,
model=model,
)
headers.update(auth_header)
if "anthropic-version" not in headers: if "anthropic-version" not in headers:
headers["anthropic-version"] = DEFAULT_ANTHROPIC_API_VERSION headers["anthropic-version"] = DEFAULT_ANTHROPIC_API_VERSION
if "content-type" not in headers: if "content-type" not in headers:

View file

@ -1227,6 +1227,50 @@ class TestPassthroughAuthToken:
assert updated_headers["x-api-key"] == FAKE_REGULAR_KEY assert updated_headers["x-api-key"] == FAKE_REGULAR_KEY
assert "authorization" not in updated_headers assert "authorization" not in updated_headers
def test_passthrough_missing_credentials_raises_authentication_error(self):
"""Passthrough endpoint should raise locally instead of forwarding an unauthenticated request."""
from unittest.mock import patch as mock_patch
import litellm
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
AnthropicMessagesConfig,
)
config = AnthropicMessagesConfig()
with mock_patch.dict("os.environ", {}, clear=True):
with pytest.raises(litellm.AuthenticationError, match="Missing Anthropic API Key"):
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,
)
def test_passthrough_client_x_api_key_header_is_kept(self):
"""A client-forwarded x-api-key header should satisfy validation without env credentials."""
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", {}, clear=True):
updated_headers, _ = config.validate_anthropic_messages_environment(
headers={"x-api-key": FAKE_REGULAR_KEY},
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
def test_passthrough_get_complete_url_honours_base_url_env(self): def test_passthrough_get_complete_url_honours_base_url_env(self):
"""get_complete_url should use ANTHROPIC_BASE_URL when api_base is None.""" """get_complete_url should use ANTHROPIC_BASE_URL when api_base is None."""
from unittest.mock import patch as mock_patch from unittest.mock import patch as mock_patch