JSON repair pattern
Even with a strict prompt, LLMs occasionally return JSON wrapped in markdown fences, prefaced with "Sure, here is your result:", or truncated mid-object. Your parser needs to handle these gracefully. A single json.loads call on raw model output is a production incident waiting to happen.
import json
import re
from typing import Any
def _extract_json(raw: str) -> Any:
"""Pull the first JSON object or array out of the model's response."""
if not raw:
raise ValueError("Empty response from LLM")
# Strip markdown fences if present
fenced = re.search(r"```(?:json)?\s*(\{.*?\}|\[.*?\])\s*```", raw, re.DOTALL)
if fenced:
return json.loads(fenced.group(1))
# Try plain parse first
try:
return json.loads(raw)
except json.JSONDecodeError:
pass
# Greedy scan for first balanced JSON value
match = re.search(r"(\{.*\}|\[.*\])", raw, re.DOTALL)
if not match:
raise ValueError(f"Could not find JSON in response: {raw[:200]}")
return json.loads(match.group(1))Three layers. Markdown fence first, plain parse second, greedy object scan third. Most production LLM pipelines end up with a parser like this within a month of going live.
Ordering exercise: Arrange the repair attempts in the right order
Loading practice…
Code playground: Try the repair pattern
Loading practice…
Checkpoint: Structured output checkpoint
Loading practice…