1. Pourquoi les instructions ne suffisent pas sans bons tools et métadonnées
Il faut acter une vérité un peu désagréable : le modèle ne voit pas votre code. Il ne sait pas quels contrôleurs vous avez dans Next.js, quelles fonctions en TypeScript et quelles heuristiques magiques vous avez assemblées dans le service de recommandations.
Il voit votre App à travers quelques interfaces :
- System‑prompt (contrat de rôle).
- Descriptions des outils : nom, description, inputSchema, outputSchema, annotations, etc.
- Métadonnées de l’application elle‑même : nom, icône, description courte et longue, catégories, conversation starters, etc.
Lors du traitement d’une requête, le modèle regarde le contexte du dialogue et ces métadonnées pour décider :
- faut‑il proposer une App ;
- si oui — laquelle parmi celles disponibles ;
- et si l’App est choisie — quel outil précis de cette App convient à la requête en cours.
Dans la partie précédente du Module 5, nous nous sommes occupés de ce qu’on peut « raconter » au modèle avec des mots — system‑prompt et instructions UX. Passons maintenant à ce qu’il voit en plus du texte : les tools et les métadonnées.
La tâche du Module 5 est donc double. D’abord, dans le system‑prompt, vous formulez « ce que cette App doit faire et comment elle doit se comporter », puis, dans le design des tools et des métadonnées, vous empaquetez cela sous une forme que le modèle sait réellement utiliser — y compris pour la discovery et le routage.
On peut le formuler ainsi : le system‑prompt, c’est la constitution, tandis que les tools et les métadonnées — ce sont les lois et toute la bureaucratie autour : formulaires, schémas de bases de données, etc. Si l’on se limite à la constitution, on n’ira pas bien loin.
2. Décomposition : « une tâche — un tool », mais avec mesure
Commençons par le plus délicat : combien d’outils faut‑il créer et comment les découper.
Principe intuitif : un outil — une tâche claire. Cela facilite grandement le choix du modèle : au lieu d’une fonction monstrueuse do_everything, on a plusieurs actions nettes avec de bons noms.
Pour GiftGenius, nous pourrions avoir les outils de base suivants :
- profile_to_segments — transformer une description libre du destinataire (âge, centres d’intérêt, relation, contexte) en segments normalisés comme "tech", "fitness", "gamer".
- recommend_gifts — sélectionner une liste d’id de cadeaux selon les segments, le budget, la locale et l’occasion.
- get_gift — obtenir la fiche complète d’un cadeau choisi (description, médias, SKU/variantes) à partir de son id.
- (optionnel) similar_gifts — à partir d’un cadeau choisi, proposer encore 3–5 variantes similaires.
Théoriquement, on pourrait faire un seul gift_tool avec un paramètre mode: "profile_to_segments" | "recommend" | "details" | "similar", mais vous compliquez alors la vie à vous‑même et au modèle : la description devient interminable, inputSchema enfle, et le modèle a moins d’ancres claires pour choisir l’outil.
Anti‑pattern : God Tool
Imaginez le schéma suivant :
server.registerTool(
"gift_tool",
{
description: "Opérations diverses avec les cadeaux.",
inputSchema: { /* 50 champs et indicateurs */ },
},
async ({ input }) => { /* un énorme switch sur mode */ }
);
Dans l’esprit du modèle, cela ressemble à « il existe un outil abstrait sur les cadeaux, on verra ensuite ». Cela dégrade la précision du choix, nuit à la discovery et complique votre maintenance.
Mais tomber dans l’excès inverse — créer 50 outils microscopiques pour chaque broutille — n’est pas bon non plus. Chaque outil supplémentaire entre dans le contexte, surcharge l’attention du modèle et augmente le risque d’erreurs de routage. La documentation prévient clairement : trop de petits outils — c’est mauvais pour la qualité, surtout quand leurs descriptions se recoupent.
Règle pratique utile :
- tout ce que l’utilisateur perçoit comme une « étape » unique du scénario (par exemple, une première sélection de cadeaux selon un profil) — est un bon candidat pour un tool séparé ;
- ce qui s’exécute strictement à l’intérieur de cette étape et n’a pas de sens autonome (par exemple, calculer un scoring ou journaliser l’affichage des fiches) vaut mieux le laisser dans l’implémentation de l’outil.
Supposons que vous ayez déjà découpé les scénarios en 2–4 outils selon ce principe. La question importante suivante — comment décrire les entrées de ces tools pour que le modèle puisse les utiliser sans devinettes. Commençons par là.
3. Projeter les cas d’usage sur l’Input Schema
Prenons un cas d’usage concret et regardons honnêtement quelles données sont réellement nécessaires à l’outil.
Scénario : « Donneur à la dernière minute : proposer 5–7 idées pour un ami de 25 ans, fan de football et de jeux de société, budget jusqu’à 50 $. »
Du point de vue des jobs‑to‑be‑done, la tâche du cœur de recommandation GiftGenius est de réduire le choix à une petite liste et de diminuer l’anxiété « et si je choisis n’importe quoi ». Au niveau de la conversation, l’assistant a besoin de :
- informations de base sur le destinataire (âge, sexe, relation avec le donneur) ;
- centres d’intérêt/loisirs ;
- budget et devise ;
- occasion (anniversaire, jubilé, Nouvel An, etc.) ;
- optionnel — pays/ville pour filtrer selon la livraison.
Dans l’architecture GiftGenius, cela est décomposé en deux étapes :
- profile_to_segments(input) reçoit des données « brutes » (âge, intérêts, description textuelle) et les transforme en segments normalisés, pratiques pour la suite.
- recommend_gifts(segments, budget, locale, occasion) sélectionne des id de cadeaux concrets à partir du catalogue selon les segments et le budget.
Du point de vue du contrat ChatGPT ↔ MCP, il est important de décrire précisément la deuxième étape — le schéma de recommend_gifts, car c’est cet outil qui sera utilisé dans la majorité des scénarios de sélection.
Il n’est pas nécessaire de tout demander à l’utilisateur immédiatement : le modèle peut compléter une partie via un follow‑up (« quel est le budget approximatif ? »). Donc, certains champs du profil peuvent être optionnels ; mais quand on arrive à recommend_gifts, il doit déjà y avoir un ensemble de paramètres normalisés.
Exemple : TypeScript + JSON Schema pour recommend_gifts
Dans un serveur MCP en TypeScript, cela peut ressembler à ceci :
// apps/mcp/server.ts
import { McpServer } from "@openai/mcp-server";
const server = new McpServer();
server.registerTool(
"recommend_gifts",
{
title: "Recommandations de cadeaux",
description:
"Utilise cet outil lorsqu’il faut proposer des cadeaux selon les segments du destinataire, le budget, la locale et l’occasion.",
inputSchema: {
type: "object",
properties: {
segments: {
type: "array",
description:
"Liste des segments du destinataire, p. ex. ['tech', 'football_fan']. Généralement obtenu via profile_to_segments.",
items: { type: "string" },
minItems: 1
},
budget: {
type: "object",
description:
"Plage de budget pour le cadeau dans la devise de l’utilisateur (minimum/maximum).",
properties: {
min: {
type: "number",
minimum: 0,
description: "Montant minimum que l’utilisateur est prêt à dépenser."
},
max: {
type: "number",
minimum: 0,
description: "Montant maximum que l’utilisateur est prêt à dépenser."
},
currency: {
type: "string",
minLength: 3,
maxLength: 3,
description: "Code devise à trois lettres (par ex. USD, EUR, RUB)."
}
},
required: ["min", "max", "currency"]
},
locale: {
type: "string",
description:
"Locale de l’utilisateur au format BCP‑47 (par ex. 'ru-RU' ou 'en-US')."
},
occasion: {
type: "string",
description:
"Occasion du cadeau, par ex. 'birthday', 'new_year', 'anniversary'."
}
},
required: ["segments", "budget", "locale", "occasion"]
}
},
async ({ input }) => {
// Ici, pas de sophistication pour l’instant, on renvoie un stub
return {
content: [
{
type: "text",
text: `Je sélectionne des cadeaux pour les segments ${input.segments?.join(
", "
)} avec un budget de ${input.budget?.min}–${input.budget?.max} ${input.budget?.currency}...`
}
],
structuredContent: {}
};
}
);
Notez deux points.
Premièrement, nous exploitons activement des contraintes de type enum et des descriptions explicites. Même si ce sont formellement de simples chaînes, la description suggère au modèle quelles valeurs sont attendues, ce qui augmente nettement la probabilité qu’il renseigne correctement les arguments. Au lieu d’une chaîne floue "occasion": "un truc comme un anniversaire", nous avons une occasion nette : "birthday".
Deuxièmement, les descriptions des champs ne sont pas écrites « pour les collègues de l’équipe », mais littéralement comme des indices pour le modèle : quel est ce champ, quelles valeurs typiques, un exemple s’il y en a. Les auteurs de la documentation Apps SDK recommandent explicitement d’ajouter des descriptions et des exemples compréhensibles pour chaque paramètre.
Ce qui ne doit pas figurer dans le schéma d’entrée
Champs parasites typiques que l’on tente souvent d’y glisser :
- des identifiants internes (tenantId, internalSegment), que l’on peut de toute façon ajouter côté serveur ;
- des éléments que le modèle ne peut pas connaître (par exemple, deploymentRegion) — c’est votre responsabilité ;
- des champs faisant doublon avec l’historique du chat (par exemple, userPrompt) : le modèle voit déjà le message initial, ne le forcez pas à copier‑coller.
Input Schema, c’est précisément ce que le modèle doit décider et renseigner, pas un fourre‑tout général.
4. Output Schema : pas seulement des données, mais aussi du sens
Dans l’Apps SDK, le résultat de l’outil revient dans le dialogue comme un message role: tool. Ensuite, le modèle décide quoi en faire : formater la réponse, poser des follow‑up, ouvrir un widget, etc. Le design du schéma de sortie est donc tout aussi important que celui d’entrée.
Deux approches existent.
La variante « données brutes » ressemble à ceci :
{
"items": [
{ "id": "GIFT_1" },
{ "id": "GIFT_2" }
]
}
Le modèle ne voit qu’une liste d’id, sans comprendre pourquoi ces options sont là, combien il y avait de candidats ni lesquels sont les meilleurs. Il peut broder, mais le risque d’étrangetés augmente.
Variante sémantiquement riche :
{
"items": [
{
"id": "GIFT_1",
"score": 0.92,
"reason": "Forte adéquation avec le segment 'football_fan' et respecte le budget."
},
{
"id": "GIFT_2",
"score": 0.81,
"reason": "Convient à un amateur de jeux de société, un peu proche de la limite supérieure du budget."
}
],
"meta": {
"totalCandidates": 27,
"returned": 5,
"segmentsUsed": ["football_fan", "board_games"],
"budget": { "min": 20, "max": 50, "currency": "USD" },
"advice": "Mieux vaut commencer par les options avec le score le plus élevé et une explication claire."
}
}
Le modèle peut désormais expliquer honnêtement pourquoi ces cadeaux et construire des follow‑up : « J’ai trouvé 27 options, j’en montre 5 des meilleures, voilà pourquoi. »
Exemple : décrire l’Output Schema pour recommend_gifts
Ajoutons au descriptif de l’outil le schéma du résultat (même si techniquement on peut ne pas l’indiquer, c’est mieux de le faire — cela fait partie du contrat avec le modèle) :
const recommendGiftsOutputSchema = {
type: "object",
properties: {
items: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string", description: "ID du cadeau dans le catalogue." },
score: {
type: "number",
description: "Score d’adéquation au profil (0..1)."
},
reason: {
type: "string",
description:
"Brève explication de la pertinence du cadeau (peut être générée côté backend)."
}
},
required: ["id", "score"]
},
description: "Liste de cadeaux recommandés avec leurs scores de pertinence."
},
meta: {
type: "object",
properties: {
totalCandidates: {
type: "integer",
description: "Nombre total de candidats trouvés dans le catalogue."
},
returned: {
type: "integer",
description: "Nombre de cadeaux renvoyés par cet appel."
},
advice: {
type: "string",
description:
"Recommandation générale : par exemple, par quels types de cadeaux commencer."
}
}
}
},
required: ["items"]
};
Et utilisons ce schéma dans l’implémentation :
server.registerTool(
"recommend_gifts",
{
title: "Recommandations de cadeaux",
description:
"À utiliser lorsqu’il faut proposer 3–7 cadeaux selon des segments et un budget. Retourne des id de cadeaux et des scores d’adéquation ; récupère les fiches détaillées via get_gift.",
inputSchema: /* comme ci-dessus */,
// On n’indique pas toujours formellement outputSchema, mais c’est utile pour la documentation :
// outputSchema: recommendGiftsOutputSchema
},
async ({ input }) => {
const recommendations = await recommendFromCatalog(input); // notre logique métier
return {
content: [
{
type: "text",
text: `J’ai trouvé ${recommendations.items.length} idées pertinentes. Je vous montre les meilleures.`
}
],
structuredContent: {
items: recommendations.items,
meta: {
totalCandidates: recommendations.meta.totalCandidates,
returned: recommendations.items.length,
advice: recommendations.meta.advice
}
}
};
}
);
Nous faisons deux choses : nous donnons au modèle un minimum de texte pour l’utilisateur et nous déposons en même temps un JSON sémantique, à partir duquel il peut poursuivre le dialogue et les follow‑up.
Ensuite, get_gift récupérera les fiches complètes à partir des id (nom, médias, SKU, etc.), et le widget GiftGenius les affichera sous forme de cartes cadeaux.
5. Nommage et descriptions des outils comme base de la discovery
Passons à l’essentiel : comment les noms et descriptions des tools influencent le fait qu’ils seront invoqués par le modèle ou non.
La documentation et les bonnes pratiques sur les métadonnées conseillent :
- utiliser des noms orientés action : profile_to_segments, recommend_gifts, get_gift, similar_gifts, et non tool1, search, do_stuff ;
- commencer la description par « Use this when… / Utilise cet outil quand… », en décrivant les scénarios déclencheurs et les limites (« ne l’utilise pas pour… »).
Cela est directement lié à votre golden prompt set. Les formulations de la description doivent recouper les requêtes réelles des utilisateurs. Si la description dit « Utilise quand l’utilisateur demande une sélection de cadeaux selon le budget et les intérêts du destinataire », et que votre golden prompt contient « trouve un cadeau pour un ami gamer jusqu’à 50 $ », le modèle associera beaucoup plus facilement la requête à l’outil.
Exemple d’une bonne description d’outil
Considérons l’outil supplémentaire de GiftGenius — similar_gifts, qui aide à élargir la sélection via des idées similaires basées sur un cadeau précis :
server.registerTool(
"similar_gifts",
{
title: "Cadeaux similaires",
description:
"Utilise cet outil lorsque l’utilisateur a choisi un cadeau précis et souhaite voir quelques variantes similaires. Ne l’utilise pas pour une première sélection à partir de zéro — pour cela, utilise recommend_gifts.",
inputSchema: {
type: "object",
properties: {
giftId: {
type: "string",
description:
"Identifiant du cadeau issu de la sélection précédente, pour lequel on doit trouver des variantes similaires."
},
limit: {
type: "integer",
description:
"Nombre de cadeaux similaires à renvoyer (par défaut 3–5).",
minimum: 1,
default: 5
}
},
required: ["giftId"]
}
},
async () => {
/* ... */
}
);
Points importants :
- Nous indiquons explicitement quand utiliser l’outil, et quand ne pas l’utiliser.
- La description contient des mots comme « variantes similaires », « a choisi un cadeau précis » — ceux qui apparaissent souvent dans les requêtes réelles.
- Nous évitons le recouvrement avec le domaine de recommend_gifts — cela réduit la concurrence entre les outils au moment du choix.
Exemple de mauvaise description
description: "Travail avec les cadeaux."
Le modèle ne comprend quasiment rien d’une telle description. Un tel outil ne peut fonctionner que si GPT tente déjà désespérément de lancer quelque chose « au hasard ».
6. Annotations et hints : comment indiquer au modèle la gravité d’une action
Un outil — ce n’est pas seulement un nom et un schéma, mais aussi des annotations qui indiquent à ChatGPT à quel point l’action est risquée/critique et s’il faut demander la confirmation de l’utilisateur. Dans la spécification de l’Apps SDK, il existe différents hints, comme readOnlyHint, destructiveHint, openWorldHint et d’autres.
- readOnlyHint: true indique que l’outil ne fait que lire des données et ne modifie pas l’état. L’assistant peut alors se passer de confirmations inutiles et l’appeler plus librement.
- destructiveHint: true signale que l’outil peut supprimer ou modifier irréversiblement quelque chose, d’où la nécessité d’afficher un explicite « Êtes‑vous sûr ? ».
- openWorldHint: true indique que l’action touche le monde extérieur (publication sur les réseaux sociaux, création d’un enregistrement hors compte, etc.), et qu’il est important de le signaler.
Niveau minimal — sans confirmations
Si vous avez des public readonly tools, il est pertinent de les marquer avec readOnlyHint: true. Exemple :
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"openWorldHint": false
}
Ces outils peuvent être appelés sans confirmations dialogiques supplémentaires côté GPT.
Une confirmation
Si vous avez des tools qui modifient quelque chose côté serveur, il est logique de les marquer avec readOnlyHint: false :
"annotations": {
"readOnlyHint": false,
"destructiveHint": false,
"openWorldHint": false
}
En voyant un tel outil, le modèle demandera probablement une confirmation à l’utilisateur une fois (généralement une boîte de dialogue modale dans l’UI de ChatGPT).
Action dangereuse
Si vous avez un tool qui supprime quelque chose côté serveur, marquez‑le avec destructiveHint: true :
"annotations": {
"readOnlyHint": false,
"destructiveHint": true,
"openWorldHint": false
}
Le modèle appellera cet outil avec beaucoup de prudence et confirmera deux fois :
- d’abord, il demandera une confirmation à l’utilisateur dans le texte ;
- puis la plateforme affichera une boîte de dialogue standard.
Pour notre GiftGenius, dans le cadre de ce module, nous n’écrivons pas encore d’outils « commerce », mais on peut esquisser à quoi ressemblera le futur create_gift_order :
server.registerTool(
"create_gift_order",
{
title: "Création d’une commande de cadeau",
description:
"À utiliser uniquement après le consentement explicite de l’utilisateur pour acheter le cadeau choisi. Crée une commande dans le système et renvoie un statut.",
inputSchema: {
type: "object",
properties: {
giftId: {
type: "string",
description: "ID du cadeau choisi par l’utilisateur."
},
deliveryEmail: {
type: "string",
description: "Adresse e-mail à laquelle envoyer le cadeau numérique."
}
},
required: ["giftId", "deliveryEmail"]
},
annotations: {
destructiveHint: true,
openWorldHint: true
}
},
async () => {
/* ... */
}
);
Les annotations ne remplacent pas vos contrôles d’accès côté serveur, elles aident seulement ChatGPT à construire le bon UX : demander une confirmation, afficher un avertissement et ne pas exécuter ces outils « en douce ».
7. Métadonnées de l’App et deux niveaux de discovery
Les outils — c’est la moitié de l’histoire. L’autre moitié — comment l’utilisateur trouve et lance votre App.
Dans l’écosystème ChatGPT, il existe deux niveaux clés de discovery.
Premier niveau — in‑conversation discovery. Quand l’utilisateur écrit quelque chose dans le chat (même sans mention explicite de l’App), le modèle regarde :
- le texte du message et l’historique du dialogue ;
- les descriptions des apps disponibles et de leurs outils ;
- les mentions de marque, la thématique et les mots‑clés.
Sur cette base, il décide s’il faut proposer une App, et si oui — laquelle et avec quel scénario. Ici, les descriptions des outils et de l’App sont particulièrement importantes. Si elles contiennent des « triggers » comme « sélection de cadeaux », « idée de cadeau », « budget du cadeau », la probabilité que le modèle choisisse votre App augmente fortement.
Deuxième niveau — discovery globale : le catalogue et le launcher. Là, c’est l’humain qui intervient : il choisit une App à l’œil, selon le nom, l’icône, la description courte et les tags. Il est essentiel d’expliquer honnêtement et clairement ce que fait votre application, pour qui elle est, et quelle est sa proposition de valeur principale.
On peut résumer cela dans un petit tableau :
| Couche | Ce que voit le modèle/l’utilisateur | Ce qui est important dans les métadonnées |
|---|---|---|
| In‑conversation | Texte du dialogue, descriptions des tools et de l’App | Formulations déclencheuses, noms orientés action, limites |
| Catalogue/launcher | Nom, icône, short/long description, tags | Positionnement clair, value props compréhensible |
Pour GiftGenius, on peut par exemple formuler :
- Nom : GiftGenius — sélection de cadeaux en 60 secondes.
- Description courte : Construit le profil du destinataire et propose 5–7 idées de cadeaux avec possibilité d’achat instantané dans ChatGPT.
- Description pour l’in‑conversation : Utilise cette application lorsque l’utilisateur demande de l’aide pour choisir un cadeau, ne sait pas quoi offrir, mentionne un budget, les intérêts du destinataire ou une occasion.
Il est hautement souhaitable d’aligner ces formulations sur ce que vous avez déjà écrit dans le system‑prompt et dans les descriptions de l’outil recommend_gifts. Le modèle verra alors un ensemble cohérent plutôt qu’un patchwork de textes contradictoires.
8. Comment le routage fonctionne « dans la tête » de ChatGPT
Assemblons le tout et regardons un chemin typique d’une requête — sans entrer dans le détail du protocole MCP, ce sera pour les prochains modules.
Supposons que l’utilisateur écrive :
« Aide‑moi à trouver un cadeau pour mon frère, il adore le football et les jeux de société, budget jusqu’à 50 dollars. »
Algorithme grossièrement simplifié :
- Le modèle analyse le message et l’historique. Il voit les mots « cadeau », « frère », « football », « jeux de société », « budget 50 ».
- Il compare cela aux descriptions des Apps disponibles et de leurs outils. Pour GiftGenius, les descriptions contiennent explicitement « sélection de cadeaux selon les intérêts et le budget », le modèle estime donc que l’App est pertinente.
- Si l’App n’est pas encore active dans cette session, le modèle formule une réplique‑annonce : « Je peux ouvrir l’application GiftGenius qui aide à sélectionner un cadeau selon vos paramètres. Ouvrir ? » — nous l’avons prévu dans les instructions UX.
- Après l’accord de l’utilisateur, le modèle choisit dans l’App l’outil recommend_gifts, car sa description correspond le mieux à l’intention actuelle. Le nom, la description et la structure de l’inputSchema servent ici de signaux d’entrée.
- Le modèle renseigne les arguments de l’outil à partir de la requête : d’abord (si nécessaire) il appelle profile_to_segments pour extraire des segments ["football_fan", "board_games"] à partir du texte « frère, aime le football et les jeux de société », puis il appelle recommend_gifts avec segments, budget: {min: 0, max: 50, currency: "USD"}, locale, occasion: "birthday".
- Le serveur MCP exécute l’outil, forme un structured output avec items et meta et le renvoie.
- Le modèle lit le JSON que vous avez décrit dans l’outputSchema, et construit la réponse : explique ce qu’il a trouvé, pourquoi ces cadeaux‑là, et propose des follow‑up (« voulez‑vous restreindre par catégorie ? », « montrer des cadeaux similaires à celui‑ci ? » ou « passer la commande de ce cadeau ? »).
Voici un simple organigramme de ce processus :
flowchart TD A[Utilisateur : requête sur un cadeau] --> B[ChatGPT analyse le contexte] B --> C[Comparaison avec les métadonnées de l’App et des tools] C -->|pertinent| D[Annonce de GiftGenius] D -->|utilisateur d’accord| E["Appel recommend_gifts (+ profile_to_segments)"] E --> F[Serveur MCP GiftGenius] F --> G[Résultat JSON avec items/meta] G --> H[Le modèle formule la réponse et le follow-up]
Plus vos outils et cas d’usage sont bien décrits, moins il y a d’aléatoire ici et plus le routage est stable.
Insight : Tool Call SEO
Dans l’écosystème des Apps, vous aurez bientôt non seulement une concurrence pour l’attention des gens dans le catalogue, mais aussi une concurrence pour l’attention du modèle lui‑même. Pour une même requête utilisateur, ChatGPT peut appeler une dizaine d’applications, et le choix ne se fera pas sur le plus beau design de présentation, mais dans la « SERP » invisible à l’intérieur de la tête du modèle. Cette couche invisible rappelle de plus en plus le SEO, sauf qu’au lieu de pages, vous avez des tools et des serveurs MCP.
Le modèle, en substance, classe les candidats : d’abord au niveau de l’App, puis au niveau des outils individuels. Il regarde le nom, les descriptions, les schémas, les annotations et les met en regard des formulations de la requête. Si la description de recommend_gifts contient « sélection de cadeaux selon le budget et les intérêts du destinataire », et que la requête dit « trouve un cadeau pour un ami gamer pour 50 $ », cet outil a plus de chances d’« arriver en tête » que l’outil abstrait search avec la description « travail avec les cadeaux ».
D’où l’idée pratique de Tool Call SEO : considérez les noms, descriptions, valeurs d’enum et métadonnées comme des mots‑clés et des extraits. Vous ne décrivez pas seulement un contrat pour les développeurs — vous l’optimisez pour le trafic réel de requêtes issu de votre golden prompt set. Des formulations trop générales, des domaines d’outils qui se chevauchent, des God Tools sans niche claire — tout cela réduit le « CTR » de votre App dans la tête du modèle.
9. Petit exercice pratique
Essayez mentalement (ou dans votre dépôt) de faire ce qui suit.
Choisissez d’abord l’un des scénarios clés de GiftGenius — par exemple, « Choisir un cadeau pour un collègue avec un budget limité ».
Formulez pour celui‑ci :
- Quel outil distinct est nécessaire pour ce scénario : s’agit‑il d’un recommend_gifts pur, avez‑vous besoin d’un outil spécialisé pour le cas B2B, ou suffit‑il d’utiliser similar_gifts après recommend_gifts pour des variantes ?
- Quels champs sont réellement nécessaires dans le schéma d’entrée de recommend_gifts. Quels champs peut‑on demander séparément à l’utilisateur (via follow‑up), au lieu de forcer le modèle à deviner.
- À quoi doit ressembler l’outputSchema pour que le modèle puisse expliquer honnêtement le choix et proposer les prochaines étapes (par exemple, passer en mode B2B, n’afficher que des cadeaux numériques, resserrer par tranche de prix).
Puis reprenez votre golden prompt set de la leçon précédente et vérifiez :
- y a‑t‑il pour chaque requête de référence un outil évident (recommend_gifts, get_gift, similar_gifts, etc.) ;
- n’est‑il pas arrivé que deux outils soient tout aussi « pertinents » pour une même requête (overlapping tools) ;
- faut‑il renforcer les descriptions ou renommer un tool pour que le modèle se trompe moins.
C’est exactement le processus que vous répéterez avant chaque modification sérieuse du prompt, des schémas ou de la logique — en fait, un mini‑éval de la qualité de la discovery.
Si l’on résume le tout sous forme de check‑list, à ce stade, vous devez :
- découper honnêtement les scénarios en 2–4 outils pertinents ;
- décrire soigneusement inputSchema/outputSchema avec des exemples et des enum ;
- mettre au clair les noms, descriptions et annotations ;
- synchroniser cela avec le system‑prompt et les métadonnées de l’App.
Dans les modules suivants, nous verrons comment tout cela fonctionne via MCP et comment diagnostiquer un comportement étrange de discovery/de routage.
10. Erreurs courantes lors de la conception des tools et des métadonnées
Erreur n° 1 : « Nous avons tout décrit dans le system‑prompt, les outils se débrouilleront ».
Si vous avez décrit en détail le rôle de l’App, ses limites de responsabilité et son comportement UX, mais que vous avez laissé des outils nommés tool1, search, do_stuff avec des schémas sans descriptions, le modèle ne pourra tout simplement pas relier votre beau texte aux appels réels. Pour ChatGPT, les outils sont l’interface principale ; sans métadonnées soignées, aucun system‑prompt ne sauvera la mise.
Erreur n° 2 : God Tool qui fait tout et n’importe quoi.
La tentation « d’optimiser » et de créer une seule fonction avec un paramètre mode est compréhensible, mais elle conduit à des JSON schemas monstrueux, à des descriptions confuses et à un routage dégradé. Le modèle se met à deviner quel mode utiliser, et vous — à maintenir un énorme switch côté serveur. Mieux vaut plusieurs outils clairs pour des étapes concrètes du scénario qu’un « fais‑tout ».
Erreur n° 3 : Schéma d’entrée saturé de champs « au cas où ».
Souvent, les développeurs essaient de faire passer dès l’inputSchema tous les paramètres qui pourraient un jour servir, plus quelques champs internes. Au final, le modèle tente de deviner ce qu’il ne peut pas savoir (par exemple, tenantId), et vous vous étonnez des valeurs étranges. L’Input Schema doit contenir uniquement ce que le modèle peut réellement déduire du dialogue ou préciser par une question. Ajoutez les détails internes côté serveur.
Erreur n° 4 : Données de sortie « muettes » sans méta‑informations.
Il est tentant de retourner juste un tableau d’objets. Mais vous privez ainsi le modèle de la compréhension du pourquoi. Sans champs comme score, reason, searchCriteria, totalCandidates, il lui est plus difficile de construire des explications honnêtes et des follow‑up. Ajouter un petit meta avec des critères de recherche et des conseils améliore souvent radicalement la qualité de la réponse.
Erreur n° 5 : Platitudes dans les descriptions : « Travail avec les cadeaux », « Recherche de cours », « Traitement des données ».
Ces descriptions sont mauvaises, car elles ne donnent ni triggers ni limites au modèle. Il ne sait pas quand appeler l’outil ni dans quel domaine il s’applique. Une bonne description commence par « Utilise cet outil quand… » et contient des scénarios concrets et des interdits du type « Ne l’utilise pas pour… ». Idéalement, ces formulations recoupent les requêtes phares de votre golden prompt set.
Erreur n° 6 : Ignorer les annotations et mélanger read‑only et actions modifiant l’état.
Si vous ne marquez pas les outils qui ne font que lire (readOnlyHint) et ceux qui effectuent des actions (destructiveHint, openWorldHint), le modèle ne peut pas construire le bon UX de confirmations. Résultat : soit des « Êtes‑vous sûr ? » superflus à chaque étape, soit au contraire des achats et modifications silencieux sans consentement. Les annotations sont un moyen simple et efficace d’indiquer l’importance d’une opération.
Erreur n° 7 : Métadonnées de l’App pour le catalogue et métadonnées pour l’in‑conversation dans deux univers différents.
Il arrive que la description courte dans le catalogue soit écrite par un marketeur (« Assistant IA révolutionnaire qui change votre vie »), tandis que les descriptions des tools et le system‑prompt le soient par un développeur (« sélection de cadeaux selon le budget »). Au final, dans le catalogue, on ne comprend pas de quoi parle l’App, et le modèle ne peut pas associer des requêtes du type « c’est quoi ce service ? » aux capacités réelles de l’App. Rédigez les métadonnées comme une spécification unifiée, pas comme deux textes marketing indépendants.
GO TO FULL VERSION