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
- 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)
- Log the full response object and request id to identify what the provider actually returned
- If using an OpenAI-compatible gateway, verify its /chat/completions implementation returns standard shapes
- 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
- Wrap completions in bounded retry with backoff for transient provider anomalies
- Log full provider responses (id + shape) when anomalies occur
- Verify OpenAI-compatible gateways return standard completion shapes before adopting them
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
- LLM returned empty JSON content
- t('step5.noResponse')
- LLM_API_KEY 未配置
- Ontology result must be an object
- LLM_API_KEY 未配置
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/9b61e1beb08cb477.
Report an issue: GitHub.