Alors que nous commençons à entraîner des LLM en tant qu’Agents, nous devons réfléchir à la meilleure façon de transmettre des informations depuis et vers le modèle dans l’environnement réel. Par exemple, s’il appelle une fonction externe, comment les arguments doivent-ils être passés ? Comment les données de l’environnement doivent-elles être fournies au modèle ? La solution la plus simple (et la plus générale) consiste à utiliser des formats de données structurées, comme le JSON. Ces formats peuvent encoder des structures de données arbitrairement imbriquées, avec différents types.
Mais le JSON est-il le bon choix ? Nous avons de nombreuses options, comme TOML, YAML, XML, etc. Dans cet article, nous examinons et mesurons certaines métriques qui nous aideront à faire le bon choix.
Efficacité des jetons
Une limite fondamentale des LLM actuels est le contexte fini. Nous ne pouvons pas simplement injecter en continu l’intégralité des données mondiales dans le modèle au fil du temps. Cela signifie que nous voulons communiquer les informations les plus utiles possibles avec le moins de jetons.
Ainsi, testons quels formats de données structurées sont tokenisés de la manière la plus efficace, sans télémétrie du monde réel. Pour ce faire, nous générons des données structurées (des dict et list imbriqués) avec des clés aléatoires construites en utilisant le dictionnaire système. Par exemple, en JSON :
[
{
"leptomeninx_xylitone": [
147.396,
{ "asellus_spinelike_triliterality": null }
]
},
{
"costively_zoetic": [["decipherably_wheat"]],
"neurectome_sorcery_tangleproof": null
}
]
Le processus d’échantillonnage implique de sélectionner une taille d’arbre , et de sélectionner récursivement de manière aléatoire les types de conteneurs et les valeurs terminales. Vous remarquerez peut-être que le nombre de jetons structurels utilisés dépend du type de données que nous traitons. Si l’entrée de votre agent ne traite pas de données arbitrairement imbriquées, une spécification plus simple pourrait suffire. Nous définissons donc un ensemble de formes, qui est exactement ce que cela semble être :
imbriqué: combinaisons profondes de dict/list se terminant par des valeurs scalaires.Exemple
[ { "comforter": { "dosadh_disruption_prosodiac": { "unsnatch_moslem": 837 }, "tone_redefine": { "cribrose_aoul": [ [ "christianization-casuariiformes-overbravery-chevronel" ] ] } } }, { "bovarysm": [ "oropharynx_consentant_fibronuclear", "bajardo-liquidy-calibered-belucki" ], "materialistic": { "paleostylic": -27.23, "praediality_juvenilify_benempt": 104, "roquelaure": -407 } }, { "filicites": [ "unpalatableness-allocaffeine", 126.204, { "manesheet": "emery_tricyclene" } ], "imposing_elchee_mentation": 3, "inadvisability": -12.726 } ]creux: principalement des valeurs nulles avec de rares scalaires numériques ou textuels dans des structures imbriquées.Exemple
[ { "areole_auramine_kojiki": { "hyperabsorption_uraniscorrhaphy": -776 }, "maplebush_piete": [ { "shadowgraphist": null, "stakeholder_busybodyness_crebrity": 644 } ], "preadamite": null }, { "bellmaking_brachydont": { "jalapin_chandelier_accelerando": null, "mandative": -79, "totora_peristaphylitis_graphy": null }, "subferryman_dephlegmator": [ { "manuka_uncriminally_archdeceiver": null } ] }, { "daytime": [ { "overfeminine_catholicist": -242.239, "sulfophthalein_irreciprocal": null } ], "gata": null, "macaranga_circuitman": null, "ostraciidae_subsidiariness": "throneward" } ]tabulaire: tables basées sur des colonnes avec des lignes de valeurs scalaires et un schéma partagé.Exemple
{ "columns": [ "viragoish_isogonality_swarming", "supralocally_nuncioship", "zoomorph", "cavitary_visie", "permutableness_impunity_bipack", "forby_archly", "rivinian", "unheal_annelidian_samurai" ], "rows": [ [ true, false, "cincinnatia-cyanhidrosis-auto", false, true, null, "acetosoluble nonexclamatory homogangliate croupal", -219 ], [ null, 836, -904, "metasomatic-mundanism-hotchpotchly-secantly", null, 309.642, "floodgate-baluchitherium-unimaginary-sheepkeeper", -396 ], [ "postcritical-tug", true, -948, 0.135, 399.166, -123, "palaeoniscus", true ] ] }
Nous considérons les formats suivants :
json: JSON entièrement minifié avec clés triées et séparateurs compacts.Exemple
[{"backlotter_overboast":"calligraphist_megabar_uninstructively","landspout_souper":[null],"liquefier_unconvicting":-151.898,"unbegot":[961],"unreformedness":-189.15},{"detriment_muckender":[469.486,{"aspergillum_sharebroker_akebia":337},-302.978],"heeder_aerophyte_unbase":499.655,"metamer_powsoddy":null},{"fascicled_fibrous_bajardo":{"octaeterid_pharmacolite_tentativeness":{"underfellow":83.76},"plethysmography_unchangeably_positioned":432.985,"transvestitism":82},"mirror":{"uninfallibility_benny":null}}]yaml: Sérialisation YAML en style bloc avec un ordre de clés déterministe.Exemple
- backlotter_overboast: calligraphist_megabar_uninstructively landspout_souper: - null liquefier_unconvicting: -151.898 unbegot: - 961 unreformedness: -189.15 - detriment_muckender: - 469.486 - aspergillum_sharebroker_akebia: 337 - -302.978 heeder_aerophyte_unbase: 499.655 metamer_powsoddy: null - fascicled_fibrous_bajardo: octaeterid_pharmacolite_tentativeness: underfellow: 83.76 plethysmography_unchangeably_positioned: 432.985 transvestitism: 82 mirror: uninfallibility_benny: nulltoml: Document TOML enveloppant les enregistrements sous un tableau d’enregistrements, avec les valeurs nulles converties en chaînes.Exemple
[[records]] landspout_souper = [ "null", ] backlotter_overboast = "calligraphist_megabar_uninstructively" liquefier_unconvicting = -151.898 unreformedness = -189.15 unbegot = [ 961, ] [[records]] detriment_muckender = [ 469.486, { aspergillum_sharebroker_akebia = 337 }, -302.978, ] heeder_aerophyte_unbase = 499.655 metamer_powsoddy = "null" [[records]] [records.fascicled_fibrous_bajardo] transvestitism = 82 plethysmography_unchangeably_positioned = 432.985 [records.fascicled_fibrous_bajardo.octaeterid_pharmacolite_tentativeness] underfellow = 83.76 [records.mirror] uninfallibility_benny = "null"xml: Arborescence XML verbeuse utilisant des balises sémantiques et des noms de type explicites.Exemple
<records> <object name="record" index="0"> <array name="landspout_souper"> <null name="0" /> </array> <string name="backlotter_overboast">calligraphist_megabar_uninstructively</string> <number name="liquefier_unconvicting">-151.898</number> <number name="unreformedness">-189.15</number> <array name="unbegot"> <number name="0">961</number> </array> </object> <object name="record" index="1"> <array name="detriment_muckender"> <number name="0">469.486</number> <object name="1"> <number name="aspergillum_sharebroker_akebia">337</number> </object> <number name="2">-302.978</number> </array> <number name="heeder_aerophyte_unbase">499.655</number> <null name="metamer_powsoddy" /> </object> <object name="record" index="2"> <object name="fascicled_fibrous_bajardo"> <number name="transvestitism">82</number> <object name="octaeterid_pharmacolite_tentativeness"> <number name="underfellow">83.76</number> </object> <number name="plethysmography_unchangeably_positioned">432.985</number> </object> <object name="mirror"> <null name="uninfallibility_benny" /> </object> </object> </records>csv: Lignes séparées par des virgules avec en-tête générées à partir d’enregistrements tabulaires.Exemple
bicellular_russification_unsinister,crude_paynim,isoetales,postembryonic_encrisp braza apology catalufa tofu,,rampager,triformous ,True,481.226, 421.281,868,photodysphoria,escortage
Maintenant, pour chaque format, puis pour chaque forme, nous pouvons tracer une carte de chaleur du nombre moyen de jetons par nœud. Les décomptes de jetons proviennent de la moyenne entre les tokenizers Qwen 3, Llama 3.2 et gpt-oss.
D’un coup d’œil, nous pouvons voir que csv est un gagnant clair pour les données tabulaires, et json obtient les meilleures performances en moyenne.
Pour avoir une image plus claire, nous pouvons faire la moyenne pour chaque forme afin de voir le nombre moyen de jetons par format.
Cela montre qu’en matière d’efficacité des jetons seule, le classement est json > yaml > toml > xml.
Cependant, ce n’est pas parce qu’un format est dense qu’il est bon. Mais comment pouvons-nous quantifier cela ?
Qu’est-ce qui fait qu’un format est bon pour les LLM ? Je propose une métrique simple, qui fait également office de benchmark de contexte long/précision, et qui résume cela.
Intuitivité du format
Un format intuitif est facile à analyser et à générer pour les modèles de langage. Pour mesurer l’intuitivité, nous proposons le benchmark suivant. Toutes les exécutions utilisent DeepSeek V3 (2025-09) en mode chat brut sans utilisation d’outils, donc le modèle doit exécuter mentalement l’extrait de code Python.
- Étant donné un format , une taille d’arbre d’entrée et une taille d’arbre de sortie .
- Générer un arbre de données d’entrée avec nœuds
- Générer un programme Python qui définit une variable
target, qui s’évalue en un arbre de données imbriqué de taille , qui interroge l’arbre de données d’entrée - Demander au modèle de générer
targetsérialisé dans notre format
Exemple de Prompt pour JSON
Format : json_min Nœuds d’entrée observés : 8 Nœuds de sortie cible : 9
Instructions :
- Analysez le jeu de données dans une variable Python nommée
data. - Exécutez l’extrait de code Python ci-dessous pour peupler une variable nommée
target. - Séralisez
targeten utilisant le format d’origine (json_min) et placez le résultat dans un bloc de code délimité étiquetéjson. - Le bloc de code ne doit contenir que les données sérialisées.
- Soyez très attentif à ce que le format et la structure correspondent exactement.
Exemples : Exemple 1 : Jeu de données :
{"results":{"lo_unaddicted":[{"fleeting_geneserine_desmodynia":[-163.354]},{"subcrepitation_maddeningly":{"homoanisic":-3}},"helminth_vengeable"],"touchiness":[{"cataphyllum_educand":"remilitarize","unhumiliated_poorwill_oryctognostically":"resound","herrnhuter":false},["uptrace",["subastringent"],"scruff","theurgically_tritonymph",[-123]]],"ichthyornithes_revisionary":{"alcogel_freckle":{"inquisition":"lehi"},"oniomaniac_flamineous_ledgerdom":{"tylotoxeate":-141,"hemeralopia":272.837},"unremember":[false,[-30],true]},"amphiumidae":{"unenterprised_meltage":[149],"psilanthropist_garrulinae":{"averrable_deporter":399.228,"riotproof_terebratuloid_monophyodontism":-22},"coed":{"indigoid_pulicid":"airbrush_oenothera","paillasse":"rutelinae"},"inhume_photoprinting_pasturability":["chiselly_backfilling"],"route_anisopogonous":[{"kotal_schematization_zestfulness":-91}]},"unexcised_seamless_intwist":{"cordaitean":-108,"unrising":"monarchist"}}}
Extrait Python :
target = [
data["amphiumidae"]["route_anisopogonous"][0],
data["amphiumidae"]["inhume_photoprinting_pasturability"],
data["touchiness"][1],
]
Réponse :
{"results":[{"kotal_schematization_zestfulness":-91},["chiselly_backfilling"],["uptrace",["subastringent"],"scruff","theurgically_tritonymph",[-123]]]}
Exemple 2 : Jeu de données :
{"results":[[["selachostomous",88.259,"altair_assiniboin",{"samphire_symbolology":{"scarfed_wambutti":-28}},"bocca_ponerid"],[["gibberosity","footway_antecardium",[true],["myxosporous"],"repopulate"]],{"prairied":-13,"amara_huccatoon_massivity":34,"alehouse_uncumber":154}],{"tartary_loculose":[[{"counterwind":"endophasic"}],[{"subhyaline_asiatical_tobikhar":"angolar_cheeriness","scutelliform_riverweed_putback":-7,"thirdsman_phlogistical_tropacocaine":"bawdry"}]],"hydrophore":[{"insubvertible":119,"overwomanize":{"cobble_orography_caprice":-127},"queriman_episcopally_railway":{"unadoration":["weedage"]},"stactometer_toggle_cleavability":[453.262]},{"forejudge_tacnode":{"undersupport":105},"floorward":-170,"dormer_abysmal_occasional":-484.491,"wheatgrower":346.849,"phobism_intendingly":91.698}]},{"conirostres":[{"monorhymed_kioway":"taxlessly","ungloriousness_urosternite":true},["pendanting_allegation",-30],["hemiobol","monont_paradoxial"]],"sistrum":[{"untaintable_polladz":true},[-162,true],{"preclassic_standoffishness_pagina":true}]},[{"earlock_unmantled":{"philoradical_micranthropos":-10,"derout":["unfrock",90.415]},"hepatologist_unrushed":-270.882},[[["argyrol_art"]],["daftness"],[-12,149.452]],[[{"loatuko":"floriken_tecali"},[-153.065],-51,153.874,"pile"]],{"hexacanth":[[-3,-19]]}]]}
Extrait Python :
target = [
data[1]["tartary_loculose"][0][0],
data[1]["hydrophore"][1]["wheatgrower"],
data[1]["tartary_loculose"][1],
data[1]["hydrophore"][0]["queriman_episcopally_railway"]["unadoration"],
data[0][1][0][1],
data[0][2],
data[2]["conirostres"][0]["monorhymed_kioway"],
data[3][2],
]
Réponse :
{"results":[{"counterwind":"endophasic"},346.849,[{"subhyaline_asiatical_tobikhar":"angolar_cheeriness","scutelliform_riverweed_putback":-7,"thirdsman_phlogistical_tropacocaine":"bawdry"}],["weedage"],"footway_antecardium",{"prairied":-13,"amara_huccatoon_massivity":34,"alehouse_uncumber":154},"taxlessly",[[{"loatuko":"floriken_tecali"},[-153.065],-51,153.874,"pile"]]]}
Jeu de données :
{"results":["relict",{"intolerant_ignify":"cragginess_reapprobation","detriment_wholesalely_spillway":-49},true,"stewardess",-94]}
Extrait Python :
target = [
data[1]["intolerant_ignify"],
data[4],
data[1]["detriment_wholesalely_spillway"],
data[2],
data[3],
data[1],
]
Nous allons omettre XML à cause de son extrême verbosité. Pour chaque taille d’entrée et de sortie, nous générons 5 arbres de données et demandons au LLM. En traçant la proportion de réponses correctes, nous obtenons
Les graphiques peuvent être interprétés comme suit : si nous voyons du vert le long de l’axe Y, cela signifie que le score s’adapte bien aux grandes entrées, et le format est lisible. Si nous voyons du vert loin le long de l’axe X, cela signifie que le score s’adapte aux grands arbres de sortie, et le format est facile à générer. YAML est étonnamment médiocre, contrairement à mon intuition selon laquelle c’est un format plus ergonomique. Le modèle semble préférer TOML et JSON de manière similaire.
Cependant, utiliser une correspondance exacte comme métrique pourrait être trop strict. Nous pouvons plutôt attribuer plus de crédit aux tentatives qui partagent plus de structure avec la référence. Nous faisons cela en calculant l’indice de Jaccard, ou l’intersection sur l’union entre la réponse soumise et la référence. En traçant cela, en utilisant les mêmes données que le graphique précédent, nous obtenons
Nous voyons une différence plus marquée entre les performances de JSON et TOML. Le modèle est capable d’avoir un chevauchement beaucoup plus élevé avec la réponse correcte avec TOML par rapport à JSON. YAML continue de performer médiocrement.
Conclusion
Pour moi, la principale conclusion des données est n’utilisez pas YAML. J’ai vu beaucoup de gens en ligne dire que c’est mieux que JSON pour les LLMs, mais ce n’est définitivement pas vrai. Il utilise ~19 % de tokens en plus en moyenne, et est moins lisible et facile à écrire. Les performances de lecture/écriture de TOML semblent mieux évoluer comparé à JSON, mais il utilise ~44 % de tokens en plus pour encoder les mêmes données. Pour la plupart des usages, JSON semble être le meilleur choix.
Reproduisez les résultats avec le code : https://github.com/nathom/token-efficiency.