invoke-ai/InvokeAI · error · ExternalProviderRequestError

Gemini response payload missing candidates

Error message

Gemini response payload missing candidates

What it means

ExternalProviderRequestError raised when the parsed Gemini JSON object has no 'candidates' key or its value is not a list. A successful generateContent response must contain a candidates array; its absence means the request was rejected at the model level (e.g. safety blocking with only promptFeedback present) or the response shape is wrong. The provider cannot extract any images without candidates.

Source

Thrown at invokeai/app/services/external_generation/providers/gemini.py:132

            if response.status_code == 429:
                retry_after = _parse_retry_after(response.headers.get("retry-after"))
                raise ExternalProviderRateLimitError(
                    f"Gemini rate limit exceeded. {f'Retry after {retry_after:.0f}s.' if retry_after else 'Please try again later.'}",
                    retry_after=retry_after,
                )
            raise ExternalProviderRequestError(
                f"Gemini request failed with status {response.status_code} for model '{model_id}': {response.text}"
            )

        data = response.json()
        if not isinstance(data, dict):
            raise ExternalProviderRequestError("Gemini response payload was not a JSON object")
        images: list[ExternalGeneratedImage] = []
        text_parts: list[str] = []
        finish_messages: list[str] = []
        candidates = data.get("candidates")
        if not isinstance(candidates, list):
            raise ExternalProviderRequestError("Gemini response payload missing candidates")
        for candidate in candidates:
            if not isinstance(candidate, dict):
                continue
            finish_message = candidate.get("finishMessage")
            finish_reason = candidate.get("finishReason")
            if isinstance(finish_message, str):
                finish_messages.append(finish_message)
            elif isinstance(finish_reason, str):
                finish_messages.append(f"Finish reason: {finish_reason}")
            for part in _iter_response_parts(candidate):
                inline_data = part.get("inline_data") or part.get("inlineData")
                if isinstance(inline_data, dict):
                    encoded = inline_data.get("data")
                    if encoded:
                        image = decode_image_base64(encoded)
                        images.append(ExternalGeneratedImage(image=image, seed=request.seed))
                        continue
                file_data = part.get("fileData") or part.get("file_data")

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Inspect the full response logged by the provider (it logs data on the no-images path) — look for promptFeedback.blockReason.
  2. Rephrase the prompt to avoid content that triggers safety filters; remove problematic reference images.
  3. Verify the API key has access to the requested Gemini image model and billing is enabled.
  4. If a proxy is in play, bypass it or fix its passthrough of the Gemini response.

Example fix

// before: prompt likely to be blocked
prompt = "...violent/NSFW wording..."
// after: sanitised prompt less likely to be safety-blocked
prompt = sanitize(prompt)  # strip flagged terms
result = provider.generate(request)
Defensive patterns

Strategy: type-guard

Type guard

def has_candidates(data: object) -> bool:
    return isinstance(data, dict) and isinstance(data.get("candidates"), list) and bool(data["candidates"])
# a blocked prompt yields {"promptFeedback": {"blockReason": ...}} with no candidates

Try / catch

try:
    result = provider.generate(request)
except ExternalProviderRequestError as e:
    if "missing candidates" in str(e):
        notify_user("Prompt or images were likely blocked by Gemini safety filters; rephrase and retry.")
    else:
        raise

Prevention

When it happens

Trigger: data.get('candidates') is missing or not a list despite HTTP 200 — typically when Google returns {"promptFeedback": {"blockReason": "SAFETY"}} for a blocked prompt, an empty object from a broken proxy, or a MODEL_NOT_FOUND-style JSON error body returned with a 200 status by a gateway.

Common situations: Prompt (or init/reference images) tripping Gemini's safety filters; API key lacking access to the requested image model; content-filtered prompts in regions/policies; proxy stripping the real response.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/affcf916737e5bbe. Report an issue: GitHub.