CodeGym /Cours /ChatGPT Apps /Résilience : rollback des étapes, retries et maîtrise des...

Résilience : rollback des étapes, retries et maîtrise des erreurs

ChatGPT Apps
Niveau 11, Leçon 3
Disponible

1. Pourquoi, dans une ChatGPT App, les erreurs sont la norme, pas un incident critique

Dans le cours précédent, nous avons expliqué comment découper une tâche en étapes et construire un workflow multi‑étapes dans une ChatGPT App. Ajoutons maintenant une dose de réalité : erreurs, timeouts et interruptions par l’utilisateur.

Sur le Web classique, on construit souvent la logique autour du « happy path », et les erreurs sont perçues comme quelque chose de rare et d’exceptionnel — la page rouge 500, etc. Dans une ChatGPT App, le tableau est différent : vous travaillez dans un système distribué avec une LLM, des API externes, MCP, un widget, et avec un utilisateur qui peut fermer l’onglet à tout moment. Les erreurs et interruptions sont le quotidien.

Plusieurs particularités compliquent la vie :

  • Premièrement, un LLM est non déterministe. Même avec le même prompt, il peut prendre une autre décision : appeler un autre outil, changer des paramètres, ou décider qu’il vaut mieux « redemander ».
  • Deuxièmement, il y a des contraintes réseau et d’infrastructure. Un tool‑call depuis ChatGPT a des timeouts (souvent des dizaines de secondes), tout comme votre back‑end Next.js/Vercel. Si une API externe rame, tout peut s’interrompre en plein milieu.
  • Troisièmement, il y a le facteur UX : l’utilisateur s’est distrait, a fermé le chat, est revenu un jour plus tard, et vous ne pouvez pas garder une transaction ouverte en base tout ce temps.

D’où la thèse principale du cours :

Un workflow résilient = un scénario où l’on suppose d’emblée que n’importe quelle étape peut échouer et où l’on définit explicitement ce qui se passe alors.

Une erreur n’est pas seulement un prétexte pour afficher un message à l’utilisateur, mais aussi un signal pour le modèle, qui peut changer de stratégie, proposer un rollback, essayer un autre outil ou terminer le scénario proprement.

2. Typologie des erreurs dans un workflow : lesquelles rencontre‑t‑on

Pour bien gérer les pannes, il faut d’abord apprendre à les distinguer. Dans une application LLM basée sur les ChatGPT Apps, on rencontre typiquement plusieurs classes d’erreurs.

Erreurs techniques. Toute la classique des systèmes distribués : timeouts réseau, 5xx de vos API ou d’API externes, crash du serveur MCP, bug dans le code du handler de l’outil. Par exemple, dans GiftGenius, votre outil MCP search_products interroge un catalogue qui répond 503 Service Unavailable. C’est un bon candidat pour un retry automatique.

Erreurs logiques (côté modèle). Cela inclut les refus du modèle (il juge que la requête viole une politique), les hallucinations ou, par exemple, un JSON cassé dans la réponse d’un outil. Le modèle a pu générer des arguments incorrects pour un tool‑call, et votre validation JSON les a rejetés. C’est le plus souvent une erreur de données d’entrée, pas d’infrastructure.

Erreurs métier. Elles concernent le sens : produit en rupture, budget de l’utilisateur trop faible pour les filtres choisis, code promo invalide, réservation expirée. Dans GiftGenius, c’est la situation « sur 500 candidats, aucun ne respecte les contraintes ». Ici, le retry aide rarement : il faut soit changer les paramètres, soit expliquer à l’utilisateur que la contrainte n’est pas réaliste.

Interruptions UX. L’utilisateur interrompt lui‑même le scénario : il ferme ChatGPT, clique sur « Retour » dans le widget, annule une action, modifie sa réponse à l’étape précédente. Il faut aussi considérer cela comme un flux normal, pas comme une erreur. Il est important de savoir restaurer et faire un rollback de l’état dans ces cas‑là, nous en parlerons un peu plus loin.

Un cas problématique à la frontière entre erreurs logiques et techniques — les boucles infinies de l’agent : le modèle reçoit une erreur, se dit « hum, je réessaie », de nouveau une erreur, et ainsi de suite jusqu’à épuisement du contexte ou du budget. Se protéger contre ce comportement est un volet important du design des erreurs.

