Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Ajouter LUIS à un bot

Poster un commentaire

L’article précédent fut l’occasion de découvrir une des applications proposées par Microsoft dans son Cognitive Services. Il ne reste plus qu’une dernière étape: intégrer le service au bot.

Je vais reprendre l’application TravellingAgency. Le LUIS qui correspond a été monté lors de l’article qui précède:

https://amethyste16.wordpress.com/2017/02/19/tutoriel-luis/

 

Pour l’instant LUIS ne se connecte qu’à deux canaux :

  1. Slack
  2. Bot Framework

Nous allons explorer la seconde option.

En fait je suis sur de rien pour Slack car les paramétrages semblent avoir disparus lorsque je compare le portail actuel avec les copies d’écran d’anciennes versions trouvées dans des blogs.

Architecture de l’application

Nous allons construire 4 IDialog:

  1. LuisTravelDialog
  2. CongratulationDialog
  3. ReservationDialog
  4. None

 

LuisTravelDialog est le dialogue responsable de l’interaction avec le client et comme le suggère son nom, il se fera aider de LUIS. Le but de ce dialogue est d’identifier une des 4 intentions que LUIS sait repérer et donner la main à un dialogue adapté.

Notez la présence de None, c’est une réponse possible de LUIS, il est donc important que le bot puisse le traiter.

 

Le dialogue LuisDialog

La trame générale d’un dialogue Luis est celle-ci:


[Serializable]
[LuisModel("3c34bc60-bc0f-40f0-8bf7-3cbe5f5a817b", "32910e551add4572bc25f23317fb53c0")]
public class LuisTravelDialog: LuisDialog<object>
{
}

 

On découvre un nouvel attribut: LuisModelAttribute. Il attend deux paramètres:

  1. Application Id
  2. Subscription key

 

Il existe plusieurs façon d’obtenir ces informations. Par exemple le dialogue de publication:

2017-02-20_22-55-45

Les informations se trouvent dans l’url vers le site web.

Depuis le portail également.

Application Id se trouve dans le menu des settings:

2017-02-20_23-02-21

 

Qui donne accès à:

2017-02-20_23-03-23

 

Pour la clef d’abonnement:

2017-02-20_23-04-20

 

Qui donne accès à:

2017-02-20_23-05-20

 

Cette clef a les limitations évoquées dans l’article précédent, en particulier 10 000 requêtes par mois de gratuites. Si on a besoin d’aller au delà, on peut acheter sur son abonnement Azure une autre clef. Je ne vais pas développer ce scénario ici.

Intention None

Ceci étant précisé, terminons le dialogue. Commençons par None car c’est le plus simple. L’affichage d’un simple message suffira:


[Serializable]
[LuisModel("3c34bc60-bc0f-40f0-8bf7-3cbe5f5a817b", "32910e551add4572bc25f23317fb53c0")]
public class LuisTravelDialog:LuisDialog<Object>
{
   [LuisIntent("")]
   public async Task NoneAsync(IDialogContext context, LuisResult luisResult)
   {
      await context.PostAsync("Désolé, je ne comprends pas ce que vous voulez");
      context.Wait(MessageReceived);
   }
}

 

LuisIntentAttribute est l’attribut chargé de faire le lien entre l’intention retournée par LUIS est la méthode en charge de son traitement. Pour None on se contente de mettre une chaîne vide (on pourrait aussi mettre NONE). Le code de cette méthode est très classique, nous l’avons déjà rencontré dans les exemples précédents. La seule différence est que la classe LuisDialog<T> fournit une méthode MessageReceived. Notez que la méthode est virtuelle.

 

On pourrait à ce stade faire un test. Côté contrôleur on ajoute ceci:


await Conversation.SendAsync(activity, () => Chain.From(() => new LuisTravelDialog()));

 

Et on lance le site web ainsi que l’émulateur:

2017-02-20_23-28-32

 

Super, ça fonctionne. Histoire de l’avoir vu au moins une fois, que se passe t’il avec une intention non encore implémentée?

