googleapis / googleapis/python-genai

FinishReason enum is missing MALFORMED_RESPONSE, which the API actually returns

Aperta
#2,937 1 commento 0 reazioni 1 assegnatario Rivendicata da @Venkaiahbabuneelam Vedi su GitHub
priority: p2 type: bug
Lingua principale
Python
Stelle
4k
Fork
1k
Merge medio
2g 12h
PR unite (30g)
41

Descrizione

### 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](https://ai.google.dev/gemini-api/docs/api-errors) (which documents `malformed_function_call` and `malformed_tool_call`) and the [troubleshooting guide](https://ai.google.dev/gemini-api/docs/troubleshooting).

### What happens today

`CaseInSensitiveEnum._missing_` (`google/genai/_common.py`) does not return `None` for an unknown value — it warns and then fabricates a member:

```python
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:

```python
x = types.FinishReason("MALFORMED_RESPONSE") # warns

repr(x) #
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:

- badlogic/pi-mono#2028 — `mapStopReason` throws on the unknown value, killing the agent run
- openclaw/openclaw#42149 — same root cause

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:

```python
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

1. Add `MALFORMED_RESPONSE` to the `FinishReason` enum (and to `js-genai` / `go-genai` for parity).
2. 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.
3. 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.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.