srbhr/Resume-Matcher · error · ValueError

JSON extraction exceeded max recursion depth: {_depth}

Error message

JSON extraction exceeded max recursion depth: {_depth}

What it means

_extract_json in llm.py recursively parses/extracts JSON out of LLM text. To prevent stack exhaustion on pathological model output, it enforces MAX_JSON_EXTRACTION_RECURSION and raises this ValueError once _depth exceeds the limit.

Source

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

    tag. Strip these so JSON extraction finds the real output.
    """
    # Remove <think>...</think> blocks (including multiline)
    stripped = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL)
    # Also handle unclosed <think> tag (model may still be "thinking" at end)
    stripped = re.sub(r"<think>.*", "", stripped, flags=re.DOTALL)
    return stripped.strip()


def _extract_json(content: str, _depth: int = 0) -> str:
    """Extract JSON from LLM response, handling various formats.

    LLM-001: Improved to detect and reject likely truncated JSON.
    LLM-007: Improved error messages for debugging.
    JSON-010: Added recursion depth and size limits.
    """
    # JSON-010: Safety limits
    if _depth > MAX_JSON_EXTRACTION_RECURSION:
        raise ValueError(
            f"JSON extraction exceeded max recursion depth: {_depth}")
    if len(content) > MAX_JSON_CONTENT_SIZE:
        raise ValueError(
            f"Content too large for JSON extraction: {len(content)} bytes")

    original = content

    # Strip thinking model tags (deepseek-r1, qwq, etc.)
    if "<think>" in content:
        content = _strip_thinking_tags(content)

    # Remove markdown code blocks
    if "```json" in content:
        content = content.split("```json")[1].split("```")[0]
    elif "```" in content:
        parts = content.split("```")
        if len(parts) >= 2:
            content = parts[1]

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Retry the request asking the model for flatter, simpler JSON output
  2. Reduce the requested payload size/complexity in the prompt (fewer nested objects)
  3. If legitimately needed, raise MAX_JSON_EXTRACTION_RECURSION in llm.py
  4. Pre-sanitize/validate the model output shape with a schema validator before deep extraction
Defensive patterns

Strategy: validation

Validate before calling

def json_depth_ok(s: str, limit: int = 32) -> bool:
    depth, in_str, esc = 0, False, False
    for c in s:
        if in_str:
            if esc: esc = False
            elif c == '\\': esc = True
            elif c == '"': in_str = False
        elif c == '"': in_str = True
        elif c in '{[': depth += 1;  depth = depth
        elif c in '}]': depth -= 1
        if depth > limit: return False
    return True

Try / catch

try:
    data = await complete_json(prompt)
except ValueError as e:
    if "max recursion depth" in str(e):
        data = await complete_json(prompt + " Keep the JSON structure flat, max 3 levels deep.")
    else:
        raise

Prevention

When it happens

Trigger: complete_json receives LLM output whose structure drives the recursive extraction past MAX_JSON_EXTRACTION_RECURSION — typically heavily nested or adversarial/pathological JSON-ish content.

Common situations: Model returns extremely deeply nested JSON; prompt-injected content designed to blow up the parser; a loop where the same bad response is re-extracted recursively.

Related errors


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