CodeGym /Cours /ChatGPT Apps /Description des outils : JSON Schema, typage, annotations...

Description des outils : JSON Schema, typage, annotations

ChatGPT Apps
Niveau 4 , Leçon 1
Disponible

1. L’outil comme contrat : ce que nous décrivons précisément

Lorsque vous enregistrez un outil sur un serveur MCP, vous le décrivez à l’aide d’un petit objet. La structure simplifiée pour le TypeScript‑SDK ressemble à ceci :

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description: "Sélectionne des cadeaux selon le profil du destinataire.",
    inputSchema: {
      type: "object",
      // c’est ici que nous allons approfondir
    },
  },
  async ({ input }) => {
    // votre code
  }
);

Le modèle ne sait pas ce qu’il y a à l’intérieur du gestionnaire async ({ input }) => { ... }. Pour lui, il n’y a que trois éléments :

  1. name/title — comment s’appelle l’outil.
  2. description — quand il est pertinent de l’utiliser.
  3. inputSchema — quels arguments fournir et dans quel format.

Tout ce que nous faisons dans ce cours concerne le point 3 (et un peu les métadonnées _meta/annotations, dont nous parlerons plus tard).

Il est important de comprendre : dans le contexte d’une ChatGPT App, JSON Schema n’est pas un validateur ennuyeux, mais une partie du prompt pour le modèle. Le modèle lit réellement le description des champs, comprend ce qu’est un enum, remarque minItems, format, etc.

Autrement dit, vous ne faites pas que protéger le back‑end contre des données bancales, vous expliquez au modèle d’IA comment appeler correctement votre fonction.

2. JSON Schema de base pour l’outil suggest_gifts

Commençons simple. Disons que nous avons le scénario suivant :

L’utilisateur écrit :
« Choisis un cadeau pour mon frère de 25 ans, budget 50–70 dollars, il aime les jeux vidéo et les jeux de société. »

L’outil suggest_gifts doit accepter à peu près les arguments suivants :

  • l’âge du destinataire ;
  • le type de relation (frère, collègue, partenaire, etc.) ;
  • le budget minimal et maximal ;
  • la liste des centres d’intérêt.

Décrivons cela comme un JSON Schema « brut », sans Zod, avec un objet simple :

const suggestGiftsInputSchema = {
  type: "object",
  properties: {
    age: {
      type: "integer",
      minimum: 0,
      maximum: 120,
      description: "Âge du destinataire du cadeau, en années.",
    },
    relationship: {
      type: "string",
      enum: ["friend", "partner", "sibling", "colleague", "parent"],
      description:
        "Type de relation avec le destinataire : friend, partner, sibling (frère/sœur), colleague, parent.",
    },
    minBudget: {
      type: "number",
      minimum: 0,
      description: "Budget minimal dans la devise de l’utilisateur.",
    },
    maxBudget: {
      type: "number",
      minimum: 0,
      description: "Budget maximal dans la devise de l’utilisateur.",
    },
    interests: {
      type: "array",
      items: {
        type: "string",
        description:
          "Intitulé court de l’intérêt, par exemple: videogames, boardgames, books.",
      },
      minItems: 1,
      description: "Liste des centres d’intérêt du destinataire.",
    },
  },
  required: ["relationship", "maxBudget"],
};

Quelques points importants à préciser immédiatement.

Premièrement, le description des champs. Dans une API habituelle, vous pourriez vous en passer — un développeur front end lira Swagger et comprendra. Mais ici, le « client » est le modèle, qui tente d’inférer le sens à partir du nom et de la description. Plus vous direz clairement : « âge en années », « budget dans la devise de l’utilisateur », « enum avec des valeurs fixes », moins vous verrez d’arguments étranges à l’exécution.

Deuxièmement, enum est l’un des moyens les plus puissants pour guider le modèle. Si vous autorisez le modèle à mettre n’importe quelle chaîne dans relationship, vous obtiendrez « bro », « girlfriend », « bestie », « teammate » et encore plus créatif. Si vous définissez un enum, le modèle choisira, avec une très forte probabilité, uniquement parmi ces valeurs. C’est une réduction directe des « hallucinations » dans les arguments.