2017-02-20_23-30-36

 

C’est NoneAsync qui est lancé, le SDK est cohérent. On voit toute l’importance de ne pas oublier de traiter cette intention, d’autant plus que si c’est NoneAsync qui manque:

2017-02-20_23-30-36

 

Pas très sympa pour l’utilisateur.

Il est possible de détecter si aucune méthode n’a pu traiter une intention en examinant les états de la propriété luisResult:

2017-02-20_23-39-53

L’intention proposée n’est pas None. On pourrait alors envisager un log. J’avoue tout de même être assez surpris du score proposé. En fait il faut vraiment saisir des énoncés tels sdfaisdfsdqiufggs, pour trouver un none. Un point à avoir en tête lors du développement d’un bot avec LUIS.

Intention Congratulation

Les méthodes qui suivent se feront sur le même modèle que NoneAsync.

Le comportement de la démo est très simple: le bot se présente et réclame à l’utilisateur son nom. Il pourrait toutefois devenir plus sophistiquer et réclamer des informations supplémentaires telle une adresse afin de créer un compte pour l’utilisateur.

Contrairement à None qui a peu de chance d’évoluer, Congratulation mérite qu’on lui consacre un IDialog:

[Serializable]
public class CongratulationDialog : IDialog
{
   public async Task StartAsync(IDialogContext context)
   {
      await context.PostAsync("Salut je suis M. Bot à votre service");
      await context.PostAsync("Quel est votre nom?");
 
      context.Wait(MessageReceivedAsync);
   }
 
   public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
   {
      var message = await argument;
      string userName = message.Text;
 
      context.Done(message);
   }
}

Je ne crois pas qu’il soit très difficile de reconstituer les événements. Notez juste une originalité, le nom de l’utilisateur n’est pas sauvegardé par le dialogue, il est simplement passé en paramètre de Done() afin que le dialogue parent puisse le récupérer.

Le dialogue parent se situe évidemment dans la classe LuisTravelDialog:

[LuisIntent("Congratulation")]
public async Task CongratulationAsync(IDialogContext context, LuisResult result)
{
   context.Call(new CongratulationDialog(), Callback);
}
 
private async Task Callback(IDialogContext context, IAwaitable<object> result)
{
   Activity message = (Activity) await result;
   string userName = message.Text;
 
   await context.PostAsync(String.Format("Merci {0}.  Vous souhaitez faire quelle réservation?", userName));
 
   context.Wait(MessageReceived);
}

La méthode qui réagit à l’intention Congratulation s’appelle CongratulationAsync. Un context.Call() lance le dialogue que nous venons de construire. Une fois que celui-ci a terminé son office il donne la main à la méthode de rappel Callback().

Celle-ci récupère le nom de l’utilisateur et lui demande de faire une réservation. Visuellement:

2017-02-22_22-57-39

Le dialogue est sans états. Si je relance quelque chose comme hello ou Yo, on redémarre le dialogue Congratulation.

Il ne reste plus qu’à implémenter un dernier dialogue qui va gérer les demandes de réservation.

Intention Reservation

La particularité de cette intention est que nous avons besoin de récupérer ses entités. Comment fait t’on cela?

Un petit test nous le dira:


[LuisIntent("Reservation")]
public async Task TravelAgencyDialogAsync(IDialogContext context, LuisResult result)
{
   context.Wait(MessageReceived);
}

Un petit point d’arrêt et on lance l’énoncé: book me a flight from paris to london

On trouve:

2017-02-23_22-11-43

Une collection Entities nous donnera toutes les informations utiles. Le Type nous indiquera la destination et le point de départ.

Je ne vais pas développer complètement le dialogue, mais il faudrait aussi gérer le cas où l’une de ces informations manque, également demander la date de départ, le nombre de places…

On peut le faire soit directement depuis un IDialog général, soit créer des intentions spécifiques et gérer les états.

Bibliographie

 

 

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s