{"record":{"id":"aabd3c946fb2d893","repo":"ZhuLinsen/daily_stock_analysis","slug":"empty-response","errorCode":null,"errorMessage":"empty_response","messagePattern":"empty_response","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/analyzer.py","lineNumber":4376,"sourceCode":"            prefix = \"### 上一次输出如下，请在该输出基础上补齐缺失字段，并重新输出完整 JSON。不要省略已有字段：\"\n        return \"\\n\\n\".join([\n            base_prompt,\n            prefix,\n            previous_output,\n            complement,\n        ])\n\n    def _apply_placeholder_fill(self, result: AnalysisResult, missing_fields: List[str]) -> None:\n        \"\"\"Delegate to module-level apply_placeholder_fill.\"\"\"\n        apply_placeholder_fill(result, missing_fields)\n\n    def _extract_analysis_json_object(self, response_text: str) -> Tuple[str, Dict[str, Any]]:\n        \"\"\"Extract the single allowed JSON object from an LLM response.\"\"\"\n\n        text = response_text or \"\"\n        stripped = text.strip()\n        if not stripped:\n            raise ValueError(\"empty_response\")\n\n        fence_pattern = re.compile(\n            r\"```[ \\t]*(?P<lang>[A-Za-z0-9_-]*)[ \\t]*\\n?(?P<body>.*?)```\",\n            flags=re.DOTALL,\n        )\n        fenced_matches = list(fence_pattern.finditer(text))\n        if len(fenced_matches) > 1:\n            raise ValueError(\"ambiguous_json\")\n        if len(fenced_matches) == 1:\n            match = fenced_matches[0]\n            outside = (text[:match.start()] + text[match.end():]).strip()\n            if outside:\n                raise ValueError(\"ambiguous_json\")\n            fence_lang = (match.group(\"lang\") or \"\").strip().lower()\n            if fence_lang not in {\"\", \"json\"}:\n                raise ValueError(\"ambiguous_json\")\n            json_str = match.group(\"body\").strip()\n            data = self._load_analysis_json_candidate(json_str)","sourceCodeStart":4358,"sourceCodeEnd":4394,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/analyzer.py#L4358-L4394","documentation":"ValueError('empty_response') raised by _extract_analysis_json_object when the LLM response text, after strip(), is empty. This parser expects exactly one JSON object (optionally in a single code fence) describing the AnalysisResult, and an empty body cannot contain one. Unlike the empty-completion case in the generation loop, this error propagates out of the extraction step rather than being retried per-model.","triggerScenarios":"Calling _extract_analysis_json_object('') or with whitespace-only / None-coerced text ('' after response_text or '' check). Typically the downstream half of a completion that returned empty content: generation's empty check was bypassed or the text was passed through from another source (mock, cached entry, plugin).","commonSituations":"Caching layer storing an empty analysis body and replaying it; tests or mocks returning '' as the assistant message; a prior pipeline stage 'sanitizing' the response into emptiness; refusal responses reduced to blank text.","solutions":["Check the upstream completion: if content is empty, do not invoke extraction — branch to retry/re-fallback at the generation layer.","Purge or overwrite cached empty analysis responses so they are never re-extracted.","Fix mocks/tests to return a realistic JSON body."],"exampleFix":"# before\ntext = cached_response.text  # ''\njson_str, data = analyzer._extract_analysis_json_object(text)  # ValueError: empty_response\n\n# after\nif not (cached_response.text or '').strip():\n    cached_response = regenerate()\njson_str, data = analyzer._extract_analysis_json_object(cached_response.text)","handlingStrategy":"validation","validationCode":"def has_extractable_text(text: str) -> bool:\n    return bool((text or \"\").strip())","typeGuard":null,"tryCatchPattern":"try:\n    json_str, data = analyzer._extract_analysis_json_object(text)\nexcept ValueError as e:\n    if str(e) == \"empty_response\":\n        text = regenerate_response()  # retry generation, not extraction\n        json_str, data = analyzer._extract_analysis_json_object(text)\n    else:\n        raise","preventionTips":["Never feed cached/mock/blank texts into the extractor without a strip() check.","Purge cached empty analysis bodies.","Fix tests and mocks to return realistic JSON payloads."],"tags":["llm","parsing","empty-response","json-extraction"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}