La plupart des bases de code d'agents contiennent un fichier que personne n'aime maintenir. Il contient quarante définitions d'outils, chacune étant un schéma JSON écrit à la main décrivant un point d'API qui a déjà un schéma ailleurs. L'équipe API livre un nouveau champ obligatoire, la spécification est mise à jour, la documentation est mise à jour, et l'agent continue d'envoyer l'ancienne charge utile jusqu'à ce que quelqu'un remarque les `400`.
Vous avez déjà une description lisible par machine de chaque point d'API. C'est le document OpenAPI. Le travail consiste à le transformer en définitions d'outils que le modèle peut appeler, et à maintenir les deux en synchronisation automatiquement au lieu de le faire de mémoire.
Ce guide explique comment les opérations OpenAPI sont mappées aux schémas d'outils, ce que le générateur doit corriger en cours de route, comment réduire une spécification de 200 points d'API à quelque chose qu'un modèle peut comprendre, et comment tester le comportement des outils générés. Si vous êtes plus en amont dans la pile, notre article sur si vous avez encore besoin d'un outil API quand les agents écrivent le code donne le contexte plus large.
Apidog est important ici car la spécification doit être correcte avant que quoi que ce soit qui en soit généré puisse l'être. Une définition d'outil hérite de chaque lacune du document dont elle provient.
Le coût des définitions d'outils écrites à la main
Écrire des outils à la main semble bien pour cinq points d'API. Cela cesse d'être bien autour de vingt, pour trois raisons.
Les définitions dérivent. La spécification est générée à partir du code ou maintenue par l'équipe API. Le fichier d'outils est maintenu par quiconque a construit l'agent. Rien ne les connecte, donc ils divergent silencieusement, et le premier symptôme est un agent qui a "soudainement" cessé de fonctionner.
Les descriptions deviennent minces. Lorsqu'une personne écrit quarante schémas à la main, les vingt derniers obtiennent des descriptions d'une ligne. Les modèles choisissent les outils en lisant ces descriptions, donc un texte mince dégrade directement la sélection des outils. Notre article sur la conception de schémas d'outils API pour les agents approfondit la raison pour laquelle la formulation a tant de poids.
Les erreurs sont invisibles jusqu'à l'exécution. Un schéma écrit à la main qui dit qu'un champ est une chaîne alors que l'API attend un entier produit un `422` la première fois que l'agent l'essaie, en production, sur une tâche réelle.
La génération à partir de la spécification corrige les trois à la fois. Il y a une source unique de vérité, les descriptions proviennent du même texte que votre documentation utilise, et les types proviennent du même schéma que le serveur valide.
Comment une opération OpenAPI devient un outil
Le mappage est plus direct qu'il n'y paraît. Prenons une seule opération :
paths:
/orders/{orderId}/refund:
post:
operationId: refundOrder
summary: Rembourser une commande
description: >
Émet un remboursement complet ou partiel pour une commande complétée.
Les remboursements sont irréversibles. Les remboursements partiels nécessitent un montant
pas supérieur au solde remboursable restant.
parameters:
- name: orderId
in: path
required: true
schema: { type: string }
description: La commande à rembourser.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [reason]
properties:
amount:
type: integer
description: Montant en centimes. Omettre pour un remboursement complet.
reason:
type: string
enum: [duplicate, fraudulent, requested_by_customer]
La définition d'outil qui en découle :
{
"name": "refundOrder",
"description": "Émet un remboursement complet ou partiel pour une commande complétée. Les remboursements sont irréversibles. Les remboursements partiels nécessitent un montant pas supérieur au solde remboursable restant.",
"input_schema": {
"type": "object",
"required": ["orderId", "reason"],
"properties": {
"orderId": { "type": "string", "description": "La commande à rembourser." },
"amount": { "type": "integer", "description": "Montant en centimes. Omettre pour un remboursement complet." },
"reason": { "type": "string", "enum": ["duplicate", "fraudulent", "requested_by_customer"] }
}
}
}
Quatre règles accomplissent la majeure partie du travail :
- `operationId` devient le nom de l'outil. Si une opération n'a pas d'`operationId`, générez-en un stable à partir de la méthode et du chemin, puis ajoutez-le à la spécification.
- Les paramètres de chemin, de requête et de corps sont aplatis en un seul objet de propriétés. Le modèle ne se soucie pas de l'endroit où une valeur circule sur le fil. Votre exécuteur, lui, s'en soucie, alors conservez une table annexe qui enregistre quel paramètre va où.
- `summary` plus `description` devient la description de l'outil. Les deux, joints. Le résumé seul est généralement trop concis pour guider la sélection.
- Les tableaux `required` fusionnent. Un paramètre de chemin requis et un champ de corps requis atterrissent tous deux dans la même liste `required`.
L'exécuteur est l'autre moitié, et il est petit :
def execute(tool_name, args, spec_index, http):
op = spec_index[tool_name] # method, path template, param locations
path = op.path
query, body = {}, {}
for name, value in args.items():
location = op.locations[name] # "path" | "query" | "header" | "body"
if location == "path":
path = path.replace("{" + name + "}", str(value))
elif location == "query":
query[name] = value
elif location == "body":
body[name] = value
return http.request(op.method, path, params=query, json=body or None)
C'est tout le pont. Tout le reste est du nettoyage en cours de route.
Ce que le générateur doit corriger
Un simple transfert de la spécification vers les schémas d'outils produit des outils que les modèles gèrent mal. Cinq ajustements sont importants.
Résoudre les pointeurs `$ref`. La plupart des API d'appel d'outils acceptent un sous-ensemble de JSON Schema et ne suivront pas les références vers une section `components`. Intégrez-les directement. Attention aux schémas récursifs, que l'intégration développera à l'infini ; coupez la récursion à une profondeur fixe et décrivez la structure plus profonde en prose.
Abandonner les mots-clés non pris en charge. `oneOf`, `allOf`, `discriminator` et `nullable` sont courants dans les spécifications et mal pris en charge par les schémas d'outils. Réduisez `allOf` en fusionnant les propriétés. Pour `oneOf`, choisissez la variante dominante ou divisez l'opération en deux outils, un par forme. Cette deuxième option produit généralement une meilleure sélection d'outils de toute façon.
Aplatir les imbrications profondes. Un corps imbriqué sur trois niveaux est difficile à remplir correctement pour un modèle. Si votre charge utile de création de commande imbrique `customer.address.postal_code`, envisagez une surface d'outil plus plate et réassemblez la forme imbriquée dans l'exécuteur.
Élaguer les schémas de réponse. Les définitions d'outils décrivent les entrées. Le schéma de réponse complet n'a pas sa place dans la définition, et l'inclure gaspille du contexte. L'apparence de la réponse est importante lorsque le résultat est renvoyé, et c'est un problème distinct abordé dans notre article sur le maintien des réponses API dans la fenêtre de contexte de l'agent.
Transporter les drapeaux de sécurité. Les opérations d'écriture doivent être marquées afin que votre exécuteur puisse les acheminer via une porte d'approbation. Si votre spécification utilise une extension telle que `x-agent-requires-approval`, lisez-la et respectez-la. Associez cela aux modèles de notre guide sur les garde-fous des agents IA.
Ne donnez pas tous les 200 points d'API au modèle
Le plus grand problème pratique n'est pas la conversion. C'est le volume. Une API mature a des centaines d'opérations, et les coller toutes dans la liste d'outils produit deux échecs simultanés : le contexte se remplit de schémas avant même que la tâche ne commence, et la précision de la sélection diminue parce que le modèle choisit parmi des options quasi identiques.
Trois façons de réduire la liste, à peu près dans l'ordre de leur efficacité.
Filtrer par balise. Les opérations OpenAPI portent des balises, et les balises correspondent généralement à des domaines de produits. Un agent qui gère les remboursements a besoin des balises `orders` et `payments`, pas `admin` ou `analytics`. C'est un filtre d'une ligne qui supprime généralement la majeure partie de la surface.
Organiser une liste blanche. Notez les opérations que cet agent est autorisé à appeler, par `operationId`, et ne générez que celles-ci. Cela sert également de contrôle de sécurité, car un agent qui n'a pas d'outil pour un point d'API ne peut pas l'appeler par accident. Notre article sur comment empêcher les agents IA de détruire vos API préconise exactement ce type de surface étroite.
Récupérer les outils à la demande. Pour les très grandes API, indexez les opérations et sélectionnez-en une poignée par tour en fonction de la tâche. Cela ajoute une étape de récupération et ses propres modes de défaillance, alors n'y ayez recours qu'après que le filtrage et la curation ne suffisent plus.
Il y a aussi la voie du protocole. Le Model Context Protocol standardise la manière dont un serveur expose des outils à un client, et un serveur MCP soutenu par votre document OpenAPI vous offre un point d'intégration unique au lieu d'un par framework. Notre explication sur ce qu'est le MCP couvre le modèle, et la construction d'un serveur MCP avec Apidog couvre la réalisation.

La spécification doit être correcte en premier
La génération déplace le problème de qualité en amont. Une description vague dans votre document OpenAPI devient une description d'outil vague, et le modèle choisit le mauvais point d'API. Un champ facultatif que le serveur exige réellement devient un outil que l'agent appelle incorrectement au premier essai.
Auditez donc la spécification à travers les yeux d'un agent avant de générer quoi que ce soit :
- Chaque opération a un `operationId`, et il se lit comme un verbe plus un nom.
- Chaque opération a une description qui dit ce qu'elle fait, ce qu'elle change et quand ne pas l'utiliser. "Supprime un utilisateur" ne suffit pas. "Supprime définitivement un utilisateur et toutes ses sessions. Ne peut pas être annulé. Utilisez `desactivateUser` pour désactiver temporairement l'accès." l'est.
- Chaque paramètre a une description avec des unités et un format. `amount` est ambigu. "Montant en centimes, minimum 50" ne l'est pas.
- Les énumérations sont déclarées plutôt que décrites en prose, de sorte que le modèle obtient un ensemble fermé au lieu de deviner.
- `required` est précis. Les spécifications ont tendance à tout marquer comme facultatif, ce qui déplace les échecs de validation vers l'exécution.
C'est une hygiène de spécification ordinaire, et elle rapporte doublement, car le même texte alimente votre documentation publiée. Dans Apidog, la spécification, la documentation, le serveur de maquette et les tests proviennent d'un seul projet, donc affiner une description les améliore tous à la fois. Notre guide sur la gestion du versionnement d'API dans Apidog couvre l'autre moitié pour maintenir l'intégrité des outils générés au fil du temps.
Partagez l'ensemble d'outils, ne le copiez pas
Un ensemble d'outils généré est une configuration, et une configuration qui vit dans le répertoire d'un développeur dérive de la même manière que les schémas écrits à la main. La liste de filtres, la liste blanche et la version de spécification épinglée doivent être des artefacts partagés, versionnés à côté de la spécification dont ils proviennent.
Certaines plateformes en font l'unité par défaut. Dans Sharkly, un Agent est une configuration de travail sauvegardée plutôt qu'une invite unique : ses instructions, son Runtime, ses Skills, ses dépôts et ses paramètres d'exécution voyagent avec lui et peuvent être partagés dans un Espace, de sorte qu'une configuration d'outil fonctionnelle devient quelque chose qu'une équipe réutilise au lieu de quelque chose que chaque personne reconstruit. Le runtime sous-jacent est toujours Claude Code, Codex, ou ce que vous utilisez déjà. Ce qui change, c'est que la configuration autour de lui cesse d'être locale.

