fix: Remove display_name for non-Vertex file uploads

Merge https://github.com/google/adk-python/pull/1211

### Description

When using the Google.GenAI backend (GEMINI_API), file uploads fail if the `file_data` or `inline_data` parts of the request contain a `display_name`. The Gemini API (non-Vertex) does not support this attribute, causing a `ValueError`.

This commit updates the `_preprocess_request` method in the `Gemini` class to sanitize the request. It now iterates through all content parts and sets `display_name` to `None` if the determined backend is `GEMINI_API`. This ensures compatibility, similar to the existing handling of the `labels` attribute.

Fixes #1182

### Testing Plan

**1. Unit Tests**

- Added a new parameterized test `test_preprocess_request_handles_backend_specific_fields` to `tests/unittests/models/test_google_llm.py`.
- This test verifies:
  - When the backend is `GEMINI_API`, `display_name` in `file_data` and `inline_data` is correctly set to `None`.
  - When the backend is `VERTEX_AI`, `display_name` remains unchanged.
- All unit tests passed successfully.

```shell
pytest ./tests/unittests/models/test_google_llm.py                                    ░▒▓ ✔  adk-python   base   system   21:14:02 
============================================================================================ test session starts ============================================================================================
platform darwin -- Python 3.12.10, pytest-8.3.5, pluggy-1.6.0
rootdir: /Users/leo/PycharmProjects/adk-python
configfile: pyproject.toml
plugins: anyio-4.9.0, langsmith-0.3.42, asyncio-0.26.0, mock-3.14.0, xdist-3.6.1
asyncio: mode=Mode.AUTO, asyncio_default_fixture_loop_scope=function, asyncio_default_test_loop_scope=function
collected 20 items

tests/unittests/models/test_google_llm.py ....................                                                                                                                                        [100%]

============================================================================================ 20 passed in 3.19s =============================================================================================
```

**2. Manual End-to-End (E2E) Test**
I manually verified the fix using `adk web`. The test was configured to use a **Google AI Studio API key**, which is the scenario where the bug occurs.

- **Before the fix:**
  When uploading a file, the request failed with the error: `{"error": "display_name parameter is not supported in Gemini API."}`. This confirms the bug.

<img width="968" alt="Screenshot 2025-06-06 at 21 22 35" src="https://github.com/user-attachments/assets/f1ab2db2-d5ec-40fc-a182-9932562b21e1" />

- **After the fix:**
  With the patch applied, the same file upload was processed successfully. The agent correctly analyzed the file and responded without errors.

<img width="973" alt="Screenshot 2025-06-06 at 21 23 24" src="https://github.com/user-attachments/assets/e03228f6-0b7d-4bf9-955a-ac24efb4fb72" />

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/1211 from ystory:fix/display-name d3efebe74aca635a7a255063e64f07cc44016f05
PiperOrigin-RevId: 769278445
This commit is contained in:
Yongsul Kim
2025-06-09 13:48:02 -07:00
committed by Copybara-Service
parent f38c08b305
commit cf5d7016a0
2 changed files with 107 additions and 2 deletions
+23 -1
View File
@@ -23,6 +23,7 @@ import sys
from typing import AsyncGenerator
from typing import cast
from typing import TYPE_CHECKING
from typing import Union
from google.genai import Client
from google.genai import types
@@ -244,10 +245,19 @@ class Gemini(BaseLlm):
def _preprocess_request(self, llm_request: LlmRequest) -> None:
if llm_request.config and self._api_backend == GoogleLLMVariant.GEMINI_API:
if self._api_backend == GoogleLLMVariant.GEMINI_API:
# Using API key from Google AI Studio to call model doesn't support labels.
if llm_request.config:
llm_request.config.labels = None
if llm_request.contents:
for content in llm_request.contents:
if not content.parts:
continue
for part in content.parts:
_remove_display_name_if_present(part.inline_data)
_remove_display_name_if_present(part.file_data)
def _build_function_declaration_log(
func_decl: types.FunctionDeclaration,
@@ -324,3 +334,15 @@ Raw response:
{resp.model_dump_json(exclude_none=True)}
-----------------------------------------------------------
"""
def _remove_display_name_if_present(
data_obj: Union[types.Blob, types.FileData, None],
):
"""Sets display_name to None for the Gemini API (non-Vertex) backend.
This backend does not support the display_name parameter for file uploads,
so it must be removed to prevent request failures.
"""
if data_obj and data_obj.display_name:
data_obj.display_name = None
+83
View File
@@ -14,6 +14,7 @@
import os
import sys
from typing import Optional
from unittest import mock
from google.adk import version as adk_version
@@ -23,6 +24,7 @@ from google.adk.models.google_llm import _AGENT_ENGINE_TELEMETRY_TAG
from google.adk.models.google_llm import Gemini
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.adk.utils.variant_utils import GoogleLLMVariant
from google.genai import types
from google.genai import version as genai_version
from google.genai.types import Content
@@ -337,3 +339,84 @@ async def test_connect(gemini_llm, llm_request):
):
async with gemini_llm.connect(llm_request) as connection:
assert connection is mock_connection
@pytest.mark.parametrize(
(
"api_backend, "
"expected_file_display_name, "
"expected_inline_display_name, "
"expected_labels"
),
[
(
GoogleLLMVariant.GEMINI_API,
None,
None,
None,
),
(
GoogleLLMVariant.VERTEX_AI,
"My Test PDF",
"My Test Image",
{"key": "value"},
),
],
)
def test_preprocess_request_handles_backend_specific_fields(
gemini_llm: Gemini,
api_backend: GoogleLLMVariant,
expected_file_display_name: Optional[str],
expected_inline_display_name: Optional[str],
expected_labels: Optional[str],
):
"""
Tests that _preprocess_request correctly sanitizes fields based on the API backend.
- For GEMINI_API, it should remove 'display_name' from file/inline data
and remove 'labels' from the config.
- For VERTEX_AI, it should leave these fields untouched.
"""
# Arrange: Create a request with fields that need to be preprocessed.
llm_request_with_files = LlmRequest(
model="gemini-1.5-flash",
contents=[
Content(
role="user",
parts=[
Part(
file_data=types.FileData(
file_uri="gs://bucket/file.pdf",
mime_type="application/pdf",
display_name="My Test PDF",
)
),
Part(
inline_data=types.Blob(
data=b"some_bytes",
mime_type="image/png",
display_name="My Test Image",
)
),
],
)
],
config=types.GenerateContentConfig(labels={"key": "value"}),
)
# Mock the _api_backend property to control the test scenario
with mock.patch.object(
Gemini, "_api_backend", new_callable=mock.PropertyMock
) as mock_backend:
mock_backend.return_value = api_backend
# Act: Run the preprocessing method
gemini_llm._preprocess_request(llm_request_with_files)
# Assert: Check if the fields were correctly processed
file_part = llm_request_with_files.contents[0].parts[0]
inline_part = llm_request_with_files.contents[0].parts[1]
assert file_part.file_data.display_name == expected_file_display_name
assert inline_part.inline_data.display_name == expected_inline_display_name
assert llm_request_with_files.config.labels == expected_labels