{"record":{"id":"f9becd1b4dd856b2","repo":"srbhr/Resume-Matcher","slug":"failed-to-parse-json-after-retries-1-attempts","errorCode":null,"errorMessage":"Failed to parse JSON after {retries + 1} attempts: {e}","messagePattern":"Failed to parse JSON after (.+?) attempts: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"apps/backend/app/llm.py","lineNumber":1321,"sourceCode":"\n        except json.JSONDecodeError as e:\n            # Content quality — malformed JSON, retry with prompt hint\n            logging.warning(f\"JSON parse failed (attempt {attempt + 1}): {e}\")\n            if use_json_mode and not json_mode_failed:\n                # JSON-012: Registry claimed JSON mode support but the upstream\n                # failed to return valid JSON. Disable JSON mode for retries.\n                json_mode_failed = True\n                logging.warning(\n                    \"JSON mode failed for %s, falling back to prompt-only (attempt %d)\",\n                    model_name, attempt + 1,\n                )\n            if attempt < retries:\n                messages[-1][\"content\"] = (\n                    prompt\n                    + \"\\n\\nIMPORTANT: Output ONLY a valid JSON object. Start with { and end with }.\"\n                )\n                continue\n            raise ValueError(\n                f\"Failed to parse JSON after {retries + 1} attempts: {e}\")\n\n        except ValueError as e:\n            # Content quality — empty response, JSON extraction failure\n            logging.warning(f\"Content extraction failed (attempt {attempt + 1}): {e}\")\n            if attempt < retries:\n                continue\n            raise\n\n        except litellm.BadRequestError as e:\n            # JSON-012b: some OpenAI-compatible servers (e.g. LM Studio) report\n            # response_format support via the registry but reject\n            # {\"type\": \"json_object\"} with a 400 (issue #857). The Router does\n            # not retry bad requests, so recover here by disabling JSON mode and\n            # retrying prompt-only. Unrelated 400s (e.g. context length) still\n            # propagate.\n            if (\n                use_json_mode","sourceCodeStart":1303,"sourceCodeEnd":1339,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/llm.py#L1303-L1339","documentation":"complete_json attempts to parse the extracted JSON with retries; on the final allowed attempt, if parsing still fails it raises this ValueError including the attempt count and the underlying parse exception. Each retry appends a reminder telling the model to output only a valid JSON object.","triggerScenarios":"After retries + 1 LLM calls, json.loads/_extract_json still throws — the model persistently emits near-JSON output (trailing commas, unescaped quotes, comments, truncated output) that never parses.","commonSituations":"Model generates long JSON that gets truncated by max_tokens; model adds prose or markdown around JSON even after the corrective reminder; JSON mode unsupported for the chosen model.","solutions":["Increase the retries parameter to give the model more corrective attempts","Enable JSON mode (response_format json_object) with a model that supports it (see _supports_json_mode)","Raise max_tokens so output isn't truncated mid-object","Inspect logs for the parse error 'e' to see exactly which syntax the model keeps producing; simplify the requested schema"],"exampleFix":"// before\nconst data = await complete_json(prompt, { retries: 1 });\n// after\nconst data = await complete_json(prompt, { retries: 3, maxTokens: 4096 });","handlingStrategy":"retry","validationCode":"import json\ndef is_valid_model_json(text: str) -> bool:\n    try:\n        json.loads(text)\n        return True\n    except json.JSONDecodeError:\n        return False\n\n# quick pre-check on a sample response before wiring into prod flows","typeGuard":null,"tryCatchPattern":"try:\n    data = await complete_json(prompt, retries=3)\nexcept ValueError as e:\n    if e.message.startswith('Failed to parse JSON after'):\n        log.error(f\"LLM JSON parse exhausted: {e}\")\n        return graceful_degradation_result()\n    raise","preventionTips":["Pass retries >= 3 for structured-output calls","Enable JSON mode (response_format json_object) on supported models","Raise max_tokens to avoid truncation mid-JSON","Simplify the requested JSON schema; verify sample outputs in tests"],"tags":["llm","json-parse","retry-exhausted","backend"],"backgroundTag":"json-parse-failed","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}