ZhuLinsen/daily_stock_analysis · error · ValueError

LLM returned empty response

Error message

LLM returned empty response

What it means

ValueError('LLM returned empty response') raised inside the per-model retry loop of the LiteLLM completion path when a model call succeeded structurally but produced empty content. Because it is raised inside the try block, it is caught by the generic 'except Exception' handler, logged as a warning, recorded as last_error, and the loop continues to the next model — so it only surfaces to callers wrapped in _AllModelsFailedError after every model failed.

Source

Thrown at src/analyzer.py:3305

                content = self._extract_completion_text(response)
                if content:
                    usage_messages = None if audit_context is not None else call_kwargs["messages"]
                    usage = self._normalize_usage(
                        extract_usage_payload(response),
                        model=usage_model or model,
                        provider=usage_provider,
                        messages=usage_messages,
                    )
                    if audit_context is not None:
                        usage = _attach_usage_audit(usage, call_kwargs["messages"])
                    last_response_text = content
                    last_model = model
                    last_usage = usage
                    if response_validator is not None:
                        response_validator(content)
                    return (content, model, usage)
                raise ValueError("LLM returned empty response")

            except Exception as e:
                safe_error = self._sanitize_litellm_exception_text(e, config=config, model=model)
                logger.warning("[LiteLLM] %s failed: %s", model, safe_error)
                last_error = RuntimeError(f"{type(e).__name__}: {safe_error}")
                continue

        raise _AllModelsFailedError(
            f"All LLM models failed (tried {len(models_to_try)} model(s)). Last error: {last_error}",
            last_response_text=last_response_text,
            last_model=last_model,
            last_usage=last_usage,
        )

    def generate_text(
        self,
        prompt: str,
        max_tokens: int = 2048,

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. If you see this inside _AllModelsFailedError, check the warning logs — each model's specific empty/error reason is logged per attempt.
  2. Raise max_tokens and/or trim the prompt so the model has room to emit content.
  3. Disable or replace the offending model in the model list; empty-content providers poison the whole chain.
  4. If a response_validator is too strict, loosen it so valid-but-short responses pass.

Example fix

# before
completion_kwargs = {"max_tokens": 8, ...}  # model emits nothing

# after
completion_kwargs = {"max_tokens": 4096, ...}
Defensive patterns

Strategy: retry

Validate before calling

def response_has_content(response) -> bool:
    try:
        return bool((response.choices[0].message.content or "").strip())
    except (IndexError, AttributeError):
        return False

Try / catch

# The loop already retries across models; handle the terminal case:
try:
    content, model, usage = generate(...)
except _AllModelsFailedError as e:
    if "LLM returned empty response" in str(e.last_error if hasattr(e, 'last_error') else e):
        raise max_tokens_or_filter_issue(e)  # distinguish from auth/network failures
    raise

Prevention

When it happens

Trigger: A model returns a completion whose content is empty/None (refusals, content-filter, truncation, or provider quirk). It becomes user-visible only if ALL models in models_to_try return empty or fail; otherwise the next model's valid response masks it. A response_validator rejecting content similarly lands in the same path.

Common situations: Over-long prompts causing silent truncation to empty content; safety filters returning empty bodies; a misconfigured model id that some gateways answer with an empty 200; max_tokens set so low the model emits nothing.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/a96a88dcf879169e. Report an issue: GitHub.