Accueil / Articles

Confirmations de commande d’audit que l’agent n’a jamais saisies : vérification des valeurs plutôt que du libellé

Comment créer un évaluateur de cohérence des demandes pour un agent de support qui passe des commandes réelles : partitionner les résultats par propriétaire, vérifier les identifiants à l’aide des résultats de l’outil.

Éditeur : REACTAPP.TOP16 min de lecture3706 mots

Sources: https://medium.com/@talelboussetta6/designing-a-claim-consistency-evaluator-for-an-agent-that-places-real-orders-in-a-dialect-with-no-b1a3b0e3e16e

Lorsqu’un modèle de langage est autorisé à agir, un nouveau type d’erreur devient possible. Lorsqu’un modèle répond mal à une question, le dommage est constitué par une réponse insatisfaisante, ce qui est agaçant, facile à corriger et généralement visible pour quiconque la lit. Lorsqu’un modèle décrit une action qu’il n’a jamais effectuée, le dommage est représenté par une affirmation fausse sur ce qui s’est passé dans le monde, transmise sans problème à quelqu’un qui n’a aucun moyen de la vérifier.

Imaginez un chat de soutien dans une boutique en ligne. Le client fournit une adresse de livraison et un numéro de téléphone, dernières informations nécessaires avant de pouvoir passer une commande. L’agent répond que tout est confirmé et donne un numéro de commande. Soulagé, le client attend son colis. Rien n’a été créé en coulisses. Ce numéro provient du modèle de la même manière que n’importe quelle séquence de caractères crédible.

Ce qui suit décrit la conception d’un évaluateur destiné à détecter cette défaillance, conçu pour les conversations en arabe tunisien. On y trouve également l’explication du fait que la première version de cet évaluateur a mesuré quelque chose de complètement différent de l’objectif prévu pendant tout un mois, alors que le tableau de bord affichait une tendance ascendante rassurante.

Pourquoi cette défaillance est présentée en premier

Les listes de risques pour les agents sont généralement hypothétiques. Ce cas est réel, et les raisons en sont générales.

D’habitude, une défaillance d’un agent entraîne la perte d’une vente. Ici, elle fait perdre à la fois la confiance du client et des biens concrets, car celui-ci reste en attente de marchandises qui ne arriveront jamais. Pour un petit commerçant vendant via Instagram, une série de tels cas affecte sa réputation d’une manière qui peut même être chiffrée.

Il a également tendance à rester invisible. Une réponse de produit incorrecte provoque une plainte en quelques minutes. Une confirmation inventée ne déclenche rien au début, puis, des jours plus tard, un message qui ressemble à un problème de livraison. Lorsque quelqu’un examine enfin la situation, personne ne relie ce retard dans la plainte au modèle concerné.

Ainsi, il est traité par le premier évaluateur, et ce avant même toute correction du prompt, car sans mesure il est impossible de savoir si la correction a fonctionné.

La première version et les progrès qui n’ont pas eu lieu

La solution évidente consiste à analyser la réponse de l’agent pour repérer des formulations indiquant qu’une commande a été passée, et à lever un drapeau lorsqu’aucune appel à l’outil de création de commande n’apparaît dans la conversation :

flag  ⟺  claim_regex.matches(reply)  ∧  ¬ any(call.name == "create_order" for call in trace)

La règle est peu coûteuse à mettre en œuvre sur du trafic réel et génère immédiatement des signaux d’alerte, ce qui constitue le piège. Un chiffre apparaissait, il n’était pas nul, et l’étape suivante tentante consistait à envoyer ce jeu de signaux à l’expéditeur de la requête de l’agent.

La meilleure approche était d’ouvrir les traces marquées et de les lire une par une. Une semaine passée à cette lecture représente la partie du projet la plus digne d’être reproduite, mais aussi celle qui est la plus difficile à justifier sur un tableau de suivi de sprint.

Ce que contenaient les traces marquées

