# Build a Bounded JSON Repair Loop for LLM Output in Python

> Source: <https://dev.to/magickong/build-a-bounded-json-repair-loop-for-llm-output-in-python-1dm4>
> Published: 2026-07-15 11:22:10+00:00

“Return JSON” is a request, not a guarantee. An LLM can produce invalid syntax, valid JSON with the wrong shape, or a structurally valid answer that violates the task.

A repair loop should classify those failures and stop after a small number of attempts.

This example uses only Python's standard library:

``` python
import json

CATEGORIES = {"bug", "feature", "question"}

def validate(raw: str):
    try:
        value = json.loads(raw)
    except json.JSONDecodeError as exc:
        return None, f"syntax: {exc.msg} at position {exc.pos}"

    if not isinstance(value, dict):
        return None, "shape: root must be an object"
    if set(value) != {"category", "summary"}:
        return None, "shape: expected category and summary only"
    if value["category"] not in CATEGORIES:
        return None, "semantic: category is not allowed"
    if not isinstance(value["summary"], str) or not value["summary"].strip():
        return None, "semantic: summary must be non-empty text"

    return value, None
```

Try three fixtures:

```
samples = [
    '{"category":"bug","summary":"Crash on save",}',
    '{"category":"bug","title":"Crash on save"}',
    '{"category":"urgent","summary":"Crash on save"}',
]

for sample in samples:
    print(validate(sample)[1])
```

Expected categories are `syntax`

, `shape`

, and `semantic`

.

```
MAX_ATTEMPTS = 2

raw = first_model_response
for attempt in range(MAX_ATTEMPTS + 1):
    value, error = validate(raw)
    if error is None:
        break
    if attempt == MAX_ATTEMPTS:
        raise RuntimeError(f"invalid model output: {error}")
    raw = ask_model_to_repair(raw, error)  # your API adapter
```

Send the original output, the exact validation error, and the required schema to the repair call. Do not add an expanding conversation history. It increases cost and can distract from the one correction needed.

A parser can identify malformed output. It cannot decide whether “Crash on save” really describes a bug. Validate factual support against source evidence in a separate stage. Automatically repairing an unsupported claim can make it more polished without making it true.

Also log attempt count, error class, model/version, and final disposition—but avoid logging sensitive prompts or full user data by default.

The important pattern is not the particular schema. It is the control flow:

``` php
generate -> parse -> validate -> bounded repair -> accept or fail
```

Once you can observe each transition, you can measure how often repair helps and when it only burns tokens. “Usually valid JSON” becomes a testable rate instead of a hopeful integration plan.
