Chloé: L’évolution, ou Développer un agent conversationnel pour la Covid-19 avec Rasa – Épisode 2

par | Sep 10, 2020 | Blogue

Dans la première partie de cette courte série d’articles, j’ai décrit la façon dont nous avons implémenté les multiples flux de dialogue de Chloé, un agent conversationnel sur la Covid-19 développé avec Rasa. J’ai aussi décrit comment notre implémentation a fluctué entre histoires et formulaires¹, et à quel point il a été difficile, parfois, compte tenu de la façon dont Rasa est conçu et de l’évolution constante du design, de savoir quelle serait la meilleure approche. Dans cet épisode, je vais décrire les étapes subséquentes de l’implémentation, incluant des améliorations au module de Q&R, la recherche de cliniques de dépistage, l’ajout progressif du traitement automatique du langage naturel (TALN) ainsi que la gestion d’erreurs.

Retour sur l’épisode 1: TALN et gestion d’erreurs

Flashback – Scène 4: Répondre à des questions et suivre TED

Comme je l’ai mentionné dans la première partie, l’objectif du module de question-réponse (Q&R) était de permettre à l’utilisatrice² de poser une question au sujet de la Covid-19, pour laquelle nous afficherions la réponse retournée par l’API du modèle du Mila. Il y a eu plusieurs versions de cette portion de l’application, allant de très basique à plutôt complexe, et celle-ci a été incorporée à un nombre de plus en plus grand d’endroits dans le dialogue.

Dans la version initiale du flux de Q&R, l’utilisatrice devait choisir l’option “J’ai des questions” du menu principal ou après une évaluation, après quoi l’application recueillait sa question. Nous avons prévu quatre résultats possibles provenant de l’appel à l’API (le quatrième n’était toujours pas disponible au moment où notre participation au projet a pris fin):

  • Échec: l’appel à l’API a échoué
  • Succès: l’appel à l’API a réussi et le modèle a fourni une réponse
  • Hors distribution: l’appel à l’API a réussi mais n’a fourni aucune réponse
  • Requiert une évaluation: l’appel à l’API a réussi mais l’utilisatrice devrait évaluer ses symptômes pour obtenir une réponse à sa question

Si le résultat était un succès, Chloé posait une question supplémentaire afin de savoir si la réponse avait été utile (l’interface de clavardage que nous devions utiliser ne fournissait pas de boutons de type “j’aime/je n’aime pas” (thumbs-up/thumbs-down) qui nous auraient permis de facilement sauter cette interaction). Si le résultat était hors distribution, alors on demandait à l’utilisatrice de reformuler sa question.

Recueillir la question, la reformulation de celle-ci et la rétroaction sur l’utilité de la réponse pouvaient tous être inclus dans un formulaire; cependant, l’implémentation des transitions vers les autres portions du dialogue était moins claire. Il y avait 6 transitions différentes à partir du flux de Q&R selon le résultat et la présence de la case “self_assess_done” décrite précédemment. Nous avons vaguement considéré la possibilité de demander à l’usager ce qu’il voulait faire ensuite à l’intérieur du formulaire, afin de centraliser la logique du flux de Q&R, mais cette idée a été mise de côté puisque nous n’avons pas trouvé de façon élégante d’implémenter cela. Pour cette raison, nous avons finalement eu recours aux histoires et à la politique TED pour prédire les transitions déterministes.

Nous étions aussi confrontés au problème découlant du fait que certaines de ces transitions étaient des questions de type “affirm/deny” (autrement dit une intention booléenne), “affirm” menant soit vers une évaluation ou vers une autre question. À ce point, nos histoires d’évaluation de base démarraient directement avec une intention “get_assessment” comme raccourci pour la mémoïsation, et démarrer une histoire avec “get_assessment OR affirm” aurait évidemment généré des correspondances indésirables. Nous avons remis cet enjeu à plus tard avec une solution qui fonctionnait seulement parce que nous contrôlions les réponses des utilisateurs par le biais de boutons. Comme ceci:

