diff --git a/litellm/llms/vertex_ai/gemini/transformation.py b/litellm/llms/vertex_ai/gemini/transformation.py index 7945c44d44c..41cead57739 100644 --- a/litellm/llms/vertex_ai/gemini/transformation.py +++ b/litellm/llms/vertex_ai/gemini/transformation.py @@ -208,6 +208,14 @@ def _process_gemini_media( return _apply_gemini_3_metadata( part, model, media_resolution_enum, video_metadata ) + elif "https://generativelanguage.googleapis.com/v1beta/files/" in image_url: + # Gemini Files API URI — mime type is already known by the API, + # no need to resolve it from the URL (which would cause a 403). + file_data = FileDataType(file_uri=image_url) + part = {"file_data": file_data} + return _apply_gemini_3_metadata( + part, model, media_resolution_enum, video_metadata + ) elif ( "https://" in image_url and (image_type := format or _get_image_mime_type_from_url(image_url)) diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py index ce3d2daa743..1f3841b6738 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_ai_gemini_transformation.py @@ -3,6 +3,7 @@ from litellm.litellm_core_utils.prompt_templates.factory import ( ) from litellm.llms.vertex_ai.gemini.transformation import ( _gemini_convert_messages_with_history, + _process_gemini_media, _transform_request_body, check_if_part_exists_in_parts, _get_highest_media_resolution, @@ -1812,3 +1813,39 @@ def test_multi_turn_function_calling_roles(): assert ( content["role"] == "user" ), f"Content block {i} with function_response has role='{content['role']}', expected 'user'" + + +def test_process_gemini_media_files_api_uri_no_mime_type_required(): + """ + Regression test for https://github.com/BerriAI/litellm/issues/24907. + + Gemini Files API URIs (https://generativelanguage.googleapis.com/v1beta/files/*) + must be forwarded as file_data without attempting to resolve a mime type from the + URL. The URL has no file extension and the endpoint returns 403 for unauthenticated + requests, so any attempt to download or sniff the mime type raises an error. + """ + gemini_file_uri = ( + "https://generativelanguage.googleapis.com/v1beta/files/37eh7rsw1vfe" + ) + + part = _process_gemini_media(image_url=gemini_file_uri) + + assert "file_data" in part + assert part["file_data"]["file_uri"] == gemini_file_uri + # mime_type must NOT be set — the Files API already knows the type + assert part["file_data"].get("mime_type") is None + + +def test_process_gemini_media_files_api_uri_with_explicit_format(): + """ + When the caller explicitly provides a format/mime type for a Gemini Files API URI, + it should still be forwarded as file_data (not attempted as an inline download). + """ + gemini_file_uri = ( + "https://generativelanguage.googleapis.com/v1beta/files/n1vhxa28lyaw" + ) + + part = _process_gemini_media(image_url=gemini_file_uri, format="text/plain") + + assert "file_data" in part + assert part["file_data"]["file_uri"] == gemini_file_uri