bots-garden/mini-mepublic Fork
⑂ v0.3.0
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. 0f8196a · on v0.3.0 · k33g · Sep 21, 2026
measure-skill-routing.md · 90 lines · 6.0 KBmarkdown
Blame HistoryOpen raw

Mesurer si le modèle choisit le bon skill

Lancer l'eval de routage, lire son tableau, et s'en servir pour décider si un changement du catalogue ou du prompt a aidé.

mm affiche un skill dans le catalogue et espère que le modèle ira le chercher. Qu'il le fasse ou non est une propriété du modèle, pas du code : aucun test ordinaire ne peut y répondre. Celui-ci interroge un modèle vivant, vingt fois.

Le lancer

MM_EVAL=1 AGENT_CONFIG=tests/agent.dmr-qwen.yaml go test ./internal/eval -v -timeout 25m

Les deux variables sont nécessaires en pratique : sans MM_EVAL le test se saute, et sans AGENT_CONFIG il évalue les défauts intégrés plutôt que la configuration qui vous intéresse. go test ./... le saute, donc rien dans la CI ne dépend d'un serveur démarré.

Comptez quelques minutes : un tour de modèle par cas, et les petits modèles ne sont pas rapides. Les lignes s'affichent au fur et à mesure.

Lire le tableau

[ 6/20] ✓ hit    want=what-time-is-it  got=what-time-is-it  1.204s  "quelle heure est-il ?"  ← French
[14/20] ≠ wrong  want=greetings        got=vulcan-salute    0.981s  "Salue Bob de ma part"   ← two skills claim salut/saluer
[16/20] · miss   want=bg-jobs          got=(none)           2.017s  "lance le serveur en tâche de fond"
[17/20] ! extra  want=(none)           got=greetings        0.774s  "write a haiku about pointers"
Marque Sens Ce que ça désigne
✓ hit le skill attendu, ou correctement aucun —
≠ wrong un skill a été chargé, mais pas celui-là le catalogue : deux entrées que le modèle ne distingue pas
· miss un skill s'appliquait et aucun n'a été chargé le prompt : le modèle n'a pas pensé à chercher
! extra aucun skill ne s'appliquait et un a été chargé instruction trop insistante, ou entrée qui promet trop
✗ error le tour a échoué jamais un score de routage. Soit l'instrument (un stub qui a affamé le modèle, caseTimeout trop court, un serveur parti), soit le modèle incapable de composer un appel d'outil valide (unexpected end of JSON input, ou le garde-fou anti-emballement qui coupe des arguments répétés). Le message dit lequel. Fait échouer le test, car les chiffres sont alors incomplets.

wrong et miss sont séparés à dessein. Les confondre masque lequel des deux problèmes vous avez, et ils n'ont pas le même remède.

S'en servir

Le chiffre seul ne veut rien dire. Il n'y a pas de seuil et le test n'échoue pas sur un mauvais score — un chiffre qui casse le build devient une cible, et la première chose qu'on fait devant un build rouge, c'est modifier les cas.

L'eval est un avant/après :

  1. Le lancer, garder la sortie.
  2. Changer une chose — les keywords d'un skill, le bloc SKILLS du prompt système, une description.
  3. Le relancer sur le même modèle, avec la même configuration.
  4. Comparer.

Un changement à la fois. Deux d'un coup et vous avez mesuré leur somme.

Les petits modèles à temperature: 0.0 sont assez reproductibles pour que ça ait un sens ; relancez deux fois avant de croire à un écart d'un seul cas.

Ce qu'il mesure, et ce qu'il ne mesure pas

Il mesure le catalogue — la description d'outil que le modèle lit — sous un prompt système donné. Un tour de modèle par cas, avec des outils qui enregistrent ce qui a été demandé et n'exécutent rien : un skill qui dit « lance cet echo » ne doit pas echo vingt fois, et laisser tourner la boucle de l'agent mesurerait la récupération autant que le routage.

Il ne mesure pas si le skill, une fois chargé, a été correctement suivi. C'est une autre question, et il faut un autre instrument.

Il ne mesure pas non plus si le modèle sait émettre un appel d'outil valide. Quand il ne sait pas — et les petits modèles souvent ne savent pas — vous obtenez ✗ error, pas un mauvais score de routage. Ces deux échecs n'ont rien à voir, et aucun travail sur le catalogue ne corrige le second. Mesuré sur qwen3.5-4b : trois cas sur vingt échouent ainsi, de façon reproductible, sur les demandes qui appellent un appel plus structuré.