Troisièmement, il n’est pas obligatoire de tout mettre en required. Par exemple, age peut être facultatif : si l’utilisateur ne l’indique pas, le modèle n’inventera pas un « âge approximatif » (si vous formulez la description en ce sens). C’est là que commence l’art : trouver un équilibre entre flexibilité et rigueur.

Utilisons maintenant ce schéma lors de l’enregistrement de l’outil :

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description:
      "Propose des idées de cadeaux selon le budget, le type de relation et les centres d’intérêt du destinataire.",
    inputSchema: suggestGiftsInputSchema,
  },
  async ({ input }) => {
    // ici, input correspond déjà à peu près au schéma
    // ...
  }
);

Un objet « manuel » de ce type convient bien pour des expérimentations rapides. Mais à mesure que l’application grandit, il devient un monde à part, qui diverge facilement de vos types TypeScript. Nous reviendrons un peu plus tard sur ce problème et verrons comment le résoudre avec Zod et la génération de JSON Schema à partir des types.

3. JSON Schema comme prompt : comment rédiger description pour éviter les erreurs du modèle

Formellement, JSON Schema concerne la validation. Informellement, dans le monde des LLM, c’est aussi un prompt structuré. Quelques règles pratiques :

  1. Le champ description doit répondre à la question « quoi mettre ici et dans quel format ».
    Une formulation comme « Date » n’aide pas. « Date ISO 8601 au format YYYY-MM-DD, par exemple "2025-02-14" » — aide énormément.
  2. Si le champ est lié à de l’argent — précisez l’unité.
    Mieux vaut écrire explicitement « Montant dans la devise de l’utilisateur » ou « Montant en dollars américains ». Sinon, le modèle pourra simplement écrire 50 et vous vous demanderez si c’est 50 yens ou 50 euros.
  3. Les « catégories » de type chaîne sont presque toujours mieux gérées via enum.
    Si le champ est une chaîne représentant une « catégorie », mieux vaut créer un enum et décrire chaque valeur dans le description de l’outil. Par exemple, pour relationship, on peut écrire dans la description de l’outil : « relationship : l’un de friend (ami(e)), partner (partenaire), sibling (frère ou sœur), colleague (collègue), parent (parent). N’invente pas d’autres valeurs. »
  4. Pour les tableaux, il est utile de définir minItems et d’expliquer de quel type de liste il s’agit.
    Si le champ est un tableau, indiquez minItems et expliquez brièvement de quelle liste il s’agit. Par exemple, interests — ce n’est pas une « description libre de la personne », mais un « ensemble de courts tags ».

Tout cela peut sembler tatillon, mais en pratique, la différence entre « avec descriptions » et « sans descriptions » — c’est la différence entre une application stable et une loterie permanente « qu’est-ce que le modèle va envoyer aujourd’hui ».

Insight

Les outils MCP ont des limites de taille strictes — et ce sont précisément elles qui deviennent le plus souvent la cause de plantages « mystiques », d’erreurs étranges et du fait que l’assistant cesse soudainement de voir vos tools.

La règle clé est simple : l’outil doit tenir en ~4 KB de JSON au total. Ce n’est pas seulement le texte de description, mais toute la structure :

  • la description de l’outil,
  • le schéma des arguments (inputSchema),
  • les objets imbriqués et les enum,
  • _meta et les annotations.

Si votre outil enfle, la plateforme se met à se comporter de manière imprévisible : des erreurs apparaissent comme "Tool description is too long", "Schema validation failed", "Manifest exceeds size limits", et parfois ChatGPT arrête simplement de charger l’outil ou « oublie » son existence.

Recommandation : gardez le description entre 10002000 caractères, et l’outil entier dans une limite « sûre » d’environ ~4 KB. Si la description devient trop longue, c’est presque toujours le signe que l’outil fait trop de choses à la fois. Les outils séparés doivent être étroits et très clairs — ainsi le modèle comprend mieux leurs limites et se trompe moins sur les données d’entrée.

4. TypeScript et Zod : une seule source de vérité au lieu de deux

Écrire un JSON Schema « à la main » est pénible pour un développeur TypeScript. Il faut maintenir deux mondes parallèles :

  • les types dans le code TS ;
  • le JSON Schema pour le modèle.

À mesure que l’application grandit, ils commencent à diverger. Aujourd’hui vous changez un champ dans un type TypeScript, demain vous oubliez de mettre à jour le schéma — et dans une semaine vous avez un plantage en prod.

L’approche standard de facto dans l’écosystème TS est d’utiliser Zod et la conversion Zod -> JSON Schema.

Installons les dépendances (si ce n’est pas déjà fait) :

npm install zod zod-to-json-schema

Décrivons le schéma d’entrée pour suggest_gifts avec Zod :

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const SuggestGiftsInputZod = z.object({
  age: z
    .number()
    .int()
    .min(0)
    .max(120)
    .describe("Âge du destinataire du cadeau, en années."),
  relationship: z
    .enum(["friend", "partner", "sibling", "colleague", "parent"])
    .describe(
      "Type de relation : friend (ami(e)), partner (partenaire), sibling (frère/sœur), colleague (collègue), parent (parent)."
    ),
  minBudget: z
    .number()
    .min(0)
    .optional()
    .describe("Budget minimal dans la devise de l’utilisateur."),
  maxBudget: z
    .number()
    .min(0)
    .describe("Budget maximal dans la devise de l’utilisateur."),
  interests: z
    .array(
      z
        .string()
        .min(1)
        .describe(
          "Étiquette courte d’intérêt, par exemple: videogames, boardgames, books."
        )
    )
    .min(1)
    .describe("Liste des centres d’intérêt du destinataire."),
});

Vous avez maintenant :

  1. Validation à l’exécution : SuggestGiftsInputZod.parse(input) ;
  2. Type TypeScript : type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;
  3. JSON Schema pour le modèle : zodToJsonSchema(SuggestGiftsInputZod).

Utilisons cela lors de l’enregistrement de l’outil :

type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;

const suggestGiftsInputSchemaJson = zodToJsonSchema(
  SuggestGiftsInputZod,
  "SuggestGiftsInput"
);

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description:
      "Propose des idées de cadeaux selon le budget, le type de relation et les centres d’intérêt du destinataire.",
    inputSchema: suggestGiftsInputSchemaJson,
  },
  async ({ input }) => {
    // ici, input peut en plus être validé par Zod :
    const args = SuggestGiftsInputZod.parse(input) as SuggestGiftsInput;

    // on travaille ensuite avec args typé
  }
);

Cette approche fournit justement la source unique de vérité : vous décrivez le schéma une fois, et le type TypeScript ainsi que le JSON Schema sont générés automatiquement.

Dans le monde réel, vous ajouterez aussi des tests qui vérifient que zodToJsonSchema produit la structure attendue, mais cela relève du module sur les tests.

Insight: ChatGPT gère mal les paramètres optionnels

L’une des douleurs en production : dès que vous utilisez activement des champs optional dans les schémas d’outils, la qualité des tool‑calls chute sensiblement. Le modèle « comprend » théoriquement ce que sont des paramètres facultatifs, mais en pratique il ne les envoie souvent pas du tout — même lorsque, d’un point de vue métier, vous en avez vraiment besoin.

Cette problématique a été élégamment traitée dans le Response API : on a simplement supprimé les champs optionnels — tous les paramètres d’un outil doivent être déclarés comme required. Mais le problème demeure : l’idée « je marque la moitié des champs comme facultatifs et le modèle décidera lui‑même quoi remplir » se fracasse contre la réalité : en général, il n’envoie rien.

5. Où s’arrête le « schéma » et où commence le « design d’interface »

Jusqu’ici, nous avons parlé de inputSchema — c’est‑à‑dire des arguments que le modèle doit générer pour lancer l’outil. Mais après l’appel de l’outil, la vie continue : il faut encore afficher le résultat dans l’UI.

Il est utile de distinguer deux niveaux :

  • Le schéma de l’outil décrit les arguments d’entrée que le modèle doit générer. C’est toujours du JSON, qui vit dans l’espace MCP / tool‑call.
  • Le composant UI (widget) lit toolOutput.structuredContent et construit l’interface sur cette base. Le format de structuredContent, vous le concevez également, mais ce n’est plus le JSON Schema pour le modèle (même si vous pouvez aussi le formaliser pour vous‑mêmes).

Parfois, les développeurs essaient de faire d’une pierre deux coups avec un seul objet JSON — fusionner les entrées pour le modèle et le format de données pour l’UI. Cela se termine rarement bien. Il est plus confortable de séparer :

  • inputSchema — ce dont le modèle a besoin pour lancer l’outil ;
  • structuredContent — ce dont l’UI a besoin pour afficher le résultat.

Par exemple, inputSchema pour suggest_gifts ne contient aucun id de cadeaux. À l’inverse, structuredContent contient une liste de cartes avec id, title, price, un lien d’achat, etc.

6. Annotations et _meta : comment influer sur l’UX et la sécurité

Au‑delà du schéma de paramètres et de la structure de la réponse, il existe une autre couche — la manière dont la plateforme considère l’outil et l’affiche à l’utilisateur. Cela relève des métadonnées et des annotations.

Outre les champs standard title, description, inputSchema, un outil peut avoir des métadonnées supplémentaires et des annotations. Dans Apps SDK et MCP, une partie de ces éléments vit dans _meta (par exemple, securitySchemes), et d’autres dans des champs dédiés comme des indices spécifiques à OpenAI tels que readOnlyHint et destructiveHint.

Point important : ces annotations ne changent pas le JSON Schema, mais influencent la façon dont ChatGPT présente l’outil à l’utilisateur et la manière dont il considère son appel.

Exemple : readOnlyHint et destructiveHint

Supposons que vous avez deux outils :

  • list_gifts — obtenir simplement une liste de cadeaux (sans danger) ;
  • create_order — créer une commande (potentiellement dangereux : argent, adresse, etc.).

Vous pouvez les annoter ainsi (pseudo‑code) :

server.registerTool(
  "list_gifts",
  {
    title: "List gift suggestions",
    description: "Récupère la liste des cadeaux disponibles selon les filtres fournis.",
    inputSchema: listGiftsInputSchema,
    _meta: {
      readOnlyHint: true,
    },
  },
  async ({ input }) => { /* ... */ }
);

server.registerTool(
  "create_order",
  {
    title: "Create gift order",
    description:
      "Crée une commande pour un cadeau spécifique au nom de l’utilisateur. À utiliser uniquement après confirmation explicite.",
    inputSchema: createOrderInputSchema,
    _meta: {
      destructiveHint: true,
    },
  },
  async ({ input }) => { /* ... */ }
);

La sémantique est la suivante. readOnlyHint signale à ChatGPT que l’outil ne modifie rien et est sûr ; le modèle et l’UI peuvent l’appeler plus librement. destructiveHint indique que l’outil effectue des actions irréversibles ou critiques, d’où davantage de confirmations côté utilisateur et une plus grande prudence du modèle.

Dans votre application Gift, suggest_gifts est clairement read‑only, alors que les outils de création de commande, de débit et de modification des données utilisateur devraient être marqués comme potentiellement destructifs.

openWorldHint et champs similaires

Dans certains cas, vous souhaitez indiquer au modèle que l’outil fonctionne dans un « monde ouvert », c’est‑à‑dire que ses résultats ne sont pas exhaustifs. Par exemple, search_products ne renverra jamais tous les produits existants, mais seulement les plus pertinents.

De telles annotations aident le modèle à éviter des conclusions hâtives du type « si le produit n’est pas trouvé dans search_products, alors il n’existe pas ». C’est un subtil point d’UX, mais dans les applications de production, la différence est nette.

_meta autour de l’affichage de l’UI

Quand votre outil renvoie un résultat, vous pouvez préciser dans _meta des paramètres qui influencent le widget. Par exemple : quel modèle HTML utiliser comme modèle de sortie, faut‑il des bordures, quel libellé afficher pendant l’appel, etc.

Dans l’exemple officiel, le serveur enregistre séparément le widget HTML comme ressource MCP, puis s’y réfère via _meta["openai/outputTemplate"].

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description: "Propose des idées de cadeaux.",
    inputSchema: suggestGiftsInputSchemaJson,
    _meta: {
      "openai/outputTemplate": "ui://widget/gifts.html", // C’est l’identifiant de la ressource MCP : server.registerResource(...)
      "openai/toolInvocation/invoking": "Je cherche des cadeaux…",		// Affiché pendant la recherche
      "openai/toolInvocation/invoked": "J’ai trouvé des idées de cadeaux",   // Affiché quand la recherche est terminée
    },
  },
  async ({ input }) => {
    // ...
    return {
      content: [],
      structuredContent: { items: gifts },
    };
  }
);

Ainsi, vous décrivez au même endroit :

  • la forme des données d’entrée pour le modèle (inputSchema) ;
  • la manière dont l’outil sera affiché et se comportera dans l’UI (_meta).

7. Conception des schémas : ce qu’il faut demander au modèle, et ce qu’il ne faut pas

Un piège classique — essayer de confier tout le travail au modèle. Par exemple, vous décrivez dans inputSchema un champ giftId, et dans le description vous écrivez : « UUID du cadeau dans notre base de données ». Le modèle essaiera bien sûr de générer un UUID du genre "0f21b5f0-5a3a-4d1b-8f0b-9f1a6e3c1234", seulement le problème est que ce cadeau n’existe probablement pas chez vous.

Règle utile : ne demandez pas au modèle de générer des identifiants techniques et des données liées à votre monde interne.

À la place, mettez en place un scénario en plusieurs étapes :

  1. suggest_gifts renvoie une liste de cadeaux avec id, title, price, etc. ;
  2. l’UI/le modèle permettent à l’utilisateur de choisir l’une des options proposées ;
  3. create_order accepte un giftId issu de l’ensemble déjà existant.

Du point de vue des schémas, cela signifie que :

  • les inputSchema des outils orientés « utilisateur » décrivent uniquement ce qu’une personne peut raisonnablement saisir : paramètres de recherche, filtres, critères ;
  • les inputSchema des outils qui manipulent des entités internes s’appuient sur des id déjà connus, et ne demandent pas au modèle de les inventer.

Pour votre application Gift, cela signifie que dans suggest_gifts vous ne demandez pas au modèle « d’inventer un code SKU », mais uniquement les paramètres de requête. Les SKU seront associés côté back‑end, et l’UI les affichera à l’utilisateur.

Remarque : un SKU est un code produit unique. Exemple "GFT-CHC-500-BS".

8. Petit bloc pratique : rassembler le tout

Rassemblons en un seul endroit tout ce dont nous avons parlé : schéma Zod, génération de JSON Schema, enregistrement de l’outil avec _meta et utilisation du schéma dans la logique métier. Assemblons un exemple minimal mais cohérent pour l’application Gift.

D’abord le schéma Zod et le type :

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const SuggestGiftsInputZod = z.object({
  relationship: z
    .enum(["friend", "partner", "sibling", "colleague", "parent"])
    .describe("Type de relation avec le destinataire du cadeau."),
  maxBudget: z
    .number()
    .min(0)
    .describe("Budget maximal dans la devise de l’utilisateur."),
  interests: z
    .array(
      z
        .string()
        .min(1)
        .describe("Tag court d’intérêt, par exemple: videogames.")
    )
    .min(1)
    .describe("Liste des centres d’intérêt du destinataire."),
});

type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;

const suggestGiftsInputSchemaJson = zodToJsonSchema(
  SuggestGiftsInputZod,
  "SuggestGiftsInput"
);

Ensuite — enregistrement de l’outil avec _meta pour l’UI :

server.registerTool(
  "suggest_gifts",
  {
    title: "Suggest gifts",
    description:
      "À utiliser quand il faut proposer des idées de cadeaux selon le budget, la relation et les centres d’intérêt.",
    inputSchema: suggestGiftsInputSchemaJson,
    _meta: {
      "openai/outputTemplate": "ui://widget/gifts.html",
      "openai/toolInvocation/invoking": "Je cherche des cadeaux…",
      "openai/toolInvocation/invoked": "J’ai trouvé des idées de cadeaux",
      readOnlyHint: true,
    },
  },
  async ({ input }) => {
    const args = SuggestGiftsInputZod.parse(input) as SuggestGiftsInput;

    const gifts = await findGifts(args); // votre logique métier

    return {
      content: [],
      structuredContent: {
        items: gifts,
      },
    };
  }
);

Quelque part à proximité, vous aurez une fonction métier typée :

async function findGifts(input: SuggestGiftsInput) {
  // ici, vous pouvez utiliser input.relationship, input.maxBudget, input.interests
  // et renvoyer un tableau d’objets de type Gift
  return [
    {
      id: "gift-1",
      title: "Jeu de société inspiré des jeux vidéo",
      price: 45,
      currency: "USD",
    },
  ];
}

Côté widget, vous récupérerez ensuite window.openai.toolOutput.structuredContent.items et vous afficherez des cartes, mais nous détaillerons cela dans un prochain cours.

9. Erreurs typiques lors de la description des outils

Erreur n°1 : des descriptions de champs trop générales ou vides de sens.
Si vous écrivez description: "Date" ou description: "Paramètre de filtre", le modèle reçoit pratiquement zéro information utile. C’est comme une documentation « la méthode fait quelque chose d’important ». Utilisez des descriptions qui répondent à « quoi mettre ici » et « dans quel format ». Par exemple : « Date ISO 8601 au format YYYY-MM-DD, p. ex. "2025-02-14" » ou « Montant dans la devise de l’utilisateur, exemple : 49.99 ».

Erreur n°2 : absence d’un enum là où il s’impose.
Souvent, les développeurs hésitent à transformer des chaînes en enum et laissent type: "string". Résultat : le modèle invente ses propres valeurs, le back‑end s’étonne, l’UI casse. S’il existe un ensemble fini d’options (relationship, types de statuts, modes de tri) — il est presque toujours pertinent d’utiliser un enum et d’énumérer les valeurs possibles. Cela augmente fortement la prévisibilité des tool‑calls.

Erreur n°3 : deux sources de vérité pour le schéma et les types.
Classique : dans TypeScript, vous remplacez le champ maxBudget par priceMax, mais vous oubliez le JSON Schema. Le modèle continue d’envoyer maxBudget, le code attend priceMax, tout plante. Souvent, ces erreurs ne sont découvertes qu’en prod. Il vaut mieux utiliser dès le départ Zod ou un outil analogue, qui génère à la fois le type et le JSON Schema à partir d’une seule déclaration.

Erreur n°4 : demander au modèle de générer des identifiants internes.
Des champs comme userId, giftId, orderId, si vous les décrivez comme « UUID de l’utilisateur dans notre système », seront inévitablement remplis par le modèle avec des valeurs inventées. Même si vous ajoutez un pattern pour un UUID, le modèle générera juste des UUID « valides en apparence » qui ne correspondent à rien. Ces champs doivent être remplis côté back‑end selon le contexte (authentification, tool‑call précédent), pas demandés au modèle.

Erreur n°5 : des schémas géants « divins » pour tous les cas de figure.
Parfois, on veut créer un seul outil do_everything avec un énorme objet, la moitié des champs nullable, l’autre moitié optional. Le modèle s’y noie. Mieux vaut découper la fonctionnalité en plusieurs outils aux schémas plus étroits et compréhensibles : l’un pour la recherche de cadeaux, un autre pour obtenir les détails d’un cadeau spécifique, un troisième pour créer une commande.

Erreur n°6 : ignorer _meta et les annotations.
Beaucoup de développeurs se limitent à name, description et inputSchema, en négligeant des champs _meta comme openai/outputTemplate et des indices comme destructiveHint. Au final, des outils qui effectuent « silencieusement » des actions risquées ne sont pas accompagnés d’indices et de confirmations dans l’UI. Cela dégrade la confiance des utilisateurs et crée un risque d’opérations inattendues. Utilisez les annotations pour marquer explicitement les outils read‑only et dangereux, et pour définir des statuts d’exécution conviviaux.

Erreur n°7 : absence de validation d’entrée côté serveur.
Même si JSON Schema et Zod décrivent tout, s’en remettre uniquement au modèle est risqué. Parfois, le modèle peut produire des données partiellement valides ou vous pouvez changer le schéma et oublier les contraintes métier. Encapsuler le gestionnaire dans un try { parse } catch { ... } avec une erreur conviviale donne au modèle une chance de corriger les arguments et vous évite de faire tomber tout le service à cause d’un seul tool‑call raté.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION