Spaces:
Sleeping
Sleeping
File size: 1,736 Bytes
80a4a65 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | """JSON parsing helpers used by every AI call site."""
import json
import re
def parse_json_safe(raw: str) -> dict:
"""Best-effort JSON parser for raw LLM output.
Handles three common failure modes:
1. The model wraps the object in ```json ... ``` fences.
2. The model adds prose before/after the object.
3. The model emits literal control characters (newlines, tabs)
inside a string value that ``json.loads`` rejects.
"""
cleaned = raw.strip()
fence_match = re.search(r"```(?:json)?\s*([\s\S]*?)```", cleaned, re.IGNORECASE)
if fence_match:
cleaned = fence_match.group(1).strip()
start = cleaned.find("{")
end = cleaned.rfind("}")
if start != -1 and end != -1 and end > start:
cleaned = cleaned[start:end + 1]
cleaned = re.sub(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]", "", cleaned)
try:
# Use strict=False to allow literal control characters (like newlines) in strings
return json.loads(cleaned, strict=False)
except json.JSONDecodeError as exc:
# One last ditch effort: replace literal newlines with escaped ones
try:
# This is risky but sometimes helps with unescaped newlines in middle of strings
cleaned_fix = cleaned.replace('\n', '\\n').replace('\r', '\\r')
# But the start/end might be messed up now if it was already formatted.
# Let's just try strict=False first as it's the standard solution for "control character" errors.
return json.loads(cleaned, strict=False)
except Exception:
preview = raw[:500].replace("\n", " ")
raise ValueError(f"Invalid JSON from model: {exc}. Raw preview: {preview}") from exc
|