Skip to content

🐛 Fix Gemini gcs_uri MIME handling without extension#2

Draft
S0ngRu1 wants to merge 12 commits into
mainfrom
cursor/fix-gcs-uri-mime-fallback-2a2b
Draft

🐛 Fix Gemini gcs_uri MIME handling without extension#2
S0ngRu1 wants to merge 12 commits into
mainfrom
cursor/fix-gcs-uri-mime-fallback-2a2b

Conversation

@S0ngRu1
Copy link
Copy Markdown
Owner

@S0ngRu1 S0ngRu1 commented Apr 29, 2026

Relevant issues

Fixes gcs_uri without extension failure in Gemini media transformation by auto-reading GCS object metadata content type.

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Type

🐛 Bug Fix
✅ Test

Changes

  • Added automatic content type resolution for extension-less gs:// URIs in Gemini media conversion.
    • New helper reads GCS object metadata via JSON API (fields=contentType).
    • Uses Vertex credentials/project when available to fetch metadata.
    • Falls back to unauthenticated metadata read for public objects.
  • Kept existing extension-based inference path when extension exists.
  • Kept explicit MIME override support (format / mime_type / content_type).
  • If both extension inference and metadata lookup fail, now returns a clear actionable BadRequestError.
  • Passed litellm_params through _transform_messages into _gemini_convert_messages_with_history so media conversion has access to vertex auth context.
  • Added regression tests for extension-less GCS URI behavior:
    • Success path when metadata resolver returns image/jpeg.
    • Existing clear-error path when no extension and metadata cannot be resolved.
Open in Web Open in Cursor 

Summary by Sourcery

Improve Gemini Vertex AI media handling for GCS image URIs without file extensions and allow MIME metadata from OpenAI-style image payloads to drive MIME selection.

Bug Fixes:

  • Handle GCS image URIs without extensions in Gemini media processing by requiring an explicit MIME type and surfacing a clear BadRequestError when it is missing.
  • Respect image_url.mime_type and image_url.content_type fields, in addition to image_url.format, when converting OpenAI image messages to Gemini parts.

Tests:

  • Add regression tests covering GCS URIs without extensions succeeding when a MIME type is provided on image_url and failing with a clear error when it is omitted.
  • Add a conversion test ensuring gs:// image URLs without extensions correctly propagate the provided MIME type into Gemini FileDataType.

github-actions Bot and others added 11 commits March 18, 2026 03:31
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…dling

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
2. When MIME type cannot be determined, with special handling for ImageFetchError

Add unit tests covering all three error paths and MIME type URL detection tests.
fix(vertex_ai): use BadRequestError in Gemini transformation file handling
… 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#24193
…ort values

Replace ValueError with litellm.BadRequestError in _map_reasoning_effort so that
empty strings and unrecognized values return a 400 response with a clear message
instead of being wrapped as a 500 APIConnectionError.

Add unit tests covering empty string, invalid string, None, and all valid values.
Resolve merge conflict in transformation.py:
- Add trailing comma to llm_provider argument
- Add exception chaining (from e) for BadRequestError raises
- Add explicit re-raise for BadRequestError to preserve original message
Co-authored-by: Cai Songrui <3230809014@stu.cuit.edu.cn>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 29, 2026

Reviewer's Guide

Adjusts Gemini media handling for GCS URIs without extensions by deriving MIME type from additional image_url fields, raising a clear BadRequestError when type cannot be inferred, and adds regression tests covering the new behavior.

Flow diagram for updated _process_gemini_media GCS URI MIME handling

