As asked
LLMs often produce near-valid JSON with trailing commas, unquoted keys, or truncated output. Write a Python function that attempts to parse LLM JSON output, applies common recovery heuristics, and raises a typed error with a diagnostic message if all recovery attempts fail.
Sample answer outline
A strong answer tries json.loads first, then applies ordered heuristics: strip markdown fences, truncation recovery by finding the last complete object bracket, trailing comma removal via regex, and finally a permissive parser like json5 or demjson3. Each step is logged so the caller can diagnose which recovery was applied. A custom ParseError carries the original text, the attempted repairs, and the position of the first parse error.
Reference implementation (python)
import json, re
from dataclasses import dataclass
@dataclass
class ParseError(Exception):
original: str
repairs_tried: list[str]
detail: str
def parse_llm_json(text: str) -> dict | list:
attempts = []
try:
return json.loads(text)
except json.JSONDecodeError:
attempts.append('direct')
stripped = re.sub(r'^```[a-z]*\n?|```$', '', text.strip(), flags=re.MULTILINE).strip()
try:
return json.loads(stripped)
except json.JSONDecodeError:
attempts.append('strip-fences')
no_trailing = re.sub(r',\s*(\}|\])', r'\1', stripped)
try:
return json.loads(no_trailing)
except json.JSONDecodeError as e:
attempts.append('trailing-commas')
for end in range(len(no_trailing), 0, -1):
try:
return json.loads(no_trailing[:end])
except json.JSONDecodeError:
continue
raise ParseError(original=text, repairs_tried=attempts, detail=str(e))Expect these follow-ups
- When would you use constrained decoding instead of post-hoc repair?
- How does your function behave if the model emits a valid JSON array nested inside prose text?