Environment
google-api-core 2.34.0 (also reproduced on 2.33.0)
- Python 3.11
- Surfaced via Vertex AI
streamGenerateContent (through langchain-google-vertexai, but the bug is provider-agnostic within api-core)
The bug
Some REST error responses carry the JSON body as an array wrapping the error object rather than a bare object. Vertex AI's streamGenerateContent does this — a quota 429 arrives as:
[{"error": {"code": 429, "message": "Resource exhausted...", "status": "RESOURCE_EXHAUSTED"}}]
google.api_core.exceptions.format_http_response_error assumes a dict payload:
# google/api_core/exceptions.py (2.34.0)
payload = {} if not payload else payload
error_message = payload.get("error", {}).get("message", "unknown error")
A non-empty list is truthy, so it passes the {} if not payload guard and crashes on payload.get:
AttributeError: 'list' object has no attribute 'get'
Why this matters more than a cosmetic traceback
The AttributeError replaces the real exception. The caller never receives ResourceExhausted / TooManyRequests, so every retry predicate keyed on api-core exception types — including LangChain's Vertex retry list and any tenacity retry_if_exception_type(ResourceExhausted) — never fires. A transient, expected-under-Dynamic-Shared-Quota 429 becomes an immediate unretryable hard failure on streaming call paths.
Reproduction
from unittest import mock
from google.api_core import exceptions
response = mock.Mock(status_code=429, request=mock.Mock(url="https://example"))
payload = [{"error": {"code": 429, "message": "Resource exhausted", "status": "RESOURCE_EXHAUSTED"}}]
exceptions.format_http_response_error(response, "POST", "https://example", payload)
# AttributeError: 'list' object has no attribute 'get'
Suggested fix
Normalize a list payload to its first dict element before the existing dict logic:
if isinstance(payload, list):
payload = next((p for p in payload if isinstance(p, dict)), {})
Related (optional but valuable): a 429 is currently mapped to the base TooManyRequests by HTTP status alone, even when the body declares "status": "RESOURCE_EXHAUSTED". Mapping that case to ResourceExhausted (a TooManyRequests subclass, so existing predicates keep matching) would let retry predicates that key on ResourceExhausted see quota errors from REST/streaming paths the way they do from gRPC.
We are currently carrying exactly this shim in production and would like to retire it once fixed upstream.
Prior reports
This was previously reported as #15021 (filed against python-api-core, transferred here, then bulk-closed for inactivity as not-planned — the bug was never fixed; the crash lines above are from the current 2.34.0 release) and the list-shaped Vertex body itself was described in #14703. Filing fresh per the bulk-close message's invitation, with a self-contained repro and the retry-predicate consequence spelled out.
Environment
google-api-core2.34.0 (also reproduced on 2.33.0)streamGenerateContent(throughlangchain-google-vertexai, but the bug is provider-agnostic within api-core)The bug
Some REST error responses carry the JSON body as an array wrapping the error object rather than a bare object. Vertex AI's
streamGenerateContentdoes this — a quota 429 arrives as:[{"error": {"code": 429, "message": "Resource exhausted...", "status": "RESOURCE_EXHAUSTED"}}]google.api_core.exceptions.format_http_response_errorassumes a dict payload:A non-empty list is truthy, so it passes the
{} if not payloadguard and crashes onpayload.get:Why this matters more than a cosmetic traceback
The
AttributeErrorreplaces the real exception. The caller never receivesResourceExhausted/TooManyRequests, so every retry predicate keyed on api-core exception types — including LangChain's Vertex retry list and any tenacityretry_if_exception_type(ResourceExhausted)— never fires. A transient, expected-under-Dynamic-Shared-Quota 429 becomes an immediate unretryable hard failure on streaming call paths.Reproduction
Suggested fix
Normalize a list payload to its first dict element before the existing dict logic:
Related (optional but valuable): a 429 is currently mapped to the base
TooManyRequestsby HTTP status alone, even when the body declares"status": "RESOURCE_EXHAUSTED". Mapping that case toResourceExhausted(aTooManyRequestssubclass, so existing predicates keep matching) would let retry predicates that key onResourceExhaustedsee quota errors from REST/streaming paths the way they do from gRPC.We are currently carrying exactly this shim in production and would like to retire it once fixed upstream.
Prior reports
This was previously reported as #15021 (filed against
python-api-core, transferred here, then bulk-closed for inactivity as not-planned — the bug was never fixed; the crash lines above are from the current 2.34.0 release) and the list-shaped Vertex body itself was described in #14703. Filing fresh per the bulk-close message's invitation, with a self-contained repro and the retry-predicate consequence spelled out.