Actions : connecter votre backend
Maîtriser les Actions du Apps SDK
Les Actions sont le cœur de toute application ChatGPT non triviale. Elles permettent au modèle d’exécuter du code côté serveur, d’interroger des API externes et de manipuler des données en temps réel. Cette leçon approfondit leur fonctionnement.
Anatomie d’une action
Une action se compose de quatre éléments :
import { defineAction } from "@openai/apps-sdk";
export const myAction = defineAction({
// 1. Identité
name: "searchProducts",
description: "Recherche des produits dans le catalogue par mot-clé et catégorie",
// 2. Paramètres (schema JSON)
parameters: {
query: {
type: "string",
description: "Terme de recherche",
required: true,
},
category: {
type: "string",
enum: ["electronics", "books", "clothing"],
description: "Catégorie de produits",
},
limit: {
type: "number",
description: "Nombre maximum de résultats",
default: 10,
},
},
// 3. Handler (logique métier)
handler: async ({ query, category, limit }) => {
const results = await db.products.search({ query, category, limit });
return { products: results, total: results.length };
},
// 4. Métadonnées (optionnel)
rateLimit: { maxCalls: 10, windowMs: 60000 },
cacheTtl: 300,
});
La description, c’est critique
Le modèle utilise la description pour décider quand appeler votre action. Une description vague produit des appels erronés. Soyez précis et explicite sur ce que l’action fait et ne fait pas.
// Mauvais — trop vague
description: "Cherche des trucs"
// Bon — précis et contextuel
description: "Recherche des produits dans le catalogue e-commerce par mot-clé. Supporte le filtrage par catégorie. Retourne le nom, prix et disponibilité."
Appels HTTP vers votre API
La plupart des actions font des requêtes vers votre backend existant :
export const createOrder = defineAction({
name: "createOrder",
description: "Crée une commande pour les produits sélectionnés",
parameters: {
items: {
type: "array",
items: {
type: "object",
properties: {
productId: { type: "string" },
quantity: { type: "number" },
},
},
required: true,
},
shippingAddress: {
type: "string",
description: "Adresse de livraison complète",
required: true,
},
},
handler: async ({ items, shippingAddress }, context) => {
const userId = context.user.id;
const response = await fetch("https://api.monsite.com/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${context.auth.accessToken}`,
},
body: JSON.stringify({ userId, items, shippingAddress }),
});
if (!response.ok) {
throw new ActionError("ORDER_FAILED", "Impossible de créer la commande");
}
const order = await response.json();
return {
orderId: order.id,
total: order.total,
estimatedDelivery: order.deliveryDate,
};
},
});
Gestion des erreurs
Les erreurs dans les actions doivent être explicites pour que le modèle puisse informer correctement l’utilisateur :
import { ActionError } from "@openai/apps-sdk";
handler: async ({ productId }) => {
try {
const product = await fetchProduct(productId);
if (!product) {
throw new ActionError(
"NOT_FOUND",
`Le produit ${productId} est introuvable dans notre catalogue`
);
}
if (!product.inStock) {
throw new ActionError(
"OUT_OF_STOCK",
`${product.name} est actuellement en rupture de stock`
);
}
return product;
} catch (error) {
if (error instanceof ActionError) throw error;
throw new ActionError("INTERNAL", "Erreur technique, réessayez plus tard");
}
}
Types d’erreurs
| Code | Usage | Comportement du modèle |
|---|---|---|
| NOT_FOUND | Ressource introuvable | Suggère des alternatives |
| AUTH_REQUIRED | Authentification nécessaire | Déclenche le flux OAuth |
| RATE_LIMITED | Trop de requêtes | Informe et attend |
| INTERNAL | Erreur serveur | Message générique |
Actions composées
Vous pouvez chaîner plusieurs opérations dans une action :
export const checkoutFlow = defineAction({
name: "checkout",
description: "Valide le panier, vérifie le stock et crée la commande",
parameters: {
cartId: { type: "string", required: true },
},
handler: async ({ cartId }, context) => {
// Étape 1 : Récupérer le panier
const cart = await getCart(cartId);
// Étape 2 : Vérifier le stock pour chaque article
const stockCheck = await Promise.all(
cart.items.map((item) => checkStock(item.productId, item.quantity))
);
const outOfStock = stockCheck.filter((s) => !s.available);
if (outOfStock.length > 0) {
return {
status: "stock_issue",
unavailable: outOfStock.map((s) => s.productName),
};
}
// Étape 3 : Calculer le total avec promotions
const total = await calculateTotal(cart, context.user.id);
return {
status: "ready",
items: cart.items.length,
total: total.amount,
currency: "EUR",
};
},
});
Mise en pratique
Créez une action getUserProfile qui :
- Reçoit un
userIden paramètre - Appelle votre API pour récupérer le profil
- Retourne le nom, l’email et la date d’inscription
- Gère le cas où l’utilisateur est introuvable avec une
ActionError
Points clés à retenir
- La description de l’action guide le modèle — soyez précis
- Les paramètres utilisent un schema JSON typé avec validation automatique
- Utilisez
ActionErrorpour des erreurs explicites que le modèle peut interpréter - Le
contextfournit l’identité de l’utilisateur et les tokens d’authentification - Les actions composées permettent de chaîner plusieurs opérations en une seule étape