fix(image-gen): add thought_signature to ImageObject for Gemini 3 Pro

Fixes #17184 - Gemini 3 Pro image preview model returns a thoughtSignature
field required for interactive image editing. This change:

- Adds thought_signature field to ImageObject class
- Updates Gemini and Vertex AI transformations to extract thoughtSignature
- Adds test for thought_signature in response transformation
This commit is contained in:
Chesars 2025-11-27 17:40:06 -03:00 committed by Sameer Kankute
parent 27b4052231
commit b2463291c7
4 changed files with 47 additions and 1 deletions

View file

@ -258,6 +258,7 @@ class GoogleImageGenConfig(BaseImageGenerationConfig):
model_response.data.append(ImageObject(
b64_json=inline_data["data"],
url=None,
thought_signature=part.get("thoughtSignature"),
))
# Extract usage metadata for Gemini models

View file

@ -298,6 +298,7 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM):
model_response.data.append(ImageObject(
b64_json=inline_data["data"],
url=None,
thought_signature=part.get("thoughtSignature"),
))
if usage_metadata := response_data.get("usageMetadata", None):

View file

@ -2129,6 +2129,7 @@ class ImageObject(OpenAIImage):
b64_json: The base64-encoded JSON of the generated image, if response_format is b64_json.
url: The URL of the generated image, if response_format is url (default).
revised_prompt: The prompt that was used to generate the image, if there was any revision to the prompt.
thought_signature: The thought signature returned by Gemini image generation models (used for interactive image editing).
https://platform.openai.com/docs/api-reference/images/object
"""
@ -2136,9 +2137,11 @@ class ImageObject(OpenAIImage):
b64_json: Optional[str] = None
url: Optional[str] = None
revised_prompt: Optional[str] = None
thought_signature: Optional[str] = None
def __init__(self, b64_json=None, url=None, revised_prompt=None, **kwargs):
def __init__(self, b64_json=None, url=None, revised_prompt=None, thought_signature=None, **kwargs):
super().__init__(b64_json=b64_json, url=url, revised_prompt=revised_prompt) # type: ignore
self.thought_signature = thought_signature
def __contains__(self, key):
# Define custom behavior for the 'in' operator

View file

@ -230,6 +230,47 @@ class TestVertexAIGeminiImageGenerationConfig:
assert result.data[0].b64_json == "image1"
assert result.data[1].b64_json == "image2"
def test_transform_image_generation_response_signature(self):
"""Test response transformation includes thoughtSignature for Gemini 3 Pro"""
mock_response = MagicMock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json.return_value = {
"candidates": [
{
"content": {
"parts": [
{
"inlineData": {
"mimeType": "image/png",
"data": "base64_encoded_image_data",
},
"thoughtSignature": "test_signature_abc123",
}
]
}
}
]
}
mock_response.headers = {}
from litellm.types.utils import ImageResponse
model_response = ImageResponse()
result = self.config.transform_image_generation_response(
model="gemini-3-pro-image-preview",
raw_response=mock_response,
model_response=model_response,
logging_obj=MagicMock(),
request_data={},
optional_params={},
litellm_params={},
encoding=None,
)
assert len(result.data) == 1
assert result.data[0].b64_json == "base64_encoded_image_data"
assert result.data[0].thought_signature == "test_signature_abc123"
class TestVertexAIImagenImageGenerationConfig:
def setup_method(self):