fix(gemini): use JSON instead of form-data for image edit requests (#18012)

* fix(gemini): use JSON instead of form-data for image edit requests

Gemini's image edit API expects JSON body, not multipart/form-data.
The handler was sending form-encoded data which caused 400 errors:
"Invalid JSON payload received. Unexpected token."

Changes:
- Add use_multipart_form_data() method to BaseImageEditConfig (default True)
- Modify image_edit_handler to use json= when use_multipart_form_data() is False
- Override use_multipart_form_data() in GeminiImageEditConfig to return False

* test(gemini): add test for use_multipart_form_data
This commit is contained in:
Cesar Garcia
2025-12-16 07:41:02 +05:30
committed by GitHub
parent 4fdbbdfe6d
commit 999ffabc39
4 changed files with 60 additions and 14 deletions
@@ -109,6 +109,15 @@ class BaseImageEditConfig(ABC):
) -> ImageResponse:
pass
def use_multipart_form_data(self) -> bool:
"""
Return True if the provider uses multipart/form-data for image edit requests.
Return False if the provider uses JSON requests.
Default is True for backwards compatibility with OpenAI-style providers.
"""
return True
def get_error_class(
self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers]
) -> BaseLLMException:
+36 -14
View File
@@ -3768,13 +3768,24 @@ class BaseLLMHTTPHandler:
)
try:
response = sync_httpx_client.post(
url=api_base,
headers=headers,
data=data,
files=files,
timeout=timeout,
)
# Check if provider uses multipart/form-data or JSON
if image_edit_provider_config.use_multipart_form_data():
# Use form-data (OpenAI style)
response = sync_httpx_client.post(
url=api_base,
headers=headers,
data=data,
files=files,
timeout=timeout,
)
else:
# Use JSON (Gemini style)
response = sync_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout,
)
except Exception as e:
raise self._handle_error(
@@ -3853,13 +3864,24 @@ class BaseLLMHTTPHandler:
)
try:
response = await async_httpx_client.post(
url=api_base,
headers=headers,
data=data,
files=files,
timeout=timeout,
)
# Check if provider uses multipart/form-data or JSON
if image_edit_provider_config.use_multipart_form_data():
# Use form-data (OpenAI style)
response = await async_httpx_client.post(
url=api_base,
headers=headers,
data=data,
files=files,
timeout=timeout,
)
else:
# Use JSON (Gemini style)
response = await async_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout,
)
except Exception as e:
raise self._handle_error(
@@ -63,6 +63,10 @@ class GeminiImageEditConfig(BaseImageEditConfig):
headers["Content-Type"] = "application/json"
return headers
def use_multipart_form_data(self) -> bool:
"""Gemini uses JSON requests, not multipart/form-data."""
return False
def get_complete_url(
self,
model: str,
@@ -147,3 +147,14 @@ class TestGeminiImageEditTransformation:
headers={},
)
def test_use_multipart_form_data_returns_false(self) -> None:
"""
Gemini uses JSON requests, not multipart/form-data.
This is critical because httpx sends data differently:
- data=dict sends form-encoded
- json=dict sends JSON
Without this, Gemini returns: "Invalid JSON payload received. Unexpected token."
"""
assert self.config.use_multipart_form_data() is False