mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
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:
parent
d7c419bfee
commit
6a61333b4f
2 changed files with 100 additions and 10 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
Loading…
Add table
Reference in a new issue