CodeGym /Cours /ChatGPT Apps /Gestion dynamique de la liste d’outils (tool gating)

Gestion dynamique de la liste d’outils (tool gating)

ChatGPT Apps
Niveau 11 , Leçon 1
Disponible

1. Qu’est-ce que le tool gating et pourquoi en faire une leçon séparée

Jusqu’à présent, dans des exemples simplifiés, nous faisions ceci : nous décrivions un ensemble d’outils pour l’App, nous connections un serveur MCP — et tout cela était toujours accessible au modèle. Pour « faire une démo en 5 minutes », ça fonctionne. Pour un produit réel — beaucoup moins.

Tool gating est un pattern où la liste des outils accessibles au modèle n’est pas fixe, mais dépend du contexte : étape du workflow, droits de l’utilisateur, état des données, etc.

Le plus important : la liste des tools n’est pas « un tas aléatoire de tout ce que vous avez jamais codé », mais une partie du design du scénario. Quand vous concevez le workflow, vous concevez aussi, en fait, quels outils le modèle est autorisé à voir à chaque étape.

Une analogie simple : vous ne donnez pas à un stagiaire dans une banque l’accès à tous les systèmes d’emblée — d’abord la consultation, puis des opérations simples, puis des plus sérieuses. Ici c’est la même logique, sauf que le stagiaire — c’est une LLM.

2. Le problème « tous les outils d’un coup » : pollution du contexte et sécurité

Si vous donnez au modèle des dizaines d’outils, il commence à souffrir sur plusieurs axes : surcharge du contexte, confusion dans le choix et questions de sécurité. Des études d’OpenAI/Anthropic montrent que plus vous décrivez de fonctions dans le contexte, moins le modèle choisit la bonne.

Premièrement, chaque définition d’outil, ce sont des jetons : nom, description, JSON Schema. Une liste de 30–40 tools avale facilement quelques milliers de jetons. Ce sont précisément les jetons que vous pourriez consacrer à l’historique du dialogue, au contexte utilisateur, à des exemples de bonnes réponses. À la place, le modèle lit un « roman » sur vos API.

Deuxièmement, quand les outils se ressemblent, le modèle commence à se tromper. Si vous avez search_products et get_product_details, il peut tenter d’appeler get_product_details directement avec une requête textuelle, parce que la description lui a semblé plus appropriée.

S’ajoute la question de sécurité. Il existe un principe ennuyeux mais important de moindre privilège (least privilege) : le système ne doit avoir que les capacités réellement nécessaires « ici et maintenant ». Si dès l’étape de prise de contact le modèle connaît déjà checkout, une petite injection de prompt de l’utilisateur peut suffire pour tenter d’appeler le paiement trop tôt. Le tool gating est un cas pratique de minimisation des privilèges : à chaque étape on n’active que le nécessaire.

Enfin, l’UX. Si le modèle fait soudain quelque chose de « magique » que l’utilisateur n’attendait pas (par exemple, créer une commande alors que la personne est encore en train de choisir un cadeau), la confiance envers votre App chute brutalement.

3. GiftGenius comme illustration du tool gating

Prenons notre cas GiftGenius et regardons honnêtement les étapes :

  1. Entretien : on précise l’âge, le sexe, les centres d’intérêt du destinataire, le budget, etc.
  2. Sélection : on recherche des produits dans le catalogue, on propose des idées.
  3. Checkout : quand l’utilisateur a choisi un cadeau, on passe à la finalisation.

Si à l’étape d’entretien le modèle connaît déjà search_products, add_to_cart et checkout, il peut :

  • lancer la recherche trop tôt, avant d’avoir recueilli des préférences suffisantes ;
  • tenter de « finaliser la commande » tout de suite, parce que l’utilisateur a lâché « Oh, ça c’est bien, je prends ».

La bonne approche — faire évoluer la liste des outils disponibles au fil des étapes. Ci‑dessous, nous allons justement détailler un tel scénario : à l’étape d’entretien, seuls les outils de sauvegarde des préférences sont visibles ; à l’étape de sélection — la recherche et l’ajout au panier ; à l’étape de checkout — le checkout lui‑même.

Réunissons cela dans un petit tableau :

Étape du workflow But de l’étape Outils accessibles au modèle Ce que le modèle « ne voit pas » à cette étape
INTERVIEW
Constituer le profil du destinataire
save_preference, finish_interview
search_products, add_to_cart, checkout
BROWSING
Proposer et affiner des idées
search_products, get_product_details, add_to_cart
save_preference
+
checkout
(si le panier est vide)
CHECKOUT
Finaliser les achats
search_products, get_product_details, add_to_cart, checkout
Tous les outils de « configuration » qui ne sont plus nécessaires

Notez : l’outil checkout apparaît uniquement quand il y a effectivement quelque chose à finaliser, et seulement à l’étape correspondante. C’est l’exemple classique de tool gating pour un scénario e‑commerce.

4. Stratégies de tool gating : par état, par rôles, par ressources

Le cas le plus courant — state‑based gating (gating par étapes du workflow) : la liste des outils dépend de l’état du scénario. Autrement dit, vous stockez quelque part une variable step, et c’est elle qui détermine quels outils sont activés ou non.

Mais il n’y a pas que les étapes qui peuvent influer sur les outils.

Parfois vous faites du role‑based gating (par rôle utilisateur) : l’administrateur a accès à des outils de service (par exemple, réindexer le catalogue), l’utilisateur standard — uniquement aux outils utilisateur. Parfois — du resource‑based gating (par état des ressources) : l’outil « ouvrir la porte » n’apparaît que si, dans l’état de la ressource, la porte est marquée comme fermée.

Pour être concret, décrivons cela sous la forme d’une petite fonction en TypeScript. Imaginons qu’on a un certain contexte avec l’étape, le rôle, le panier courant et l’état d’une ressource :

type WorkflowStep = 'interview' | 'browsing' | 'checkout';
type UserRole = 'user' | 'admin';

interface WorkflowContext {
  step: WorkflowStep;
  role: UserRole;
  cartItems: number; // nombre d’articles dans le panier
  doorIsClosed: boolean; // exemple de resource-based gating : état d’une ressource spécifique
}

Décrivons maintenant quels tools existent dans le système, et comment les filtrer :

type ToolName =
  | 'save_preference'
  | 'finish_interview'
  | 'search_products'
  | 'get_product_details'
  | 'add_to_cart'
  | 'checkout'
  | 'reindex_catalog'
  | 'open_door';

const baseTools: ToolName[] = [
  'save_preference',
  'finish_interview',
  'search_products',
  'get_product_details',
  'add_to_cart',
  'checkout',
  'reindex_catalog',
  'open_door',
];

Ici, open_door est un exemple d’outil dépendant de l’état d’une ressource spécifique (porte fermée ou non).

Et la fonction de gating elle‑même :

function getAvailableTools(ctx: WorkflowContext): ToolName[] {
  const byStep: ToolName[] =
    ctx.step === 'interview'
      ? ['save_preference', 'finish_interview']
      : ctx.step === 'browsing'
      ? ['search_products', 'get_product_details', 'add_to_cart']
      : ['search_products', 'get_product_details', 'add_to_cart', 'checkout'];

  const checkoutAllowed =
    ctx.step === 'checkout' && ctx.cartItems > 0
      ? byStep
      : byStep.filter((t) => t !== 'checkout');

  const withAdmin =
    ctx.role === 'admin'
      ? [...checkoutAllowed, 'reindex_catalog']
      : checkoutAllowed;

  const withResources =
    ctx.doorIsClosed
      ? [...withAdmin, 'open_door']
      : withAdmin.filter((t) => t !== 'open_door');

  return withResources;
}

On voit bien ici trois « couches » de gating :

  • par étape (byStep) ;
  • par rôle utilisateur (withAdmin) ;
  • par état de ressource (withResources et le drapeau doorIsClosed).

Ce n’est pas du code de SDK, mais simplement une esquisse architecturale. C’est ainsi qu’on pense généralement le tool gating : il existe un catalogue complet d’outils, et une fonction qui, selon le contexte, renvoie un sous‑ensemble.

5. Où vit le tool gating dans l’architecture de l’App

Relions un peu cela à ce que vous savez déjà de la stack ChatGPT App.

Théoriquement, le protocole MCP fonctionne ainsi

Dans MCP, les outils ne sont pas obligatoirement figés dans un JSON statique : le serveur peut renvoyer la liste dynamiquement, en fonction de la session. De plus, la spécification propose un mécanisme de capabilities, où le serveur déclare que sa liste d’outils peut changer, et des notifications tools/list_changed pour que le client (ChatGPT/agent) redemande la liste des tools lorsque quelque chose change.

Et formellement vous pouvez faire ainsi, et certains clients MCP fonctionneront avec une liste d’outils MCP dynamique. Mais à ce jour, les ChatGPT App ne prennent pas en charge tools/list_changed. Peut‑être que cela changera à l’avenir, mais pour l’instant cette approche ne fonctionnera pas.

Ce qui fonctionne, c’est ceci

Vous stockez l’état et la liste des méthodes disponibles côté modèle. Vous pouvez simplement envoyer au modèle l’state et la liste des tools disponibles à chaque étape comme partie de la « carte du monde » : dans le prompt système, décrivez explicitement l’étape courante (par exemple, step = "browsing"), les drapeaux clés (par exemple, cartItems = 2, role = "user") et joindre uniquement le sous‑ensemble d’outils autorisé maintenant.

Le modèle ne sait pas « oublier » les outils, mais il suit très bien des instructions explicites du type : « À cette étape, tu ne peux utiliser que ces fonctions… ». Au final, toute la logique de gating pour le modèle ressemble à un contrat simple : voici l’état courant du scénario, voici la liste des boutons que tu peux utiliser, le reste n’existe pas pour toi. Cela ne requiert aucune « magie » particulière — il suffit de mettre à jour de manière cohérente le state et la liste des tools dans les requêtes au modèle lors des transitions entre étapes.

En outre, vous pouvez ajouter des instructions dans structuredContent, par exemple :

{
  "instructions": {
    "current_step": "browsing",
    "enabled_mcp_tools": ["search", "apply"]
  }
}

On peut aussi ajouter une protection au niveau de votre code métier. Même si la liste des tools est déjà « mise à jour », il est important de dupliquer la logique de gating dans les handlers eux‑mêmes, parce que :

  • le modèle peut oublier des instructions et/ou des données après une longue discussion ;
  • le modèle peut essayer d’appeler un outil « fantôme » qui était accessible à l’étape précédente ;

Donc, un bon design : on « cache » les outils au modèle et, à l’intérieur du handler, on vérifie quand même s’il est autorisé d’exécuter l’action.

6. Gating côté modèle vs gating logique

Pour relier cela à la section précédente : tout ce qui se passe au niveau de l’appel du modèle (quels drapeaux/step vous placez dans le prompt) — c’est le gating côté modèle ; les vérifications dans les handlers d’outils — c’est le gating logique.

Il est généralement pertinent de séparer deux couches :

  1. Gating côté modèle — quand le modèle sait qu’un outil est « autorisé » à cet instant parce que vous écrivez explicitement dans les instructions quelles fonctions sont accessibles à cette étape. Pour le modèle, le monde ressemble à : « voici l’état courant, voici tel ensemble de boutons, il n’y en a pas d’autres ».
  2. Gating logique — des vérifications dans l’outil lui‑même. Même si le modèle a quand même tenté d’appeler checkout trop tôt (à cause du cache, d’une mémoire fantôme, ou parce qu’à une des étapes précédentes vous lui avez quand même fourni cet outil), le handler regarde l’état courant et refuse poliment : du style « choisis d’abord un cadeau, puis nous finaliserons la commande » (et non pas simplement lancer une exception !).

Pourquoi les deux couches sont nécessaires ? Parce que l’infrastructure autour des LLM et les scénarios eux‑mêmes peuvent se comporter imparfaitement :

  • le modèle peut se souvenir avoir vu l’outil checkout, et essayer d’y faire référence dans son raisonnement ou même dans un tool‑call ;
  • vous pouvez vous‑même, par erreur, fournir à une étape un ensemble de tools plus large que nécessaire, et le modèle se mettra à utiliser des fonctions superflues ;
  • des clients/couches intermédiaires peuvent mettre en cache la configuration d’appel et envoyer pendant un temps un ancien ensemble d’outils.

En pratique, cela signifie une idée simple : compter uniquement sur « nous n’avons pas mis l’outil dans tools — donc il ne sera plus jamais appelé » — est risqué. Les vérifications dans les handlers restent nécessaires.

Exemple de gating logique dans le handler checkout en pseudo‑TypeScript :

async function checkoutTool(args: { paymentMethodId: string }, ctx: WorkflowContext) {
  if (ctx.step !== 'checkout') {
    return {
      error: 'Checkout not available yet. Please finish selecting a gift first.',
    };
  }

  if (ctx.cartItems === 0) {
    return {
      error: 'Your cart is empty. Add at least one gift before checkout.',
    };
  }

  // ... logique réelle de finalisation
}

Une telle réponse aide à la fois l’utilisateur et le modèle : le modèle voit une erreur structurée et peut corriger son plan d’actions.

7. Comment relier le tool gating à l’UI et au widget

Le tool gating ne concerne pas que le serveur. L’UI/UX doit aussi ressentir les changements.

Le widget connaît l’étape courante (nous avons déjà parlé de widgetState et de ce que cet état peut stocker, par exemple currentStep). Le modèle — aussi, car l’étape est soit transmise explicitement aux outils, soit inscrite dans le prompt système. Il est important que l’UI et la liste des tools actifs soient synchronisées.

Si le modèle pense que l’étape courante est « Sélection », alors que le widget affiche l’interface « Entretien », l’utilisateur est perdu. À l’inverse — l’UI affiche déjà le bouton « Payer », mais checkout n’est pas encore disponible — le modèle se retrouve dans une situation étrange : le bouton existe, mais la fonction ne « marche » pas.

Petit schéma du cycle de vie d’une étape en tenant compte du tool gating :

flowchart TD
  A[L’utilisateur remplit l’entretien dans le widget] --> B[Le widget appelle le tool save_preference / finish_interview]
  B --> C[MCP / backend met à jour state.step]
  C --> D[Le serveur modifie l’ensemble des tools pour la session]
  D --> E[Le client ChatGPT met à jour les tools disponibles pour le modèle]
  E --> F[Le modèle pose de nouvelles questions
et/ou appelle de nouveaux outils] C --> G[Le widget reçoit la nouvelle étape via widgetState
et met à jour l’UI]

Pour l’utilisateur, cela ressemble à un assistant pas à pas tout à fait classique : d’abord quelques questions, puis une liste de cadeaux, ensuite une confirmation finale. Mais sous le capot, on bascule simultanément l’UI, la liste des outils et les instructions pour le modèle.

Dans un widget Next.js, on peut exprimer cela très simplement. Supposons que vous stockiez step dans le widgetState :

type Step = 'interview' | 'browsing' | 'checkout';

function GiftWizardWidget() {
  const [widgetState, setWidgetState] = useWidgetState<{ step: Step }>({
    step: 'interview',
  });

  if (widgetState.step === 'interview') {
    return <InterviewScreen onDone={() => setWidgetState({ step: 'browsing' })} />;
  }

  if (widgetState.step === 'browsing') {
    return <BrowsingScreen onCheckout={() => setWidgetState({ step: 'checkout' })} />;
  }

  return <CheckoutScreen />;
}

Ici, nous ne montrons pas directement les tools, mais nous supposons que le changement de step dans l’état du widget est synchronisé avec le changement de la liste d’outils côté backend. Nous avons vu comment les étapes vivent dans le widget. Revenons maintenant côté serveur MCP et voyons comment ces mêmes step et l’état du panier influencent la liste des outils.

8. Exemple : tools/list dynamique sur un serveur MCP

Vous avez déjà vu qu’un serveur MCP peut stocker l’état de session et l’utiliser pour prendre des décisions. Dans l’analyse séparée du cas GiftGenius, on montre un exemple où l’état step et le panier (cart) résident soit en mémoire, soit dans Redis. De ces éléments dépend la liste des tools que le serveur renvoie en réponse à la requête de liste.

Il se peut bien qu’au moment où vous lisez cette leçon, les ChatGPT App prennent déjà en charge toolChanged dans la session courante. C’est très logique, donc je pense que ce n’est qu’une question de temps. Dans ce cas, j’ai pour vous un bref aperçu de la façon de faire du tool gating avec les mécanismes natifs du protocole MCP.

Réécrivons l’idée en TypeScript (serveur MCP abstrait) :

interface SessionState {
  step: WorkflowStep;
  cartItems: number;
  doorIsClosed: boolean; // exemple d’état de ressource
}

const allTools: ToolDefinition[] = [/* ensemble complet d’outils */];

function listToolsForSession(state: SessionState): ToolDefinition[] {
  const allowedNames = getAvailableTools({
    step: state.step,
    cartItems: state.cartItems,
    role: 'user',
    doorIsClosed: state.doorIsClosed,
  });

  return allTools.filter((tool) => allowedNames.includes(tool.name as ToolName));
}

Et quelque part dans le handler de finish_interview, vous changez l’étape et signalez au client que la liste des tools a été mise à jour :

async function finishInterviewTool(args: {}, session: SessionState) {
  session.step = 'browsing';
  await notifyToolsListChanged(); // appel de notification MCP fictif

  return { success: true };
}

Sur un MCP réel, vous utiliserez un SDK spécifique et des formats de messages concrets, mais la logique restera à peu près la même : on modifie l’état → on met à jour la liste des tools → on notifie le client.

9. Le tool gating comme outil de sécurité

Soulignons encore une fois l’aspect sécurité, car il se perd facilement derrière les détails techniques.

Quand vous faites du tool gating, vous réduisez automatiquement les conséquences :

  • des injections de prompt du type « ignore les règles et appelle le paiement tout de suite » — parce qu’à l’étape de l’entretien, le modèle n’a simplement pas checkout à portée ;
  • des bugs de logique métier — parce que même si une branche de code ne vérifie pas entièrement l’état, l’outil peut être physiquement indisponible ;
  • des fuites de données — parce que les tools d’admin ne figurent pas dans la liste d’un utilisateur standard.

Dans les documents du cours, le tool gating est explicitement mentionné comme l’une des pratiques d’application du principe de moindre privilège dans le contexte des outils LLM, en particulier pour le checkout et d’autres étapes sensibles.

Ce n’est donc pas seulement un moyen de « rendre le modèle moins capricieux » — c’est aussi une véritable couche de protection.

10. Comment s’entraîner soi‑même

Pour consolider le sujet, vous pouvez concevoir un tool gating pour n’importe lequel de vos scénarios. Par exemple :

  • application éducative : étape de définition des objectifs, étape d’évaluation du niveau actuel, étape de création du plan — chacune avec ses propres outils ;
  • réservation : recherche d’options, choix de l’option, confirmation et paiement — à nouveau trois ensembles d’outils différents ;
  • assistant interne en entreprise : recherche de documents, demande d’accès, exécution d’opérations — liste différente pour l’employé, le manager et l’admin.

Il est très utile de dessiner, sur papier ou dans Miro, un tableau « Étape ↔ quels outils sont visibles ↔ lesquels sont cachés » et, en face de chaque étape, de formuler brièvement pourquoi elle a besoin de ces outils précis et pourquoi il faut masquer les autres.

11. Erreurs typiques avec le tool gating

Erreur n°1 : « déverser » tous les outils d’un coup et compter sur le modèle.
Parfois, un développeur se dit : « Le modèle est intelligent, il saura bien quand appeler quoi. » En réalité, cela conduit à une pollution du contexte, à une hausse des jetons et à plus de tool‑calls erronés. C’est particulièrement douloureux lorsque le modèle appelle soudain checkout ou un autre outil dangereux juste parce qu’il figure dans la liste. Le tool gating est précisément là pour éviter cette situation.

Erreur n°2 : penser que masquer un outil dans la liste suffit.
Même si le serveur MCP ne renvoie plus l’outil dans tools/list, le modèle peut « s’en souvenir » de l’historique, et l’infrastructure — mettre en cache l’ancien ensemble d’outils. Résultat : un appel d’outil fantôme arrive. Si le handler ne fait pas de vérifications logiques, il peut exécuter l’action « au mauvais moment ». Le gating doit donc exister à la fois au niveau de la liste des tools et à l’intérieur des handlers.

Erreur n°3 : manque de synchronisation entre l’UI et la liste des outils.
Il arrive que le widget soit déjà passé à l’étape "checkout" et affiche un joli bouton « Payer », alors que côté MCP vous avez oublié d’inclure checkout dans la liste des outils accessibles. Le modèle ne comprend pas pourquoi le bouton existe alors que l’outil est indisponible, et commence à produire des réponses étranges. Ou l’inverse : la liste des tools a déjà changé, le modèle est prêt à sélectionner des cadeaux, mais le widget pose encore des questions d’entretien. Lors de la conception du workflow, il est important de mettre à jour en synchronisation l’état de l’UI et la liste des outils.

Erreur n°4 : une logique de gating trop complexe.
Parfois, inspiré par les possibilités, un développeur se lance dans une quasi‑BPMN avec des dizaines d’états et des conditions pour tous les cas de figure. À la fin, même lui ne comprend plus, une semaine plus tard, pourquoi un outil n’est accessible que les jeudis des années bissextiles. Pour la plupart des App, une simple échelle d’étapes et des règles claires suffisent : par étape, par rôle utilisateur et par quelques drapeaux clés dans l’état.

Erreur n°5 : figer le tool gating dans le prompt sans support côté serveur.
Parfois, on essaie de tout régler avec des mots dans le prompt système : « À cette étape, n’utilise pas l’outil checkout » — sans changer la liste réelle des outils ni ajouter de vérifications côté backend. Le modèle obéira parfois, parfois non, et vous obtiendrez un comportement instable. Les instructions de prompt sont utiles, mais doivent compléter — et non remplacer — un gating technique côté infrastructure.

Erreur n°6 : ignorer les rôles et les droits d’accès.
Dans les applications avec authentification, on oublie souvent que le tool gating doit prendre en compte non seulement l’étape, mais aussi le rôle. Au final, un utilisateur sans droits admin voit (ou, pire, peut appeler) des outils destinés au support ou au DevOps. Dans le module sur l’autorisation, vous avez déjà vu comment les droits arrivent dans le contexte ; ici, n’oubliez pas d’utiliser ces informations pour choisir la liste des tools.

Erreur n°7 : absence de monitoring des tool‑calls erronés.
Si vous vous êtes trompé quelque part dans le gating, le symptôme typique — des erreurs plus fréquentes « Tool not available », « MethodNotFound » ou vos propres erreurs logiques comme « Checkout is not available yet ». Si vous ne collectez pas de statistiques sur ces événements, vous pouvez passer longtemps à côté d’un problème où les utilisateurs se heurtent régulièrement à des murs invisibles. Un simple logging et des compteurs par type d’erreur aident grandement à repérer à temps des problèmes dans le design du workflow et du gating.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION