Lección 2 — Le standard et le chargeur

La divulgation progressive

7 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

La divulgation progressive

Six skills, c'est déjà 9 261 tokens si on colle tout dans le prompt système : les corps, les barèmes, les scripts. À soixante skills, l'ordre de grandeur passe à 90 000 tokens par question, avant même que l'utilisateur ait écrit un mot. La plupart des questions n'ont besoin d'aucun skill, les autres d'un seul. Le harness doit donc envoyer peu au départ et charger le reste au moment précis où le modèle le demande.

Le mécanisme

La spécification décrit trois niveaux. Le harness en est responsable ; le modèle n'en voit que les effets.

NiveauContenuQuandCoût indicatif (spécification)Mesuré ici
1. Cataloguename + description de chaque skillà chaque requête50 à 100 tokens par skill1 208 tokens pour 6 skills, instructions comprises
2. Instructionscorps de SKILL.mdquand le modèle active le skillmoins de 5 000 recommandés411 à 1 423 tokens
3. Ressourcesscripts/, references/, assets/quand les instructions les citentvariable69 à 886 tokens par fichier

Nos descriptions font environ 700 caractères, avec phrases déclencheuses bilingues : 170 à 200 tokens chacune, au-dessus de la fourchette de la spécification. C'est un choix : la description porte tout le déclenchement (leçon suivante).

sequenceDiagram
  participant U as Utilisateur
  participant H as Harness
  participant M as Gemini
  participant B as Bac à sable
  U->>H: « Contrôle ma note de frais : … »
  H->>M: instructions + catalogue (niveau 1) + 3 outils
  M-->>H: activate_skill(expense-report-checker)
  H->>M: corps SKILL.md + liste des ressources (niveau 2)
  M-->>H: run_skill_script(scripts/check_expenses.py, --csv …)
  H->>B: copie jetable, sans réseau
  B-->>H: JSON : total_remboursable 140.0
  M-->>H: read_skill_file(assets/rapport.md) (niveau 3)
  M-->>H: réponse finale

La mise en œuvre

Le chargeur (loader.py) expose trois outils au modèle. Le nom du skill est une énumération dans le schéma JSON : le modèle reçoit la liste exacte des noms valides au lieu d'en deviner un, et le harness rejette tout nom absent du catalogue.

files/skillkit/loader.py
def tool(self) -> types.Tool:
    names = {"type": "string", "enum": sorted(self.skills)}
    return types.Tool(function_declarations=[
        types.FunctionDeclaration(name="activate_skill", ...,
            parameters_json_schema={"type": "object", "properties": {"name": names}, "required": ["name"]}),
        types.FunctionDeclaration(name="read_skill_file", ...),
        types.FunctionDeclaration(name="run_skill_script", ...),
    ])

L'activation renvoie le corps entouré de balises et liste les ressources sans les lire. Une seconde activation du même skill ne réinjecte rien.

files/skillkit/loader.py
def activate(self, name: str) -> str:
    s = self._get(name)
    if name in self.active:
        return f'<skill_content name="{name}">déjà chargé plus haut dans la conversation</skill_content>'
    self.active.append(name)
    res = "\n".join(f"  <file>{r}</file>" for r in s.resources)
    return (f'<skill_content name="{name}" version="{s.version}">\n{s.body}\n\n'
            f"<skill_resources>\n{res}\n</skill_resources>\n</skill_content>")

La lecture d'un fichier passe par une liste blanche : seul un chemin présent dans skill.resources est servi. ../hr-policy-qa/SKILL.md, /etc/passwd ou SKILL.md lui-même sont refusés, et la réponse d'erreur part au modèle au lieu de lever une exception. La boucle (harness.py) désactive l'exécution automatique des fonctions du SDK et cumule usage_metadata.prompt_token_count à chaque appel : c'est la seule mesure qui compte, car chaque tour renvoie tout l'historique.

Résultat obtenu

python run.py tokens, API count_tokens, gemini-3.5-flash, 2026-10-15 (extrait) :

{
 "niveau1_catalogue": 1208,
 "niveau2_corps": {"csv-data-profiler": 411, "expense-report-checker": 543, "hr-policy-qa": 1177,
                   "leave-balance-calculator": 538, "meeting-notes-to-actions": 1309, "ticket-triage": 1423},
 "tout_charger": 9261,
 "economie_par_requete_sans_skill": 8053,
 "ratio": 7.7
}

Tokens d'entrée cumulés sur des requêtes réelles du même jour (run.py eval-quality et run.py e2e) :

RequêteProgressifTout chargerAppels (progressif / tout)
Hors sujet (médiane contre moyenne)1 611non mesuré, au moins 9 2611 / -
Compte rendu de réunion4 99419 8272 / 2
Tri de 6 tickets5 12819 8442 / 2
Note de frais avec script14 33330 4895 / 3

Le gain est d'un facteur 2 à 4 par requête, pas 7,7 : le mode progressif fait plus d'allers-retours, et chacun renvoie l'historique entier. Le cas de la note de frais le montre : cinq appels (activer, exécuter, lire le gabarit, lire la politique, répondre) contre trois.

Ce qui casse en production

SymptômeCauseParade
Le modèle « oublie » la procédure au 15e tourla compaction du contexte a résumé le corps du skillmarquer les blocs <skill_content> comme protégés lors de la compaction
Le même corps apparaît trois fois dans l'historiquepas de déduplication des activationsliste active, réponse courte si déjà chargé
Le modèle lit tous les fichiers de references/ « par sécurité »instructions vagues ; chaque lecture coûte un tour et des tokensécrire dans le corps quand lire chaque fichier (« si l'utilisateur conteste une règle »)
Coût en hausse malgré la divulgation progressiveun tour supplémentaire renvoie tout l'historique ; avec 8 outils lus, le progressif dépasse le tout-chargersuivre les tokens cumulés par requête, pas la taille du prompt système
Le modèle appelle un skill qui n'existe pluscatalogue mis en cache côté client après suppressionénumération régénérée à chaque session, skills filtrés retirés du catalogue

La règle de décision

Utilisez la divulgation progressive dès que le total « tout charger » dépasse quelques milliers de tokens ou que la majorité des requêtes n'utilise aucun skill. Restez en chargement complet pour deux ou trois skills courts appelés presque à chaque requête : vous économisez un tour, et un préfixe stable profite du cache de prompt. Mesurez toujours les tokens cumulés d'une requête réelle, jamais seulement la taille du catalogue.

Pratique

Votre catalogue compte 4 skills de 300 tokens de corps chacun, et 90 % des requêtes en utilisent un. Le passage en divulgation progressive a augmenté la facture. Quelle explication retenez-vous ?

Un skill de 40 lignes cite un fichier references/bareme-2026.md de 12 000 tokens. Le modèle le lit à chaque activation. Que changez-vous en premier ?

Exercice. Ajoutez au résultat de SkillAgent.ask le nombre de tokens apportés par chaque niveau (catalogue, corps, ressources), en vous servant des réponses d'outils. Comparez-le aux tokens cumulés : quelle part vient de la répétition de l'historique ?

Voir la solution

Dans SkillLoader.dispatch, mesurez la taille de ce qui est renvoyé (ou appelez count_tokens en mode diagnostic) et cumulez par outil : activate_skill pour le niveau 2, read_skill_file et run_skill_script pour le niveau 3. Sur la note de frais, l'apport direct des niveaux 2 et 3 est d'environ 1 000 tokens (corps 543, gabarit 115, politique 244, sortie du script), contre 14 333 tokens cumulés : la majorité vient du renvoi de l'historique et du catalogue à chacun des cinq appels. Leçon : réduire le nombre de tours (un script qui renvoie directement le rapport) rapporte plus que réduire la taille du corps.

  • Je sais expliquer les trois niveaux et ce qui déclenche chacun
  • Je sais contraindre les noms de skills par une énumération
  • Je sais servir les ressources par liste blanche
  • Je sais mesurer les tokens cumulés d'une requête et pas seulement le prompt système
Siguiente