Mesurer une ligne de base zero-shot et few-shot
Le principe : un seul script, un seul jeu de test
Une ligne de base (baseline) est la note obtenue par la solution la plus simple. Toute adaptation ultérieure doit la battre, sur le même jeu de test, avec le même script. Sinon on compare des pommes et des poires.
flowchart LR T["test.jsonl : 15 tickets jamais vus"] --> E["evaluate.py"] M1["Modèle de base, zero-shot"] --> E M2["Modèle de base, few-shot"] --> E M3["Modèle réglé (plus tard)"] --> E E --> R["baseline_report.json : exactitude, hors étiquette, tokens"]
- Zero-shot : le modèle reçoit seulement la consigne et le ticket.
- Few-shot : on ajoute quelques exemples résolus avant le ticket, ici 2 par étiquette, soit 10.
Les exemples few-shot au format du réglage
Les exemples sont passés comme des tours de conversation user / model, exactement la forme des lignes du jeu de réglage. Ainsi le few-shot et le fine-tuning voient la même chose.
def build_contents(ticket, shots):
"""Les exemples few-shot sont des tours user/model : le même format que le jeu de réglage."""
contents = []
for t, l in shots:
contents += [{"role": "user", "parts": [{"text": t}]}, {"role": "model", "parts": [{"text": l}]}]
return contents + [{"role": "user", "parts": [{"text": ticket}]}]Les exemples sont pris dans train.jsonl (jamais dans le test) et toujours les mêmes, pour que deux exécutions soient comparables.
Les métriques
| Métrique | Définition | Pourquoi |
|---|---|---|
| exactitude | part des tickets bien classés | la note principale |
| hors étiquette | part des réponses qui ne sont pas une étiquette connue | mesure l'instabilité du format |
| rappel par étiquette | pour chaque étiquette, part des tickets de cette classe retrouvés | repère la classe qui pose problème |
| tokens d'entrée moyens | usage_metadata.prompt_token_count | le coût de l'approche |
def model_classifier(client, model):
from google.genai import types
cfg = types.GenerateContentConfig(system_instruction=SYSTEM, temperature=0, max_output_tokens=20)
def classify(contents):
r = generate(client, model, contents, cfg)
return r.text, r.usage_metadata.prompt_token_count or 0
return classifytemperature=0 réduit la part d'aléatoire ; max_output_tokens=20 évite de payer une longue réponse quand on attend un seul mot. La fonction generate de common.py réessaie avec une attente croissante sur les erreurs 429 (quota) et 503 (surcharge).
Pourquoi les exemples few-shot doivent-ils venir de train.jsonl et pas de test.jsonl ?
C'est une fuite de données : le modèle verrait la bonne réponse d'un ticket qu'il doit ensuite classer. La note ne mesurerait plus sa capacité à généraliser.
Lancer la mesure
# sur GCP, sur le modèle qu'on réglerait ensuite
GOOGLE_GENAI_USE_VERTEXAI=TRUE GOOGLE_CLOUD_PROJECT=mon-projet GOOGLE_CLOUD_LOCATION=europe-west1 \
MODEL=gemini-2.5-flash-lite python evaluate.py
# en local avec une clé d'API
MODEL=gemini-3.5-flash-lite python evaluate.pyDeux modes × 15 tickets = 30 appels. Mesurez la ligne de base sur le modèle que vous comptez régler : comparer un gemini-2.5-flash-lite réglé à un autre modèle non réglé ne dirait rien de l'effet du réglage.
Zero-shot et few-shot obtiennent la même exactitude, mais le few-shot consomme 5,6 fois plus de tokens d'entrée. Que conclure ?
À qualité égale, la solution la moins chère gagne. Le fine-tuning ne s'impose que si l'on a un écart de qualité à combler ou un coût à réduire ; ici le zero-shot est déjà très bon.
Exercice : trouver la classe faible
Ouvrez baseline_report.json. Quelle étiquette a le plus faible rappel ? Lisez les deux tickets mal classés : est-ce le modèle qui se trompe, ou l'étiquette qui est discutable ?
Voir la solution
C'est resiliation (rappel 0,67 dans les deux modes). Les erreurs réelles :
- « C'est beaucoup trop compliqué de partir de chez vous, le bouton pour désactiver… » → classé
acces_compte; - « Bonjour, nous souhaitons rétrograder notre formule vers la version basique gratuite… » → classé
facturation.
Les deux sont ambigus : le premier parle d'un bouton (accès), le second d'un changement d'offre (facturation). Avant tout réglage, la bonne action est de préciser la consigne : « un client qui veut partir ou passer à l'offre gratuite pour arrêter de payer relève de resiliation ». Améliorer la définition des étiquettes est souvent plus rentable qu'un fine-tuning.
Résultat obtenu
Exécution réelle le 2026-10-15, MODEL=gemini-2.5-flash-lite, Vertex AI europe-west1, 15 tickets de test :
zero-shot exactitude=0.933 hors_etiquette=0.000 tokens_entree_moyens=88
few-shot exactitude=0.933 hors_etiquette=0.000 tokens_entree_moyens=497"zero-shot": {
"exactitude": 0.933,
"rappel_par_etiquette": {"facturation": 1.0, "acces_compte": 1.0, "bug_technique": 1.0,
"demande_fonctionnalite": 1.0, "resiliation": 0.67},
"tokens_entree_moyens": 88
}Je sais faire
- Écrire un script d'évaluation réutilisable avant et après réglage
- Passer des exemples few-shot sous forme de tours
user/model - Mesurer qualité et coût en tokens d'une approche
- Me méfier des écarts minuscules sur un petit jeu de test