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
- Retry the request asking the model for flatter, simpler JSON output
- Reduce the requested payload size/complexity in the prompt (fewer nested objects)
- If legitimately needed, raise MAX_JSON_EXTRACTION_RECURSION in llm.py
- 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
- Request flat JSON schemas in prompts (avoid deeply nested structures)
- Sanitize/validate model output shape before deep parsing
- Keep MAX_JSON_EXTRACTION_RECURSION as a deliberate guard rather than disabling it
- Treat repeated depth failures as a prompt-injection signal
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
- Content too large for JSON extraction: {len(content)} bytes
- No JSON found in response: {original[:200]}
- LLM completion failed. Please check your API configuration a
- Empty response from LLM
- Failed to parse JSON after {retries + 1} attempts: {e}
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/1f1c65d722e656fa.
Report an issue: GitHub.