1. Qu’est‑ce qu’un scénario Multi‑App et pourquoi vous en avez besoin
Jusqu’ici, nous regardions GiftGenius comme une application externe unique dans une conversation donnée : l’utilisateur choisit votre App dans la liste, ChatGPT lève vos tools, et vous êtes le « héros principal » de l’histoire. Dans le Store réel, c’est différent : l’utilisateur peut connecter plusieurs Apps en même temps, et ChatGPT décide quelle application appeler en réponse à chaque requête.
Par exemple, dans un même chat, on peut avoir :
- une App de calendrier d’entreprise, qui connaît les dates d’anniversaire des collègues ;
- GiftGenius, qui propose des idées de cadeaux ;
- une App commerce de l’entreprise, capable de créer des commandes et de gérer le paiement.
L’utilisateur écrit : « Rappelle‑moi les anniversaires des collègues et propose tout de suite quoi offrir et comment acheter. » Le modèle peut appeler successivement trois Apps différentes : l’une pour le calendrier, la deuxième pour les idées de cadeaux, la troisième pour le checkout.
Il est important de comprendre : l’utilisateur n’a pas de bouton « appelle, s’il te plaît, l’App n°2 et voici son endpoint HTTP ». Il parle en langage naturel, et ChatGPT joue le rôle de routeur — il lit les descriptions et les métadonnées de toutes les applications disponibles et décide qui appeler et quand.
Il en découle trois idées clés :
- Il y a compétition pour le contexte. Votre App doit être choisie parmi des dizaines d’autres sur la base des descriptions, des noms et du comportement.
- Les métadonnées deviennent votre « SEO pour LLM » — ce sont elles qui déterminent si le modèle verra GiftGenius au bon moment ou l’ignorera.
- Il faut penser à l’interopérabilité : vos réponses doivent être utiles non seulement à la personne dans le chat, mais aussi aux autres Apps qui lisent le même contexte.
En somme, nous transformons un ChatGPT App isolé en composant d’un système plus vaste.
2. Comment le modèle choisit une App : modèle mental du routage
Le routage dans les scénarios Multi‑App fonctionne à peu près ainsi (fortement simplifié, mais utile pour le développement) :
- ChatGPT dispose d’une liste d’Apps disponibles et de leurs tools avec métadonnées (nom, description, JSON Schema des paramètres, annotations et _meta).
- L’utilisateur envoie un message.
- Le modèle construit une représentation interne de l’intention et effectue en pratique une recherche sémantique sur les descriptions des tools et des applications, afin de comprendre quels outils sont pertinents.
- Si les critères correspondent — il appelle le tool ou propose d’ouvrir l’App.
Il y a un point important : les descriptions doivent être suffisamment distinctives (discriminantes). La formulation « Recherche de produits » ne se distingue guère de « Recherche d’idées cadeaux » ou « Recherche de livres », alors que « Recherche d’idées cadeaux via la base de partenaires GiftGenius » resserre fortement le domaine et augmente les chances que votre outil soit choisi pour des requêtes cadeaux.
Deuxième détail — évitez les collisions de noms. Un outil nommé get_data ne signifie rien dans un monde avec des dizaines d’Apps, alors que giftgenius_get_gift_catalog est bien plus clair. Et surtout — en combinaison avec une description précise.
Enfin, le modèle s’appuie sur le contexte : si la conversation mentionne déjà « cadeaux », « anniversaires » et même le nom GiftGenius, cela met votre App en évidence aux yeux du routeur.
3. Métadonnées et descriptions comme LLM‑SEO
Pour ne pas considérer les métadonnées comme une « formalité obligatoire dans le JSON », il est utile de les voir comme un travail de rédaction produit. Les recommandations officielles disent explicitement : treat metadata like product copy et concevez « one job per tool ».
On peut distinguer plusieurs niveaux de description :
| Niveau | Pour qui | Ce que cela décrit |
|---|---|---|
| Manifest description | Humain + modèle | La mission globale de l’App : pourquoi l’activer dans le chat |
| Tool description | Modèle (routage) | Quand utiliser un tool précis et pour quelles tâches |
| Parameter descriptions | Modèle (remplissage des slots) | Comment renseigner les arguments, quelles valeurs sont autorisées |
|
Modèle (UI) | Ce qui apparaît exactement dans le widget et s’il faut le dupliquer dans la réponse textuelle du modèle |
widgetDescription est particulièrement important dans un monde de widgets : le modèle ne « voit » pas votre code React, il sait seulement quels props vous lui passerez et dans quel but. Un champ bien rempli lui évite d’« inventer à votre place », et au contraire, l’aide à adapter les réponses textuelles en tenant compte de l’UI déjà affichée.
La documentation de l’Apps SDK souligne : ChatGPT décide quand et comment appeler votre connecteur (App) en s’appuyant sur les métadonnées. Des descriptions soignées et des docs de paramètres précises augmentent le recall — la part des situations où le modèle pense à votre App — et réduisent les faux positifs.
Mini‑exemple : ancien vs nouveau description pour GiftGenius
Supposons qu’auparavant nous avions quelque chose comme :
export const appDescription = `
GiftGenius — assistant pour la recherche et l'achat de cadeaux.
`;
Du point de vue humain c’est correct, mais pour le routage dans un monde Multi‑App il vaut mieux insister sur quand utiliser l’App et ce qu’il ne fait pas :
export const appDescription = `
GiftGenius — assistant pour des idées de cadeaux.
Utilise cette application lorsque l'utilisateur demande d'imaginer un cadeau
pour une personne ou une occasion précise et de respecter un budget.
Ne l'utilise pas pour du shopping en ligne générique ni pour la planification des finances personnelles.
`;
Désormais, le modèle distingue plus facilement GiftGenius d’une App d’e‑commerce généraliste ou d’un conseiller financier.
4. _meta["openai/widgetDescription"] : expliquer notre UI au modèle
Dans le tableau ci‑dessus, nous avons mentionné séparément _meta["openai/widgetDescription"]. Concentrons‑nous maintenant sur ce niveau : il aide le modèle à « se représenter » votre widget et à comprendre quelles parties de la réponse sont déjà couvertes par l’UI, et ce qu’il convient d’exprimer en texte.
Supposons que notre outil principal suggest_gifts renvoie une liste de cadeaux, et que le widget les rende sous forme de carrousel horizontal de cartes. Dans la description de l’outil, nous avons déjà expliqué quand l’utiliser, et dans widgetDescription nous expliquons à quoi ressemble le résultat.
Exemple de fragment de descripteur d’outil (simplifié, inspiré des recommandations) :
const suggestGiftsTool = {
name: "suggest_gifts",
description: "Use this to generate gift ideas within user's budget.",
inputSchema: { /* ... */ },
_meta: {
"openai/widgetDescription":
"Affiche une liste horizontale de cartes cadeaux avec le prix et un bouton 'Acheter'. Ne répète pas les noms des cadeaux dans la réponse textuelle."
}
};
Nous atteignons plusieurs objectifs d’un coup :
- Le modèle sait que l’UI affichera déjà les noms et les prix — il peut donc se concentrer, dans la réponse textuelle, sur les explications et conseils plutôt que dupliquer la liste.
- D’autres Apps (via le modèle) comprennent que le toolOutput n’est pas qu’un paragraphe de texte, mais une liste structurée, qu’elles peuvent « récupérer » dans leur contexte.
Et soyons honnêtes : rédiger de telles descriptions est moins amusant que coder, mais elles vous feront gagner des heures de debug d’un comportement étrange du modèle.
5. Annotations des outils : readOnlyHint, destructiveHint, openWorldHint
Dans un monde Multi‑App, il est important non seulement de savoir « quand appeler », mais aussi dans quelle mesure il est sûr d’appeler un outil donné. Pour cela, l’Apps SDK introduit un ensemble d’annotations dans les descripteurs d’outils.
L’idée est la suivante : les annotations sont des indices souples sur la nature de l’opération. Elles ne remplacent pas l’autorisation côté serveur, mais elles influencent fortement la manière dont ChatGPT se comporte dans les chaînes.
Bref résumé (conceptuel) :
| Annotation | Sens | Comportement typique du modèle |
|---|---|---|
|
Ne modifie pas les données | Peut être appelé souvent, sans confirmations superflues |
|
Change l’état (achats, suppression) | Demander confirmation à l’utilisateur avant l’appel |
|
Accède au « monde extérieur » (recherche, web) | Le modèle reste prudent sur le volume et la qualité du résultat |
Les annotations (readOnlyHint, destructiveHint, openWorldHint) font partie de la description standard d’un outil et peuvent être utilisées au‑delà de ChatGPT. Le champ _meta["openai/isConsequential"] est un signal plus étroit, spécifique à ChatGPT, qui aide en plus le modèle à différencier les appels « sûrs » et « conséquents ».
Regardons deux outils de GiftGenius :
- suggest_gifts — lecture du catalogue, sans risque.
- create_checkout_session — création d’un checkout, action à effet de bord explicite.
Exemple de description de l’outil suggest_gifts
const suggestGiftsTool = {
name: "suggest_gifts",
description:
"Use this when the user asks for gift ideas for a person or occasion.",
inputSchema: { /* ... */ },
annotations: {
readOnlyHint: true
},
_meta: {
"openai/widgetDescription": "Carrousel de cadeaux avec prix et lien.",
"openai/isConsequential": false
}
};
Un tel outil peut être appelé plusieurs fois de suite par le modèle, y compris de manière « préventive », pour préparer des options à l’avance, sans interroger l’utilisateur pour chaque action.
Exemple de description de l’outil create_checkout_session
const createCheckoutTool = {
name: "create_checkout_session",
description:
"Finalize purchase of selected gifts via Instant Checkout.",
inputSchema: { /* ... */ },
annotations: {
destructiveHint: true
},
_meta: {
"openai/isConsequential": true
}
};
Ici, nous signalons explicitement : c’est une opération d’écriture, avec des conséquences (argent débité, commande créée), et le modèle doit demander la confirmation de l’utilisateur avant l’appel, en particulier dans des chaînes longues avec plusieurs Apps.
Il ne faut pas surestimer la « magie » : même avec destructiveHint, vous devez revérifier côté serveur les données d’entrée, les jetons et les droits, comme discuté dans les modules sur la sécurité et l’autorisation. Mais du point de vue de l’orchestration Multi‑App, les annotations aident le modèle à ne pas « tirer » sur de tels outils sans nécessité.
6. App isolée vs écosystème : préciser les frontières de GiftGenius
Quand GiftGenius était la seule App du chat, on pouvait se permettre un scope assez large : sélection de cadeaux, conseils d’emballage, rappels de fêtes, voire de petits textes de félicitations. Le modèle n’appelait de toute façon que vos outils.
Dans un scénario Multi‑App, une approche « je fais tout » commence à nuire :
- le routeur distingue moins bien quand vous êtes le candidat idéal, et quand il vaut mieux utiliser une autre App ;
- vous vous superposez au calendrier, au gestionnaire de tâches, au planificateur financier, etc. ;
- lors de l’utilisation conjointe de plusieurs applications, le modèle peut choisir le « mauvais » exécutant et se tromper d’outils.
La meilleure approche — délimiter clairement la zone de responsabilité :
- GiftGenius : idées cadeaux uniquement + aide à l’achat via ACP/Checkout ;
- CalendarApp : événements et rappels ;
- Finance‑App : budget global de l’utilisateur, plan financier personnel.
Dans les descriptions de l’App et des outils, il est utile d’écrire explicitement non seulement « Use this when… », mais aussi « Do not use when… ». Le discovery‑playbook officiel recommande précisément cela.
Mini‑exemple de description d’outil :
description: `
Use this tool when the user explicitly asks for gift suggestions.
Do not use for generic product discovery or price comparison.
`
De telles limites aident non seulement le routage, mais rendent aussi le comportement de votre App plus prévisible pour le produit et la QA.
7. Patterns de composition d’Apps : pipeline, handoff, shared context
Dans les scénarios Multi‑App, trois idées liées reviennent en pratique :
- pipeline — plusieurs Apps s’enchaînent (calendrier → cadeaux → commerce), chacune réalise sa propre étape ;
- handoff — la sortie d’une App devient l’entrée de la suivante ;
- shared context — tout ce transfert se fait via le contexte textuel partagé du chat, sans appels HTTP directs entre applications.
Comme nous l’avons déjà suggéré, un scénario Multi‑App n’est pas la magie « l’App A appelle l’App B en HTTP ». Dans l’implémentation actuelle des ChatGPT Apps, l’isolation est assez stricte : les applications ne s’appellent pas directement, la communication passe par le contexte textuel partagé.
On peut formuler le pattern de base ainsi :
- App A renvoie dans le chat du texte ou du JSON (souvent dans structuredContent/widget).
- Le modèle lit cette sortie.
- Au tour suivant, il peut appeler l’App B, en injectant des détails de la réponse A dans les arguments de ses tools.
On appelle cela un text/context handoff : « Sortie de l’App A → modèle → entrée de l’App B ».
Exemple : CalendarApp + GiftGenius + CommerceApp
Analysons un scénario concret.
Utilisateur : « Mon manager a son anniversaire demain, propose un cadeau et finalise tout de suite l’achat. »
Étape par étape :
-
Le modèle comprend qu’il faut d’abord connaître la date et la personne. Il appelle l’outil de l’App calendrier, par exemple corporate_calendar.list_upcoming_birthdays, et obtient la structure :
[ { "name": "Aleksey Bykov", "date": "2025-11-22", "relation": "manager" } ] -
Ensuite, le modèle décide qu’il est temps d’appeler GiftGenius. Il appelle votre suggest_gifts avec des arguments obtenus du calendrier :
{ "recipientName": "Aleksey", "occasion": "birthday", "budget": 150, "relationship": "manager" }Le widget GiftGenius affiche un carrousel de cadeaux, et la réponse textuelle explique pourquoi ces idées sont pertinentes.
-
L’utilisateur choisit une ou deux options (via le bouton du widget → widgetState), et le modèle appelle l’outil de l’App commerce, par exemple corp_checkout.create_gift_order, avec les ID des SKU sélectionnés et l’adresse de livraison.
Du point de vue de ChatGPT, ce sont trois applications différentes, mais pour l’utilisateur — une conversation unique. La clé pour que cela fonctionne :
- des descriptions nettes pour les outils de chaque App ;
- des noms soignés (corporate_calendar.list_upcoming_birthdays, et non simplement list_events) ;
- un format de données structurées cohérent (afin qu’une idée cadeau soit décrite de façon à être comprise par l’App commerce).
Schéma visuel
On peut représenter ce pipeline ainsi :
sequenceDiagram
participant U as Utilisateur
participant C as ChatGPT (Router)
participant Cal as CalendarApp
participant G as GiftGenius
participant Com as CommerceApp
U->>C: Mon manager a son anniversaire demain, propose et finalise l'achat d'un cadeau
C->>Cal: tools.call(list_upcoming_birthdays)
Cal-->>C: [{ name, date, relation }]
C->>G: tools.call(suggest_gifts, { recipient, occasion, budget })
G-->>C: gift suggestions (+ widget)
C-->>U: Explications + widget GiftGenius
U->>C: J'aime l'option #2, achète-la
C->>Com: tools.call(create_gift_order, { skuId, address })
Com-->>C: Confirmation de commande
C-->>U: C'est fait, la commande est passée
Votre tâche en tant que développeur de GiftGenius — faire en sorte que, dans ce chœur, votre voix soit claire et pertinente, sans gêner les autres.
8. Interopérabilité : rendre les réponses exploitables par d’autres Apps
Dans un monde Multi‑App, « bien répondre à l’utilisateur » ne suffit pas. Il est souhaitable que votre toolOutput puisse être traité automatiquement par une autre application : App commerce, agent analytique, orchestrateur de workflows, etc.
Cela implique quelques pratiques concrètes :
- utiliser du JSON structuré dans les réponses des outils, plutôt que du texte « humain » sérialisé ;
- s’efforcer de conserver des champs stables et compréhensibles.
Par exemple, on peut typer le résultat de suggest_gifts ainsi :
export type GiftSuggestion = {
id: string;
title: string;
description: string;
price: number;
currency: string;
forPerson: string;
occasion: string;
purchaseUrl: string;
};
Et renvoyer dans la réponse de l’outil un tableau de tels objets :
{
"gifts": [
{
"id": "sku_123",
"title": "Planétarium de bureau",
"description": "Mini-projecteur de ciel étoilé...",
"price": 89.99,
"currency": "USD",
"forPerson": "Aleksey",
"occasion": "birthday",
"purchaseUrl": "https://shop.example.com/sku_123"
}
]
}
Le widget GiftGenius prendra cela comme props et rendra joliment les cartes, et l’App commerce, voyant ce JSON dans le contexte, pourra récupérer id et purchaseUrl pour poursuivre le checkout.
La pratique des scénarios Multi‑App montre : une bonne App renvoie des données que d’autres Apps peuvent « consommer », et pas seulement les yeux d’un humain.
9. Refonte pratique de GiftGenius pour le Multi‑App
Rassemblons tout cela en quelques changements concrets dans notre application d’apprentissage.
Préciser le manifest‑description
Imaginons que nous ayons un openai-app.json (ou équivalent dans un template Next.js) avec la description :
{
"name": "GiftGenius",
"description": "Gift assistant for finding and buying presents."
}
Rendons‑le plus explicite pour le routage :
{
"name": "GiftGenius",
"description": "Assistant for gift ideas and purchase flows. Use this app when the user asks what to gift a specific person or for a specific occasion within a budget. Do not use for generic online shopping or personal finance planning."
}
Il apparaît désormais clairement qu’il ne s’agit pas d’un shopping généraliste, ni d’un conseiller financier, ni d’un calendrier.
Réécrire les descriptions des outils
Outil de recherche de cadeaux :
const suggestGiftsTool = {
name: "giftgenius_suggest_gifts",
description: `
Use this when the user asks for gift ideas for a specific person or group,
optionally with a budget or occasion.
Do not use for non-gift product recommendations or travel booking.
`
};
Outil qui récupère les détails d’un SKU depuis votre catalogue (read‑only) :
const getGiftDetailsTool = {
name: "giftgenius_get_gift_details",
description: `
Use this to fetch more details for a gift suggested earlier by GiftGenius,
for example when the user asks “tell me more about option #2”.
`,
annotations: { readOnlyHint: true }
};
L’outil d’achat — avec destructiveHint, comme déjà montré.
Mettre à jour _meta["openai/widgetDescription"]
Supposons que notre widget contienne déjà des cartes avec un CTA « Acheter ». Indiquons‑le au modèle :
const giftWidgetMeta = {
_meta: {
"openai/widgetDescription": `
Affiche une liste de cartes cadeaux avec description, prix et un bouton 'Acheter'.
Le modèle ne doit pas répéter la liste complète en texte, mais plutôt commenter le choix et aider à la décision.
`
}
};
Le modèle rédigera alors moins souvent un pavé de dix cadeaux dans le chat s’ils sont déjà visibles dans le widget, et se concentrera sur les explications et la logique — utile à la fois pour l’UX et le coût en tokens.
10. Un état d’esprit Multi‑App pour votre futur produit
Il est important de passer du mode « comment battre tous les concurrents et être la seule App de l’utilisateur » au mode « comment faire de mon App un module idéal dans un grand écosystème ».
Cette approche apporte plusieurs avantages pratiques :
- il est plus facile d’expliquer à l’utilisateur et aux réviseurs du Store à quoi sert votre App et quand elle est pertinente ;
- le modèle décide plus facilement du routage : moins de confusion, moins d’appels « hors sujet » ;
- vous pourrez concevoir consciemment des compositions : aujourd’hui avec le calendrier et le commerce, demain avec un bot RH d’entreprise ou un CRM interne.
Les guides officiels sur le discovery insistent : concevez « one job per tool » et considérez les métadonnées comme un artefact vivant, à tester et à mettre à jour, et non un texte statique du premier commit.
Ce qui aidera beaucoup, c’est ce que vous avez déjà fait dans les sujets précédents du Module 20 : golden cases, LLM‑evals, exécution en CI. Vous pouvez étendre l’ensemble de cas par des scénarios « le chat contient GiftGenius et CalendarApp » et suivre comment les changements dans les descriptions influencent le choix de l’App et la qualité des réponses.
11. Erreurs courantes avec le Multi‑App et la composition
Erreur n° 1 : description d’App « je sais tout faire ».
Si vous écrivez dans le manifest‑description quelque chose comme « assistant intelligent pour toutes les tâches », vous êtes en concurrence non seulement avec d’autres Apps, mais aussi avec le ChatGPT de base. Le routeur a du mal à comprendre quand il doit vous appeler, et quand les capacités intégrées suffisent. Dans un monde Multi‑App, les applications au positionnement clair et étroit gagnent : « sélection de cadeaux », « gestion de calendrier », « analyse de logs ».
Erreur n° 2 : descriptions d’outils floues et collisions de noms.
Des outils nommés get_data, process_request et décrits comme « traite les données de l’utilisateur » sont parfaits pour embrouiller le modèle. Dans un monde à plusieurs Apps, il est facile de se retrouver avec votre tool appelé alors que le domaine n’a rien à voir. La bonne voie — relier domaine et action (giftgenius_get_gift_catalog, calendar.list_birthdays) et décrire explicitement « Use this when… / Do not use when… ».
Erreur n° 3 : ignorer _meta["openai/widgetDescription"].
Les développeurs remplissent souvent seulement la description, et ne pensent au _meta au mieux que pour la locale. Résultat : le modèle ne comprend pas ce qu’affiche le widget et commence soit à dupliquer l’UI en texte, soit à promettre à l’utilisateur une « table des prix » que votre widget n’a pas. Quelques lignes dans widgetDescription évitent bien des malentendus.
Erreur n° 4 : absence d’annotations readOnlyHint/destructiveHint.
Si tous vos outils semblent également « neutres », le modèle ne distingue pas ceux qu’on peut appeler souvent de ceux qui exigent la confirmation de l’utilisateur. Dans des scénarios multi‑étapes avec plusieurs Apps, c’est particulièrement critique : on peut enchaîner par accident plusieurs opérations d’écriture sans participation explicite de l’humain. N’oubliez pas d’indiquer les tools read‑only et de marquer explicitement les actions consequential/destructive.
Erreur n° 5 : réponses pensées uniquement pour l’humain, pas pour d’autres Apps.
Renvoyer depuis l’outil un simple « énuméré de cadeaux » en une ligne de texte est tentant, mais toute autre App aura alors plus de mal à exploiter ce résultat. Un JSON structuré avec des champs clairs (id, price, currency, purchaseUrl, occasion) vous offre un bonus à la fois en UI et en composition : le modèle peut injecter ces données dans les arguments d’autres tools sans parser du langage naturel.
Erreur n° 6 : tenter d’implémenter dans une seule App tout ce dont l’utilisateur pourrait avoir besoin.
La tentation est grande : « puisque j’ai fait GiftGenius, qu’il gère aussi le calendrier, envoie des e‑mails aux collègues, et planifie le budget ». Dans un monde isolé, c’est encore tolérable, mais dans un contexte Multi‑App vous devenez une machine‑à‑tout‑faire qui entre en conflit avec d’autres Apps pointues et bien focalisées. Mieux vaut se fixer une règle : mon App fait X et le fait parfaitement ; le reste relève d’autres responsabilités. Un tel design simplifie beaucoup l’UX et l’évolution de l’écosystème.
Erreur n° 7 : absence de tests de comportement en présence d’autres Apps.
Les développeurs testent souvent leur application en mode Dev dans un chat « propre », où il n’y a pas d’autres Apps. Or, dans le Store, l’utilisateur peut facilement avoir une dizaine d’applications connectées, dont certaines recouvrent conceptuellement la vôtre. Prenez le temps de créer un scénario de test où le chat contient des Apps voisines (calendrier, shopping généraliste, finances), et exécutez des golden cases : le modèle choisit‑il correctement GiftGenius pour les requêtes cadeaux et ne le confond‑il pas avec d’autres participants ?
GO TO FULL VERSION