Fix Gemini exception messages to show GeminiException
- Update exception mapping to show 'GeminiException' instead of 'VertexAIException' - Add comprehensive HTTP status code mapping for Gemini provider (401, 403, 404, 408, 429, 500+) - Maintain OpenAI-compatible error handling patterns - Use LlmProviders enum constants for consistent provider identification - Add comprehensive test coverage for Gemini exception mapping
This commit is contained in:
parent
321d5299b2
commit
4d80a4a0fb
@ -6,6 +6,7 @@ import httpx
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
from ..exceptions import (
|
||||
APIConnectionError,
|
||||
@ -1168,9 +1169,8 @@ def exception_type( # type: ignore # noqa: PLR0915
|
||||
exception_status_code=original_exception.status_code,
|
||||
)
|
||||
elif (
|
||||
custom_llm_provider == "vertex_ai"
|
||||
or custom_llm_provider == "vertex_ai_beta"
|
||||
or custom_llm_provider == "gemini"
|
||||
custom_llm_provider == LlmProviders.VERTEX_AI
|
||||
or custom_llm_provider == LlmProviders.VERTEX_AI_BETA
|
||||
):
|
||||
if (
|
||||
"Vertex AI API has not been used in project" in error_str
|
||||
@ -1360,7 +1360,7 @@ def exception_type( # type: ignore # noqa: PLR0915
|
||||
llm_provider=custom_llm_provider,
|
||||
model=model,
|
||||
)
|
||||
elif custom_llm_provider == "palm" or custom_llm_provider == "gemini":
|
||||
elif custom_llm_provider == "palm" or custom_llm_provider == LlmProviders.GEMINI:
|
||||
if "503 Getting metadata" in error_str:
|
||||
# auth errors look like this
|
||||
# 503 Getting metadata from plugin failed with error: Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate.
|
||||
@ -1417,6 +1417,62 @@ def exception_type( # type: ignore # noqa: PLR0915
|
||||
llm_provider="palm",
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
if original_exception.status_code == 401:
|
||||
exception_mapping_worked = True
|
||||
raise AuthenticationError(
|
||||
message=f"GeminiException - {error_str}",
|
||||
llm_provider=custom_llm_provider,
|
||||
model=model,
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
if original_exception.status_code == 403:
|
||||
exception_mapping_worked = True
|
||||
raise PermissionDeniedError(
|
||||
message=f"GeminiException - {error_str}",
|
||||
llm_provider=custom_llm_provider,
|
||||
model=model,
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
if original_exception.status_code == 404:
|
||||
exception_mapping_worked = True
|
||||
raise NotFoundError(
|
||||
message=f"GeminiException - {error_str}",
|
||||
llm_provider=custom_llm_provider,
|
||||
model=model,
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
if original_exception.status_code == 408:
|
||||
exception_mapping_worked = True
|
||||
raise Timeout(
|
||||
message=f"GeminiException - {error_str}",
|
||||
llm_provider=custom_llm_provider,
|
||||
model=model,
|
||||
exception_status_code=original_exception.status_code,
|
||||
)
|
||||
if original_exception.status_code == 429:
|
||||
exception_mapping_worked = True
|
||||
raise RateLimitError(
|
||||
message=f"GeminiException - {error_str}",
|
||||
model=model,
|
||||
llm_provider=custom_llm_provider,
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
if original_exception.status_code == 500:
|
||||
exception_mapping_worked = True
|
||||
raise InternalServerError(
|
||||
message=f"GeminiException - {error_str}",
|
||||
model=model,
|
||||
llm_provider=custom_llm_provider,
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
if original_exception.status_code >= 500:
|
||||
exception_mapping_worked = True
|
||||
raise InternalServerError(
|
||||
message=f"GeminiException - {error_str}",
|
||||
model=model,
|
||||
llm_provider=custom_llm_provider,
|
||||
response=getattr(original_exception, "response", None),
|
||||
)
|
||||
# Dailed: Error occurred: 400 Request payload size exceeds the limit: 20000 bytes
|
||||
elif custom_llm_provider == "cloudflare":
|
||||
if "Authentication error" in error_str:
|
||||
|
||||
@ -3,8 +3,6 @@ import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.utils import supports_url_context
|
||||
|
||||
sys.path.insert(
|
||||
0, os.path.abspath("../..")
|
||||
) # Adds the parent directory to the system paths
|
||||
@ -834,3 +832,132 @@ def test_gemini_reasoning_effort_minimal():
|
||||
# The important part is that our known models work correctly
|
||||
print(f"Note: Unknown model test skipped due to: {e}")
|
||||
pass
|
||||
|
||||
|
||||
def test_gemini_exception_message_format():
|
||||
"""
|
||||
Test that Gemini provider exceptions show as 'GeminiException' not 'VertexAIException'.
|
||||
|
||||
This addresses issue #14586 where Gemini API errors were incorrectly showing as
|
||||
VertexAIException instead of GeminiException due to incorrect exception mapping.
|
||||
"""
|
||||
import httpx
|
||||
from unittest.mock import Mock
|
||||
from litellm.litellm_core_utils.exception_mapping_utils import exception_type
|
||||
from litellm import BadRequestError
|
||||
|
||||
# Mock a typical Gemini API error response
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = 400
|
||||
mock_response.text = "Invalid API key provided"
|
||||
mock_response.headers = {}
|
||||
|
||||
# Create a mock exception that simulates a Gemini API error
|
||||
mock_exception = httpx.HTTPStatusError(
|
||||
message="Bad Request",
|
||||
request=Mock(),
|
||||
response=mock_response
|
||||
)
|
||||
mock_exception.response = mock_response
|
||||
mock_exception.status_code = 400
|
||||
|
||||
# Test the exception mapping for Gemini provider
|
||||
try:
|
||||
exception_type(
|
||||
model="gemini-pro",
|
||||
original_exception=mock_exception,
|
||||
custom_llm_provider="gemini",
|
||||
completion_kwargs={},
|
||||
extra_kwargs={}
|
||||
)
|
||||
# Should not reach here - exception should be raised
|
||||
assert False, "Expected BadRequestError to be raised"
|
||||
except BadRequestError as e:
|
||||
# The test should FAIL initially (before fix) because it will show VertexAIException
|
||||
# After the fix, it should show GeminiException
|
||||
error_message = str(e)
|
||||
print(f"Error message: {error_message}") # For debugging
|
||||
|
||||
# This assertion will initially FAIL - that's expected for TDD
|
||||
assert "GeminiException" in error_message, (
|
||||
f"Expected 'GeminiException' in error message, got: {error_message}. "
|
||||
f"This test should fail before the fix is implemented."
|
||||
)
|
||||
assert "VertexAIException" not in error_message, (
|
||||
f"Should not contain 'VertexAIException' in error message, got: {error_message}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status_code,expected_exception", [
|
||||
(400, "BadRequestError"),
|
||||
(401, "AuthenticationError"),
|
||||
(403, "PermissionDeniedError"),
|
||||
(404, "NotFoundError"),
|
||||
(408, "Timeout"),
|
||||
(429, "RateLimitError"),
|
||||
(500, "InternalServerError"),
|
||||
(502, "InternalServerError"),
|
||||
(503, "InternalServerError"),
|
||||
])
|
||||
def test_gemini_comprehensive_error_handling(status_code, expected_exception):
|
||||
"""
|
||||
Test comprehensive Gemini error handling for all HTTP status codes.
|
||||
|
||||
This ensures that Gemini API errors of different types are properly mapped
|
||||
to the correct LiteLLM exception types with GeminiException prefix.
|
||||
"""
|
||||
import httpx
|
||||
from unittest.mock import Mock
|
||||
from litellm.litellm_core_utils.exception_mapping_utils import exception_type
|
||||
from litellm.exceptions import (
|
||||
BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError,
|
||||
Timeout, RateLimitError, InternalServerError
|
||||
)
|
||||
|
||||
# Mock the appropriate error response
|
||||
mock_response = Mock(spec=httpx.Response)
|
||||
mock_response.status_code = status_code
|
||||
mock_response.text = f"API Error {status_code}"
|
||||
mock_response.headers = {}
|
||||
|
||||
# Create a mock exception
|
||||
mock_exception = httpx.HTTPStatusError(
|
||||
message=f"HTTP {status_code}",
|
||||
request=Mock(),
|
||||
response=mock_response
|
||||
)
|
||||
mock_exception.response = mock_response
|
||||
mock_exception.status_code = status_code
|
||||
|
||||
# Test the exception mapping
|
||||
try:
|
||||
exception_type(
|
||||
model="gemini-pro",
|
||||
original_exception=mock_exception,
|
||||
custom_llm_provider="gemini",
|
||||
completion_kwargs={},
|
||||
extra_kwargs={}
|
||||
)
|
||||
assert False, f"Expected {expected_exception} to be raised for status {status_code}"
|
||||
except Exception as e:
|
||||
# Verify the correct exception type is raised
|
||||
exception_classes = {
|
||||
"BadRequestError": BadRequestError,
|
||||
"AuthenticationError": AuthenticationError,
|
||||
"PermissionDeniedError": PermissionDeniedError,
|
||||
"NotFoundError": NotFoundError,
|
||||
"Timeout": Timeout,
|
||||
"RateLimitError": RateLimitError,
|
||||
"InternalServerError": InternalServerError,
|
||||
}
|
||||
expected_class = exception_classes[expected_exception]
|
||||
assert isinstance(e, expected_class), f"Expected {expected_exception}, got {type(e).__name__}"
|
||||
|
||||
# Verify the error message contains GeminiException
|
||||
error_message = str(e)
|
||||
assert "GeminiException" in error_message, (
|
||||
f"Expected 'GeminiException' in error message for status {status_code}, got: {error_message}"
|
||||
)
|
||||
assert "VertexAIException" not in error_message, (
|
||||
f"Should not contain 'VertexAIException' for status {status_code}, got: {error_message}"
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user