Description
The Gemini API returns MALFORMED_RESPONSE as a finishReason, but this value is not a member of the FinishReason enum in this SDK. As a result every response carrying it triggers a UserWarning and is coerced into a synthetic pseudo-member.
.../google/genai/_common.py:651: UserWarning: MALFORMED_RESPONSE is not a valid FinishReason
warnings.warn(f'{value} is not a valid {cls.__name__}')
Current enum members
google/genai/types.py class FinishReason currently defines:
FINISH_REASON_UNSPECIFIED, STOP, MAX_TOKENS, SAFETY, RECITATION, LANGUAGE, OTHER, BLOCKLIST, PROHIBITED_CONTENT, SPII, MALFORMED_FUNCTION_CALL, IMAGE_SAFETY, UNEXPECTED_TOOL_CALL, TOO_MANY_TOOL_CALLS, IMAGE_PROHIBITED_CONTENT, NO_IMAGE, IMAGE_RECITATION, IMAGE_OTHER
grep MALFORMED_RESPONSE over the package returns nothing. The same is true for js-genai (src/types.ts), where the only MALFORMED* member is MALFORMED_FUNCTION_CALL.
MALFORMED_RESPONSE also does not appear in the public docs — it is absent from both API errors (which documents malformed_function_call and malformed_tool_call) and the troubleshooting guide.
What happens today
CaseInSensitiveEnum._missing_ (google/genai/_common.py) does not return None for an unknown value — it warns and then fabricates a member:
class CaseInSensitiveEnum(str, enum.Enum):
@classmethod
def _missing_(cls, value):
try: return cls[value.upper()]
except KeyError:
try: return cls[value.lower()]
except KeyError:
warnings.warn(f'{value} is not a valid {cls.__name__}')
try:
unknown_enum_val = super().__new__(cls, value)
unknown_enum_val._name_ = str(value)
unknown_enum_val._value_ = value
return unknown_enum_val
except:
return None
The resulting object behaves inconsistently, which is the practical problem:
x = types.FinishReason("MALFORMED_RESPONSE") # warns
repr(x) # <FinishReason.MALFORMED_RESPONSE: 'MALFORMED_RESPONSE'>
isinstance(x, types.FinishReason) # True
isinstance(x, str) # True
x == "MALFORMED_RESPONSE" # True
x.name # 'MALFORMED_RESPONSE'
str(x) # 'FinishReason.MALFORMED_RESPONSE' <-- surprising
"MALFORMED_RESPONSE" in types.FinishReason.__members__ # False <-- surprising
types.FinishReason.MALFORMED_RESPONSE # AttributeError <-- surprising
So == "..." works, but __members__ lookups, attribute access, and str() comparisons all fail. Code written against the enum cannot reference this value at all.
Impact on downstream libraries
Consumers that exhaustively match on FinishReason crash rather than degrade:
A related symptom from the same "value outside the enum" family is #2024 (indefinite hang when accessing finish_reason for IMAGE_SAFETY / NO_IMAGE).
Reproduction
MALFORMED_RESPONSE is returned intermittently by Gemini 3 family models. The enum gap itself, however, reproduces without any API call:
from google.genai import types
print("MALFORMED_RESPONSE" in types.FinishReason.__members__) # False
x = types.FinishReason("MALFORMED_RESPONSE") # UserWarning
print(repr(x), x.name, str(x), x == "MALFORMED_RESPONSE")
print(types.FinishReason.MALFORMED_RESPONSE) # AttributeError
Environment
google-genai 2.7.0 (also verified absent on main)
- Python 3.13
- Observed with
gemini-3.5-flash-lite via the Gemini API; other reports cite gemini-3-flash-preview and gemini-3.1-pro-preview
Request
- Add
MALFORMED_RESPONSE to the FinishReason enum (and to js-genai / go-genai for parity).
- Document its semantics. Right now there is no authoritative definition, so downstream code has to guess — third parties variously treat it as "the model's own generation broke, server-side and transient" or map it to a generic error. Without a documented meaning, any retry or error-handling logic keyed on this value is guesswork.
- Consider whether
_missing_ should produce something more predictable for unknown values. The current pseudo-member satisfies == "STRING" but fails __members__, attribute access, and str(), which is a confusing middle ground. Even a documented note that unknown values are coerced this way would help.
Happy to supply more detail if useful.
Description
The Gemini API returns
MALFORMED_RESPONSEas afinishReason, but this value is not a member of theFinishReasonenum in this SDK. As a result every response carrying it triggers aUserWarningand is coerced into a synthetic pseudo-member.Current enum members
google/genai/types.pyclass FinishReasoncurrently defines:FINISH_REASON_UNSPECIFIED,STOP,MAX_TOKENS,SAFETY,RECITATION,LANGUAGE,OTHER,BLOCKLIST,PROHIBITED_CONTENT,SPII,MALFORMED_FUNCTION_CALL,IMAGE_SAFETY,UNEXPECTED_TOOL_CALL,TOO_MANY_TOOL_CALLS,IMAGE_PROHIBITED_CONTENT,NO_IMAGE,IMAGE_RECITATION,IMAGE_OTHERgrep MALFORMED_RESPONSEover the package returns nothing. The same is true forjs-genai(src/types.ts), where the onlyMALFORMED*member isMALFORMED_FUNCTION_CALL.MALFORMED_RESPONSEalso does not appear in the public docs — it is absent from both API errors (which documentsmalformed_function_callandmalformed_tool_call) and the troubleshooting guide.What happens today
CaseInSensitiveEnum._missing_(google/genai/_common.py) does not returnNonefor an unknown value — it warns and then fabricates a member:The resulting object behaves inconsistently, which is the practical problem:
So
== "..."works, but__members__lookups, attribute access, andstr()comparisons all fail. Code written against the enum cannot reference this value at all.Impact on downstream libraries
Consumers that exhaustively match on
FinishReasoncrash rather than degrade:mapStopReasonthrows on the unknown value, killing the agent runA related symptom from the same "value outside the enum" family is #2024 (indefinite hang when accessing
finish_reasonforIMAGE_SAFETY/NO_IMAGE).Reproduction
MALFORMED_RESPONSEis returned intermittently by Gemini 3 family models. The enum gap itself, however, reproduces without any API call:Environment
google-genai2.7.0 (also verified absent onmain)gemini-3.5-flash-litevia the Gemini API; other reports citegemini-3-flash-previewandgemini-3.1-pro-previewRequest
MALFORMED_RESPONSEto theFinishReasonenum (and tojs-genai/go-genaifor parity)._missing_should produce something more predictable for unknown values. The current pseudo-member satisfies== "STRING"but fails__members__, attribute access, andstr(), which is a confusing middle ground. Even a documented note that unknown values are coerced this way would help.Happy to supply more detail if useful.