mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge ea490ec978 into 63cb18db7d
This commit is contained in:
commit
1dca1b5bc5
3 changed files with 107 additions and 4 deletions
|
|
@ -75,8 +75,10 @@ def is_anthropic_oauth_key(value: str | None) -> bool:
|
|||
"""Check if a value contains an Anthropic OAuth token (sk-ant-oat*)."""
|
||||
if value is None:
|
||||
return False
|
||||
# Handle both raw token and "Bearer <token>" format
|
||||
value = value.removeprefix("Bearer ")
|
||||
# Handle both raw token and "Bearer <token>" format, case-insensitive scheme
|
||||
scheme, _, token = value.partition(" ")
|
||||
if scheme.lower() == "bearer":
|
||||
value = token
|
||||
return value.startswith(ANTHROPIC_OAUTH_TOKEN_PREFIX)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from litellm.constants import (
|
|||
ALLOWED_VERTEX_AI_PASSTHROUGH_HEADERS,
|
||||
BEDROCK_AGENT_RUNTIME_PASS_THROUGH_ROUTES,
|
||||
)
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
|
||||
from litellm.llms.anthropic.common_utils import AnthropicModelInfo, is_anthropic_oauth_key
|
||||
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
|
||||
from litellm.proxy._types import *
|
||||
from litellm.proxy.auth.handle_jwt import JWTHandler
|
||||
|
|
@ -643,7 +643,10 @@ async def anthropic_proxy_route(
|
|||
is_streaming_request: Final = await is_streaming_request_fn(request)
|
||||
|
||||
## CREATE PASS-THROUGH
|
||||
auth_header: Final = AnthropicModelInfo.get_auth_header(anthropic_api_key or None)
|
||||
client_forwards_own_oauth: Final = is_anthropic_oauth_key(request.headers.get("authorization"))
|
||||
auth_header: Final = (
|
||||
None if client_forwards_own_oauth else AnthropicModelInfo.get_auth_header(anthropic_api_key or None)
|
||||
)
|
||||
endpoint_func: Final = create_pass_through_route(
|
||||
endpoint=endpoint,
|
||||
target=str(updated_url),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
|
|||
BaseOpenAIPassThroughHandler,
|
||||
RouteChecks,
|
||||
_join_url_paths,
|
||||
anthropic_proxy_route,
|
||||
azure_proxy_route,
|
||||
bedrock_llm_proxy_route,
|
||||
bedrock_proxy_route,
|
||||
|
|
@ -3010,6 +3011,103 @@ class TestOpenAIPassthroughRoute:
|
|||
assert result == {"id": "asst_123", "object": "assistant"}
|
||||
|
||||
|
||||
class TestAnthropicProxyRoute:
|
||||
"""Regression test for issue #37344."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_passthrough_omits_server_api_key_when_client_forwards_oauth(self):
|
||||
mock_request = MagicMock(spec=Request)
|
||||
mock_request.method = "POST"
|
||||
mock_request.headers = {"authorization": "Bearer sk-ant-oat01-canary"}
|
||||
mock_request.query_params = {}
|
||||
mock_response = MagicMock(spec=Response)
|
||||
mock_user_api_key_dict = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials",
|
||||
return_value="sk-ant-server-configured-key",
|
||||
),
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
|
||||
) as mock_create_route,
|
||||
):
|
||||
mock_endpoint_func = AsyncMock(return_value={"id": "msg_123"})
|
||||
mock_create_route.return_value = mock_endpoint_func
|
||||
|
||||
await anthropic_proxy_route(
|
||||
endpoint="v1/messages",
|
||||
request=mock_request,
|
||||
fastapi_response=mock_response,
|
||||
user_api_key_dict=mock_user_api_key_dict,
|
||||
)
|
||||
|
||||
call_args = mock_create_route.call_args[1]
|
||||
assert call_args["custom_headers"] == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_passthrough_uses_server_api_key_without_client_oauth(self):
|
||||
mock_request = MagicMock(spec=Request)
|
||||
mock_request.method = "POST"
|
||||
mock_request.headers = {}
|
||||
mock_request.query_params = {}
|
||||
mock_response = MagicMock(spec=Response)
|
||||
mock_user_api_key_dict = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials",
|
||||
return_value="sk-ant-server-configured-key",
|
||||
),
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
|
||||
) as mock_create_route,
|
||||
):
|
||||
mock_endpoint_func = AsyncMock(return_value={"id": "msg_123"})
|
||||
mock_create_route.return_value = mock_endpoint_func
|
||||
|
||||
await anthropic_proxy_route(
|
||||
endpoint="v1/messages",
|
||||
request=mock_request,
|
||||
fastapi_response=mock_response,
|
||||
user_api_key_dict=mock_user_api_key_dict,
|
||||
)
|
||||
|
||||
call_args = mock_create_route.call_args[1]
|
||||
assert call_args["custom_headers"] == {"x-api-key": "sk-ant-server-configured-key"}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_anthropic_passthrough_omits_server_api_key_for_lowercase_bearer_scheme(self):
|
||||
mock_request = MagicMock(spec=Request)
|
||||
mock_request.method = "POST"
|
||||
mock_request.headers = {"authorization": "bearer sk-ant-oat01-canary"}
|
||||
mock_request.query_params = {}
|
||||
mock_response = MagicMock(spec=Response)
|
||||
mock_user_api_key_dict = MagicMock()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials",
|
||||
return_value="sk-ant-server-configured-key",
|
||||
),
|
||||
patch(
|
||||
"litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route"
|
||||
) as mock_create_route,
|
||||
):
|
||||
mock_endpoint_func = AsyncMock(return_value={"id": "msg_123"})
|
||||
mock_create_route.return_value = mock_endpoint_func
|
||||
|
||||
await anthropic_proxy_route(
|
||||
endpoint="v1/messages",
|
||||
request=mock_request,
|
||||
fastapi_response=mock_response,
|
||||
user_api_key_dict=mock_user_api_key_dict,
|
||||
)
|
||||
|
||||
call_args = mock_create_route.call_args[1]
|
||||
assert call_args["custom_headers"] == {}
|
||||
|
||||
|
||||
def _resolve_route_name(method: str, path: str) -> str | None:
|
||||
from starlette.routing import Match
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue