Alors que nous commençons à entraîner des LLM en tant qu’Agents, nous devons réfléchir à la meilleure façon de transmettre les informations depuis et vers l’environnement réel. 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) utilise des formats de données structurées, comme le JSON. Ces formats peuvent encoder des structures de données arbitrairement imbriquées et de types hétérogènes.
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 des métriques qui nous aideront à faire le bon choix.
Efficacité des tokens
Une limite fondamentale des LLM actuels est le contexte fini. On ne peut pas simplement injecter en continu l’intégralité des données du monde dans le modèle au fil du temps. Cela signifie que nous voulons communiquer les informations les plus utiles possibles avec le moins de tokens.
Testons donc quels formats de données structurées sont tokenisés de la manière la plus efficace, sans télémétrie réelle. Pour ce faire, nous générons aléatoirement des données structurées (dicts et lists imbriqués) avec des clés construites à partir d’un 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 consiste à choisir une taille d’arbre , puis à 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 tokens structurels utilisés dépend du type de données traité. Si l’entrée de votre agent ne traite pas de données arbitrairement imbriquées, une spécification plus simple pourrait suffire. Pour en tenir compte, nous définissons un ensemble de formes :
nested(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 } ]sparse(épars) : 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" } ]tabular(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 tableaurecords, 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: Arbre XML verbeux utilisant des balises sémantiques et des noms de types 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 thermique du nombre moyen de tokens par nœud. Les comptes de tokens proviennent de la moyenne entre les tokenizers de Qwen 3, Llama 3.2 et gpt-oss.
D’un coup d’œil, on voit que csv est clairement le meilleur pour les données tabulaires, et json obtient les meilleures performances en moyenne.
Pour avoir une image plus claire, nous pouvons prendre la moyenne sur chaque forme pour voir le nombre moyen de tokens par format.
Cela montre qu’en termes d’efficacité des tokens uniquement, 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 rend un format bon pour les LLM ? Je propose une métrique simple — qui se trouve également être un benchmark de contexte long/précision — qui encapsule cela.
Intuitivité du format
Un format intuitif est facile pour les modèles de langage à analyser et générer. 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 évalue à 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érielez
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 qu’il s’agirait d’un format plus ergonomique. Le modèle semble préférer TOML et JSON de manière similaire.
Cependant, utiliser la 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
Le principal enseignement des données est de ne pas utiliser YAML. J’ai vu beaucoup de gens en ligne dire qu’il est meilleur que le JSON pour les LLMs, mais ce n’est presque certainement pas vrai. Il utilise en moyenne ~19 % de tokens en plus, et est beaucoup moins lisible. Les performances de lecture/écriture du TOML semblent mieux évoluer par rapport au JSON, mais il utilise ~44 % de tokens en plus pour encoder les mêmes données. Pour la plupart des usages, le JSON est le meilleur choix.
Reproduisez les résultats avec le code sur GitHub.