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 :
| Champ | Obligatoire | Contenu |
|---|---|---|
contents | oui | la conversation : messages user, puis la réponse attendue en model |
systemInstruction | non | la consigne système ; seules des parts text sont admises, son role est ignoré |
tools | non | des 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 :
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) :
| Limite | Valeur |
|---|---|
| tokens par exemple (entrée + sortie) | 131 072 |
| taille d'un fichier JSONL d'entraînement | 1 Go |
| exemples de validation | 5 000 au plus |
| taille d'adaptateur | 1, 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 ?
Gemini attend contents avec des rôles user / model et des parts. Le format messages / assistant / content est celui d'autres fournisseurs ; input / output était utilisé par d'anciens modèles.
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 :
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 ?
C'est une fuite : le test doit contenir des cas jamais vus à l'entraînement, sinon on mesure la mémoire du modèle plutôt que sa capacité à généraliser.
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.jsonlTrois 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/partsvalide - 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