Ajouter un cas

internal/eval/routing_test.go, la table cases :

{"lance le serveur en tâche de fond", "bg-jobs", "French"},
{"what is 17 times 23?", none, ""},

Deux règles qui gardent les chiffres honnêtes :

  • Ne formulez pas la demande avec les mots du catalogue. Un cas qui dit « utilise le skill greetings » ne mesure rien.
  • Gardez les cas none. Un catalogue qui se déclenche sur tout est aussi cassé qu'un catalogue qui ne se déclenche jamais, et s'abstenir à raison est un succès.

Quand vous trouvez un vrai échec de routage, ajoutez-le comme cas avant de le corriger. C'est ainsi que l'eval empêche le même échec de revenir.

Écrivez des cas capables de séparer les explications

Un cas isolé dit que quelque chose a échoué. Un ensemble de cas dit pourquoi, et c'est ce qui rend l'eval utile.

Exemple vécu. bg-jobs échouait sur les demandes anglaises et réussissait sur les françaises, et la lecture évidente était « la couverture française du catalogue est faible » — sauf que bg-jobs n'a aucun mot-clé français. Trois cas ont tranché :

Cas Résultat
start the dev server in the background — anglais, nomme le concept ✓ hit
démarre le serveur de dev et vérifie qu'il répond — français, juste une situation · miss
lance le serveur en tâche de fond — français, nomme le concept ✓ hit
start the dev server and check it answers — anglais, juste une situation · miss

Un 2×2 net : la langue n'a rien à voir. Ce qui décide, c'est que la demande nomme le concept dont parle le skill. Ça a déplacé le problème du catalogue vers le prompt — le modèle n'a aucune raison de savoir qu'un serveur bloque le tour — et aucune retouche de mots-clés ne l'aurait trouvé.

Construisez les cas par paires qui ne diffèrent que d'une chose. Un cas seul est une anecdote.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# Mesurer si le modèle choisit le bon skill

> Lancer l'eval de routage, lire son tableau, et s'en servir pour décider si un changement du catalogue ou du prompt a aidé.

`mm` affiche un skill dans le catalogue et espère que le modèle ira le chercher. Qu'il le fasse ou non est une propriété du **modèle**, pas du code : aucun test ordinaire ne peut y répondre. Celui-ci interroge un modèle vivant, vingt fois.

## Le lancer

```bash
MM_EVAL=1 AGENT_CONFIG=tests/agent.dmr-qwen.yaml go test ./internal/eval -v -timeout 25m
```

Les deux variables sont nécessaires en pratique : sans `MM_EVAL` le test se saute, et sans `AGENT_CONFIG` il évalue les défauts intégrés plutôt que la configuration qui vous intéresse. `go test ./...` le saute, donc rien dans la CI ne dépend d'un serveur démarré.

Comptez quelques minutes : un tour de modèle par cas, et les petits modèles ne sont pas rapides. Les lignes s'affichent au fur et à mesure.

## Lire le tableau

```
[ 6/20] ✓ hit    want=what-time-is-it  got=what-time-is-it  1.204s  "quelle heure est-il ?"  ← French
[14/20] ≠ wrong  want=greetings        got=vulcan-salute    0.981s  "Salue Bob de ma part"   ← two skills claim salut/saluer
[16/20] · miss   want=bg-jobs          got=(none)           2.017s  "lance le serveur en tâche de fond"
[17/20] ! extra  want=(none)           got=greetings        0.774s  "write a haiku about pointers"
```

| Marque | Sens | Ce que ça désigne |
|---|---|---|
| `✓ hit` | le skill attendu, ou correctement aucun | — |
| `≠ wrong` | un skill a été chargé, mais pas celui-là | le **catalogue** : deux entrées que le modèle ne distingue pas |
| `· miss` | un skill s'appliquait et aucun n'a été chargé | le **prompt** : le modèle n'a pas pensé à chercher |
| `! extra` | aucun skill ne s'appliquait et un a été chargé | instruction trop insistante, ou entrée qui promet trop |
| `✗ error` | le tour a échoué | **jamais un score de routage.** Soit l'instrument (un stub qui a affamé le modèle, `caseTimeout` trop court, un serveur parti), soit le modèle incapable de composer un appel d'outil valide (`unexpected end of JSON input`, ou le garde-fou anti-emballement qui coupe des arguments répétés). Le message dit lequel. Fait échouer le test, car les chiffres sont alors incomplets. |

`wrong` et `miss` sont séparés à dessein. Les confondre masque lequel des deux problèmes vous avez, et ils n'ont pas le même remède.

## S'en servir

**Le chiffre seul ne veut rien dire.** Il n'y a pas de seuil et le test n'échoue pas sur un mauvais score — un chiffre qui casse le build devient une cible, et la première chose qu'on fait devant un build rouge, c'est modifier les cas.

L'eval est un **avant/après** :

1. Le lancer, garder la sortie.
2. Changer **une** chose — les `keywords` d'un skill, le bloc `SKILLS` du prompt système, une `description`.
3. Le relancer sur le même modèle, avec la même configuration.
4. Comparer.

Un changement à la fois. Deux d'un coup et vous avez mesuré leur somme.

Les petits modèles à `temperature: 0.0` sont assez reproductibles pour que ça ait un sens ; relancez deux fois avant de croire à un écart d'un seul cas.

## Ce qu'il mesure, et ce qu'il ne mesure pas

Il mesure le **catalogue** — la description d'outil que le modèle lit — sous un prompt système donné. Un tour de modèle par cas, avec des outils qui enregistrent ce qui a été demandé et **n'exécutent rien** : un skill qui dit « lance cet echo » ne doit pas echo vingt fois, et laisser tourner la boucle de l'agent mesurerait la récupération autant que le routage.

Il ne mesure pas si le skill, une fois chargé, a été correctement suivi. C'est une autre question, et il faut un autre instrument.

Il ne mesure pas non plus si le modèle sait émettre un appel d'outil valide. Quand il ne sait pas — et les petits modèles souvent ne savent pas — vous obtenez `✗ error`, pas un mauvais score de routage. Ces deux échecs n'ont rien à voir, et aucun travail sur le catalogue ne corrige le second. Mesuré sur `qwen3.5-4b` : trois cas sur vingt échouent ainsi, de façon reproductible, sur les demandes qui appellent un appel plus structuré.

## Ajouter un cas

`internal/eval/routing_test.go`, la table `cases` :

```go
{"lance le serveur en tâche de fond", "bg-jobs", "French"},
{"what is 17 times 23?", none, ""},
```

Deux règles qui gardent les chiffres honnêtes :

- **Ne formulez pas la demande avec les mots du catalogue.** Un cas qui dit « utilise le skill greetings » ne mesure rien.
- **Gardez les cas `none`.** Un catalogue qui se déclenche sur tout est aussi cassé qu'un catalogue qui ne se déclenche jamais, et s'abstenir à raison est un succès.

Quand vous trouvez un vrai échec de routage, ajoutez-le comme cas **avant** de le corriger. C'est ainsi que l'eval empêche le même échec de revenir.

## Écrivez des cas capables de séparer les explications

Un cas isolé dit que quelque chose a échoué. Un **ensemble** de cas dit pourquoi, et c'est ce qui rend l'eval utile.

Exemple vécu. `bg-jobs` échouait sur les demandes anglaises et réussissait sur les françaises, et la lecture évidente était « la couverture française du catalogue est faible » — sauf que `bg-jobs` n'a aucun mot-clé français. Trois cas ont tranché :

| Cas | Résultat |
|---|---|
| `start the dev server in the background` — anglais, **nomme** le concept | ✓ hit |
| `démarre le serveur de dev et vérifie qu'il répond` — français, juste une situation | · miss |
| `lance le serveur en tâche de fond` — français, **nomme** le concept | ✓ hit |
| `start the dev server and check it answers` — anglais, juste une situation | · miss |

Un 2×2 net : **la langue n'a rien à voir.** Ce qui décide, c'est que la demande nomme le concept dont parle le skill. Ça a déplacé le problème du catalogue vers le prompt — le modèle n'a aucune raison de savoir qu'un serveur bloque le tour — et aucune retouche de mots-clés ne l'aurait trouvé.

Construisez les cas par paires qui ne diffèrent que d'**une** chose. Un cas seul est une anecdote.