srbhr/Resume-Matcher · error · ValueError

Failed to parse JSON after {retries + 1} attempts: {e}

Error message

Failed to parse JSON after {retries + 1} attempts: {e}

What it means

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.

Source

Thrown at apps/backend/app/llm.py:1321

        except json.JSONDecodeError as e:
            # Content quality — malformed JSON, retry with prompt hint
            logging.warning(f"JSON parse failed (attempt {attempt + 1}): {e}")
            if use_json_mode and not json_mode_failed:
                # JSON-012: Registry claimed JSON mode support but the upstream
                # failed to return valid JSON. Disable JSON mode for retries.
                json_mode_failed = True
                logging.warning(
                    "JSON mode failed for %s, falling back to prompt-only (attempt %d)",
                    model_name, attempt + 1,
                )
            if attempt < retries:
                messages[-1]["content"] = (
                    prompt
                    + "\n\nIMPORTANT: Output ONLY a valid JSON object. Start with { and end with }."
                )
                continue
            raise ValueError(
                f"Failed to parse JSON after {retries + 1} attempts: {e}")

        except ValueError as e:
            # Content quality — empty response, JSON extraction failure
            logging.warning(f"Content extraction failed (attempt {attempt + 1}): {e}")
            if attempt < retries:
                continue
            raise

        except litellm.BadRequestError as e:
            # JSON-012b: some OpenAI-compatible servers (e.g. LM Studio) report
            # response_format support via the registry but reject
            # {"type": "json_object"} with a 400 (issue #857). The Router does
            # not retry bad requests, so recover here by disabling JSON mode and
            # retrying prompt-only. Unrelated 400s (e.g. context length) still
            # propagate.
            if (
                use_json_mode

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Increase the retries parameter to give the model more corrective attempts
  2. Enable JSON mode (response_format json_object) with a model that supports it (see _supports_json_mode)
  3. Raise max_tokens so output isn't truncated mid-object
  4. Inspect logs for the parse error 'e' to see exactly which syntax the model keeps producing; simplify the requested schema

Example fix

// before
const data = await complete_json(prompt, { retries: 1 });
// after
const data = await complete_json(prompt, { retries: 3, maxTokens: 4096 });
Defensive patterns

Strategy: retry

Validate before calling

import json
def is_valid_model_json(text: str) -> bool:
    try:
        json.loads(text)
        return True
    except json.JSONDecodeError:
        return False

# quick pre-check on a sample response before wiring into prod flows

Try / catch

try:
    data = await complete_json(prompt, retries=3)
except ValueError as e:
    if e.message.startswith('Failed to parse JSON after'):
        log.error(f"LLM JSON parse exhausted: {e}")
        return graceful_degradation_result()
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28). Data as JSON: /api/errors/f9becd1b4dd856b2. Report an issue: GitHub.