L'agent demande un dossier client. Votre API renvoie le client, plus ses 200 dernières commandes, plus chaque article de ces commandes, plus des horodatages dans trois formats et un bloc _links pour chacun. Quarante mille jetons atterrissent dans la fenêtre de contexte. L'agent avait besoin de l'adresse e-mail.
Faites cela quatre fois en une exécution et l'agent aura dépensé la majeure partie de son budget à lire du JSON qu'il n'avait pas demandé. Ensuite, les échecs intéressants commencent : il oublie l'instruction originale, il résume la tâche au lieu de la terminer, et le coût par exécution grimpe tandis que la qualité diminue.
Il s'agit d'un problème de conception au niveau de l'API, pas d'un problème d'invite. Les agents consomment les réponses via une fenêtre fixe, et chaque champ que vous renvoyez est en concurrence avec les instructions, la conversation et le plan. Ce guide couvre d'où vient le surpoids, les modèles de sélection de champs et de pagination qui le corrigent, comment optimiser au sein de la couche d'outils lorsque vous ne contrôlez pas l'API, et comment mesurer la différence. Notre pilier sur pourquoi les agents IA échouent en production traite l'épuisement du contexte comme l'un des modes d'échec principaux, et ceci en est la moitié pratique.
Apidog aide côté mesure : vous pouvez voir la taille réelle de la réponse pour chaque point de terminaison avant qu'un agent ne l'appelle, et simuler la forme optimisée que vous souhaitez avant que l'équipe API ne la livre.
Où vont les jetons
Les réponses conçues pour les navigateurs et les tableaux de bord transportent beaucoup de fret qui coûte de l'argent réel à un agent.
Enveloppes verbeuses. Un encapsuleur data, meta, links, included autour d'un objet à cinq champs peut doubler la charge utile. Les liens hypermédia sont utiles à un client qui les suit. Les agents ne le font presque jamais, et chaque URL représente des jetons.
Clés répétées. JSON répète chaque nom de champ sur chaque élément de tableau. Une liste de 200 éléments avec 15 champs par élément paie pour 3 000 chaînes de clés. C'est pourquoi les points de terminaison de liste dominent l'utilisation du contexte.
Expansion imbriquée par défaut. Les points de terminaison qui intègrent des ressources connexes sont pratiques jusqu'à ce qu'un agent les atteigne. Un client plus ses commandes plus les articles est un arbre, et les arbres poussent vite.
Formats redondants. created_at, created_at_unix et created_at_human sur le même objet représentent un coût triple pour une seule valeur.
Nulls et vides. De nombreux sérialiseurs émettent chaque champ même lorsqu'il n'est pas défini. Vingt nulls par enregistrement sont du pur gaspillage.
Une façon utile de le voir : le coût des jetons suit la taille du texte sérialisé, pas le nombre d'enregistrements. Deux cents enregistrements avec cinq champs chacun peuvent être moins chers qu'un objet profondément imbriqué.
Règle un : renvoyez les champs, pas les ressources
Le changement ayant la plus grande valeur ajoutée est de permettre à l'appelant de demander ce dont il a besoin.
GET /v1/customers/8812?fields=id,email,plan,status
{ "id": "8812", "email": "dana@example.com", "plan": "pro", "status": "active" }
Ceci représente une réduction de 90 % par rapport à un enregistrement complet sur la plupart des API, et cela prend un après-midi à ajouter. Le guide de conception d'API de Google documente le modèle de masque de champ si vous voulez une version avec un précédent derrière elle, et GraphQL résout le même problème en rendant la sélection obligatoire.
Deux notes d'implémentation. Validez la liste des champs par rapport au schéma et rejetez les noms inconnus, de sorte qu'un champ halluciné produise une erreur claire au lieu d'un objet tronqué silencieusement. Et conservez un petit ensemble par défaut pour les appelants qui n'envoient rien, plutôt que de tout retourner par défaut.
Ensuite, exposez le paramètre au modèle dans la description de l'outil, avec les champs spécifiés :
{
"name": "getCustomer",
"description": "Récupère un client par ID. Toujours passer `fields` avec seulement ce dont vous avez besoin. Disponible : id, email, name, plan, status, created_at, billing_address, order_count.",
"input_schema": {
"type": "object",
"required": ["customerId", "fields"],
"properties": {
"customerId": { "type": "string" },
"fields": {
"type": "array",
"items": { "type": "string" },
"description": "Noms des champs à retourner. Gardez cette liste minimale."
}
}
}
}
Les descriptions sont le seul endroit où le modèle apprend ces règles, et le guide d'appel de fonction OpenAI et la documentation d'utilisation des outils Anthropic leur accordent la même importance. Rendre fields obligatoire est l'astuce. Un paramètre facultatif est ignoré ; un paramètre obligatoire force le modèle à réfléchir à ce dont il a réellement besoin.
Règle deux : limitez toujours la liste
Les points de terminaison de liste illimités sont la deuxième grande source d'explosions. Un agent demande "commandes récentes" et obtient tout depuis 2019.
Définissez un maximum strict côté serveur, pas seulement une valeur par défaut. Si l'agent envoie limit=5000, retournez 100 et indiquez-le. Nos guides sur la pagination d'API REST et la conception de la pagination pour des millions d'enregistrements couvrent les mécanismes ; les règles spécifiques aux agents sont plus étroites :
- Limitez la page à quelque chose qu'un modèle peut lire, de l'ordre de 20 à 50 éléments pour des enregistrements typiques.
- Renvoiez un nombre total afin que l'agent puisse savoir s'il a tout vu sans paginer pour le découvrir.
- Utilisez la pagination par curseur. Les décalages dérivent lorsque les données changent en cours d'exécution, et un agent qui pagine lentement rencontrera ce problème.
- Incluez une déclaration simple dans la réponse, telle que
"truncated": true, afin que le modèle sache qu'il y en a plus. Les modèles jugent de manière peu fiable l'exhaustivité à partir de la seule longueur d'un tableau.
Donnez également à l'agent un moyen d'éviter de paginer du tout. Un point de terminaison count, une recherche filtrée avec une fenêtre étroite, ou un objet récapitulatif répondront souvent à la question sans retourner aucun enregistrement. La réponse la moins chère est celle qui ne contient pas les données.
Règle trois : optimisez dans la couche d'outils lorsque l'API n'est pas la vôtre
Les API tierces n'ajouteront pas de sélection de champs parce que vous l'avez demandé. Mettez plutôt l'optimisation dans votre exécuteur, entre la réponse HTTP et le modèle.
KEEP = {
"getCustomer": ["id", "email", "plan", "status"],
"listOrders": ["id", "total", "status", "created_at"],
}
def project(tool_name, payload):
keep = KEEP.get(tool_name)
if keep is None:
return payload
if isinstance(payload, list):
return [{k: item.get(k) for k in keep if k in item} for item in payload]
return {k: payload.get(k) for k in keep if k in payload}
Trois raffinements permettent de maintenir cela en pratique.
Stockez la réponse complète et donnez la projection au modèle. Conservez la charge utile non optimisée dans votre journal d'exécution afin que le débogage soit toujours possible. Notre article sur le traçage des appels d'outils d'agent couvre ce qu'il faut enregistrer.
Dites au modèle ce que vous avez supprimé. Une ligne telle que "_omitted": ["billing_address", "notes", "metadata"] lui permet de demander l'enregistrement complet lorsqu'il en a réellement besoin, au lieu de conclure que les données n'existent pas.
Convertissez les listes en un format compact. Pour les résultats tabulaires, CSV ou un tableau Markdown coûte beaucoup moins de jetons que JSON car les noms de champs n'apparaissent qu'une seule fois au lieu de par ligne. Les modèles lisent les deux sans problème.
id,total,status,created_at
ord_91,4900,paid,2026-08-21
ord_92,1200,refunded,2026-08-22
Règle quatre : résumez sur le serveur pour les cas lourds
Certaines questions n'ont pas du tout besoin d'enregistrements. "Ce client a-t-il eu des paiements échoués ce mois-ci ?" est une question booléenne. Renvoyer 40 objets de paiement pour que le modèle puisse le déterminer est la manière coûteuse de répondre.
Lorsqu'une question est récurrente, ajoutez le point de terminaison qui y répond directement. Un résumé de l'état du compte, un récapitulatif de statut, une petite agrégation. Cela ressemble à un travail de conception d'API ordinaire parce que c'en est un, et c'est la version la plus précieuse de tout ce qui précède : au lieu de réduire une réponse volumineuse, vous évitez d'en produire une.
Deux garde-fous. Maintenez les résumés stables dans leur forme afin que les agents puissent s'y fier, et versionnez-les, car l'invite d'un agent est écrite en fonction d'une forme et un changement silencieux la rompt. Notre article sur ce qui se passe lorsque l'API change sous un agent couvre ce risque, et la meilleure stratégie de versioning d'API couvre les mécanismes.
Mesurez avant et après
Rien de tout cela ne vaut la peine d'être fait à l'aveuglette. Trois chiffres vous disent où se trouve le problème.
Octets par réponse, par point de terminaison. Envoyez une requête réaliste à chaque outil que votre agent peut appeler et enregistrez la taille de la charge utile. Tout ce qui dépasse quelques kilo-octets est un candidat. Dans Apidog, vous pouvez exécuter chaque point de terminaison une fois et lire la taille directement depuis la réponse, puis enregistrer la requête afin que la vérification se répète lorsque l'API change.