Test des outils générés
Les outils générés échouent d'une manière que les outils écrits à la main n'ont pas, alors testez la génération ainsi que les appels.
Commencez par une vérification de l'aller-retour du schéma. Pour chaque outil généré, construisez un exemple valide à partir du schéma et envoyez-le. Tout ce qui renvoie `400` ou `422` signifie que le schéma de l'outil et le serveur ne sont pas d'accord, et c'est la spécification qu'il faut corriger.
Ensuite, testez la sélection. Rédigez un petit ensemble d'invites de tâches avec un outil correct connu, exécutez-les et enregistrez l'outil que le modèle a choisi. C'est une suite de régression bon marché qui détecte le jour où quelqu'un renomme une opération ou raccourcit une description. Étant donné que la sortie est non déterministe, affirmez le nom de l'outil plutôt que les arguments exacts, conformément à notre guide sur le test des agents IA non déterministes.
Enfin, exécutez l'agent contre des maquettes avant toute mise en ligne. Un serveur de maquette généré à partir de la même spécification vous donne des réponses réalistes sans effets secondaires, et il vous permet d'injecter les `500` et les délais d'attente que votre logique de réessai est censée gérer.
Où cela vous mène
La spécification est le contrat, et la liste d'outils doit en être une projection, pas une copie parallèle maintenue à la main. Générez les outils, filtrez-les strictement, maintenez la sincérité des descriptions et testez à la fois les formes et la sélection.
Commencez par exporter votre document OpenAPI et comptez les opérations qui n'ont pas de description. Ce nombre représente le travail qui vous sépare d'outils d'agent fiables. Téléchargez Apidog si vous voulez la spécification, les maquettes et les tests au même endroit pendant que vous corrigez tout.
Foire aux questions
Puis-je générer des outils à partir d'un document Swagger 2.0 ? Oui, mais convertissez-le d'abord en OpenAPI 3.x. Le modèle de corps 2.0 diffère suffisamment pour que les générateurs le gèrent de manière incohérente, et 3.x est ce que les outils actuels ciblent. Le dépôt de la spécification OpenAPI documente les différences.
Combien d'outils un modèle peut-il gérer à la fois ? La précision commence à se dégrader bien avant la limite technique, et le plafond pratique est généralement de quelques dizaines. Considérez toute liste au-delà comme un signal pour filtrer par balise ou organiser une liste blanche plutôt que comme une limite à tester.
Les noms des outils doivent-ils correspondre exactement à `operationId` ? Oui, lorsque l'`operationId` est lisible. Cela vous donne une recherche directe de l'appel d'outil vers l'opération de spécification, ce qui facilite grandement le traçage et le débogage. Renommez dans la spécification si le nom est mauvais, pas dans le générateur.
Qu'en est-il des API GraphQL ? La même idée s'applique avec une source différente : introspecter le schéma et générer un outil par requête ou mutation. Le problème de volume est pire car un schéma GraphQL expose une plus grande surface, donc le filtrage est encore plus important.
Dois-je encore écrire des outils à la main ? Quelques-uns. Les outils composites qui enchaînent plusieurs appels en une seule action, et les outils qui encapsulent autre chose que HTTP, sont toujours écrits manuellement. Le fait est que les wrappers de routine à un point d'API cessent d'être un travail manuel.
Comment empêcher l'agent d'appeler des points d'API d'écriture pendant les tests ? Générez un ensemble d'outils en lecture seule pour les exécutions de tests en filtrant sur la méthode HTTP, et pointez l'agent vers une maquette pour tout ce qui écrit. Notre article sur pourquoi les agents devraient utiliser des maquettes, pas la production couvre la configuration.
