Écrire le gestionnaire d’actions
Après avoir créé une action dans l’interface utilisateur de Adobe LLM Apps, les métadonnées sont stockées dans l’API LLM Apps, mais il n’y a pas encore de code derrière. Ce guide vous guide tout au long de l’écriture de la fonction de gestionnaire qui s’exécute lorsqu’une plateforme LLM (telle que ChatGPT ou Claude) appelle votre action.
Pour plus d’informations sur la disposition du projet, le développement local et les tests, voir Développement.
Contrat de développement
Vous écrivez uniquement des gestionnaires. Tous les autres éléments (nom de l’action, description, schéma d’entrée, annotations, visibilité du widget, autorisations, CSP) résident dans l’interface utilisateur de LLM Apps et sont automatiquement transmis au runtime au moment du déploiement. Vous ne modifiez jamais manuellement les métadonnées dans votre référentiel et vous n’enregistrez jamais un outil dans le code.
actions/<name>/index.jsactions.json (instantané de métadonnées)Prise en main
Votre référentiel lié a besoin de la structure du projet avant de pouvoir écrire des gestionnaires. Clonez le modèle standard Applications Adobe LLM pour commencer avec un point de départ vide.
Envoyez le contenu au référentiel que vous avez lié lors de la création de l’application (par exemple, your-org/your-repo).
Une fois le code en place, exécutez :
npm install
Cela installe toutes les dépendances, y compris @adobe/llm-apps-runtime, le runtime qui gère la communication du protocole MCP, la découverte des actions et le routage des requêtes. Vous n’interagissez pas directement avec le runtime ; il est utilisé par les entry.js au moment de la création.
.claude/skills/llm-apps-action-author/. Il peut créer un modèle automatique pour de nouvelles actions, générer des fichiers de test, valider des formes de gestionnaire et vous guider tout au long du contrat de gestionnaire, le tout à partir de votre éditeur. Pour l'utiliser, demandez à Claude de « ajouter une action appelée search-products » et elle suivra automatiquement les conventions de projet correctes.Contrat de gestionnaire
Un gestionnaire est un fichier unique au actions/<name>/index.js qui exporte une fonction asynchrone :
module.exports = async (args) => {
return {
content: [{ type: 'text', text: 'response for the LLM' }],
structuredContent: { /* data for the widget */ }
}
}
La fonction reçoit les arguments d’entrée de l’action sous la forme d’un objet simple ; il s’agit des paramètres que vous avez définis dans la boîte de dialogue Créer une action . Le serveur les valide par rapport au schéma d’entrée avant l’appel de votre gestionnaire.
content (obligatoire)
Tableau de parties de contenu envoyées aux hôtes LLM et texte uniquement. C’est ce que la plateforme LLM lit pour formuler sa réponse.
content: [
{ type: 'text', text: 'Found 5 products matching category "bagged-coffee".' }
]
Renvoyez toujours content : il s’agit de la solution de secours universelle pour tout hôte.
structuredContent
Un objet JavaScript brut envoyé au widget. Ces données n’ont aucun coût de jeton — elles sont utilisées par le bloc de widget EDS pour effectuer le rendu d’une interface utilisateur riche comme un carrousel de produit ou une carte.
structuredContent: {
products: [
{ name: 'Product A', category: 'bagged-coffee', imageUrl: '...' },
{ name: 'Product B', category: 'bagged-coffee', imageUrl: '...' }
],
total: 2,
category: 'bagged-coffee'
}
La structure dépend de vous : elle doit correspondre à ce que votre bloc de widget EDS attend via bridge.toolResult.
structuredContent doit être un objet simple, et non un tableau nu._meta (facultatif)
Métadonnées supplémentaires envoyées avec le résultat. La touche openai/widgetDescription indique à la plateforme LLM comment présenter le widget :
_meta: {
'openai/widgetDescription': 'The widget displays a scrollable product carousel. '
+ 'Do NOT repeat the product list. Instead, highlight one or two recommendations.'
}
Exemple : gestionnaire de recherches de produits
Voici un exemple de gestionnaire de search-products. Elle accepte un filtre de category facultatif et un query de texte libre, effectue une recherche dans un catalogue de produits et renvoie à la fois un résumé textuel pour le LLM et des données structurées pour le carrousel de widgets.
// actions/search-products/index.js
const PRODUCTS = [
{
name: 'Product A',
description: 'A short description of Product A.',
category: 'bagged-coffee',
sub_category: 'dark-roast',
image_url: 'https://www.example.com/products/product-a/hero.jpg',
url: 'https://www.example.com/products/product-a',
productId: 'PROD-001',
rating: 4.7,
reviewCount: 58
},
// ... more products
];
const WIDGET_DESCRIPTION = 'The widget displays a scrollable product carousel '
+ 'with images, star ratings, and review counts. Do NOT repeat the product list.';
module.exports = async ({ category = '', query = '' } = {}) => {
let results = PRODUCTS;
if (category) {
const categoryLower = category.toLowerCase();
results = results.filter((p) =>
p.category.toLowerCase().includes(categoryLower)
|| p.sub_category.toLowerCase().includes(categoryLower)
);
}
if (query) {
const queryLower = query.toLowerCase();
results = results.filter((p) =>
p.name.toLowerCase().includes(queryLower)
|| p.description.toLowerCase().includes(queryLower)
);
}
const products = results.map((p) => ({
productId: p.productId,
name: p.name,
shortDescription: p.description,
category: p.category,
rating: p.rating,
reviewCount: p.reviewCount,
imageUrl: p.image_url,
productUrl: p.url,
}));
if (products.length === 0) {
return {
content: [{ type: 'text', text: `No products found for "${category}".` }],
structuredContent: { products: [], total: 0, category: null },
_meta: { 'openai/widgetDescription': WIDGET_DESCRIPTION }
};
}
return {
content: [
{ type: 'text', text: `Found ${products.length} product(s) in "${category}".` }
],
structuredContent: { products, total: products.length, category },
_meta: { 'openai/widgetDescription': WIDGET_DESCRIPTION }
};
};
Que se passe-t-il au moment de l’exécution :
- Un utilisateur demande à la plateforme LLM « Montrez-moi vos produits à base de café ».
- La plateforme LLM correspond à l’intention de Rechercher des produits et extrait les
category. - Le serveur MCP appelle votre gestionnaire avec
{ category: 'bagged-coffee' }. - Votre gestionnaire filtre le catalogue et renvoie
content(résumé textuel pour le LLM) +structuredContent(tableau de produits pour le widget). - La plateforme LLM affiche la réponse textuelle et transmet les données structurées au widget EDS, qui effectue le rendu d’un carrousel de produits.
Que se passe-t-il si le gestionnaire est absent ?
Si vous avez défini une action dans l’interface utilisateur, mais que vous n’avez pas encore créé le fichier de gestionnaire, l’action est toujours enregistrée au moment du déploiement. Les appels utilisent un gestionnaire de stub par défaut qui renvoie du contenu vide jusqu’à ce que vous ajoutiez le code réel. Cela signifie que vous pouvez d’abord définir toutes vos actions dans l’interface utilisateur et les implémenter de manière incrémentielle.