Jetons par appel d'outil. Les octets sont une approximation ; les jetons sont la facture. Passez les charges utiles à travers le tokenizeur de votre fournisseur, tel que tiktoken pour les modèles OpenAI, et classez les points de terminaison. Le classement est généralement déséquilibré, avec un ou deux points de terminaison responsables de la majeure partie du coût.
Contexte utilisé par exécution. Enregistrez le total courant sur l'ensemble d'une tâche d'agent. Si une tâche se termine près de la limite, l'optimisation vous permet d'obtenir des exécutions complètes, pas seulement moins chères.
Ensuite, concevez la forme que vous souhaitez et simulez-la avant que l'équipe API ne la construise. Un serveur de simulation renvoyant la réponse optimisée vous permet de mesurer l'amélioration et de vérifier que l'agent réussit toujours avec moins de données, ce qui est la question qui compte réellement. Notre article sur l'exécution d'agents contre des simulations plutôt que la production couvre le flux de travail.
À quoi ressemble une bonne pratique
Une réponse conviviale pour les agents est petite, plate et honnête quant à ce qu'elle a omis :
{
"customer": { "id": "8812", "email": "dana@example.com", "plan": "pro" },
"recent_orders": [
{ "id": "ord_91", "total_cents": 4900, "status": "paid" },
{ "id": "ord_92", "total_cents": 1200, "status": "refunded" }
],
"recent_orders_total": 47,
"truncated": true,
"_omitted": ["billing_address", "metadata", "order_line_items"]
}
Moins de 200 jetons. Elle répond à la question courante, elle indique qu'il y a 47 commandes plutôt que d'impliquer qu'il y en a deux, et elle dit au modèle ce qu'il peut demander ensuite.
Commencez par votre point de terminaison le plus bruyant. Mesurez-le, ajoutez la sélection de champs, plafonnez la liste, et exécutez à nouveau l'agent. L'écart entre les deux chiffres est généralement suffisant pour justifier le reste du travail. Téléchargez Apidog si vous souhaitez la mesure et la simulation dans le même projet.
Trois endroits où cela se manifeste
Triage du support. Un agent lit un ticket, récupère le client et décide s'il doit escalader. La version naïve récupère l'objet client complet et les 50 derniers tickets, brûlant 30 000 jetons avant de lire la plainte réelle. La version corrigée appelle un point de terminaison de résumé renvoyant le plan, le statut, le nombre de tickets ouverts et la date du dernier contact. Environ 80 jetons, et la décision d'escalade s'améliore car les faits pertinents ne sont pas enfouis.
Agents d'opérations internes. Un agent de déploiement vérifie l'état de santé des services sur 40 services. Les objets d'état complets saturent la fenêtre au douzième service. Un récapitulatif qui renvoie une ligne par service, nom plus état plus taux d'erreur, permet d'intégrer les 40 en quelques centaines de jetons et permet à l'agent de raisonner sur l'ensemble du parc au lieu d'oublier la première moitié.
Saisie et rapprochement de données. Un agent rapproche les factures des paiements. Retourner des documents de facture complets le fait échouer après quelques dizaines d'enregistrements. Retourner id, amount_cents, date et reference en CSV lui permet de gérer plusieurs centaines d'enregistrements en une seule passe, car la comparaison n'a jamais utilisé que quatre champs.
Le motif commun aux trois : l'agent avait besoin d'une surface de décision, et l'API lui a donné un document.
Vous avez besoin d'un historique des exécutions pour voir le modèle
Une seule exécution vous indique qu'une réponse était volumineuse. Le modèle, quel point de terminaison dépasse le budget et à quelle fréquence, n'apparaît qu'à travers les exécutions.
Cela signifie que les chiffres doivent survivre à la session. Pour un service que vous avez déployé, il s'agit de votre propre télémétrie. Pour les agents de codage exécutant un travail assigné, c'est la plateforme qui les exécute : Sharkly conserve la trace d'exécution et le résultat de chaque exécution sur la tâche dont elle provient, de sorte que la comparaison d'une exécution à l'autre est une question de lecture de l'historique des tâches plutôt que de reconstruction de sessions terminales. Dans tous les cas, l'application du budget sans historique vous indique que quelque chose est trop grand, mais pas quoi réparer en premier.

Définissez un budget par outil, pas seulement par exécution
La plupart des équipes plafonnent le contexte total et s'arrêtent là. Un budget par outil est plus utile, car il transforme un problème vague en un problème spécifique.
Donnez à chaque outil un plafond, par exemple 1 500 jetons. Lorsqu'une réponse le dépasse, l'exécuteur la réduit à la projection, ajoute le marqueur de champ omis et enregistre le dépassement. Vous disposez alors d'une liste de points de terminaison qui dépassent régulièrement le budget, classés par la fréquence à laquelle l'agent les appelle, ce qui constitue votre file d'attente de travail.
Le budget vous protège également du point de terminaison qui est petit en test et énorme pour un client réel. Les distributions ont des queues, et le compte avec 4 000 commandes est celui qui brisera une exécution à 2 heures du matin. Un plafond strict transforme cela en une réponse tronquée au lieu d'une tâche échouée.
Questions fréquemment posées
La troncature des réponses est-elle risquée si l'agent a besoin des données manquantes ? Seulement si vous masquez la troncature. Incluez un marqueur explicite et une liste des champs omis afin que le modèle puisse les demander. La troncature silencieuse est ce qui provoque des réponses incorrectes, pas la réduction elle-même.
Dois-je plutôt utiliser GraphQL pour les agents ? GraphQL rend la sélection de champs obligatoire, ce qui résout le problème proprement, mais cela ajoute de la complexité à la construction des requêtes et les modèles écrivent des requêtes invalides plus souvent qu'ils n'utilisent mal une liste de champs. L'ajout de fields aux points de terminaison REST est généralement le changement le moins important.
Quelle doit être la taille d'une réponse d'outil ? Visez moins de 1 000 jetons pour une lecture d'un seul enregistrement et moins de 2 000 pour une liste. Au-delà, demandez-vous si l'agent a besoin d'enregistrements ou d'une réponse.
La mise en cache des invites résout-elle cela ? Elle réduit le coût du contexte répété, pas l'espace qu'il occupe. Une réponse mise en cache de 40 000 jetons remplit toujours la fenêtre, donc la mise en cache aide la facture tout en laissant le problème de fiabilité intact.
Qu'en est-il des réponses binaires et de fichiers ? Ne les mettez jamais dans le contexte. Stockez le fichier, donnez à l'agent une référence et une courte description, et donnez-lui un outil séparé pour extraire uniquement ce dont il a besoin.
Où doit se situer l'optimisation, dans l'API ou dans le wrapper de l'outil ? Dans l'API lorsque vous en êtes propriétaire, car chaque appelant en bénéficie et les octets ne traversent jamais le réseau. Dans le wrapper lorsque vous ne l'êtes pas. Faire les deux est acceptable.
