{"record":{"id":"a96a88dcf879169e","repo":"ZhuLinsen/daily_stock_analysis","slug":"llm-returned-empty-response","errorCode":null,"errorMessage":"LLM returned empty response","messagePattern":"LLM returned empty response","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/analyzer.py","lineNumber":3305,"sourceCode":"\n                content = self._extract_completion_text(response)\n                if content:\n                    usage_messages = None if audit_context is not None else call_kwargs[\"messages\"]\n                    usage = self._normalize_usage(\n                        extract_usage_payload(response),\n                        model=usage_model or model,\n                        provider=usage_provider,\n                        messages=usage_messages,\n                    )\n                    if audit_context is not None:\n                        usage = _attach_usage_audit(usage, call_kwargs[\"messages\"])\n                    last_response_text = content\n                    last_model = model\n                    last_usage = usage\n                    if response_validator is not None:\n                        response_validator(content)\n                    return (content, model, usage)\n                raise ValueError(\"LLM returned empty response\")\n\n            except Exception as e:\n                safe_error = self._sanitize_litellm_exception_text(e, config=config, model=model)\n                logger.warning(\"[LiteLLM] %s failed: %s\", model, safe_error)\n                last_error = RuntimeError(f\"{type(e).__name__}: {safe_error}\")\n                continue\n\n        raise _AllModelsFailedError(\n            f\"All LLM models failed (tried {len(models_to_try)} model(s)). Last error: {last_error}\",\n            last_response_text=last_response_text,\n            last_model=last_model,\n            last_usage=last_usage,\n        )\n\n    def generate_text(\n        self,\n        prompt: str,\n        max_tokens: int = 2048,","sourceCodeStart":3287,"sourceCodeEnd":3323,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/analyzer.py#L3287-L3323","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["If you see this inside _AllModelsFailedError, check the warning logs — each model's specific empty/error reason is logged per attempt.","Raise max_tokens and/or trim the prompt so the model has room to emit content.","Disable or replace the offending model in the model list; empty-content providers poison the whole chain.","If a response_validator is too strict, loosen it so valid-but-short responses pass."],"exampleFix":"# before\ncompletion_kwargs = {\"max_tokens\": 8, ...}  # model emits nothing\n\n# after\ncompletion_kwargs = {\"max_tokens\": 4096, ...}","handlingStrategy":"retry","validationCode":"def response_has_content(response) -> bool:\n    try:\n        return bool((response.choices[0].message.content or \"\").strip())\n    except (IndexError, AttributeError):\n        return False","typeGuard":null,"tryCatchPattern":"# The loop already retries across models; handle the terminal case:\ntry:\n    content, model, usage = generate(...)\nexcept _AllModelsFailedError as e:\n    if \"LLM returned empty response\" in str(e.last_error if hasattr(e, 'last_error') else e):\n        raise max_tokens_or_filter_issue(e)  # distinguish from auth/network failures\n    raise","preventionTips":["Check per-attempt '[LiteLLM] ... failed' warnings to see which model returned empty.","Set adequate max_tokens and trim prompts to avoid empty truncation.","Remove chronically empty-content models from the model list."],"tags":["llm","litellm","empty-response","retry-loop"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}