flowchart TD
    A["Start _process_gemini_media with image_url"] --> B{"Does image_url contain gs://?"}
    B -- "No" --> Z["Handle non GCS media (unchanged)"]
    B -- "Yes" --> C["extension_with_dot = os.path.splitext(image_url)[-1]"]
    C --> D["extension = extension_with_dot[1:]"]
    D --> E{"Was format explicitly provided?"}

    E -- "Yes" --> F["mime_type = format"]
    F --> G["file_data = FileDataType(mime_type, file_uri=image_url)"]
    G --> H["Return or use file_data (Gemini call)"]

    E -- "No" --> I{"Does extension exist?"}
    I -- "Yes" --> J["file_type = get_file_type_from_extension(extension)"]
    J --> K{"is_gemini_1_5_accepted_file_type(file_type)?"}
    K -- "No" --> L["Raise Exception: File type not supported by gemini"]
    K -- "Yes" --> M["mime_type = get_file_mime_type_for_file_type(file_type)"]
    M --> G

    I -- "No" --> N["Raise litellm.BadRequestError with guidance:\nUnable to determine mime type for gs URI (no extension).\nAsk caller to set image_url.format / mime_type / content_type or message.content[].file.format."]
    N --> H
Loading

Flow diagram for updated format extraction in _gemini_convert_messages_with_history

flowchart TD
    A["Start processing img_element in _gemini_convert_messages_with_history"] --> B{"Is img_element[image_url] a dict?"}
    B -- "No" --> Z["Handle non dict image_url (unchanged)"]
    B -- "Yes" --> C["image_url = img_element[image_url][url]"]
    C --> D["format = img_element[image_url].get(format)"]
    D --> E{"Is format set?"}

    E -- "Yes" --> H["Use format as mime_type for Gemini media"]

    E -- "No" --> F["format = img_element[image_url].get(mime_type)"]
    F --> G{"Is format set from mime_type?"}

    G -- "Yes" --> H

    G -- "No" --> I["format = img_element[image_url].get(content_type)"]
    I --> J{"Is format set from content_type?"}
    J -- "Yes" --> H
    J -- "No" --> K["format remains None; rely on downstream MIME inference or error"]

    H --> L["detail = img_element[image_url].get(detail)"]
    L --> M["media_resolution_enum = _convert_detail_to_media_resolution_enum(detail)"]
    M --> N["Pass image_url, format (or mime_type/content_type), media_resolution_enum to Gemini media processing"]
Loading

File-Level Changes

Change Details Files
Improve GCS (gs://) URI MIME-type resolution in Gemini media processing and surface a clear BadRequestError when MIME cannot be determined.
  • For gs:// URIs, compute the path extension and only attempt file-type inference when an extension is present.
  • When no explicit format is provided and the URI has an extension, infer file_type via get_file_type_from_extension, validate it with is_gemini_1_5_accepted_file_type, then map it to a MIME type using get_file_mime_type_for_file_type.
  • When no explicit format is provided and the URI lacks an extension, raise litellm.BadRequestError with an actionable message instructing callers to provide MIME or format explicitly, instead of letting a generic ValueError propagate.
  • Ensure FileDataType for GCS media is constructed using the resolved mime_type and the original file_uri.
litellm/llms/vertex_ai/gemini/transformation.py
Allow OpenAI-style image_url objects to supply MIME information to Gemini conversion and add regression tests for the new behavior and error path.
  • Update _gemini_convert_messages_with_history to treat image_url.format, image_url.mime_type, or image_url.content_type (in that priority order) as the media format for downstream processing.
  • Add a test that gs:// URIs without extension are correctly processed when mime_type is provided on the image_url object and result in a FileDataType with that mime_type and URI.
  • Add a test ensuring that calling _process_gemini_media with a gs:// URI lacking an extension and MIME information raises litellm.BadRequestError with the expected guidance message.
  • Extend the existing test_process_gemini_media suite to cover the new GCS and image_url behavior.
litellm/llms/vertex_ai/gemini/transformation.py
tests/test_litellm/llms/vertex_ai/test_vertex.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Co-authored-by: Cai Songrui <3230809014@stu.cuit.edu.cn>
"?fields=contentType"
)
try:
response = httpx.get(url=metadata_url, headers=headers, timeout=5.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants