fix(vertex_ai): use BadRequestError with exception chaining in Gemini transformation

Replace generic Exception raises with litellm.BadRequestError in
_gemini_convert_messages_with_history() for two error paths:

1. When file_id and file_data are both None — raises BadRequestError
2. When _process_gemini_media throws:
   - ImageFetchError — re-raised as BadRequestError preserving original message
   - BadRequestError — re-raised as-is
   - Any other exception — wrapped as BadRequestError with original error details

All wrapping raise statements use `raise ... from e` to preserve the
original exception chain for server-side debugging.

Added tests/test_litellm/llms/vertex_ai/gemini/test_gemini_transformation_exception_handling.py
covering all error paths.

Fixes BerriAI/litellm#24193

Co-authored-by: Kris Xia <xiajiayi0506@gmail.com>
This commit is contained in:
S0ngRu1 2026-03-20 15:53:01 +08:00
parent d7c419bfee
commit 6a61333b4f
No known key found for this signature in database
2 changed files with 100 additions and 10 deletions

View file

@ -3,6 +3,7 @@ Transformation logic from OpenAI format to Gemini format.
Why separate file? Make it easy to see how transformation works
"""
import json
import os
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union, cast
@ -225,7 +226,11 @@ def _process_gemini_media(
return _apply_gemini_3_metadata(
part, model, media_resolution_enum, video_metadata
)
raise Exception("Invalid image received - {}".format(image_url))
raise litellm.BadRequestError(
message=f"Invalid image received - {image_url}. Supported formats are http://, https://, gs://, or base64 data.",
model=model,
llm_provider="vertex_ai",
)
except Exception as e:
raise e
@ -381,10 +386,11 @@ def _gemini_convert_messages_with_history( # noqa: PLR0915
video_metadata = file_element["file"].get("video_metadata")
passed_file = file_id or file_data
if passed_file is None:
raise Exception(
"Unknown file type. Please pass in a file_id or file_data"
raise litellm.BadRequestError(
message="Unknown file type. Please pass in a file_id or file_data",
model=model,
llm_provider="vertex_ai",
)
# Convert detail to media_resolution_enum
media_resolution_enum = (
_convert_detail_to_media_resolution_enum(detail)
@ -399,12 +405,23 @@ def _gemini_convert_messages_with_history( # noqa: PLR0915
video_metadata=video_metadata,
)
_parts.append(_part)
except Exception:
raise Exception(
"Unable to determine mime type for file_id: {}, set this explicitly using message[{}].content[{}].file.format".format(
file_id, msg_i, element_idx
)
)
except Exception as e:
if isinstance(e, litellm.ImageFetchError):
# ImageFetchError (e.g., 403/404 on URL) — wrap with model/provider context
raise litellm.BadRequestError(
message=str(e),
model=model,
llm_provider="vertex_ai",
) from e
elif isinstance(e, litellm.BadRequestError):
# Other BadRequestError (e.g., unsupported format) — preserve original message
raise
else:
raise litellm.BadRequestError(
message=f"Unable to determine mime type for file_id: {file_id}, set this explicitly using message[{msg_i}].content[{element_idx}].file.format. Original error: {str(e)}",
model=model,
llm_provider="vertex_ai",
) from e
user_content.extend(_parts)
elif _message_content is not None and isinstance(_message_content, str):
_part = PartType(text=_message_content)

View file

@ -0,0 +1,73 @@
import pytest
from typing import List, cast
from unittest.mock import patch
import litellm
from litellm.llms.vertex_ai.gemini.transformation import (
_gemini_convert_messages_with_history,
)
from litellm.types.llms.openai import AllMessageValues
def test_missing_file_id_and_file_data_raises_bad_request_error():
"""When file element has neither file_id nor file_data, a BadRequestError is raised."""
messages = cast(List[AllMessageValues], [{"role": "user", "content": [{"type": "file", "file": {}}]}])
with pytest.raises(litellm.BadRequestError) as exc_info:
_gemini_convert_messages_with_history(messages, model="gemini-1.5-pro")
assert "Unknown file type" in str(exc_info.value)
def test_image_fetch_error_raises_bad_request_error():
"""ImageFetchError from _process_gemini_media is re-raised as BadRequestError."""
messages = cast(
List[AllMessageValues],
[{"role": "user", "content": [{"type": "file", "file": {"file_id": "some_id"}}]}],
)
with patch(
"litellm.llms.vertex_ai.gemini.transformation._process_gemini_media",
side_effect=litellm.ImageFetchError(
message="403 Forbidden",
model="gemini-1.5-pro",
llm_provider="vertex_ai",
),
):
with pytest.raises(litellm.BadRequestError) as exc_info:
_gemini_convert_messages_with_history(messages, model="gemini-1.5-pro")
assert "403 Forbidden" in str(exc_info.value)
def test_generic_exception_raises_bad_request_error_with_mime_message():
"""Generic exception from _process_gemini_media is wrapped as BadRequestError with MIME message."""
messages = cast(
List[AllMessageValues],
[{"role": "user", "content": [{"type": "file", "file": {"file_id": "some_id"}}]}],
)
with patch(
"litellm.llms.vertex_ai.gemini.transformation._process_gemini_media",
side_effect=ValueError("cannot determine mime type"),
):
with pytest.raises(litellm.BadRequestError) as exc_info:
_gemini_convert_messages_with_history(messages, model="gemini-1.5-pro")
assert "Unable to determine mime type" in str(exc_info.value)
assert "cannot determine mime type" in str(exc_info.value)
def test_bad_request_error_from_process_gemini_media_is_re_raised_as_is():
"""BadRequestError from _process_gemini_media is re-raised verbatim (not replaced by generic message)."""
original_message = "Invalid image received - https://example.com/img.png. Supported formats are..."
messages = cast(
List[AllMessageValues],
[{"role": "user", "content": [{"type": "file", "file": {"file_id": "some_id"}}]}],
)
with patch(
"litellm.llms.vertex_ai.gemini.transformation._process_gemini_media",
side_effect=litellm.BadRequestError(
message=original_message,
model="gemini-1.5-pro",
llm_provider="vertex_ai",
),
):
with pytest.raises(litellm.BadRequestError) as exc_info:
_gemini_convert_messages_with_history(messages, model="gemini-1.5-pro")
assert original_message in str(exc_info.value)
assert "Unable to determine mime type" not in str(exc_info.value)