mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Merge 8a23564d6e into 22a349ee70
This commit is contained in:
commit
3a23fe8bfb
2 changed files with 121 additions and 17 deletions
|
|
@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Annotated, Any, Final, cast
|
|||
import httpx
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, WebSocket
|
||||
from fastapi.responses import StreamingResponse
|
||||
from google.auth.exceptions import GoogleAuthError, TransportError
|
||||
from starlette.websockets import WebSocketState
|
||||
|
||||
import litellm
|
||||
|
|
@ -1924,23 +1925,37 @@ async def _prepare_vertex_auth_headers(
|
|||
else:
|
||||
raise ValueError("No vertex credentials found")
|
||||
|
||||
_auth_header, vertex_project = await vertex_llm_base._ensure_access_token_async(
|
||||
credentials=vertex_credentials_str,
|
||||
project_id=vertex_project,
|
||||
custom_llm_provider="vertex_ai_beta",
|
||||
)
|
||||
try:
|
||||
_auth_header, vertex_project = await vertex_llm_base._ensure_access_token_async(
|
||||
credentials=vertex_credentials_str,
|
||||
project_id=vertex_project,
|
||||
custom_llm_provider="vertex_ai_beta",
|
||||
)
|
||||
|
||||
auth_header, _ = vertex_llm_base._get_token_and_url(
|
||||
model="",
|
||||
auth_header=_auth_header,
|
||||
gemini_api_key=None,
|
||||
vertex_credentials=vertex_credentials_str,
|
||||
vertex_project=vertex_project,
|
||||
vertex_location=vertex_location,
|
||||
stream=False,
|
||||
custom_llm_provider="vertex_ai_beta",
|
||||
api_base="",
|
||||
)
|
||||
auth_header, _ = vertex_llm_base._get_token_and_url(
|
||||
model="",
|
||||
auth_header=_auth_header,
|
||||
gemini_api_key=None,
|
||||
vertex_credentials=vertex_credentials_str,
|
||||
vertex_project=vertex_project,
|
||||
vertex_location=vertex_location,
|
||||
stream=False,
|
||||
custom_llm_provider="vertex_ai_beta",
|
||||
api_base="",
|
||||
)
|
||||
except TransportError:
|
||||
raise
|
||||
except (GoogleAuthError, ValueError) as e:
|
||||
raise ProxyException(
|
||||
message=(
|
||||
f"Failed to get a Google access token for project={vertex_project} + location={vertex_location}: {e}. "
|
||||
"Set `vertex_credentials` on the deployment marked `use_in_pass_through: true`, or on "
|
||||
"`default_vertex_config` / `DEFAULT_GOOGLE_APPLICATION_CREDENTIALS`."
|
||||
),
|
||||
type=ProxyErrorTypes.auth_error,
|
||||
param="vertex_credentials",
|
||||
code=401,
|
||||
) from e
|
||||
|
||||
# Use allowlist approach - only forward specific safe headers
|
||||
headers = get_vertex_ai_allowed_incoming_headers(request)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from google.auth.exceptions import DefaultCredentialsError, TransportError
|
||||
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
|
||||
_base_vertex_proxy_route,
|
||||
)
|
||||
from litellm.types.router import DeploymentTypedDict
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -348,6 +348,95 @@ async def test_vertex_passthrough_forwards_anthropic_beta_header():
|
|||
assert headers_passed_through is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vertex_passthrough_credential_failure_raises_auth_error():
|
||||
"""
|
||||
A project/location configured for pass-through without usable Google credentials used to
|
||||
bubble google.auth errors out of the route, so callers only saw an opaque 500
|
||||
{"error": {"message": "Internal server error"}} with no hint about what to configure.
|
||||
"""
|
||||
from starlette.datastructures import Headers
|
||||
|
||||
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
|
||||
from litellm.proxy._types import ProxyErrorTypes, ProxyException
|
||||
from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
|
||||
_prepare_vertex_auth_headers,
|
||||
)
|
||||
|
||||
mock_request = MagicMock()
|
||||
mock_request.headers = Headers({"authorization": "Bearer sk-litellm-key"})
|
||||
mock_request.state._cached_headers = None
|
||||
|
||||
mock_vertex_credentials = MagicMock()
|
||||
mock_vertex_credentials.vertex_project = "test-project"
|
||||
mock_vertex_credentials.vertex_location = "us-central1"
|
||||
mock_vertex_credentials.vertex_credentials = None
|
||||
|
||||
with patch.object(
|
||||
VertexBase,
|
||||
"_ensure_access_token_async",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=DefaultCredentialsError("Your default credentials were not found"),
|
||||
):
|
||||
with pytest.raises(ProxyException) as exc_info:
|
||||
await _prepare_vertex_auth_headers(
|
||||
request=mock_request,
|
||||
vertex_credentials=mock_vertex_credentials,
|
||||
router_credentials=None,
|
||||
vertex_project="test-project",
|
||||
vertex_location="us-central1",
|
||||
base_target_url="https://us-central1-aiplatform.googleapis.com",
|
||||
get_vertex_pass_through_handler=MagicMock(),
|
||||
)
|
||||
|
||||
assert exc_info.value.code == "401"
|
||||
assert exc_info.value.type == ProxyErrorTypes.auth_error.value
|
||||
assert "test-project" in exc_info.value.message
|
||||
assert "us-central1" in exc_info.value.message
|
||||
assert "vertex_credentials" in exc_info.value.message
|
||||
assert "Your default credentials were not found" in exc_info.value.message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vertex_passthrough_transport_failure_is_not_reported_as_auth_error():
|
||||
"""
|
||||
Reaching Google's token endpoint can fail for reasons the operator cannot fix with
|
||||
credentials, so those must not be relabelled as authentication errors.
|
||||
"""
|
||||
from starlette.datastructures import Headers
|
||||
|
||||
from litellm.llms.vertex_ai.vertex_llm_base import VertexBase
|
||||
from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import (
|
||||
_prepare_vertex_auth_headers,
|
||||
)
|
||||
|
||||
mock_request = MagicMock()
|
||||
mock_request.headers = Headers({"authorization": "Bearer sk-litellm-key"})
|
||||
mock_request.state._cached_headers = None
|
||||
|
||||
mock_vertex_credentials = MagicMock()
|
||||
mock_vertex_credentials.vertex_project = "test-project"
|
||||
mock_vertex_credentials.vertex_location = "us-central1"
|
||||
mock_vertex_credentials.vertex_credentials = None
|
||||
|
||||
with patch.object(
|
||||
VertexBase,
|
||||
"_ensure_access_token_async",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=TransportError("connection reset by peer"),
|
||||
):
|
||||
with pytest.raises(TransportError):
|
||||
await _prepare_vertex_auth_headers(
|
||||
request=mock_request,
|
||||
vertex_credentials=mock_vertex_credentials,
|
||||
router_credentials=None,
|
||||
vertex_project="test-project",
|
||||
vertex_location="us-central1",
|
||||
base_target_url="https://us-central1-aiplatform.googleapis.com",
|
||||
get_vertex_pass_through_handler=MagicMock(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vertex_passthrough_does_not_forward_litellm_auth_token():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue