TL;DR : Quand votre principal consommateur est un LLM, XML et JSON gaspillent des jetons en répétant la structure à chaque élément. Un format compact et positionnel réduit la consommation de 50 %. Il s’avère que cette idée avait déjà un nom : TOON (Token-Oriented Object Notation). Même pression sélective — jetons précieux et clés répétitives — même solution.
Anthropic utilise XML pour tout. Leurs system prompts sont enveloppés dans <instructions>, leurs exemples dans <example>, leurs outils dans <function>. Si vous travaillez avec Claude, vous vivez entouré de balises.
Donc, lorsque j’ai demandé à Claude de concevoir la sortie d’un CLI destiné à être consommé par des LLMs, la tentation évidente était : XML. Si le modèle reçoit des informations en XML, le CLI ne devrait-il pas lui retourner des données en XML ?
Eh bien… non.
Le problème : la répétition compulsive
Imaginez un CLI qui liste des tickets dans un gestionnaire de projet. Il y a 50 tickets. En XML, chacun ressemble à ça :
<issue>
<id>PROD-587</id>
<state>En attente</state>
<labels>backend</labels>
<title>Importer des sessions à partir de la sauvegarde du NAS</title>
<age_days>14</age_days>
</issue>
Beau. Auto-descriptif. Chaque champ a son propre nom. Un parseur XML sait exactement quoi reconnaître.
Maintenant, multipliez ça par 50 tickets. Les balises <issue>, <id>, <state>, <labels>, <title>, <age_days> se répètent 50 fois chacune. Ce sont 300 balises qui n’apportent aucune nouvelle information après la première répétition. Environ 70 jetons par ticket, 3 500 jetons pour afficher 50 tickets.
JSON améliore un peu la situation — il élimine les balises de fermeture — mais continue à répéter les clés :
{
"id": "PROD-587",
"state": "En attente",
"labels": ["backend"],
"title": "Importer des sessions à partir de la sauvegarde du NAS",
"age_days": 14
}
Chaque ligne répète "id":, "state":, "labels":, "title":, "age_days":. Environ 50 jetons par ticket, 2 500 pour les 50.
Et si l’on supprimait toutes ces répétitions ?
PROD-587 [En attente] backend — Importer des sessions à partir de la sauvegarde du NAS (14j)
25 jetons. Pas de clés. Pas de balises. Pas d’accolades ni de guillemets. 1 250 jetons pour 50 tickets.
Attention à ce détail : moitié moins que JSON, moins d’un tiers de XML. Pour exactement la même tâche.
“Mais les LLMs ont besoin de structure”
C’est là que les gens me regardent bizarrement. “Et comment le LLM sait ce que représente chaque champ ?”
Question légitime. Et la réponse est : exactement comme vous le savez.
Regardez cette ligne :
PROD-587 [En attente] backend — Importer des sessions à partir de la sauvegarde du NAS (14j)
Avez-vous besoin que quelqu’un vous dise que PROD-587 est l’ID ? Que [En attente] est l’état ? Que ce qui suit le tiret long est le titre ? Non. Vous le déduisez grâce à la position et au format visuel.
Les LLMs font exactement la même chose. Ce sont des machines qui reconnaissent des motifs dans le texte. Un format positionnel cohérent — ID en premier, état entre crochets, labels en texte libre, titre après le tiret, métadonnées entre parenthèses — ils le comprennent immédiatement. Ils n’ont pas besoin de <state>En attente</state> pour savoir que “En attente” est un état.
La clé est de distinguer deux opérations qui semblent identiques mais ne le sont pas :
LIRE est ce que fait un LLM. Il possède un contexte, comprend la sémantique et en déduit la structure. Un humain lisant un rapport n’a pas besoin que chaque mot soit balisé — il comprend par la position et la convention.
PARSER est ce que fait un programme. Il n’a pas de contexte, ne comprend pas la sémantique, et a besoin de délimiteurs explicites pour extraire les champs. Un jq '.state' nécessite la clé "state" parce qu’il ne sait pas ce qu’est un état.
XML et JSON sont conçus pour le parsing. Ce sont des formats d’échange entre machines qui ne comprennent pas le contenu. Les LLMs lisent. La structure explicite est redondante pour eux — et cette redondance coûte en jetons.
Le spectre : quand utiliser quoi
Je ne dis pas que XML et JSON sont mauvais. Ils sont mauvais dans ce cas particulier. Pour faire simple, voici le spectre :
| Format | Jetons/ticket | Auto-descriptif | Idéal pour |
|---|---|---|---|
| XML | ~70 | Total | APIs SOAP, configurations, documents avec schéma |
| JSON | ~50 | Total | APIs REST, échange entre services |
| JSONL | ~50 | Total | Scripts, jq, pipelines de données |
| Positionnel | ~25 | Non | LLMs, humains, tableaux de bord compacts |
La règle est simple : si le consommateur comprend le contexte (humain, LLM), vous pouvez supprimer la structure explicite. Si le consommateur ne comprend pas le contexte (script, parseur), vous avez besoin de clés.
C’est pourquoi mon CLI propose deux modes : format positionnel compact par défaut (pour le LLM et pour vous au terminal), et --json pour les cas où vous devez analyser la sortie avec jq ou un script.
La surprise : quelqu’un y avait déjà pensé
Voici la meilleure partie.
Quelques semaines après avoir adopté le format proposé par Claude, quelqu’un m’a demandé : “As-tu regardé TOON ?”
TOON — Token-Oriented Object Notation — est un format qui a émergé en novembre 2025. Son principe : un encodage compact du modèle de données JSON, conçu spécifiquement pour minimiser les jetons dans les prompts des LLMs.
Voici à quoi ça ressemble :
issues[3]{id,state,labels,title,age_days}:
PROD-587,En attente,backend,Importer des sessions à partir de la sauvegarde du NAS,14
PROD-612,À faire,tokamak,Résoudre le problème de rafraîchissement des jetons,2
PROD-501,Fait,frontend,Migrer la base de données vers un nouveau schéma,30
Regardez. Un en-tête avec les champs ({id,state,labels,title,age_days}) et le nombre d’éléments ([3]). Ensuite, uniquement des données positionnelles séparées par des virgules. Pas de répétition de clés. Pas de balises. Pas de guillemets.
C’est exactement ce que Claude avait proposé pour mon CLI — à une différence près : TOON ajoute un en-tête explicite qui rend le format auto-descriptif.
La version de mon CLI :
PROD-587 [En attente] backend — Importer des sessions à partir de la sauvegarde du NAS (14j)
PROD-612 [À faire] tokamak — Résoudre le problème de rafraîchissement des jetons (2j)
PROD-501 [Fait] frontend — Migrer la base de données vers un nouveau schéma (30j)
Essentiellement identique. Format positionnel, sans répétition de clés, ~25 jetons par ligne. La différence : TOON inclut un en-tête de schéma, tandis que mon format repose sur des conventions visuelles (crochets pour l’état, tiret pour séparer le titre).
Évolution convergente
En biologie, il existe un concept fascinant : l’évolution convergente. Des espèces qui ne sont pas apparentées développent des traits similaires parce qu’elles affrontent les mêmes pressions sélectives. Les yeux d’un poulpe et ceux d’un humain sont structurellement similaires, mais ont évolué indépendamment. Même pression — le besoin de voir — même solution.
TOON et le format de mon CLI sont l’incarnation de l’évolution convergente dans le design logiciel. La pression sélective est la même : jetons coûteux, clés répétées, consommateur capable de reconnaitre les motifs. La solution convergente : éliminer la redondance et se fier à l’ordre.
La différence entre TOON et ce que fait mon CLI est l’en-tête. Et cette différence est importante.
Sans en-tête, le format fonctionne lorsque le LLM a déjà le contexte sur ce que représente chaque champ (via un system prompt ou parce que le modèle est évident). Mais si quelqu’un voit la sortie pour la première fois, il doit deviner. TOON résout ce problème : l’en-tête indique une fois quels champs sont présents, et le LLM n’a plus besoin qu’on lui répète.
C’est la différence entre un contrat implicite et un contrat explicite. Et en ingénierie, les contrats explicites finissent souvent par s’imposer.
Dois-je migrer vers TOON ?
Je me suis posé la question. Et la réponse honnête est : ça dépend de qui consomme votre sortie.
Si votre CLI est consommé toujours par le même agent avec le même system prompt qui décrit le format, le format positionnel sans en-tête fonctionne bien. Il est plus compact (pas de ligne d’en-tête) et le contexte est déjà donné.
Si votre CLI est consommé par différents agents ou par des humains sans contexte préalable, TOON est meilleur. Une ligne supplémentaire pour l’en-tête est un coût marginal offrant une auto-description.
Dans mon cas, le consommateur est toujours le même agent avec un CLAUDE.md décrivant le format. Donc, je reste sur ma bidouille positionnelle. Mais si je packagais le CLI pour un usage public, je migrerais sans hésiter vers TOON.
Ce que j’ai appris
Trois choses :
1. Ne concevez pas pour des parseurs si votre consommateur n’est pas un parseur. XML et JSON sont excellents pour les machines qui nécessitent des clés explicites. Les LLMs ne sont pas ces machines. Concevez pour la façon dont votre consommateur traite l’information, pas pour la façon dont vous pensez qu’il devrait la traiter.
2. La répétition est l’ennemi. Dans une liste de 50 éléments, chaque clé répétée 50 fois est une information superflue. C’est comme mettre “Nom :” devant chaque nom sur une liste de courses. Après le deuxième, votre cerveau ne le lit plus — mais cela occupe de l’espace.
3. Si votre solution converge avec quelque chose qui existe déjà, cela signifie probablement que vous êtes sur la bonne voie. Je ne dis pas qu’il faut toujours réinventer la roue. Je dis que si elle est ronde — que vous la proposiez ou qu’elle émerge d’une autre source — c’est un bon signe.
La prochaine fois que vous concevez la sortie d’un outil que consommera un LLM, posez-vous une question avant de taper serde_json::to_string() : est-ce que mon consommateur a besoin de parser cela ou simplement de le lire ?
Si la réponse est “lire”, chaque clé répétée est un jeton que vous gaspillez pour rien. Et les jetons, comme l’argent au bar, partent plus vite que vous ne le pensez.