Un raccourci d’intentions avec des boutons

Ainsi, nous n’avions pas besoin d’ajouter des histoires du module de question-réponse suivi d’une évaluation, mais avec le recul, nous aurions dû le faire d’entrée de jeu, puisque l’ajout d’histoires d’évaluations suivies de Q&R fonctionnait (somme toute) bien, et nous avons dû le faire de toute façon lors de l’ajout du TALN.

Scène 5.25: Détours de l’inscription au suivi quotidien

Le design du flux d’inscription au suivi quotidien a été approfondi pour y inclure certains cas d’exceptions. Cela était nécessaire puisque le numéro de téléphone et le code de validation étaient recueillis auprès de l’utilisatrice directement sous forme de texte. Voici les cas plus problématiques que nous avons traités:

  • Le numéro de téléphone n’est pas valide
  • L’utilisatrice dit qu’elle n’a pas de numéro de téléphone
  • Elle veut annuler l’inscription parce qu’elle ne veut pas fournir son numéro
  • Le code de validation est invalide
  • L’utilisatrice n’a pas reçu de code de validation et veut en recevoir un nouveau
  • Elle n’a pas reçu de code de validation et désire changer de numéro de téléphone

Certains de ces cas se trouvent à mi-chemin entre une digression et de la gestion d’erreur, et nous avons pensé les implémenter comme des digressions “contrôlées”, de la même façon que nous l’avions fait pour l’explication des antécédents médicaux, qui va comme suit:

Mais puisque la plupart impliquaient le recours à des compteurs et messages d’erreurs de même qu’à d’autres éléments plus complexes, nous avons décidé de gérer tous ces cas à l’intérieur de formulaires plutôt que de distribuer la logique entre formulaires, histoires et correspondances (mappings) d’intentions. Cela a présenté des inconvénients (au-delà des centaines de lignes de code supplémentaires): une partie de la logique s’échelonnait sur plusieurs interactions et nous avons dû ajouter plusieurs cases pour disposer de compteurs et de drapeaux³ pour en suivre la progression (notre version finale du formulaire utilise une dizaine de telles cases).

Flashback – 5.75: Pause – TED a besoin d’un cours d’appoint

Après quelques essais, il a été porté à notre attention qu’il était possible pour une utilisatrice de se retrouver coincée dans une boucle infinie dans le Q&R puisque la seule option offerte était de reformuler la question. Le design a été modifié afin de permettre à l’utilisatrice de soit réessayer, soit sortir du Q&R, et nous avons ajouté 2 transitions pour ce cas.

En ajoutant ces transitions, nous avons frappé un petit noeud: la politique TED n’apprenait pas le bon comportement après le Q&R; elle confondait les impacts des cases “question_answering_status” et “symptoms”. La redistribution équilibrée dans nos histoires des exemples de Q&R entre les évaluations sans symptômes, avec symptômes légers ou modérés, a représenté un travail de moine, mais cela a fonctionné. Au final, la politique prédisait le bon comportement pour les conversations qui n’étaient pas représentées dans nos histoires.

Scène 6: L’implémentation de la recherche de cliniques de dépistage sur le pilote automatique

La recherche de cliniques de dépistage, après avoir eu maille à partir avec les transitions du Q&R et la gestion d’erreurs de l’inscription au suivi quotidien, n’a présenté aucun nouveau défi. Le dialogue comprenait trois étapes principales:

  1. Expliquer le fonctionnement et demander à l’utilisatrice si elle veut poursuivre
  2. Si oui, collecter son code postal et en valider le format et l’existence, en annulant la tâche après trop d’erreurs
  3. Afficher les résultats ou offrir un autre essai s’il n’y en avait aucun.

Suivant la logique de nos implémentations précédentes, nous avons utilisé un formulaire pour le code postal et la gestion d’erreurs, les appels à l’API et pour offrir le deuxième essai; et des histoires pour afficher les explications et gérer les transitions vers les autres flux de dialogue. Les transitions, encore une fois, variaient en fonction du résultat de l’appel à l’API et de la valeur de la case “self_assess_done”.

Scène 7: Explorer les chemins sinueux du TALN

Après être passés à travers l’implémentation de toutes les fonctionnalités en mode boutons-seulement-sans-gestion-d’erreur, nous avons pu commencer à explorer l’intégration du TALN et à nous attaquer aux entrées imprévues. Nous avons commencé nos essais par la toute première question au menu principal. Toute réponse n’étant pas reconnue comme l’une des options serait envoyée à l’API du Q&R; cependant, puisque le TALN n’est pas contextuel dans Rasa, et étant donné que nous nous attendions à une grande variété de questions pour le Q&R, “toute réponse” pouvait être n’importe quelle intention, avec n’importe quel score. “Comment gérer toutes ces intentions?” n’était pas une question aussi anodine qu’elle puisse paraître.

Option 1: Ajouter des exemples aux histoires

La manière évidente de procéder était d’ajouter des histoires avec des intentions non supportées et le comportement d’erreur voulu (c’est-à-dire envoyer le texte au formulaire de Q&R), mais de combien d’exemples aurions-nous besoin? La politique TED n’allait vraisemblablement pas pouvoir apprendre que le comportement des exemples était le comportement par défaut à utiliser aussi pour toute intention exclue des exemples. De plus, utiliser des OR pour inclure toutes les intentions non supportées aurait multiplié la durée de l’entraînement des modèles de façon exponentielle dès que nous aurions appliqué cette approche aux autres cas d’utilisation. Cette avenue était un cul-de-sac.

Option 2: Action de repli

En excluant complètement les intentions non supportées de nos histoires, la politique TED prédisait quand même quelque chose, mais nous pouvions espérer que le score de confiance soit bas et utiliser un seuil pour déclencher une action de repli (fallback action). Cette action remplacerait l’intention retournée par une intention “fallback” et nous pourrions gérer celle-ci dans nos histoires. Or, les comportements attendus n’avaient pas de très bons scores, certains n’étant pas beaucoup mieux que ce qu’une intention “affirm” mal placée pouvait obtenir, puisqu’elle était dans plusieurs histoires. En conséquence, nous n’avons pas voulu dépendre d’un seuil de confiance pour déclencher l’action de repli.

La solution: La politique d’intention non supportée

Nous avons finalement récupéré l’idée de l’intention “fallback”, mais à l’aide d’une politique déterministe. La politique prédisait l’action qui remplaçait l’intention reconnue par une intention “fallback” si la conversation respectait deux conditions: la dernière action pertinente avant l’entrée de l’utilisatrice était la question du menu principal, et l’intention reconnue n’était pas dans la liste des intentions supportées. Des histoires et la politique de mémoïsation ont été utilisées pour déclencher le formulaire de Q&R et gérer les transitions particulières par la suite (l’échec de l’appel à l’API ainsi que le résultat hors distribution au menu principal étaient suivis d’un message d’erreur au lieu des messages habituels):

Utilisation du message déclencheur dans le formulaire de Q&R

Scène 8: Explorations supplémentaires

Dans un deuxième temps, nous avons ajouté le TALN dans les questions oui-non qui, selon le design, déclenchaient simplement, en cas d’erreur, une question reformulée avec des boutons et sans champ texte. La majorité d’entre elles étaient dans des formulaires, certaines avec des exceptions à la convention de message “utter_ask_{nom_de_la_case}”. Les exceptions s’appliquaient également aux messages d’erreurs, ce qui fait qu’une approche générique ne couvrant même pas tous les cas semblait trop complexe pour les bénéfices envisagés. Nous avons laissé cette idée de côté. Il semblait plus simple et plus rapide de simplement tout gérer dans des formulaires, comme ceci:

Intermission: Laisser le fantôme de la rétroaction loin derrière

En ajoutant du TALN, et par le fait même de la flexibilité, nous nous sommes rappelé de l’interaction de rétroaction obligatoire et laborieuse qui nous hantait toujours, et avons décidé de la rendre plus flexible aussi. Nous n’avions toujours pas de “widget” pour la rétroaction ni le temps d’en implémenter un, donc nous avons conservé la question, mais adapté la réaction: si l’utilisatrice répondait quoi que ce soit d’autre que oui ou non, la réponse serait traitée comme s’il s’agissait de la réponse à la prochaine question, qui elle se trouvait à offrir de poser une autre question et pouvait mener aux autres fonctionnalités. Il a fallu quelques contorsions afin de sortir du formulaire de manière préventive et de “reproduire” l’entrée de l’utilisatrice:

Scène 9: Sprint final pour l’ajout du TALN

Puisque nous avions déjà la politique pour remplacer des intentions par “fallback”, la gestion d’erreurs en dehors des formulaires consistait essentiellement en l’ajout d’entrées au dictionnaire des dernières intentions supportées par des actions, ainsi que l’ajout d’histoires pour réagir à l’intention “fallback”, soit en entrant dans le formulaire Q&R ou en affichant un message d’erreur en fonction du design. À l’intérieur des formulaires, nous avons appliqué la même approche que pour les questions oui-non. Nous avons dû faire quelques changements collatéraux, comme l’ajout d’une entité pour la province ou l’ajout d’histoires (principalement sous forme de OR) pour gérer les transitions là où “affirm” ou “deny” étaient valides (maintenant que le raccourci via les boutons n’était plus disponible). Nous avons aussi dû revenir en arrière et modifier notre façon élégante de gérer la digression des antécédents médicaux puisque la solution simple utilisant la politique de correspondance (mapping policy) ne pouvait pas s’appliquer à la gestion d’erreurs. Nous avons donc décidé de gérer cette digression dans un formulaire comme les autres.

Fin

Avec le recul, bien que nous ayons ajouté le TALN, nous avons l’impression d’avoir pris plusieurs raccourcis et approches plus-ou-moins-rasa-esques. Notre cas d’utilisation, qui était entièrement prévisible, sans navigation aléatoire mais rempli d’exceptions et de petites variations, ne correspondait pas à un cas d’utilisation typique pour Rasa. Nous avons frappé plusieurs obstacles qui surgissent naturellement lorsqu’on essaie d’implémenter un design de type diagramme de processus avec Rasa. Néanmoins, Rasa offre beaucoup de flexibilité par l’intermédiaire de code et d’ajouts possibles, et au final, nous avons souvent choisi d’utiliser du code pour représenter des patrons de dialogue car lorsque le temps est compté, le chemin qui nous est familier est le façon la plus sûre de se rendre à bon port.

Dans un futur épisode, nous irons plus loin dans l’exploration des différentes façons d’implémenter deux des principales fonctionnalités des designs de type diagramme de processus, soit les arbres de décision et les dialogues modulaires. Ces derniers sont difficiles à implémenter avec Rasa et nous allons explorer les différentes méthodes pour ce faire. Nous allons également évaluer si et comment Rasa 2.0, toujours au stade alpha au moment d’écrire ces lignes, pourra nous faciliter la tâche.

¹Je rappelle que nous avons librement choisi de traduire les concepts les plus importants comme suit: story => histoire, form=> formulaire, policy=> politique, slot => case, featurized => caractérisée. Ces choix n’engagent que nous.

²Le féminin est employé simplement pour alléger le texte

³Oui, flag dans ce contexte c’est bien un drapeau, voir Le grand dictionnaire terminologique de l’OQLF

A propos de l'auteur : <a href="https://www.nuecho.com/fr/author/kdery/" target="_self">Karine Dery</a>

A propos de l'auteur : Karine Dery