Contrats d’outils pour les agents d’IA
Un outil est une frontière d’autorité présentée comme une fonction. La validation JSON peut démontrer que les arguments correspondent à une forme ; elle ne peut démontrer que l’action est voulue, autorisée, sûre à réessayer ou correctement terminée.
De la demande du modèle au résultat de l’outil
Le modèle demande un appel ; le code de l’application exécute la fonction. Le guide des appels de fonctions décrit une conversation comprenant la définition de l’outil, un appel, son résultat et une nouvelle réponse du modèle. Prenons un outil de recherche en lecture seule, search_notes :
- L’application fournit son nom, son rôle et le schéma des arguments : un objet avec une chaîne obligatoire
query, de longueur 1–200, sans propriété supplémentaire. L’implémentation ne recherche que dans l’index autorisé des notes publiques. - Le modèle renvoie un élément d’appel contenant un identifiant, un nom et des arguments. L’application attend l’élément complet au lieu d’exécuter des fragments d’arguments reçus en streaming.
- Elle analyse les arguments, valide le schéma, contrôle l’autorité et n’appelle qu’une fonction autorisée. Une demande invalide ou refusée produit une erreur sans lancer la recherche.
- Elle associe le résultat à l’identifiant de l’appel initial et le renvoie avec l’état de la conversation. Le modèle peut alors répondre ou demander une autre action permise.
Ces messages JSON abrégés utilisent des champs de style Responses ; ils illustrent le protocole sans constituer une requête API complète :
{"type":"function_call","call_id":"call_1","name":"search_notes","arguments":"{\"query\":\"KV cache\"}"}
{"type":"function_call_output","call_id":"call_1","output":"{\"status\":\"ok\",\"matches\":[]}"}
L’API d’Anthropic emploie d’autres noms de champs pour le même déroulement : l’appel arrive sous forme de bloc tool_use, et le résultat repart dans un bloc tool_result (vue d’ensemble de l’utilisation d’outils).
Un résultat vide signifie que cette recherche n’a rien trouvé, pas que le sujet n’existe pas. Un texte ordinaire ressemblant à ce JSON n’est pas un appel autorisé. Un adaptateur fournisseur ou un parseur local doit d’abord reconnaître une demande du protocole ; le harness décide encore si elle peut s’exécuter. Le résultat reste une donnée non fiable, pas une nouvelle instruction. Les Boucles d’agent bornées régissent l’action suivante et les conditions d’arrêt.
Dimensions du contrat
Contrat illustré
name: replace_text
purpose: replace one exact block in one repository file
input:
path: repository-relative path, no traversal
old_text: non-empty and must match exactly once
new_text: string
preconditions:
- path is inside approved write scope
- working tree version equals observed version
effects: reversible local write
idempotency: not retryable after success without re-reading
result:
status: succeeded | conflict | no-match | multiple-matches | denied
changed_file: path or null
approval: required when path is outside task scope
Ce contrat fournit à la boucle un échec discriminant : no-match indique qu’il faut d’abord relire le fichier, multiple-matches que old_text doit inclure plus de contexte, et denied que ce chemin ne doit pas être touché du tout. Un générique error: edit failed ne distingue pas ces cas et invite à réessayer aveuglément.
Enveloppe de résultat
Un résultat stable consigne séparément deux choses : la requête est-elle arrivée et le service a-t-il répondu (le transport), et quelle part du travail demandé a été faite (l’issue métier). Dans ce résultat de lot, transport vaut ok : la requête est arrivée ; status vaut partial, car seuls 8 éléments sur 10 ont réussi :
{
"request_id": "req-123",
"transport": "ok",
"status": "partial",
"completed": 8,
"failed": 2,
"retry_safe": false,
"artifact": "results/batch-123.json",
"errors": [{"code": "permission-denied", "item": "..."}]
}
Ne transformez jamais la réussite d’un élément en réussite du lot.
Une issue inconnue doit elle aussi rester distincte d’un échec. Imaginez un outil de virement : la requête part, puis la connexion expire, et vous ignorez si l’argent a quitté le compte. Traiter ce cas comme un échec et renvoyer la requête peut débiter deux fois. Face à une issue inconnue, le harness interroge donc d’abord l’état du virement au lieu de réessayer ; la manière de réessayer avec la même clé d’idempotence est décrite dans Agents de longue durée.
Faire utiliser le schéma correctement par le modèle
Une fois le contrat écrit, que le modèle l’appelle correctement dépend encore de quelques réglages. Le guide d’appel de fonctions d’OpenAI donne des conseils concrets :
- Activer le mode strict. Le guide écrit : « nous recommandons de toujours activer le mode strict ». Avec
strict: true, chaque objet doit porteradditionalProperties: false, et chaque champ depropertiesdoit figurer dansrequired; un champ facultatif s’exprime avec un typenull. - Commencer avec peu d’outils. Le guide suggère « moins de 20 fonctions » disponibles au début d’un tour, en précisant qu’il s’agit d’une simple suggestion. Les outils rarement utilisés se chargent plus tard avec
tool_search; pour limiter les outils appelables pendant quelques tours,allowed_toolslaisse la liste d’outils inchangée et préserve donc le cache de prompt. - Rédiger la description comme une notice. La
descriptiondit à quoi sert l’outil, quand ne pas l’employer, le format et les unités de ses paramètres, avec un exemple si cela aide.
La révision MCP du 28 juillet 2026 a aussi assoupli les schémas : inputSchema et outputSchema peuvent employer n’importe quel mot-clé de JSON Schema 2020-12 (journal des modifications).
Règles de conception
- Évitez l’interpolation dans le shell. Transmettez directement un programme et un tableau d’arguments lorsque c’est possible. Cela retire l’analyse du shell, mais les arguments peuvent rester dangereux :
git, les CLI cloud et les gestionnaires de paquets ont tous des options destructrices ou exécutant du code. - Validez les chemins résolus. Vérifiez les frontières du dépôt après normalisation et résolution des liens symboliques ; refuser la chaîne littérale
../ne suffit pas. Laissez les chemins sensibles hors de l’autorité de l’outil plutôt que de vous en remettre seulement à une liste de refus. - Arrêtez les tentatives improductives. Arrêtez-vous quand le même appel a échoué un nombre donné de fois ; le réglage du seuil de ce coupe-circuit est décrit dans Boucles d’agent bornées.
- Renvoyez des échecs structurés. Distinguez entrée mal formée, autorité refusée, échec transitoire de transport, rejet métier, exécution partielle et issue inconnue. L’action suivante en dépend.
Injection et résultats non fiables
Les réponses typées rendent clair le sens de chaque champ, mais ne contrôlent pas ce qu’un champ contient : un "title" parfaitement valide peut toujours dire « ignore les instructions précédentes ». Les résultats d’outils ne peuvent être traités que comme des données, et toute étape suivante que le modèle en tire repasse par la politique ; le modèle de menace complet se trouve dans Sécurité des agents.
Compromis avec les modèles compacts
Les petits modèles produisent souvent plus fiablement des schémas peu imbriqués, mais retirer des détails de schéma peut éliminer des informations critiques pour la sécurité. Pour savoir si un petit modèle suffit, testez-le avec les modèles de prompt et la quantification que vous déployez réellement (le fichier de modèle dont les poids ont été réduits à une précision plus basse), et comptez deux choses : la part de ses appels d’outils qui passent la validation, et la part des tâches réussies. N’affaiblissez pas silencieusement les permissions pour vous adapter à un modèle plus faible ; préférez des outils simples et ciblés, des adaptateurs contraints ou des modèles plus puissants.