Leçon 3 — Préparer un jeu de données d'ajustement

Le format JSONL du réglage supervisé Gemini

5 min de lecture

Le format JSONL du réglage supervisé Gemini

Une ligne = un exemple

Le JSONL (JSON Lines) est un fichier texte où chaque ligne est un objet JSON complet. Pour le réglage supervisé de Gemini, chaque ligne contient :

ChampObligatoireContenu
contentsouila conversation : messages user, puis la réponse attendue en model
systemInstructionnonla consigne système ; seules des parts text sont admises, son role est ignoré
toolsnondes déclarations de fonctions, pour apprendre à appeler des outils

Chaque message a un role (user ou model) et une liste parts ; pour du texte, une part est {"text": "..."}. Le dernier message model est ce que le modèle doit apprendre à produire.

flowchart TD
  L["Une ligne JSON"] --> S["systemInstruction.parts : consigne"]
  L --> C["contents"]
  C --> U["role user : le ticket"]
  C --> M["role model : l'étiquette attendue"]

Le lab fabrique les lignes avec une seule fonction :

files/common.py (extrait)
def to_example(ticket: str, label: str) -> dict:
    """Une ligne du JSONL de réglage supervisé Gemini sur Vertex AI."""
    return {
        "systemInstruction": {"parts": [{"text": SYSTEM}]},
        "contents": [
            {"role": "user", "parts": [{"text": ticket}]},
            {"role": "model", "parts": [{"text": label}]},
        ],
    }

Une vraie ligne du train.jsonl généré (mise en forme sur plusieurs lignes pour la lecture) :

{"systemInstruction": {"parts": [{"text": "Tu classes les tickets du support client d'un logiciel de gestion en ligne. Réponds UNIQUEMENT par une étiquette parmi : facturation, acces_compte, bug_technique, demande_fonctionnalite, resiliation."}]},
 "contents": [{"role": "user", "parts": [{"text": "Bonjour, serait-il envisageable d'ajouter un export comptable direct vers le format Sage ? ..."}]},
              {"role": "model", "parts": [{"text": "demande_fonctionnalite"}]}]}

Les limites documentées

Relevées le 2026-10-15 dans la documentation Vertex AI (Gemini 3.5 Flash, 3.1 Flash-Lite, et valeurs proches pour la famille 2.5) :

LimiteValeur
tokens par exemple (entrée + sortie)131 072
taille d'un fichier JSONL d'entraînement1 Go
exemples de validation5 000 au plus
taille d'adaptateur1, 2, 4, 8 ou 16 (1 à 8 pour 2.5 Pro)
point de départ conseillé100 exemples, la qualité compte plus que la quantité

Les fichiers doivent être déposés dans un bucket Cloud Storage (gs://...) avant le lancement.

Quelle ligne est valide pour le réglage supervisé de Gemini ?

Valider avant de payer

Un job de réglage met du temps à démarrer ; une erreur de format découverte au bout de 20 minutes coûte cher en temps. validate_jsonl.py vérifie chaque ligne localement :

files/validate_jsonl.py (extrait)
    if contents[-1].get("role") != "model":
        errs.append("le dernier message doit être la réponse attendue (role 'model')")
    elif labels:
        answer = contents[-1].get("parts", [{}])[0].get("text", "")
        if answer not in labels:
            errs.append(f"réponse attendue hors étiquettes : {answer!r}")

Il contrôle aussi : JSON valide, champs inconnus, rôles autorisés, parts vides, deux messages du même rôle d'affilée, estimation de tokens, doublons, et fuites entre fichiers (un même ticket dans train et test). Il renvoie le code de sortie 1 en cas d'erreur : on peut l'appeler en CI.

Pourquoi vérifier qu'aucun ticket n'apparaît à la fois dans train.jsonl et test.jsonl ?

Exercice : réparer un fichier

Créez un fichier casse.jsonl avec deux lignes valides du train, une ligne au format messages, une ligne dont la réponse est Facturation et une copie d'une ligne de test. Lancez le validateur. Combien d'erreurs ? Comment corriger chacune ?

Voir la solution
python validate_jsonl.py casse.jsonl

Trois erreurs : la ligne messages (champ inconnu et contents manquant) et l'étiquette Facturation. La copie du test n'est pas signalée car on n'a validé qu'un fichier : relancez avec python validate_jsonl.py casse.jsonl test.jsonl pour voir la fuite.

Corrections : convertir messages en contents (assistant devient model, content devient parts: [{"text": ...}]), normaliser l'étiquette en minuscules, supprimer la ligne dupliquée du test.

Résultat obtenu

Exécution réelle le 2026-10-15 sur les fichiers générés, puis sur le fichier cassé de l'exercice :

train.jsonl: {"exemples": 70, "par_etiquette": {"demande_fonctionnalite": 14, "resiliation": 14, "facturation": 14, "acces_compte": 14, "bug_technique": 14}, "tokens_estimes": 6620}
validation.jsonl: {"exemples": 15, ..., "tokens_estimes": 1478}
test.jsonl: {"exemples": 15, ..., "tokens_estimes": 1434}
OK : format valide

casse.jsonl: {"exemples": 3, "par_etiquette": {"demande_fonctionnalite": 1, "resiliation": 1, "facturation": 1}, "tokens_estimes": 329}
ERREUR casse.jsonl:3 champs inconnus : ['messages']
ERREUR casse.jsonl:3 contents manquant ou vide
ERREUR casse.jsonl:4 réponse attendue hors étiquettes : 'Facturation'
3 erreur(s)

Je sais faire

  • Écrire une ligne JSONL systemInstruction / contents / parts valide
  • Citer les principales limites d'un jeu de réglage Gemini
  • Valider un fichier localement, y compris les étiquettes et les fuites
  • Brancher la validation en CI grâce au code de sortie
Suivant