Your LLM returns broken JSON. Retrying the call is the most expensive fix.

Automation Engineering · By Caleb Sakala · May 6, 2026

Cheerful cartoon medieval blacksmith at a forge anvil tapping a cracked golden curly brace back into shape with a tiny hammer while an oversized golden catapult loaded with coins sits unused behind

Every developer building on LLM APIs hits this wall eventually. The model returns what looks like valid JSON, json.loads() throws a JSONDecodeError, and the pipeline stops. The instinct is to retry the API call with a sterner prompt about returning valid JSON this time. That retry costs another $0.003 to $0.06 depending on the model. A three-line Python fix handles broken JSON from an LLM for zero dollars.

Why LLMs produce broken JSON output

Token prediction is probabilistic, not syntactic. A 200-token JSON response has dozens of spots where a trailing comma, missing bracket, or unescaped quote can appear.

Fix broken LLM JSON output without another API call

The json_repair library has over 9 million monthly PyPI downloads as of May 2026. It fixes most malformed JSON from LLMs in a single function call:

import json_repair

broken = '{"users": [{"name": "Ada", "role": "admin",}], "count": tru}'
fixed = json_repair.loads(broken)
# Returns: {'users': [{'name': 'Ada', 'role': 'admin'}], 'count': True}

That trailing comma after "admin" and the truncated tru instead of true both get repaired. No API call. No added latency. The library handles missing quotes, dangling commas, truncated values, boolean typos, and unescaped control characters.

When the model wraps JSON in markdown code fences or explanatory prose (common with Claude and open-source models), strip the wrapper before parsing:

import re
import json_repair

def extract_and_parse(llm_response: str) -> dict:
    # Try direct parse first
    try:
        return json_repair.loads(llm_response)
    except Exception:
        pass
    # Extract from markdown code fence
    match = re.search(r'```(?:json)?\s*([\s\S]*?)```', llm_response)
    if match:
        return json_repair.loads(match.group(1))
    # Extract first JSON-like structure
    match = re.search(r'(\{[\s\S]*\}|\[[\s\S]*\])', llm_response)
    if match:
        return json_repair.loads(match.group(1))
    raise ValueError("No JSON structure found in response")

This two-step approach (direct parse, then extract from prose) handles the vast majority of JSON failures without a second API call. The cases it misses tend to be structural problems severe enough that regeneration is the only fix.

If the parsed output needs schema validation too, chain json_repair with Pydantic:

from pydantic import BaseModel, ValidationError
import json_repair

class UserResponse(BaseModel):
    users: list[dict]
    count: int

raw = '{"users": [{"name": "Ada"},], "count": "3"}'
parsed = json_repair.loads(raw)

try:
    validated = UserResponse.model_validate(parsed)
except ValidationError as e:
    # Structure is valid but data types are wrong
    # This is a prompt problem, not a parsing problem
    print(e.errors())

The split between json_repair and Pydantic is worth understanding. json_repair fixes syntax (commas, brackets, quoting). Pydantic catches semantic mismatches (wrong types, missing fields). Conflating the two leads developers to retry API calls for type errors that regeneration won't fix.

LangChain's OutputFixingParser, for example, sends the malformed output plus the parse error back to the LLM for correction. Every such retry adds 1 to 30 seconds of latency and another API charge. A workflow built on Chase Agents validates each step's output against a typed input schema instead. If step 2 returns malformed JSON, the pipeline stops there. Steps 3 through 5 never execute on bad data, and the malformed output gets routed to an error handler rather than silently corrupting downstream results.

When structured output mode saves the trouble

OpenAI, Anthropic, and Google all offer constrained generation that forces valid JSON at the token level. OpenAI's implementation is the most mature:

from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "user_list",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "users": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string"},
                                "role": {"type": "string"}
                            },
                            "required": ["name", "role"],
                            "additionalProperties": false
                        }
                    },
                    "count": {"type": "integer"}
                },
                "required": ["users", "count"],
                "additionalProperties": false
            }
        }
    }
)

Structured output mode guarantees valid, schema-conforming JSON. Zero parsing failures. So why does json_repair have 9 million monthly downloads?

Because constrained generation clips the model's flexibility. The "Let Me Speak Freely?" study (Ren et al., EMNLP 2024) found that strict format constraints reduced accuracy on reasoning benchmarks by up to 7.5%. When the schema forces strict typing, the model can't hedge ("between 3 and 5 users depending on how inactive accounts get counted"). It picks a number. For data extraction with rigid schemas, that tradeoff is worth it. For open-ended analysis or summarization where JSON is the transport format, the constraint makes responses measurably worse.

Different steps in a pipeline often need different strategies. An extraction step benefits from structured output mode. An analysis step benefits from freeform generation plus json_repair. Chase Agents lets each automation step specify its own LLM provider and model, so the extraction step runs on GPT-4o with strict JSON schema while the analysis step runs on Claude with post-hoc parsing. The output strategy matches the task instead of applying one constraint everywhere.

The retry fallback costs more than developers expect

GPT-4o pricing sits at $2.50 per million input tokens and $10 per million output tokens. A typical 500-token prompt with a 200-token response costs about $0.003 per call. A 5% JSON failure rate across 10,000 daily calls means 500 retries per day: $1.50 daily, $45 monthly. With Claude Sonnet 4 at $3/$15 per million tokens, the number climbs to roughly $60 monthly. That money buys nothing except syntax repair.

json_repair runs in under a millisecond per call. The cost per fix is $0.00.

Pick the fix that matches the failure

Use structured output mode when the schema is rigid and the task is extraction. Use json_repair plus Pydantic when the task needs flexibility or spans multiple providers. Only retry the LLM call when both layers fail and the output is unrecoverable. A pipeline that retries every JSON error is paying per-token rates for a problem that string parsing solved years ago.