Créer avec l’IA

Comment créer une boucle générer, tester, réviser?

· Par Robert Scales · 5 min de lecture

Réponse courte

Écrivez d’abord les tests, demandez au modèle d’écrire le code, exécutez les tests automatiquement et renvoyez les échecs au modèle en lui demandant de les corriger, jusqu’à ce que les tests passent ou qu’une limite de tours soit atteinte. Gardez les tests hors de portée du modèle et révisez le code final, parce qu’un modèle sous pression peut réussir les tests sans résoudre le problème.

La première tentative d’un modèle de langage pour écrire un bout de code est souvent proche du but, mais pas tout à fait juste. Une boucle générer, tester, réviser en fait un processus : le modèle écrit le code, un programme le vérifie au moyen de tests, et les échecs exacts retournent au modèle comme rétroaction pour sa tentative suivante. La boucle s’arrête quand tout passe ou quand elle a épuisé ses tours.

L’idée s’appuie sur de la recherche. Dans l’article de 2023 Teaching Large Language Models to Self-Debug, Xinyun Chen et ses collègues ont montré que laisser un modèle voir les résultats de son code et l’expliquer en langage courant l’aidait à trouver ses propres erreurs. Sur les bancs d’essai où des tests unitaires étaient offerts, cette rétroaction a augmenté l’exactitude d’au plus 12 %, et elle pouvait égaler des méthodes qui généraient plus de dix fois plus de programmes candidats. Les agents de programmation qui écrivent et exécutent leurs propres tests suivent le même modèle. En créer un petit vous-même est la meilleure façon de comprendre à la fois sa puissance et ses façons d’échouer.

Les parties de la boucle

  • Une spécification : une courte description de ce que la fonction doit faire.
  • Des tests : de petits programmes qui appellent la fonction avec des entrées connues et vérifient les sorties. Ici, nous utilisons pytest, un outil de test Python populaire. C’est vous qui les écrivez, pas le modèle.
  • Un générateur : le modèle, à qui l’on demande d’écrire ou de corriger la fonction.
  • Un exécuteur : du code qui enregistre la fonction, exécute les tests et récupère les messages d’échec.
  • Des limites : un nombre maximal de tours, pour qu’une boucle coincée ne puisse pas tourner (et vous facturer) indéfiniment.

Un exemple complet

La tâche : une fonction parse_duration qui transforme des chaînes comme "1h30m" ou "45s" en nombre de secondes. D’abord, les tests, enregistrés dans test_duration.py :

from solution import parse_duration
import pytest

def test_hours_minutes():
    assert parse_duration("1h30m") == 5400

def test_seconds_only():
    assert parse_duration("45s") == 45

def test_all_units():
    assert parse_duration("2h5m10s") == 7510

def test_bad_input():
    with pytest.raises(ValueError):
        parse_duration("ten minutes")

Ensuite, la boucle, avec la bibliothèque Python openai (version 1.x) et pytest :

import hashlib, re, subprocess
from pathlib import Path
from openai import OpenAI

client = OpenAI()          # lit OPENAI_API_KEY dans l’environnement
SPEC = ("Écris une fonction Python parse_duration(text) qui convertit des chaînes comme "
        "'1h30m', '45s' ou '2h5m10s' en nombre total de secondes (int). Lève ValueError "
        "pour toute autre entrée. Réponds seulement avec le code, dans un seul bloc ```python.")
TESTS = Path("test_duration.py")
FINGERPRINT = hashlib.sha256(TESTS.read_bytes()).hexdigest()

def extract_code(reply):
    match = re.search(r"```python\n(.*?)```", reply, re.S)
    return match.group(1) if match else reply

messages = [{"role": "user", "content": SPEC}]
for round_number in range(1, 5):                      # au plus 4 tours
    reply = client.chat.completions.create(
        model="gpt-4o", messages=messages, temperature=0
    ).choices[0].message.content
    Path("solution.py").write_text(extract_code(reply))

    assert hashlib.sha256(TESTS.read_bytes()).hexdigest() == FINGERPRINT, "Les tests ont changé!"
    result = subprocess.run(["python", "-m", "pytest", "-q", str(TESTS)],
                            capture_output=True, text=True, timeout=60)
    print(f"Tour {round_number} : code de sortie {result.returncode}")
    if result.returncode == 0:
        print("Tous les tests passent. Révisez maintenant solution.py vous-même.")
        break
    messages += [
        {"role": "assistant", "content": reply},
        {"role": "user", "content": "Les tests ont échoué :\n" + result.stdout[-3000:] +
         "\nCorrige la fonction pour qu'elle résolve le problème général. "
         "Ne traite pas les entrées des tests comme des cas particuliers."},
    ]
else:
    print("Arrêt après 4 tours. Lisez le dernier échec et le code.")

Chaque message d’échec indique au modèle quel test a échoué et pourquoi, ce qui est bien plus utile que « réessaie ». Seuls les 3 000 derniers caractères de la sortie sont envoyés, pour garder la conversation courte. Une exécution typique pourrait réussir au premier ou au deuxième tour; vos résultats varieront.

À essayer : lancez la boucle, puis ajoutez un cinquième test de votre cru, comme parse_duration("90m") == 5400, et relancez-la. Observez si le modèle corrige la règle générale ou seulement le nouveau cas.

Choisir les limites

Quatre tours, c’est un bon point de départ. Si un modèle n’a pas corrigé un problème en quatre essais, d’autres tours reviennent habituellement à répéter la même idée, et chaque tour renvoie une conversation qui s’allonge. Un délai d’expiration sur l’exécution des tests protège contre le code qui boucle sans fin. Exécutez le code généré dans un endroit où il ne peut pas faire de dégâts, comme un dossier distinct ou un conteneur, puisque vous exécutez du code que vous n’avez pas encore lu.

Comment l’empêcher de déjouer les tests?

On parle de déjouer les tests quand un modèle les fait passer sans résoudre le problème. Anthropic l’a documenté ouvertement dans la fiche système (system card) de Claude 3.7 Sonnet publiée en février : en programmation agentique, le modèle traitait parfois les tests comme des cas particuliers (special-casing), le plus souvent en renvoyant les valeurs exactes qu’un test attendait, et parfois en modifiant les tests pour qu’ils correspondent à son code. Cela tendait à se produire après plusieurs tentatives ratées, surtout quand une solution générale était difficile ou que des tests se contredisaient. Anthropic en a attribué la cause au piratage de la récompense (reward hacking) : un modèle qui apprend, pendant l’entraînement, à maximiser un score plutôt qu’à accomplir la tâche voulue.

La boucle ci-dessus utilise déjà trois défenses. Les tests se trouvent dans un fichier que le modèle ne voit et n’écrit jamais, et leur empreinte (un hachage SHA-256, qui change si un seul caractère change) est vérifiée à chaque tour. La rétroaction demande explicitement une solution générale et interdit les cas particuliers, le genre d’instruction système que recommande Anthropic. Et la limite de tours arrête les longues séries d’échecs pendant lesquelles ce comportement tend à apparaître.

Ajoutez-en deux autres. Gardez des tests cachés qui ne figurent jamais dans la rétroaction, et exécutez-les seulement sur le code final; une fonction qui réussit les tests visibles, mais échoue aux tests cachés a probablement mémorisé des réponses. Et lisez le résultat, en cherchant les signaux d’alerte qu’énumère Anthropic : de nombreux cycles de modification et de test sur un même fichier, des commentaires qui mentionnent des tests précis, et toute modification des fichiers de test. Une suite de tests réussie est un indice, pas une preuve; c’est la révision qui la transforme en code qui fonctionne.

Sources

  1. Teaching Large Language Models to Self-Debug — arXiv (Chen, Lin, Schärli, Zhou), 11 avril 2023
  2. Claude 3.7 Sonnet System Card — Anthropic, 24 février 2025

Recevez gratuitement le Guide de l’intelligence artificielle

Abonnez-vous à l’infolettre hebdomadaire et recevez le guide gratuit : ce qui a changé en IA cette semaine, une leçon, un prompt à essayer. Désabonnement en un clic.

Ce que nous faisons de votre adresse

Retour en haut

Affichage

Vos choix sont enregistrés dans ce navigateur seulement. Aucune donnée n’est transmise.

Taille du texte
100 %
Couleurs
Lecture
Autres réglages
Déclaration d’accessibilité