666ghj/MiroFish · error · LLMResponseError

LLM returned no choices

Error message

LLM returned no choices

What it means

LLMResponseError raised in _parse_json_response when the completion response has an empty or missing 'choices' list. This means the provider acknowledged the request but returned no candidate generations — distinct from a network error or invalid JSON; the response object itself is malformed/empty at the choice level.

Source

Thrown at backend/app/utils/llm_client.py:238

                # use its model-specific output limit.
                had_token_cap = request_max_tokens is not None
                request_max_tokens = None
                logger.warning(
                    "LLM returned unusable JSON (finish_reason=%s); "
                    "retrying content generation%s",
                    error.finish_reason or "unknown",
                    " without an output token cap" if had_token_cap else "",
                )

        if last_error is not None:  # pragma: no cover - defensive loop guard
            raise last_error
        raise LLMResponseError("LLM did not produce a JSON response")

    @staticmethod
    def _parse_json_response(response: Any) -> Dict[str, Any]:
        choices = getattr(response, "choices", None) or []
        if not choices:
            raise LLMResponseError("LLM returned no choices")

        choice = choices[0]
        finish_reason = getattr(choice, "finish_reason", None)
        if finish_reason == "length":
            raise LLMResponseError(
                "LLM JSON output was truncated at the token limit",
                finish_reason=finish_reason,
            )
        if finish_reason not in {None, "stop"}:
            raise LLMResponseError(
                f"LLM JSON generation stopped unexpectedly ({finish_reason})",
                finish_reason=finish_reason,
            )

        content = _clean_chat_text(extract_chat_completion_text(response))
        if not content:
            raise LLMResponseError(
                "LLM returned empty JSON content",

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Retry the request — empty choices from a provider is usually transient (upstream retry wrapper may already cover it; check whether this path was reached after retries)
  2. Log the full response object and request id to identify what the provider actually returned
  3. If using an OpenAI-compatible gateway, verify its /chat/completions implementation returns standard shapes
  4. Verify base_url points to a real OpenAI-compatible endpoint and the model name is valid for it

Example fix

# before
resp = client.chat.completions.create(**params)
value = LLMClient._parse_json_response(resp)

# after
resp = client.chat.completions.create(**params)
if not getattr(resp, "choices", None):
    logger.warning("empty choices from provider, model=%s id=%s", model, getattr(resp, "id", None))
    resp = retry_once(lambda: client.chat.completions.create(**params))
value = LLMClient._parse_json_response(resp)
Defensive patterns

Strategy: retry

Validate before calling

if not getattr(response, "choices", None):
    raise LLMResponseError("empty choices — retry or investigate provider")

Try / catch

try:
    value = LLMClient._parse_json_response(resp)
except LLMResponseError as e:
    if "no choices" in str(e):
        resp = retry_with_backoff(lambda: client.chat.completions.create(**params))
        value = LLMClient._parse_json_response(resp)
    else:
        raise

Prevention

When it happens

Trigger: A chat completion comes back with choices=[] or choices=None: provider-side incident, an OpenAI-compatible gateway returning a degenerate 200 response, or content that was filtered before generation produced any choice.

Common situations: Using OpenAI-compatible third-party endpoints (proxies, local servers) whose error paths return 200 with empty bodies; provider outages; misconfigured base_url pointing at a wrong route that returns an unexpected shape.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/9b61e1beb08cb477. Report an issue: GitHub.