Skip to content

finishMessage field is not parsed from API response in streaming mode (mldev) #2049

@snapshotpl

Description

@snapshotpl

Description

The finishMessage field from the Gemini API response is not being populated in Candidate objects when using generate_content_stream() with the Gemini API (mldev, not Vertex AI).

Environment

Component Value
Package google-genai
Version 1.62.0
API Gemini API (mldev)
Method client.models.generate_content_stream()

Current Behavior

When the API returns a response with finishMessage in the JSON:

{
  "candidates": [{
    "content": {},
    "finishReason": "IMAGE_OTHER",
    "finishMessage": "Unable to show the generated image. The model could not generate the image based on the prompt provided."
  }]
}

The Candidate object has:

  • candidate.finish_reason = FinishReason.IMAGE_OTHER (correctly populated)
  • candidate.finish_message = None (not populated)

Expected Behavior

candidate.finish_message should contain the string value from the API response:

assert candidate.finish_message == "Unable to show the generated image. The model could not generate the image based on the prompt provided."

Root Cause

In models.py, the _Candidate_from_mldev() function (line ~75) transforms the raw API response but does not map the finishMessage field:

def _Candidate_from_mldev(from_object, parent_object=None, root_object=None):
    to_object: dict[str, Any] = {}

    # ... other field mappings ...

    # This mapping exists:
    if getv(from_object, ['finishReason']) is not None:
        setv(to_object, ['finish_reason'], getv(from_object, ['finishReason']))

    # This mapping is MISSING:
    # if getv(from_object, ['finishMessage']) is not None:
    #     setv(to_object, ['finish_message'], getv(from_object, ['finishMessage']))

    # ... other field mappings ...

    return to_object

Reproduction

Minimal Example

from google import genai
from google.genai import types

client = genai.Client(api_key="YOUR_API_KEY")

stream = client.models.generate_content_stream(
    model='gemini-2.0-flash-exp',
    contents='Create a photorealistic image of a sunset over mountains',
    config=types.GenerateContentConfig(
        response_modalities=['IMAGE'],
        image_config=types.ImageConfig(aspect_ratio='16:9')
    )
)

for chunk in stream:
    for candidate in chunk.candidates:
        if candidate.finish_reason:
            print(f"finish_reason: {candidate.finish_reason}")
            print(f"finish_message: {candidate.finish_message}")  # Always None

Verification Test

Direct parsing with model_validate() works correctly, confirming the Candidate model supports the field:

from google.genai import types

# Simulate raw API response
raw_data = {
    "content": {},
    "finishReason": "IMAGE_OTHER",
    "finishMessage": "Unable to show the generated image..."
}

# Direct parsing works
candidate = types.Candidate.model_validate(raw_data)
assert candidate.finish_message == "Unable to show the generated image..."

# But after SDK transformation, it's lost
from google.genai._common import get_value_by_path as getv
from google.genai._common import set_value_by_path as setv

def simulate_sdk_transformation(from_object):
    to_object = {}
    if getv(from_object, ['finishReason']) is not None:
        setv(to_object, ['finish_reason'], getv(from_object, ['finishReason']))
    # Missing: finishMessage mapping
    return to_object

transformed = simulate_sdk_transformation(raw_data)
candidate2 = types.Candidate.model_validate(transformed)
assert candidate2.finish_message is None  # Bug confirmed

Impact

Users cannot access important diagnostic information about why content generation failed, making debugging difficult for:

  • Image generation failures (IMAGE_OTHER, IMAGE_SAFETY, IMAGE_PROHIBITED_CONTENT)
  • Content policy violations (PROHIBITED_CONTENT, SAFETY, RECITATION)
  • Other failure scenarios where the API provides explanatory messages

This is especially problematic because finishMessage often contains actionable guidance like:

"Unable to show the generated image. The model could not generate the image based on the prompt provided. Try rephrasing the prompt."

Suggested Fix

Add the missing field mapping in _Candidate_from_mldev() around line 97:

if getv(from_object, ['finishReason']) is not None:
    setv(to_object, ['finish_reason'], getv(from_object, ['finishReason']))

# Add this:
if getv(from_object, ['finishMessage']) is not None:
    setv(to_object, ['finish_message'], getv(from_object, ['finishMessage']))

Additional Context

  • The finish_message field is properly defined in types.Candidate (line ~6484 in types.py)
  • The Pydantic model uses alias_generator=to_camel which correctly maps finish_message to finishMessage
  • The field appears in raw API responses (confirmed via logging)
  • Only the transformation layer strips it out during streaming

Metadata

Metadata

Labels

priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions