Ontwikkelaars kunnen nu de kosten voor het herstellen van foutieve JSON van large language models (LLM's) onder controle houden door het aantal herstelpogingen te beperken tot twee, zoals een eenvoudige Python-loop laat zien. Het patroon — genereren, parsen, valideren, opnieuw proberen, en dan accepteren of falen — verandert "misschien geldige JSON" in een meetbaar succespercentage en voorkomt ongecontroleerd tokenverbruik.

Waarom een herstel-loop belangrijk is

LLM's krijgen vaak de instructie om "JSON te retourneren", maar de output bevat vaak syntaxfouten, ontbrekende keys of waarden die de bedrijfsregels schenden. Een downstream-systeem dat een strikte payload verwacht, zal crashen of foutieve resultaten produceren als het dergelijke output krijgt. Zonder een systematische manier om deze problemen op te vangen en te corrigeren, accepteren teams ofwel een hoog foutpercentage, of ze verspillen geld door het model herhaaldelijk te prompten totdat het goed gaat.

De drie validatielagen

Een betrouwbare loop splitst validatie op in:

  • Syntax – kan de string überhaupt als JSON worden geparst?
  • Shape – bevat het object op het hoogste niveau precies de verwachte keys?
  • Semantic – voldoen de waarden aan de domeinbeperkingen (bijvoorbeeld: een "category" moet tot een vooraf gedefinieerde set behoren)?

Door te stoppen bij de eerste falende laag, kan de loop het model een nauwkeurige foutmelding geven, wat de kans op een correcte oplossing bij de volgende poging drastisch vergroot.

Minimaal Python-voorbeeld

De volgende code gebruikt alleen de standaardbibliotheek. Het definieert een klein schema (een dictionary met de keys category en summary) en een vaste lijst met toegestane categorieën.

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}"

    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"
    if value["category"] not in CATEGORIES:
        return None, "semantic: invalid category"
    if not isinstance(value["summary"], str) or not value["summary"].strip():
        return None, "semantic: summary must be text"

    return value, None

De herstel-loop zelf legt een harde limiet op aan het aantal pogingen:

MAX_ATTEMPTS = 2
raw = model_response   # initial LLM output

for attempt in range(MAX_ATTEMPTS + 1):
    value, error = validate(raw)
    if error is None:
        break
    if attempt == MAX_ATTEMPTS:
        raise RuntimeError(f"failed after {MAX_ATTEMPTS} retries: {error}")

    # Send the error and schema back to the model for a fix
    raw = ask_model_to_repair(raw, error)

Als de JSON bij de eerste poging aan de validatie voldoet, stopt de loop onmiddellijk. Anders probeert hij het opnieuw door het model een beknopte payload te voeren: de oorspronkelijke output, de exacte foutmelding en het verwachte schema. Na twee mislukte pogingen wordt het proces afgebroken met een duidelijke exception.

Twee regels voor kosteneffectieve reparaties

  1. Houd herstel-prompts beknopt – Voeg alleen de ruwe JSON, de foutmelding en het schema toe. Het toevoegen van een lange chatgeschiedenis verhoogt het tokenverbruik en kan het model in verwarring brengen, waardoor de kosten per poging stijgen zonder de nauwkeurigheid te verbeteren.

  2. Scheid validatie van fact-checking – De parser kan structurele problemen detecteren, maar kan niet verifiëren of een "summary"-bewering waar is. Fact-checking hoort in een latere fase thuis; het proberen te "repareren" van een onjuiste bewering zorgt er alleen maar voor dat de leugen er netter uitziet.

Succes meten

Het loggen van het fouttype van elke poging (syntax, shape, semantic) en of de loop is geslaagd, geeft een concrete metriek: het aandeel LLM-responses dat bruikbaar wordt binnen de beperkte pogingen. Teams kunnen dit in de loop van de tijd volgen, promptstijlen vergelijken of experimenteren met verschillende schema-definities.

Mogelijke nadelen

Het beperken van de loop betekent dat sommige foutieve outputs worden weggegooid zodra de limiet is bereikt, wat het algemene foutpercentage kan verhogen als de ruwe kwaliteit van het model laag is. In omgevingen waar elke response cruciaal is, geven ontwikkelaars wellicht de voorkeur aan een hogere limiet voor het aantal pogingen, ten koste van extra tokens. De afweging is expliciet: meer kosten voor een hogere dekking, of strakkere budgetten met een voorspelbare limiet.

Waar je op moet letten

  • Prompt engineering voor herstel – Het verfijnen van het formaat van de foutmelding kan het aantal benodigde pogingen verminderen.
  • Alternatieve parsers – Sommige bibliotheken bieden tolerante parsing die kleine problemen automatisch kan corrigeren; het vergelijken van hun kosten met een beperkte loop is de moeite waard.
  • Modelupdates – Nieuwere LLM-versies kunnen mogelijk direct schonere JSON produceren, waardoor de limiet voor het aantal pogingen mogelijk nog verder verlaagd kan worden.

Een beperkte JSON-herstel-loop geeft ontwikkelaars een praktisch hulpmiddel om vage LLM-output om te zetten in betrouwbare data zonder dat de kosten uit de hand lopen. Door validatie te behandelen als een essentiële stap en het aantal pogingen te beperken, kunnen teams zowel de budgetten als de downstream-systemen tevreden houden.