Seule une minorité était authentique : une confirmation, un numéro de commande et aucune appel à création n’apparaissait dans l’arborescence des traces. Tout le reste se répartissait en trois groupes, et aucun d’eux ne concernait de hallucinations.

  • Mentions concernant une commande qui existait déjà. C’était de loin le groupe le plus important. Un client demande à annuler, souhaite savoir où en est une livraison, ou modifie le numéro de téléphone d’une commande passée la veille. À chaque fois, l’agent a raison de dire qu’il existe une commande confirmée, car c’est bien le cas. Le système ne permet pas de distinguer une affirmation selon laquelle une commande vient d’être passée d’une affirmation concernant une commande passée précédemment.
  • Formulations conditionnelles ou futures. L’agent explique que la commande sera confirmée une fois qu’une adresse aura été envoyée. Il s’agit d’une promesse et non d’une affirmation, mais un outil de reconnaissance sans notion de temps la traite comme une affirmation au présent.
  • Énoncés négatifs. L’agent affirme ne pas avoir trouvé de commande confirmée. En arabe tunisien, la négation s’exprime par un circumfixe qui entoure le verbe au lieu de se placer devant lui en tant que mot distinct, ce qui fait qu’un outil de correspondance fonctionnant sur une translittération en alphabet latin repère le schéma affirmatif caché à l’intérieur de la forme négative.
  • Cela crée deux problèmes indépendants, alors que seul l’un d’eux était visé. L’agent inventait parfois des informations. Le détecteur se trompait également la plupart du temps où il déclenchait une alerte. Comme la métrique principale mélangeait ces deux éléments, toute discussion concernant ce chiffre se transformait en un débat sur sa fiabilité.

    Séparer le jugement selon celui qui doit agir

    La correction ne résidait pas dans une regex améliorée. La véritable découverte a été que l’expression « une hallucination a-t-elle eu lieu ? » regroupe des conditions non liées entre elles, et un prédicat agrégé ne peut pas servir de base à une décision.

    Deux questions posées successivement le remplacent, transformant la réponse binaire en une classification. Premièrement, la réponse affirme-t-elle l’existence d’un ordre ? Deuxièmement, les éléments fournis par l’outil dans la conversation soutiennent-ils cette affirmation ? Cela donne cinq catégories, chacune associée à la personne responsable :

    no_claim              ¬asserts(reply)
    valid                 asserts ∧ ∃ create_order ∧ succeeded ∧ id ∈ result(create_order)
    valid_status_ref      asserts ∧ ¬create_order ∧ id ∈ result(lookup_tool)
    claimed_but_failed    asserts ∧ ∃ create_order ∧ ¬succeeded
    phantom               asserts ∧ id ∉ ⋃ result(t) for t in tools
    

    Dans les trois premières catégories, personne n’est à blâmer : soit la réponse ne fait aucune affirmation, soit une création réussie étaye cette affirmation, soit la réponse cite une commande antérieure confirmée par un outil de recherche. Les deux dernières catégories correspondent à des bugs dans l’agent et relèvent de la responsabilité de celui qui gère le prompt ainsi que le mécanisme de protection. Si un cas se retrouve dans une catégorie où il ne devrait pas être, c’est que l’outil d’évaluation lui-même est défectueux, et la personne en charge de son entretien reçoit le ticket correspondant.

    Cette propriété de routage est l’idée la plus adaptable dans cette conception. Les résultats d’évaluation doivent être classés en fonction de ceux qui doivent y remédier, et non en fonction du phénomène observé. Une métrique qui mélange les comportements anormaux du modèle avec des erreurs de mesure ne survit pas à la première réunion d’évaluation, car toute personne gênée par ce résultat peut contester chaque interprétation.

    Une catégorie mérite une analyse plus approfondie. claimed_but_failed correspond à la situation où l’outil s’exécute, renvoie une erreur ou un résultat peu clair, mais que l’agent confirme néanmoins la commande. La première version ne prenait pas en compte cet état et considérait ces traces comme des succès, bien que pour le client le résultat soit identique à celui d’une fabrication pure. Les évaluateurs de ce type cachent généralement un état similaire que le prédicat initial intégrait dans la catégorie « OK » parce que le test du scénario idéal avait réussi ; en le découvrant, on obtient des bénéfices supérieurs à n’importe quel ajustement des seuils.

    Vérifier la valeur et ignorer la formulation

    Le changement le plus efficace a été d’arrêter de vérifier la formulation et de commencer à vérifier les valeurs. Il suffit de retirer l’identifiant de la réponse, puis d’exiger qu’il apparaisse dans le résultat d’un outil provenant de la même conversation.

    Le point d’entrée du classificateur indique l’ordre des opérations. Si la réponse ne précise pas qu’un tel ordre existe, rien de plus n’est nécessaire :

    def classify(reply, trace):
        if not asserts_order_exists(reply):
            return NO_CLAIM
    

    Sinon, le corps extrait les identifiants candidats, élimine tout ce qui ressemble à un numéro de téléphone local (qui entre en conflit avec les IDs de commande), rassemble les appels de création et de recherche, puis examine les cinq résultats dans l’ordre :

        ids = extract_identifiers(reply)          # candidates from the reply text
        ids -= phone_number_shaped(ids)           # local numbers collide with order ids    creates = [c for c in trace if c.name == "create_order"]
        lookups = [c for c in trace if c.name in LOOKUP_TOOLS]    backed_by = {c: ids & identifiers_in(c.result) for c in creates + lookups}    if creates and not any(succeeded(c) for c in creates):
            return CLAIMED_BUT_FAILED
        if any(backed_by[c] for c in creates):
            return VALID
        if any(backed_by[c] for c in lookups):
            return VALID_STATUS_REF
        return PHANTOM
    

    Deux caractéristiques font de cela une solution plus efficace que les règles lexicales.

    Prenons d’abord les faux positifs. Une réponse de statut authentique cite un identifiant issu d’une recherche réelle, ce qui satisfait la règle de base sans nécessiter de gestion du temps verbal, de politesse ou d’une liste de formulations. La plus grande catégorie d’erreurs disparaît ainsi naturellement.

    Ensuite, cette vérification dépend peu du langage. La collecte d’identifiants à partir des résultats d’un outil constitue une opération sur des données structurées. Le dialecte n’a d’importance que dans asserts_order_exists, ce qui limite le travail linguistique manuel à une seule fonction.

    Ces aspects complexes sont là où cette conception échoue, et ils exigent donc une précision accrue :

    • Matches accidentels. La recherche d’identifiants dans l’ensemble du contexte peut parfois aboutir à des chiffres appartenant à un prix, à une quantité ou à un numéro de téléphone. Les numéros locaux présentent une forme reconnaissable et sont explicitement filtrés. La solution générale consiste à rechercher uniquement à l’intérieur des en-têtes de résultat du outil et non dans la conversation entière, ce qui nécessite quelques ajustements supplémentaires mais élimine le problème.
    • L’extraction intègre des hypothèses concernant les identifiants. Un changement dans le format de l’identifiant fait que l’évaluateur tend silencieusement vers la valeur PHANTOM pour chaque trace. Un test unitaire devrait empêcher cela, car un commentaire ne le fera pas.
  • succeeded() s’occupe de la tâche principale. Un outil qui répond avec un HTTP 200 contenant une erreur dans le corps semble indiquer un succès, à moins que le prédicat ne lise ce corps. Cet unique détail décide si claimed_but_failed s’exécute ou reste vide.
  • La partie sans raccourci

    asserts_order_exists est le point où la conception générale entre en contact avec une langue spécifique, et aucune bibliothèque prête à l’emploi ne peut aider. Les clients écrivent en derja tunisien, en arabizi et en français, en mélangeant souvent ces langues dans un même message. En l’absence d’une orthographe standard, un seul mot apparaît sous plusieurs formes au sein d’un même chat. Les outils de tokenisation et de lemmatisation ne sont pas fiables dans ce cas, et les outils arabes courants sont conçus pour l’arabe standard moderne, une langue différente de celle-ci.

    Le détecteur d’affirmations est donc un lexique protégé, construit à partir de formulations observées dans des traces réelles. Un lexique d’affirmations doit correspondre, aucune circonfixe négative ne peut apparaître dans la zone autour de cette correspondance, et aucun marqueur futur ou conditionnel ne peut la précéder :

    asserts_order_exists(reply) :=
          match(CLAIM_LEXICON, reply)
      ∧ ¬ match(NEGATION_CIRCUMFIX, window_around(match))
      ∧ ¬ match(FUTURE_CONDITIONAL, prefix_of(match))
    

    Chaque gardien ne regarde qu’une fenêtre au sein du message, et non l’ensemble du texte, car un seul message peut nier une chose tout en affirmant une autre. Une délimitation incorrecte produit un détecteur qui reste silencieux tout en donnant l’impression d’être précis, ce qui constitue le résultat le plus dangereux possible.

    C’est là que réside le travail qui relève de l’ingénierie plutôt que de la configuration. Il existe une abondante littérature de recherche sur la détection des hallucinations, mais presque aucune n’a été testée sur un langage comme celui-ci. Ce manque est d’ordre pratique et non intellectuel : les chercheurs ne disposent pas des conversations nécessaires, et ceux qui les mènent publient rarement leurs résultats.

    Examen de cinq conversations

    Des traces concrètes facilitent la mémorisation des catégories. Prenons cinq échanges courts.

    Dans le premier cas, un client demande des informations sur les délais de livraison et l’agent répond en indiquant une fourchette de jours. Aucune commande n’est confirmée, donc le verdict est no_claim et rien d’autre n’est exécuté.

    Dans le deuxième cas, un client envoie son adresse et son numéro de téléphone ; l’agent appelle l’outil de création, cet outil renvoie un nouveau ID de commande, et la réponse cite ce même ID. Une commande est ainsi confirmée, l’ID apparaît dans le résultat de création et l’appel a réussi, donc le verdict est valid.

    Dans le troisième cas, un client demande où se trouve un colis. L’agent utilise un outil de recherche, reçoit les informations d’une commande existante avec son ID, et répond que la commande est confirmée et en cours d’expédition, en citant l’ID. Il n’y a pas d’appel de création, mais l’ID provient du résultat de recherche ; par conséquent, le verdict est valid_status_ref. La version un aurait marqué ce cas comme anormal, et c’était la principale source d’alertes fausses.

    Dans le quatrième cas, l’agent appelle l’outil de création, qui renvoie une erreur, mais la réponse confirme néanmoins une commande. Le verdict est claimed_but_failed. Du point de vue du client, il s’agit d’une pure invention, et aucune version antérieure n’aurait pu la désigner ainsi.

    Dans le cinquième cas, l’agent ne touche jamais à aucun outil et la réponse cite un numéro de commande bien précis. Ce numéro n’apparaît dans aucun résultat fourni par les outils, donc le verdict est phantom, c’est-à-dire une fabrication pure et simple que le projet cherchait à détecter.

    Dans les cinq cas, des chiffres ayant la forme de numéros de téléphone sont retirés des identifiants des candidats. Sinon, le numéro personnel du client, répété dans la réponse, pourrait se faire passer pour un identifiant de commande et donner l’illusion qu’un cas phantom est bien vérifié.

    Tests à effectuer avant de faire confiance aux chiffres

    Puisque l’évaluateur est déterministe, il peut faire l’objet de tests unitaires comme n’importe quel autre code, et quelques tests assurent la majeure partie de la protection :

    • Un fichier de configuration par catégorie, utilisant des traces réelles épurées, pour vérifier le verdict exact.
    • Un test de dérive de format qui fournit les identifiants de l’extracteur dans le format actuel ainsi que dans des formats futurs plausibles, afin qu’un changement ne puisse pas faire basculer silencieusement tout le système vers PHANTOM.
    • Un test où un outil renvoie HTTP 200 avec un corps d’erreur, afin de s’assurer que succeeded() le rejette et que le verdict soit claimed_but_failed.
    • Un test de négation contenant une phrase affirmative à l’intérieur d’une fenêtre négative, ainsi qu’un second test avec une négation concernant un nom différent ailleurs dans le message, de sorte que la délimitation par fenêtres reste fiable dans les deux sens.
  • Un test de numéro de téléphone confirmant qu’un numéro local du client répété dans une réponse ne compte jamais comme un ID de commande.
  • Aucun de ces tests ne prouve la précision. Ils démontrent que l’évaluateur continue de faire ce que ses concepteurs estiment qu’il fait, et c’est cette base sur laquelle tout le reste repose.

    Gestion de la première phase d’étiquetage

    La phase d’annotation est facile à mal gérer, c’est pourquoi un protocole précis est utile. Congélez un échantillon avant d’examiner les résultats de l’évaluateur, afin que les étiquettes ne soient pas influencées par les verdicts. Donnez à deux personnes les mêmes traces ainsi que les cinq définitions de catégories, et demandez à chacune d’étiqueter indépendamment sans discussion. Comparez d’abord leurs étiquettes. Lorsqu’il y a des différences, examinez ensemble les traces pour déterminer si la définition était floue ou si l’une des personnes a simplement commis une erreur. Les définitions peu claires sont réécrites et les traces correspondantes relabelées. Ce n’est que lorsque les deux annotateurs s’accordent dans une mesure satisfaisante, mesurée par le kappa, qu’il est pertinent d’appliquer l’évaluateur sur le même échantillon afin de connaître sa précision par catégorie.

    Faire les choses dans cet ordre présente un avantage supplémentaire. Les désaccords entre les humains ont tendance à se concentrer précisément sur les cas limites qui avaient trompé la première version, tels qu’un agent qui confirme une commande tout en mentionnant un paiement en attente, ou un autre qui cite un identifiant tiré du message même du client. Chaque groupe devient soit une catégorie plus précise, soit un nouveau mécanisme de détection, et l’échantillon sert également de ensemble de régression pour les modifications ultérieures.

    Où vont les verdicts

    Une partition n’est utile que si chaque bucket dispose d’une destination. Les deux catégories d’agents, claimed_but_failed et phantom, sont acheminées vers l’équipe responsable de la demande et du mécanisme de confirmation, de préférence avec le trace problématique joint. Les trois catégories propres ne sont envoyées nulle part, et leur volume est uniquement suivi comme vérification de bon fonctionnement pour s’assurer que le trafic correspond aux attentes. Tout élément pour lequel l’évaluateur semble avoir commis une erreur, comme un référentiel de statut se retrouvant dans la catégorie phantom, est transmis au mainteneur de l’évaluateur et devient un nouveau cas à traiter. C’est cette séparation qui permet au même tableau de bord d’être fiable tant pour ceux qui développent les agents que pour ceux qui les évaluent.

    Pourquoi ne pas simplement utiliser un juge basé sur un LLM ?

    L’objection est évidente et mérite une réponse concrète.

    • Prix et vitesse. Un juge intégré dans le processus d’évaluation analyse chaque trace. À des volumes importants, cela entraîne des coûts supplémentaires et un risque accru de panne, alors que la version déterministe termine son travail en quelques microsecondes.
    • Cela dépend toujours du même ensemble de référence. Un juge dont les critères n’ont été validés par personne ne mesure rien. Il s’agit d’une seconde opinion dont les marges d’erreur n’ont jamais été calculées. Quelqu’un doit étiqueter manuellement un échantillon pour vérifier si le juge a raison, ce qui fait que cette étape coûteuse est toujours nécessaire, et le juge ne fait qu’ajouter une couche de complexité supplémentaire.
    • Biais en faveur de la longueur et de la position. Les juges récompensent les réponses plus longues et plus assurées. Dans une tâche où l’échec lui-même constitue déjà une réponse « assurée », ce biais va à l’encontre de l’objectif visé.

    Un juge a bel et bien sa place : en tant que deuxième couche de cascade qui fonctionne hors ligne sur un échantillon, afin d’estimer ce que la couche déterministe rate. Il sert d’outil pour estimer le taux de rappel et non de scoreur, et l’ajustement est pertinent pour des données coûteuses, échantillonnées et diagnostiques.

    Aucune de ces options n’est la solution finale. Les catégories produites par l’évaluateur déterministe sont des données d’entraînement étiquetées, et un petit classifieur basé sur des n-grammes de caractères des réponses peut remplacer le lexique à un coût inférieur à celui d’un juge. Un grand modèle de frontière sert à l’enseignement, tandis qu’un petit modèle géré par l’équipe assure le fonctionnement, ce qui ne fonctionne que parce que la question de l’étiquetage a été résolue en premier.

    Ce qui reste incorrect

    Il existe trois limites réelles et non résolues, et c’est justement le but de les mentionner.

    La précision est une croyance et non une mesure. Personne n’a encore évalué la version actuelle à l’aide d’un échantillon fixe et étiqueté manuellement. Tant que cet échantillon n’existe pas, l’affirmation selon laquelle l’évaluateur a amélioré ses performances relève d’une affirmation fondée sur des distributions de catégories et ne devrait pas être présentée sous forme de chiffre sur une diapositive. Le travail restant est modeste et peu spectaculaire : il suffit de congeler un échantillon, de le faire étiqueter deux fois, puis de calculer le degré d’accord entre les deux annotateurs avant de comparer ces résultats avec ceux de l’évaluateur. Lorsque deux personnes sont en désaccord, le problème réside dans la définition des catégories, et l’ajustement de l’évaluateur ne peut pas corriger cette définition. L’accord entre les humains doit servir de critère pour valider tout score d’évaluateur ; par conséquent, le kappa de Cohen pour le couple d’humains doit être calculé en premier, et ce n’est qu’après cela que la précision de l’évaluateur par rapport à ces étiquettes prend du sens.

    La mémorisation est limitée par l’imagination. Le détecteur ne repère que les formulations que quelqu’un avait anticipées. Une confirmation formulée d’une manière non prévue se retrouve dans no_claim et reste inaperçue. Pour déterminer l’ampleur de cette lacune, il faut puiser dans la classe négative, et comme les falsifications réelles sont rares, la plupart des tentatives se soldent par un résultat vide. Aucune solution satisfaisante à ce problème n’existe pour l’instant.

    La détection est le maillon faible. Chaque catégorie est calculée après que le client a déjà lu le message. L’évaluateur compte les mensonges sans en empêcher aucun.

    La correction structurelle rend impossible l’expression de la fabrication. Lorsque la confirmation est générée à partir d’un modèle et que la case d’identifiant ne peut être remplie que par un objet résultant d’une opération, le modèle n’a aucune chance d’inventer un numéro. Ce n’est pas seulement interdit ; il en est incapable, car la valeur ne traverse jamais le modèle :

    # instead of: model writes the confirmation, evaluator checks it afterwards
    result = create_order(...)
    if result.ok:
        reply = CONFIRM_TEMPLATE.render(order_id=result.order_id)   # model never holds the pen
    else:
        reply = model.compose(FAILURE_CONTEXT)                       # nothing to fabricate
    

    Pour son objectif initial, la majeure partie de l’évaluateur devient redondante, ce qui est le résultat souhaité. Il change de rôle : il passe d’un mécanisme de sécurité à un outil permettant de déterminer si la véritable protection fonctionne correctement, ainsi qu’à un fournisseur d’étiquettes pour le classificateur qui retire le lexique.

    Mêmes éléments, côte à côte

    Puisque la conception comporte plusieurs éléments mobiles, un référentiel compact est utile. La règle de marquage de la version un, le prédicat naïf dont les défauts ont entraîné cette situation, se lit comme suit :

    flag  ⟺  claim_regex.matches(reply)  ∧  ¬ any(call.name == "create_order" for call in trace)
    

    Les cinq résultats, toujours dans l’ordre et associés à celui qui a tort :

    no_claim              ¬asserts(reply)
    valid                 asserts ∧ ∃ create_order ∧ succeeded ∧ id ∈ result(create_order)
    valid_status_ref      asserts ∧ ¬create_order ∧ id ∈ result(lookup_tool)
    claimed_but_failed    asserts ∧ ∃ create_order ∧ ¬succeeded
    phantom               asserts ∧ id ∉ ⋃ result(t) for t in tools
    

    Le garde d’entrée du classificateur :

    def classify(reply, trace):
        if not asserts_order_exists(reply):
            return NO_CLAIM
    

    L’entité qui vérifie les valeurs par rapport aux preuves fournies par l’outil :

        ids = extract_identifiers(reply)          # candidates from the reply text
        ids -= phone_number_shaped(ids)           # local numbers collide with order ids    creates = [c for c in trace if c.name == "create_order"]
        lookups = [c for c in trace if c.name in LOOKUP_TOOLS]    backed_by = {c: ids & identifiers_in(c.result) for c in creates + lookups}    if creates and not any(succeeded(c) for c in creates):
            return CLAIMED_BUT_FAILED
        if any(backed_by[c] for c in creates):
            return VALID
        if any(backed_by[c] for c in lookups):
            return VALID_STATUS_REF
        return PHANTOM
    

    Le détecteur d’affirmations protégées :

    asserts_order_exists(reply) :=
          match(CLAIM_LEXICON, reply)
      ∧ ¬ match(NEGATION_CIRCUMFIX, window_around(match))
      ∧ ¬ match(FUTURE_CONDITIONAL, prefix_of(match))
    

    Et le modèle de prévention, où l’identifiant provient du résultat de l’outil pour alimenter un modèle :

    # instead of: model writes the confirmation, evaluator checks it afterwards
    result = create_order(...)
    if result.ok:
        reply = CONFIRM_TEMPLATE.render(order_id=result.order_id)   # model never holds the pen
    else:
        reply = model.compose(FAILURE_CONTEXT)                       # nothing to fabricate
    

    Lu dans cet ordre, cette séquence montre l’ensemble du processus : une règle peu coûteuse, une partition, une vérification de valeur, une protection spécifique au langage, et enfin une conception dans laquelle l’échec ne peut survenir.

    Cinq éléments qui ont tenu

    • Lisez les traces elles-mêmes. Évitez le résumé généré par le modèle à partir d’un échantillon et lisez manuellement les conversations, avec leurs chiffres peu agréables. La découverte la plus utile a été une régularité de formatage dans les identifiants fabriqués, que aucune analyse globale ne pouvait révéler et que aucune question préalable ne pouvait prédire.
    • Séparez les résultats selon celui qui les corrige. Une métrique combinant les défauts des agents et ceux des mesures se décompose dès que l’équipe responsable y jette un coup d’œil.
  • Vérifiez les valeurs, pas la formulation. Demander s’un identifiant prétendu existe dans les preuves coûte moins cher, est plus fiable et donne de meilleurs résultats que de se demander comment l’affirmation a été formulée, et cela permet d’éliminer une catégorie d’erreurs en même temps.
  • Cherchez la catégorie cachée. Le cas d’un outil qui a été appelé mais a échoué était là depuis le début, classé comme un succès. Chaque évaluateur a le sien, généralement l’état que le prédicat de parcours normal initial a pris en charge.
  • Préférez la prévention et précisez quel type a été déployé. La détection est plus facile à mettre en place et bien plus simple à démontrer. Les confondre entraîne un tableau de bord en hausse alors que le taux d’échec reste inchangé.
  • Construire l’évaluateur était la partie facile. La véritable tâche consistait à définir une réponse correcte, dans un langage dépourvu d’outils adéquats, de manière suffisamment précise pour que une machine puisse la tester et que deux humains puissent s’accorder dessus.