Aller au contenu principal

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 :

  1. Reçoit un userId en paramètre
  2. Appelle votre API pour récupérer le profil
  3. Retourne le nom, l’email et la date d’inscription
  4. 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 ActionError pour des erreurs explicites que le modèle peut interpréter
  • Le context fournit 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