3. Stratégies de base : retry, fail‑fast, rollback, implication de l’utilisateur

On peut voir toute erreur comme un point de branchement : soit on retente l’étape, soit on fait un rollback, soit on implique l’utilisateur. Et, point important, ces stratégies se combinent.

Pour les pannes techniques et temporaires (coupure réseau, API qui renvoie 503), il est logique de faire un retry limité avec backoff. Pour les erreurs logiques et métier (« le validateur n’accepte pas le budget », « les produits sont épuisés »), répéter est inutile : il faut fail‑fast et demander à l’utilisateur de modifier sa saisie ou ses paramètres.

Pour les opérations qui ont déjà modifié le monde externe (création de commande, réservation), il faut un rollback — soit comme « retour en arrière » dans l’UI/contexte, soit sous forme d’actions compensatoires réelles (annulation de commande, remboursement).

Enfin, il y a des cas qui exigent d’emblée la participation de l’utilisateur : par exemple, si le système de paiement refuse pour « carte rejetée par la banque », vous ne pouvez pas tout corriger automatiquement. Le modèle doit expliquer correctement ce qui s’est passé et proposer des options : essayer une autre carte, réduire le montant ou renoncer à l’achat.

Pour un workflow fiable, il est très utile d’écrire pour chaque étape : quels types d’erreurs sont possibles ici et que faites‑vous pour chacun d’eux — auto‑retry, rollback, sollicitation de l’utilisateur ou simple log et fin de branche.

4. Retries et backoff : quand et comment les faire

Commençons par le réflexe naturel du développeur : « Essayons simplement encore une fois. » L’idée est bonne, mais comme toujours, le diable est dans les détails.

Quelles erreurs peut‑on retenter

Une bonne heuristique issue de la pratique des intégrations : on peut retenter les erreurs réseau et les 5xx avec une pause, mais les 4xx — probablement pas.

Autrement dit, si vous avez reçu 503, 504 ou que l’API externe n’a pas répondu à temps, retenter avec un léger délai a du sens. Si le serveur a renvoyé 400 Bad Request ou 422 Unprocessable Entity, il y a fort à parier que le problème vient des données, et répéter avec les mêmes paramètres ne changera rien.

Petit utilitaire callWithRetry en TypeScript

Écrivons un petit utilitaire pour la couche MCP ou back‑end, réutilisable dans les outils :

type RetryOptions = {
  maxRetries: number;
  baseDelayMs: number;
};

async function callWithRetry<T>(
  fn: () => Promise<T>,
  { maxRetries, baseDelayMs }: RetryOptions
): Promise<T> {
  let attempt = 0;

  // nous ne voulons pas de boucles infinies
  while (true) {
    try {
      return await fn();
    } catch (err: any) {
      attempt++;
      const status = err?.status ?? err?.response?.status;

      // on ne retente pas les 4xx
      const isClientError = typeof status === "number" && status >= 400 && status < 500;
      if (attempt > maxRetries || isClientError) {
        throw err;
      }

      const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), 10_000);
      // petite pause pour éviter l'effet "troupeau" sur l'API
      const jitter = Math.random() * 200;

      await new Promise((r) => setTimeout(r, delay + jitter));
    }
  }
}

Cette fonction :

  • retente l’appel à fn un nombre limité de fois ;
  • utilise un backoff exponentiel avec un léger bruit aléatoire (jitter) pour éviter l’effet « troupeau » lors de retries simultanés ;
  • arrête les retries sur 4xx.

Elle est utile, par exemple, à l’intérieur d’un outil MCP qui interroge un catalogue de produits ou une API interne de recommandations.

Où effectuer les retries

L’erreur fréquente — tenter de répéter toutes les requêtes en chaîne, y compris à des niveaux que vous ne contrôlez pas. Dans l’écosystème ChatGPT, vous avez plusieurs endroits possibles pour les retries :

  • dans votre propre back‑end/MCP (comme nous l’avons fait dans callWithRetry) ;
  • dans un worker/une file de fond (nous parlerons plus en détail des job queues et DLQ dans de futurs modules) ;
  • parfois — dans le widget lui‑même, quand il s’agit d’une requête légère « mettre à jour la liste » sans effets de bord.

Il est important de ne pas dupliquer la logique : si votre worker fait déjà trois retries avec backoff, ce n’est pas la peine d’en rajouter cinq dans le widget. Et, bien sûr, ne faites jamais while(true) { try ... } — c’est la meilleure façon de vous infliger un DDoS.

5. Idempotence des étapes : se protéger des doublons

Les retries posent un second problème : comment éviter d’exécuter deux fois la même action. Dans le monde LLM, c’est particulièrement aigu : le modèle peut appeler par inadvertance plusieurs fois le même outil, ChatGPT peut répéter un tool‑call après un timeout, l’utilisateur peut cliquer sur « Regenerate », puis l’UI ou l’agent peuvent à leur tour ajouter un autre appel.

L’idée de l’idempotence est simple : une étape est dite idempotente si sa ré‑exécution avec les mêmes données d’entrée ne crée pas d’effets de bord supplémentaires. Demander un product feed — OK, recalculer des recommandations — OK, mais débiter à nouveau ou créer une seconde commande avec les mêmes données — pas OK du tout.

Idempotency key dans une ChatGPT App

Pattern classique : pour chaque étape logique avec effets de bord, vous générez une idempotency_key (généralement un UUID), vous la faites passer via le modèle jusqu’à l’outil MCP, où vous stockez la correspondance « clé → résultat ». Si l’outil est appelé une seconde fois avec la même clé, il ne répète pas l’action, mais renvoie simplement le résultat déjà enregistré.

Dans notre GiftGenius, il y a l’étape create_order. Imaginez que l’utilisateur clique sur le bouton « Payer », le modèle appelle l’outil, le paiement passe, mais la réponse se perd en chemin. Le modèle ou la plateforme décident de répéter l’appel, et sans idempotence, nous obtenons un doublon de commande ou un double débit.

Exemple simple d’outil idempotent en TypeScript

Faisons un handler très simplifié de l’outil MCP create_order avec une clé d’idempotence. Pour la simplicité, utilisons une Map en mémoire ; en production, ce sera une base de données ou un cache.

type CreateOrderInput = {
  userId: string;
  items: Array<{ sku: string; qty: number }>;
  idempotencyKey: string;
};

type CreateOrderResult = { orderId: string; status: "created" };

const idempotencyStore = new Map<
  string,
  { paramsHash: string; result: CreateOrderResult }
>();

export async function createOrderTool(input: CreateOrderInput): Promise<CreateOrderResult> {
  const { idempotencyKey, ...rest } = input;
  const paramsHash = JSON.stringify(rest);

  const existing = idempotencyStore.get(idempotencyKey);
  if (existing) {
    // si la clé existe déjà, on s'assure que les paramètres coïncident
    if (existing.paramsHash !== paramsHash) {
      throw new Error("Idempotency key reuse with different params");
    }
    return existing.result;
  }

  // ici nous effectuons réellement la création de la commande et le paiement
  const result: CreateOrderResult = {
    orderId: "order_" + Math.random().toString(36).slice(2),
    status: "created",
  };

  idempotencyStore.set(idempotencyKey, { paramsHash, result });
  return result;
}

Ici, nous :

  • exigeons idempotencyKey dans les données d’entrée de l’outil ;
  • stockons avec cette clé le hash des paramètres (ici, pour faire simple, JSON.stringify) ;
  • au second appel avec la même clé mais des données différentes — considérons cela comme une erreur ;
  • au second appel avec les mêmes données — renvoyons simplement le résultat précédent.

En projet réel, il convient :

  • de stocker les clés en base avec TTL (pour éviter une table infinie) ;
  • de journaliser la idempotency_key et de l’inclure dans les champs _meta des messages MCP, pour un suivi facile via l’Inspector et les dashboards.

6. Rollback d’étapes et pattern Saga

L’idempotence protège des doublons, mais ne résout pas un autre cas : que faire si une étape au milieu du scénario échoue.

En e‑commerce, c’est un classique : vous avez déjà créé une commande et réservé le stock, et quelque chose casse au niveau du paiement. Vous ne pouvez pas juste « oublier ça » — il faut revenir à l’état antérieur.

Rollback logique vs technique

Dans un workflow ChatGPT, il y a deux niveaux de rollback.

Le rollback logique — c’est le retour à l’étape précédente du scénario et l’ajustement du contexte. Par exemple, une erreur se produit à l’étape « paiement », et vous décidez de revenir à « choix du moyen de paiement » voire « choix du cadeau ». Il faut alors :

  • mettre à jour le WorkflowContext côté back‑end (étape courante, paramètres choisis) ;
  • informer le modèle du changement d’étape via un tool‑call/ToolOutput, pour qu’il « oublie » l’ancienne branche et ajuste la suite ;
  • mettre à jour l’UI du widget pour que les étapes et boutons reflètent le nouvel état.

Le rollback technique — c’est le niveau métier : annuler les entités créées, compenser les effets externes. Par exemple : annuler la commande, lever la réservation, initier un remboursement. C’est le pattern Saga : pour chaque étape « à risque », vous avez prévu une action compensatoire.

Schéma forward/compensate pour GiftGenius

Pour un checkout simplifié de GiftGenius, on peut dessiner la séquence suivante :

flowchart TD
  A[Étape 1: create_order] --> B[Étape 2: reserve_items]
  B --> C[Étape 3: charge_card]

  C -->|succès| D[Statut : terminé]

  C -->|erreur| E[Compensation : cancel_reservation]
  E --> F[Compensation : cancel_order]
  F --> G[Statut : échec + message à l’utilisateur]

À chaque action qui modifie le monde externe (création de commande, réservation, paiement) correspond une action compensatoire (annulation de commande, levée de réservation, remboursement). Elles ne sont pas toujours symétriques ni possibles un‑pour‑un, mais le principe général est celui‑ci.

Mini‑exemple avec compensation dans le code

Regardons un petit fragment de code qui exécute ces étapes :

async function completeCheckout(ctx: { userId: string }) {
  const order = await createOrderInDb(ctx.userId);

  try {
    await reserveItems(order.id);
    await chargeCard(order.id);
    return { orderId: order.id, status: "paid" as const };
  } catch (err) {
    // actions compensatoires
    await safeCancelReservation(order.id);
    await safeCancelOrder(order.id);
    throw err;
  }
}

Ici :

  • createOrderInDb, reserveItems, chargeCard — étapes forward ;
  • safeCancelReservation et safeCancelOrder — étapes compensatoires, qui doivent elles‑mêmes être idempotentes (si l’on tente d’annuler quelque chose déjà annulé, il ne se passe rien de grave).

Notez qu’en cas d’erreur, nous ne la masquons pas, mais la propageons. Le modèle (via ToolOutput) doit recevoir un message d’erreur compréhensible et l’expliquer ensuite à l’utilisateur en langage naturel, en proposant la prochaine étape.

7. Rollback d’étapes et synchronisation d’état : éviter le désalignement

Il existe un type particulier « d’erreur » qu’il est facile de sous‑estimer : le désalignement de l’état entre l’UI, le back‑end et le modèle.

Scénario typique :

  1. L’utilisateur passe les étapes 1 → 2 → 3.
  2. À l’étape 3, quelque chose se passe mal, l’utilisateur clique sur « Retour » dans le widget.
  3. Le widget remet honnêtement son état local à l’étape 2.
  4. Mais le modèle « se souvient » que nous étions à l’étape 3 et que nous avons déjà essayé de payer. Dans le message suivant, il continue à parler de paiement alors que l’utilisateur voit l’écran de choix du cadeau.

Pour éviter cela, il est utile d’introduire un événement explicite de rollback d’étape. Le widget l’envoie à MCP/au modèle — soit comme un appel d’outil, soit comme un ToolOutput.

Par exemple, on peut créer un outil simple user_navigated_to_step, qui fixe l’étape courante et son état :

type NavigateInput = {
  workflowId: string;
  stepId: string;
};

export async function userNavigatedToStep(input: NavigateInput) {
  await workflowRepo.setCurrentStep(input.workflowId, input.stepId);
  return {
    message: `User moved to step ${input.stepId}`,
  };
}

Le widget, lors d’un clic sur « Retour », appelle cet outil ; le modèle voit son résultat dans l’historique des tool‑calls et comprend qu’il faut désormais poursuivre le dialogue en fonction de la nouvelle étape.

Côté UI, cela ressemble à ce handler :

async function handleBackClick() {
  const { workflowId, prevStepId } = widgetState;

  await window.openai.tools.call("user_navigated_to_step", {
    workflowId,
    stepId: prevStepId,
  });

  setWidgetState((s) => ({ ...s, currentStepId: prevStepId }));
}

Point important : c’est le back‑end/l’agent — la source de vérité — pour l’étape courante, et le modèle la voit via les tools. Ainsi, même lors d’une restauration de session plus tard, vous pouvez synchroniser correctement le contexte.

8. UX des erreurs : ce que voit l’utilisateur et ce que voit le modèle

Nous savons déjà encaisser techniquement les erreurs (retries, rollbacks, idempotence, synchronisation d’état). Il reste à rendre cela correct pour l’utilisateur et pour le modèle.

Même un retry et un rollback impeccables ne sauveront pas un mauvais UX des erreurs « à l’ancienne servlets Java » : texte rouge, stack trace et mystérieux « Unexpected error ».

Pour une ChatGPT App, il y a deux publics pour un message d’erreur :

  • l’utilisateur, qui doit comprendre ce qui s’est passé et ce qu’il peut faire ensuite ;
  • le modèle, qui doit recevoir suffisamment d’informations structurées pour décider : retenter, changer des paramètres, proposer une alternative ou terminer le scénario.

Bonne pratique :

  • au niveau MCP/outils, retourner une erreur structurée avec code, type, indicateur retryable et un court texte technique ;
  • donner au modèle précisément cette structure (par exemple dans result.structuredContent), et non un kilomètre de stack trace ;
  • dans l’UI, afficher à l’utilisateur un message humain, court.

Mini‑exemple de structure d’erreur renvoyée par un outil :

type ToolError = {
  code: string;          // e.g. "PAYMENT_TIMEOUT"
  message: string;       // brève description technique
  retryable: boolean;    // peut-on retenter ?
};

throw {
  isError: true,
  error: <ToolError>{
    code: "PAYMENT_TIMEOUT",
    message: "Payment provider did not respond in time",
    retryable: true,
  },
};

Le modèle voit retryable : true et peut essayer un autre outil ou proposer à l’utilisateur de retenter.

Côté widget, vous mappez simplement ces codes vers des textes compréhensibles :

function ErrorBanner({ code }: { code: string }) {
  const text =
    code === "PAYMENT_TIMEOUT"
      ? "Le service de paiement n’a pas répondu à temps. Réessayez dans une minute."
      : "Un problème est survenu. Veuillez réessayer.";

  return <div className="error-banner">{text}</div>;
}

Autre point important : ne montrez pas à l’utilisateur des traces de pile d’exceptions, des tokens, des secrets. C’est à la fois inesthétique et peu sûr. Les informations techniques doivent être consignées chez vous, et l’utilisateur doit recevoir un message court et sûr.

Insight

Dans les systèmes LLM comme ChatGPT, les appels d’outils incorrects sont plutôt la norme que l’exception. Le modèle génère régulièrement des arguments qui ne passent pas la validation : types inversés, champs manquants, valeurs erronées, structures cassées. Ce n’est pas une erreur au sens ingénierie habituel — c’est la nature d’un modèle stochastique, et tout l’interface d’erreurs doit s’y adapter.

Idée clé : un message d’erreur n’est pas un signal « cassé », mais une instruction pour corriger la prochaine tentative. Son public principal — le modèle lui‑même. Si le message est structuré et contient des indications précises, le modèle peut ajuster automatiquement les paramètres et répéter l’appel correctement. C’est justement le principe des techniques de Tool‑Reflection : un feedback correct améliore l’action suivante de l’agent sans intervention humaine.

