Les développeurs peuvent désormais maîtriser le coût de la correction du JSON malformé provenant des grands modèles de langage (LLM) en limitant les tentatives de réparation à deux, comme le montre une simple boucle Python. Le modèle — générer, analyser, valider, réessayer, puis accepter ou échouer — transforme un « JSON potentiellement valide » en un taux de réussite mesurable et empêche une consommation incontrôlée de tokens.

Pourquoi une boucle de réparation est importante

Les LLM reçoivent souvent l'instruction de « renvoyer du JSON », pourtant la sortie contient fréquemment des erreurs de syntaxe, des clés manquantes ou des valeurs qui enfreignent les règles métier. Un système en aval qui attend une charge utile (payload) stricte plantera ou produira des résultats erronés s'il reçoit une telle sortie. Sans une méthode systématique pour détecter et corriger ces problèmes, les équipes doivent soit accepter un taux d'échec élevé, soit gaspiller de l'argent en sollicitant le modèle de manière répétée jusqu'à ce qu'il réussisse.

Les trois couches de validation

Une boucle fiable sépare la validation en trois étapes :

  • Syntaxe – la chaîne peut-elle être analysée comme du JSON ?
  • Structure (Shape) – l'objet de premier niveau contient-il exactement les clés attendues ?
  • Sémantique – les valeurs respectent-elles les contraintes du domaine (par exemple, une « catégorie » doit appartenir à un ensemble prédéfini) ?

En s'arrêtant à la première couche défaillante, la boucle peut fournir au modèle un message d'erreur précis, ce qui améliore considérablement les chances d'une correction correcte lors de la tentative suivante.

Exemple Python minimal

Le code suivant utilise uniquement la bibliothèque standard. Il définit un schéma minuscule (un dictionnaire avec les clés category et summary) et une liste fixe de catégories autorisées.

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

La boucle de réparation elle-même impose un plafond strict au nombre de tentatives :

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)

Si le JSON passe la validation dès le premier passage, la boucle s'arrête immédiatement. Sinon, elle réessaie en fournissant au modèle une charge utile concise : la sortie originale, la chaîne d'erreur exacte et le schéma attendu. Après deux tentatives infructueuses, le processus s'interrompt avec une exception claire.

Deux règles pour des réparations rentables

  1. Gardez les prompts de réparation concis – N'incluez que le JSON brut, le message d'erreur et le schéma. L'ajout d'un long historique de discussion gonfle l'utilisation de tokens et peut confondre le modèle, augmentant le coût par tentative sans améliorer la précision.

  2. Séparez la validation de la vérification des faits – L'analyseur peut détecter des problèmes structurels, mais il ne peut pas vérifier si une affirmation dans le « summary » est vraie. La vérification des faits appartient à une étape ultérieure ; tenter de « réparer » une affirmation fausse ne fait que rendre le mensonge plus propre.

Mesurer le succès

L'enregistrement du type d'erreur de chaque tentative (syntaxe, structure, sémantique) et du succès de la boucle fournit une métrique concrète : la proportion de réponses de LLM qui deviennent utilisables dans la limite des tentatives autorisées. Les équipes peuvent suivre cela au fil du temps, comparer des styles de prompts ou expérimenter différentes définitions de schémas.

Inconvénients potentiels

Limiter la boucle signifie que certaines sorties malformées seront rejetées une fois la limite atteinte, ce qui peut augmenter le taux d'échec global si la qualité brute du modèle est faible. Dans les environnements où chaque réponse est critique, les développeurs pourraient préférer un plafond de tentatives plus élevé au prix de tokens supplémentaires. Le compromis est explicite : plus de coûts pour une meilleure couverture, ou des budgets plus serrés avec un plafond prévisible.

À surveiller ensuite

  • Prompt engineering pour la réparation – Affiner le format du message d'erreur peut réduire le nombre de tentatives nécessaires.
  • Analyseurs alternatifs – Certaines bibliothèques proposent une analyse tolérante capable d'autocorriger des problèmes mineurs ; comparer leur coût par rapport à une boucle limitée est pertinent.
  • Mises à jour des modèles – Les nouvelles versions de LLM pourraient produire un JSON plus propre nativement, permettant potentiellement d'abaisser encore davantage la limite de tentatives.

Une boucle de réparation JSON limitée offre aux développeurs un outil pratique pour transformer des sorties de LLM floues en données fiables sans explosion des coûts. En traitant la validation comme une étape de premier plan et en plafonnant les tentatives, les équipes peuvent satisfaire à la fois leurs budgets et leurs systèmes en aval.