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:
Tim Elfrink 2025-09-15 21:48:14 +02:00
parent 321d5299b2
commit 4d80a4a0fb
2 changed files with 189 additions and 6 deletions

View File

@ -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:

View File

@ -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}"
)