{"record":{"id":"62f3ff96886edb88","repo":"666ghj/MiroFish","slug":"llm-returned-empty-json-content","errorCode":null,"errorMessage":"LLM returned empty JSON content","messagePattern":"LLM returned empty JSON content","errorType":"exception","errorClass":"LLMResponseError","httpStatus":null,"severity":"error","filePath":"backend/app/utils/llm_client.py","lineNumber":255,"sourceCode":"        if not choices:\n            raise LLMResponseError(\"LLM returned no choices\")\n\n        choice = choices[0]\n        finish_reason = getattr(choice, \"finish_reason\", None)\n        if finish_reason == \"length\":\n            raise LLMResponseError(\n                \"LLM JSON output was truncated at the token limit\",\n                finish_reason=finish_reason,\n            )\n        if finish_reason not in {None, \"stop\"}:\n            raise LLMResponseError(\n                f\"LLM JSON generation stopped unexpectedly ({finish_reason})\",\n                finish_reason=finish_reason,\n            )\n\n        content = _clean_chat_text(extract_chat_completion_text(response))\n        if not content:\n            raise LLMResponseError(\n                \"LLM returned empty JSON content\",\n                finish_reason=finish_reason,\n            )\n\n        try:\n            value = json.loads(content)\n        except json.JSONDecodeError as strict_error:\n            # Some compatible providers append a short explanation after an\n            # otherwise complete JSON object. Accept only an object decoded\n            # from the beginning; never repair or invent truncated JSON.\n            try:\n                value, end = json.JSONDecoder().raw_decode(content)\n            except json.JSONDecodeError:\n                raise LLMResponseError(\n                    \"LLM returned invalid JSON \"\n                    f\"(line {strict_error.lineno}, column {strict_error.colno})\",\n                    finish_reason=finish_reason,\n                ) from strict_error","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/utils/llm_client.py#L237-L273","documentation":"LLMResponseError raised when the completion succeeded (finish_reason ok) but the extracted text is empty after cleaning — the model returned no content for the message. There is nothing to json.loads, and the client refuses to substitute an empty object.","triggerScenarios":"Model returns a message with empty content (some providers return empty content alongside tool_calls or when the answer was fully filtered), or whitespace-only output that _clean_chat_text reduces to ''.","commonSituations":"OpenAI-compatible gateways that emit an empty content field with finish_reason 'stop', models outputting only whitespace/newlines, or response_format quirks where content lives in a field the extractor does not read.","solutions":["Retry the request — empty content with a clean finish_reason is frequently transient","Log the raw response to see where the content actually went (e.g. tool_calls, reasoning field)","Strengthen the system prompt to require a non-empty JSON object as the entire reply","If a gateway consistently returns empty content, test the same request against the upstream provider to isolate the gateway bug"],"exampleFix":"# before\nmessages = [{\"role\": \"user\", \"content\": task}]\n\n# after\nmessages = [{\"role\": \"system\", \"content\": \"Reply with exactly one non-empty JSON object and nothing else.\"},\n            {\"role\": \"user\", \"content\": task}]","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    value = LLMClient._parse_json_response(resp)\nexcept LLMResponseError as e:\n    if \"empty JSON content\" in str(e):\n        value = LLMClient._parse_json_response(retry_once(create_fn))\n    else:\n        raise","preventionTips":["Use a strict system prompt: one non-empty JSON object, nothing else","Prefer provider JSON mode (response_format json_object) where available","Log raw responses to spot content living in unexpected fields"],"tags":["llm","empty-response","json"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}