Tester le déclenchement
Un skill qui ne se déclenche pas ne sert à rien, et personne ne s'en plaint : l'assistant répond quand même, moins bien. Un skill qui se déclenche à tort coûte des tokens et produit une réponse hors sujet, et là encore personne n'ouvre de ticket. Le seul moyen de savoir si vos descriptions font leur travail est un jeu de requêtes étiquetées, rejoué à chaque modification d'une description, d'un modèle ou du catalogue.
Le mécanisme
Le jeu (evals/triggering.json) associe chaque requête au skill attendu, ou à null quand aucun ne doit se déclencher. Il suit les conseils du guide Optimizing skill descriptions : varier la formulation (formelle, familière, fautes, anglais et français), l'explicitation (« note de frais » ou seulement des lignes de dépenses), le détail et la complexité, et surtout écrire des quasi-homonymes négatifs, qui partagent le vocabulaire d'un skill sans en relever.
| Groupe | Nombre | Exemple |
|---|---|---|
| Positifs, 4 par skill | 24 | « cr de la reunion svp : - budget ok - Marc s'occupe du devis avant vendredi » |
| Quasi-homonymes négatifs | 5 | « Crée-moi un modèle de tableau vierge pour saisir mes notes de frais » |
| Négatif sans rapport | 1 | « Quelle est la différence entre la médiane et la moyenne ? » |
| Dont paires piégées entre skills (comptées dans les positifs) | 3 | « What's the company rule on reimbursing a first-class train ticket? » : hr-policy-qa, pas expense-report-checker |
flowchart LR
J["30 requêtes étiquetées"] --> R["N passages par requête (premier tour seulement)"]
R --> C{"Comparaison attendu / obtenu"}
C --> E["Exactitude : décisions justes, « aucun » compris"]
C --> P["Précision : activations justes / activations"]
C --> RA["Rappel : positifs servis par le bon skill"]
C --> F["Faux déclenchements sur les négatifs"]
E --> G{"Exactitude >= TRIGGER_MIN_ACCURACY ?"}
G -->|"non"| X["CI rouge : la description fautive est dans la liste d'erreurs"]
Quatre métriques, parce qu'une seule cache les défauts : un harness qui n'active jamais rien a une précision indéfinie et zéro faux déclenchement ; un harness qui active toujours le skill le plus proche a un rappel parfait.
La mise en œuvre
La fonction de métriques est pure et testée sans réseau (evals.py) :
def trigger_metrics(rows: list[dict]) -> dict:
n = len(rows)
ok = sum(r["got"] == r["expected"] for r in rows)
fired = [r for r in rows if r["got"] is not None]
pos = [r for r in rows if r["expected"] is not None]
neg = [r for r in rows if r["expected"] is None]
return {
"exactitude": round(ok / n, 3) if n else 0.0,
"precision": round(sum(r["got"] == r["expected"] for r in fired) / len(fired), 3) if fired else 0.0,
"rappel": round(sum(r["got"] == r["expected"] for r in pos) / len(pos), 3) if pos else 0.0,
"faux_declenchements": sum(r["got"] is not None for r in neg),
"n": n,
}run.py eval-trigger --runs 2 rejoue chaque requête deux fois avec le modèle, calcule les scores de similarité pour quatre seuils, écrit sorties/triggering-<modèle>.json et sort en erreur si l'exactitude tombe sous TRIGGER_MIN_ACCURACY (0,85 par défaut). C'est cette sortie en erreur qui en fait une porte de CI (ci/github-actions.yml).
Résultat obtenu
2026-10-15, 30 requêtes, gemini-3.5-flash deux passages (60 décisions, 2 min 52 s, 1 632 tokens d'entrée par décision en moyenne) et gemini-3.5-flash-lite un passage :
"modele": {"model": "gemini-3.5-flash", "runs": 2,
"metrics": {"exactitude": 1.0, "precision": 1.0, "rappel": 1.0, "faux_declenchements": 0, "n": 60},
"erreurs": [], "tokens_entree_moyens": 1632}
"modele": {"model": "gemini-3.5-flash-lite", "runs": 1,
"metrics": {"exactitude": 0.967, "precision": 0.96, "rappel": 1.0, "faux_declenchements": 1, "n": 30},
"erreurs": ["attendu None -> obtenu leave-balance-calculator : \"Pose 3 jours de congés du 4 au 6 novembre dans l'outil RH pour moi.\""]}Comment lire un score parfait. Zéro erreur sur 60 décisions ne prouve pas un taux d'erreur nul : la règle de trois donne, à 95 % de confiance, un taux d'erreur réel qui peut atteindre environ 3/60, soit 5 %, et les deux passages d'une même requête ne sont pas indépendants. Surtout, un score parfait signale souvent un jeu trop facile. Le seul échec observé (modèle léger, « poser des congés ») indique où durcir le jeu : des demandes d'action proches d'un skill de calcul ou de consultation.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
| 100 % en CI, plaintes en production | requêtes de test rédigées par l'auteur du skill, avec ses mots | requêtes tirées des journaux réels (anonymisées), quasi-homonymes écrits par un tiers |
| Le score varie de 5 points d'un jour à l'autre | un seul passage, sélection non déterministe | 3 passages, taux de déclenchement par requête, seuil 0,5 comme le propose agentskills.io |
| La description améliorée régresse sur d'autres requêtes | optimisation sur tout le jeu, surapprentissage | séparation fixe 60 % entraînement, 40 % validation ; on ne regarde la validation qu'à la fin |
| Un nouveau skill fait chuter un ancien | les descriptions se recouvrent | le jeu teste le catalogue entier, pas chaque skill isolément ; relancer à chaque ajout |
| Coût des évals en hausse | 3 passages x 30 requêtes x plusieurs modèles à chaque commit | évals sur étiquette de PR, make test (sans réseau) à chaque commit |
La règle de décision
Rejouez le jeu de déclenchement à chaque modification d'une description, d'un modèle ou du catalogue, et bloquez la fusion sous un seuil d'exactitude fixé à l'avance. Exigez au moins un quasi-homonyme négatif par skill. Visez une centaine de requêtes pour un catalogue en production : à 30 requêtes, vous détectez les régressions grossières, pas une dégradation de quelques points. Surveillez en production la part de conversations où un skill a été activé puis non utilisé (aucun script, réponse générique) : c'est le faux déclenchement que votre jeu n'a pas prévu.
Pratique
Votre jeu de 20 requêtes donne 100 % d'exactitude depuis trois mois. Les utilisateurs disent que le skill de calcul de congés « se lance pour rien ». Que faites-vous ?
Un jeu qui ne voit jamais d'échec ne contient pas les cas difficiles. Les journaux donnent les vraies formulations. Abaisser le seuil ne change rien au jeu ; la similarité a montré ici qu'elle déclenche plus, pas moins.
Vous réécrivez la description de hr-policy-qa. Sur l'ensemble d'entraînement, l'exactitude passe de 0,90 à 0,97 ; sur l'ensemble de validation, de 0,92 à 0,88. Que décidez-vous ?
La validation mesure la généralisation ; elle baisse. Retirer les échecs ou fusionner les ensembles détruit la seule mesure honnête. La moyenne des deux mélange ce qu'on a optimisé et ce qu'on n'a pas vu.
Exercice. Écrivez cinq requêtes supplémentaires qui font échouer gemini-3.5-flash-lite (demandes d'action proches d'un skill de calcul ou de consultation), ajoutez-les au jeu, puis modifiez une description pour les corriger sans dégrader les 30 autres.
Voir la solution
Exemples de requêtes à null : « Valide la note de frais de Léa dans l'outil », « Envoie le récap de réunion à l'équipe », « Ferme les tickets en doublon », « Annule mes congés de décembre », « Supprime les doublons de ce CSV et renvoie-le ». Ce sont des actions qu'aucun skill n'effectue.
Correction type : ajouter à chaque description concernée une phrase « It computes / drafts only and never submits, approves, sends or closes anything ». Relancez MODEL=gemini-3.5-flash-lite python run.py eval-trigger --runs 3, comparez les erreurs avant et après, et vérifiez que le rappel sur les 24 positifs reste à 1,0. Si la correction ne tient que pour le modèle léger, gardez-la quand même : la description sert tous les clients.
- Je sais construire un jeu de déclenchement avec quasi-homonymes
- Je sais distinguer exactitude, précision, rappel et faux déclenchements
- Je sais lire un score parfait avec prudence
- Je sais transformer l'éval en porte de CI