Je recommande de respecter ces exigences de format d’erreur :

  • le message doit indiquer le champ précis qui n’a pas passé la validation — pas des généralités de type « Invalid parameters » ;
  • il est important de décrire explicitement le format attendu ou les valeurs autorisées, pour que le modèle choisisse correctement ;
  • le message doit être court, formel et structuré : des champs comme error_type, field, expected ou allowed_values aident beaucoup le modèle ;
  • si possible, donnez un exemple minimal d’entrée correcte — cela augmente souvent la précision de la restauration par le modèle.

Le feedback d’erreur idéal pour le modèle contient deux faits : ce qui s’est mal passé, et l’instruction pour le corriger.

9. Journalisation et métriques des erreurs du workflow

Même si l’UX des erreurs est soigné, pour comprendre ce qui casse réellement, les seuls messages à l’utilisateur ne suffisent pas. Il faut des logs structurés et des métriques par étape.

Jeu minimal utile lors de la journalisation de chaque étape du workflow :

  • user_id ou au moins session_id ;
  • workflow_id et step_id ;
  • statut de l’étape (success, failed, retry, rolled_back) ;
  • error_code (le cas échéant) ;
  • idempotency_key et correlation_id, si l’étape est liée à des appels externes.

Dans MCP et Agents, il existe des champs _meta ; il est pratique d’y placer idempotency_key et correlation_id, pour les voir à la fois dans les logs et dans l’Inspector.

Exemple très simple de journalisation en Node.js/TypeScript (vous pouvez utiliser console, ou winston/pino) :

function logStepFailure(params: {
  userId?: string;
  workflowId: string;
  stepId: string;
  errorCode: string;
  idempotencyKey?: string;
}) {
  console.error(
    JSON.stringify({
      level: "error",
      event: "workflow_step_failed",
      ...params,
      timestamp: new Date().toISOString(),
    })
  );
}

Ces logs se parsant facilement, on peut bâtir des dashboards et calculer :

  • la conversion entre étapes ;
  • les types d’erreurs les plus fréquents ;
  • la part des étapes terminées en retry vs échec définitif.

Toutes les erreurs ne doivent pas devenir des alertes en production. Les critiques — crash de MCP, timeouts systématiques, échecs massifs sur une étape donnée — oui, il faut les monitorer. En revanche, « aucun cadeau trouvé pour des filtres trop serrés » — c’est un événement métier, pas un incident.

10. Faire évoluer GiftGenius : une étape de checkout résiliente

Rassemblons maintenant tout : retries, idempotence, Saga, synchronisation d’état, UX des erreurs et journalisation — sur un exemple d’étape de notre application pédagogique GiftGenius : le passage de commande.

Ce que nous avons déjà

À ce stade, nous avons :

  • un workflow multi‑étapes : collecte d’informations → génération d’idées → choix du cadeau → checkout ;
  • un tool gating configuré : à l’étape de checkout, seul un ensemble d’outils commerce est disponible (create_order, get_payment_methods, etc.) ;
  • un WorkflowContext qui contient le cadeau choisi, le budget, le userId et l’étape courante.

Ce que nous ajoutons dans ce cours

Pour l’étape de checkout, nous allons intégrer :

  1. idempotency_key pour l’outil create_order ;
  2. retry en cas d’erreurs temporaires du prestataire de paiement ;
  3. compensation en cas d’opérations partiellement réussies ;
  4. un UX d’erreurs correct dans le widget.

Génération de la clé d’idempotence dans le widget au clic sur « Payer » :

import { v4 as uuid } from "uuid";

async function handlePayClick() {
  const idempotencyKey = uuid();
  setWidgetState((s) => ({ ...s, idempotencyKey }));

  await window.openai.tools.call("create_order", {
    userId: widgetState.userId,
    items: [/* ... */],
    idempotencyKey,
  });
}

Côté outil create_order — le handler idempotent que nous avons écrit plus haut : il stocke la clé et le résultat, et ne crée pas de nouvelle commande en cas de répétition.

Le code d’intégration avec l’API de paiement peut être encapsulé dans callWithRetry, pour tenter plusieurs fois en cas de glitch réseau. Et n’oubliez pas d’ajouter le flag retryable : true à l’erreur, pour que le modèle comprenne qu’on peut proposer de retenter.

Si, après la création réussie de la commande et le débit, quelque chose casse (par exemple, un webhook externe n’arrive pas à temps), nous journalisons cela avec un correlation_id et un workflow_id et ensuite :

  • nous tentons un retry en tâche de fond (dans un module futur sur les files et événements) ;
  • ou nous marquons explicitement l’étape comme failed, lançons les actions compensatoires et expliquons à l’utilisateur ce qui s’est passé.

11. Erreurs typiques lors de la conception de workflows résilients

Erreur n° 1 : « On retente tout jusqu’à ce que ça marche ».
Répéter automatiquement n’importe quelle étape jusqu’à la réussite — moyen sûr de se créer un enfer local. Les erreurs réseau et 5xx peuvent être retentées avec backoff et limite d’essais. Mais les 4xx, erreurs métier et échecs logiques du modèle doivent être corrigés par les données ou expliqués à l’utilisateur. Sinon, vous obtiendrez un comportement instable, des factures étranges et des logs pollués.

Erreur n° 2 : absence d’idempotence là où il y a de l’argent et des commandes.
Si un outil comme create_order ou charge_card n’est pas idempotent, tout appel répété (suite à un timeout, à un Regenerate, à un bug de l’agent) peut conduire à des doublons. Dans les scénarios LLM, les répétitions sont nettement plus fréquentes que dans un front‑end REST classique, donc la idempotency_key n’est pas un « bonus sympa », c’est indispensable pour les étapes critiques de paiement et autres.

Erreur n° 3 : pas d’actions compensatoires (absence de Saga).
Vous avez créé une commande, réservé le produit, et au paiement vous échouez et affichez simplement « un problème est survenu ». Résultat : des demi‑commandes, des réservations pendantes, des « queues » financières. Pour chaque étape qui modifie le monde externe, il faut prévoir ce que vous ferez si l’étape suivante échoue : annuler, rembourser, marquer « expired », etc.

Erreur n° 4 : laisser l’agent partir en boucle infinie de retries.
Si vous ne limitez pas le nombre d’essais (par exemple via maxRetries dans vos helpers ou via max_iterations dans la logique d’agent) et si vous ne marquez pas les erreurs comme retryable : false là où les retries sont inutiles, le modèle peut boucler : « Je réessaie… encore… ». Cela brûle des tokens, du temps et des nerfs.

Erreur n° 5 : désalignement d’état entre l’UI et le modèle lors d’un rollback.
Les développeurs implémentent souvent le bouton « Retour » uniquement dans l’UI, en oubliant de synchroniser l’étape avec le back‑end et le modèle. Au final, l’utilisateur voit l’étape 2, tandis que le modèle vit encore à l’étape 3 et fait des propositions étranges. Solution — des événements explicites comme user_navigated_to_step et la mise à jour du WorkflowContext à chaque transition.

Erreur n° 6 : messages techniques pour l’utilisateur et absence de logs pour les développeurs.
L’utilisateur reçoit « Error: ECONNRESET at TcpSocket.onEnd… », et vous — zéro information sur quelle étape et pour quel workflow_id a cassé. Bonne approche : pour l’utilisateur — un texte court, clair et une proposition de suite ; pour le développeur — un log structuré avec workflow_id, step_id, error_code, idempotency_key et correlation_id.

Erreur n° 7 : absence de stratégie d’alerting.
Soit on alerte sur tout, y compris « aucun cadeau correspondant à votre filtre très étroit », soit on n’alerte sur rien, y compris un vrai crash de MCP. Essayez de distinguer les pannes systèmes critiques (chute d’un service, timeouts massifs, perte de webhooks) des événements métier attendus. Les premiers vont dans le monitoring et l’on‑call, les seconds — simplement dans l’analytics.

1
Mission
ChatGPT Apps, niveau 11, leçon 3
Bloqué
Erreur structurée de l'outil pour une saisie invalide
Erreur structurée de l'outil pour une saisie invalide
1
Mission
ChatGPT Apps, niveau 11, leçon 3
Bloqué
Relance de requêtes avec backoff pour une API instable
Relance de requêtes avec backoff pour une API instable
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION