Présentation de LUIS, ou comment rendre vos bots plus intelligents!

Comme évoqué dans mon dernier article, il est possible de rendre plus “intelligents” les Bots créés avec le Bot Framework en utilisant LUIS (Language Understanding Intelligent Services). Par intelligent, cela signifie que vos Bots seront en mesure d’apprendre et, surtout, de mieux comprendre ce que l’utilisateur leur demande et c’est ce que je vous propose de découvrir dans cet article.

 

Présentation de LUIS

Actuellement en version Beta, ce service proposé par Microsoft vous permet de disposer d’un moteur capable d’analyser et d’interpréter le texte pour vous permettre de comprendre ce que souhaite votre utilisateur (et ainsi vous permettre de mieux répondre à sa demande).

Pour mieux comprendre, prenons un exemple simple: lorsque votre utilisateur tape “translate France in EN”, il souhaite traduire le pays “France” en anglais. Cependant, il existe énormément de pays et de langues différentes donc vous pouvez difficilement tout gérer à la main dans votre code. Et c’est là que LUIS entre en jeu en faisant en sorte d’être capable de déterminer que lorsque l’utilisateur saisit “translate XX in YY” (ou une phrase similaire qu’il aura appris à reconnaitre), il veut traduire le nom d’un pays dans une langue, quelque soit le pays ou la langue. Ainsi, dans l’exemple ci-dessous, on constate que le moteur a bien reconnu la phrase, le pays et la langue demandée:

image

En relançant le moteur avec une autre demande, on constate que celui-ci a de nouveau bien compris ma demande (“Translate France in IT”) et que cela a été fait automatiquement (il serait trop long/fastidieux/inutile d’implémenter tous les cas possibles par code):

image

C’est là toute la force de LUIS: vous permettre de déduire les demandes de l’utilisateur et de les comprendre, en fonction du contexte ! Et pour bien comprendre comment cela fonctionne, faisons un petit tour d’horizon de l’outil, en créant un modèle capable de rechercher dans les contacts Exchange!

 

Introduction à LUIS

Une fois connecté au portail (https://www.luis.ai), vous pouvez choisir de créer ou éditer une application déjà existante:

image

LUIS s’appuie sur un ensemble d’éléments qui vous permettront de construire le moteur idéal:

  • Intent: Il s’agit de “l’intention” que vous recherchez à réaliser (traduire du texte, liste des contacts, filtrer les contacts, etc.)
  • Entities: Il s’agit des entités qui seront reconnues/manipulées. Il peut s’agir d’entités personnalisées ou d’entités pré-définies (nombre, températures, âge, monnaie, etc.)
  • Utterances: Ce sont les énonciations, autrement dit les patterns de texte, qui seront reconnues et qui vous permettront de déclencher vos intentions.

Pour construire une application LUIS, il est donc nécessaire d’ajouter des intentions permettant de dire: “Quand je demande XX, exécute l’action Y”:

image image

Dans cet exemple, je créé une intention qui dit: “Lorsque je demande à accéder à tous les contacts, appelle l’URL indiquée et affiche le contenu de la propriété Results”.

Attention, pour pouvoir utiliser les actions, il est nécessaire de passer sur la version “Preview”, via le bouton “Go To Preview”:

image

Il faut maintenant indiquer l’énonciation qui me permettra de déclencher cette intention, et c’est ce que je peux faire dans l’onglet “New utterances”:

image

Ici, LUIS m’indique qu’en saisissant le texte “display contacts”, il a reconnu à 97% qu’il s’agissait de l’action “Get all contacts”. Comme c’est bien le cas, je peux lui confirmer qu’il s’agit de la bonne intention en cliquant sur Submit. Une fois l’ensemble des énonciations ajoutées, il faut entrainer le modèle en cliquant sur le bouton “Train” situé en bas à gauche de l’écran:

image

Cela permet de déclencher l’entrainement du modèle pour qu’il affiche le nombre d’énonciations associées à chaque intention, ainsi que le nombre d’énonciations correctement prédites:

image

Dans le cas où notre intention nécessite un paramètre, ce cas est également prévu de la part de LUIS:

image image

Nous déclarons ce paramètre comme étant “Required”, impliquant que l’action associée (l’appel de l’URL) ne se fera que si le paramètre est bien reconnu par LUIS. Dans le cas contraire, le message spécifié (“Please, enter the name of the contact to search”) sera renvoyé à l’utilisateur, permettant d’avoir un dialogue avec l’utilisateur!

Etant donné que notre intention repose sur un paramètre, nous devons indiquer à notre modèle qu’il recevra ce paramètre et donc comment le recevoir. Pour cela, il faut l’entrainer en lui indiquant des énonciations possibles:

image

Là encore, lorsque la phrase “display infos for thomas” est affichée, LUIS estime à 99% qu’il s’agit d’une demande de recherche de contacts. De plus, il a identifié que “thomas” était le nom devant servir de paramètres. Dans le cas où le modèle n’est pas sûr, c’est à vous de lui indiquer où se situe le paramètre, pour qu’il puisse apprendre:

image

En saisissant “show simon”, le modèle pense qu’il s’agit d’une demande pour lister tous les contacts. Vu qu’il se trompe, je lui indique que “simon” est un nom (donc le paramètre de l’appel à mon Web Service) et qu’il s’agit bien de l’intention “Search contacts”. Ainsi, en relançant la même énonciation, le modèle m’indique bien qu’il la connait déjà:

image

Mes entités sont définies, les énonciations sont mises en place, le modèle est entrainé et publié: il ne reste plus qu’à l’utiliser! Je peux appeler son URL de publication directement (https://api.projectoxford.ai/luis/v1/application/preview?id=XXXXX&subscription-key=XXXXXX), passer en paramètres le texte nécessaire et interpréter le JSON ou bien, je peux m’appuyer sur le Bot Framework pour proposer une intégration sur différents canaux. Une fois la connexion entre LUIS et le Bot Framework mise en place, je peux le tester directement:

image

On peut voir ici qu’en saisissant “show pascal” (énonciation qui n’avait jamais été faite), le modèle a correctement compris l’intention, avec son paramètre, et a appelé le Service spécifié avec la bonne URL. Dans le cas où le modèle comprend l’intention mais ne détecte pas la valeur du paramètre, il remonte le message d’erreur qui lui est associé:

image

Une fois le test effectué (et validé) dans la console, il ne reste plus qu’à brancher votre Bot sur un channel, comme Slack par exemple, vous permettant ainsi de faire vos recherches de contacts directement depuis cet outil:

image

Dans l’image précédente, vous pouvez visualiser le principe de dialogue mis en place entre LUIS et Slack: lors de la reconnaissance du texte, LUIS n’a pas compris le nom (paramètre), il a donc affiché le message d’erreur associé. Lorsque l’utilisateur a saisit le texte (“thomas”), LUIS a continué le dialogue: il a identifié qu’il s’agissait du paramètre pour l’intention de recherche de contacts, il a donc déclencher l’action correspondante!

Parfois, votre modèle ne sera pas en mesure de comprendre ce que l’utilisateur lui demandera:

image

Mais vous pouvez l’éduquer, lui apprendre comment interpréter ce type de demande afin qu’il soit en mesure de devenir de plus en plus autonome:

image image

Cette utilisation de LUIS est pratique mais peut vite s’avérer limitée: la Web API que vous appelez doit avoir un format particulier, vous ne pouvez pas formatter le modèle de réponse comme vous le souhaitez, etc.

C’est pourquoi je vous propose de voir comment développer un bot plus évolué, plus intelligent, toujours en s’appuyant sur LUIS.

 

Création d’un bot intelligent

Dans le précédent article, nous avons vu comment créer un bot simple, qui pose des questions et qui, une fois toutes les réponses obtenues, se charge d’effectuer une action (appel d’un Web Service, etc.)

Nous allons à présent voir comment créer un bot plus intelligent, s’appuyant sur LUIS pour comprendre le contexte et pour ensuite vous permettre d’avoir une maîtrise à 100% du processus.

Comme précédemment, la première étape consiste à créer un Bot, via le modèle de projet associé:

image

A présent, nous allons créer une nouvelle classe, héritant de LuisDialog, qui se chargera des interactions/dialogues entre le bot et l’utilisateur:

[LuisModel(Constants.LUIS_MODEL_ID, Constants.LUIS_MODEL_SUBSCRIPTION_KEY)]
[Serializable]
public class ContactsManagerBotDialog : LuisDialog
{
    [LuisIntent("")]
    public async Task None(IDialogContext context, LuisResult result)
    {
        string message = "I'm sorry, my responses are limited, you have to ask me the right this.";
        await context.PostAsync(message);

        context.Wait(MessageReceived);
    }

    [LuisIntent("SearchContacts")]
    public async Task SearchContacts(IDialogContext context, LuisResult result)
    {
        EntityRecommendation name;
        if (!result.TryFindEntity("Name", out name))
        {
            context.ConversationData.SetValue<string>("DisplayName", string.Empty);

            string message = "Please, enter the name of the person your are looking for.";
            await context.PostAsync(message);
        }
        else
        {
            context.ConversationData.SetValue<string>("DisplayName", name.Entity);
        }

        context.Wait(MessageReceived);
    }
}

Plusieurs points sont à remarquer sur cette classe:

  • Elle est décorée de l’attribut LuisModel, qui permet (via le couple Id/Secret) de savoir à quel modèle LUIS cette classe correspond
  • Les méthodes portent l’attribut LuisIntent qui prends en paramètre le nom de l’intent, défini côté LUIS, qui doit déclencher cette méthode.

Le reste du code parle de lui-même: lorsque l’intent “SearchContacts” est déclenché, côté LUIS alors la méthode SearchContacts est appelée. Cette méthode regarde si, dans ce que le modèle lui renvoie, l’entité “Name” a bien été trouvé et, dans ce cas, elle ajoute la valeur dans les données de la conversation. Dès lors, l’utilisation de cette classe s’avère très simple:

[BotAuthentication]
public class MessagesController : ApiController
{
    public async Task<Message> Post([FromBody]Message message)
    {
        if (message.Type == "Message")
        {
            var reply = await Conversation.SendAsync(message, () => new ContactsManagerBotDialog());

            var displayName = reply.GetBotConversationData<string>("DisplayName");
            if (!string.IsNullOrWhiteSpace(displayName))
            {
                using (var httpClient = new HttpClient())
                {
                    var url = $"{Constants.ContactsManagerApiUrl}/api/contacts/{displayName}";

                    var contactsContents = await httpClient.GetStringAsync(url);
                    var contacts = await Task.Run(() => JsonConvert.DeserializeObject<IEnumerable<ExchangeContact>>(contactsContents));

                    return reply.CreateReplyMessage(GetContactsAsString(contacts));
                }
            }

            return reply;
        }
    }

    private string GetContactsAsString(IEnumerable<ExchangeContact> contacts)
    {
        var sb = new StringBuilder();

        foreach (var contact in contacts)
        {
            sb.Append($"{contact.DisplayName}");

            if (!string.IsNullOrWhiteSpace(contact.Email))
            {
                sb.AppendFormat($" ({contact.Email})");
            }

            sb.AppendLine(string.Empty);

            if (!string.IsNullOrWhiteSpace(contact.Company))
            {
                sb.AppendFormat($" {contact.Company}");

                sb.AppendLine(string.Empty);
            }

            if (!string.IsNullOrWhiteSpace(contact.MobilePhone))
            {
                sb.AppendFormat($" {contact.MobilePhone} (Mobile)");

                sb.AppendLine(string.Empty);
            }

            if (!string.IsNullOrWhiteSpace(contact.BusinessPhone))
            {
                sb.AppendFormat($" {contact.BusinessPhone} (Work)");
                sb.AppendLine(string.Empty);
            }

            sb.AppendLine(string.Empty);
        }

        return sb.ToString();
    }
}

Il ne vous reste plus qu’à déployer votre Bot et à régler la configuration (Endpoint) du Bot Framework pour que le reste fonctionne!

image

Attention, au niveau de la configuration, pensez à bien utiliser une adresse en HTTPS. De plus, pensez à mettre à jour vos identifiants, dans le fichier web.config pour éviter les problèmes d’autorisation:

image image

Une fois l’ensemble des étapes terminées, il ne vous reste plus qu’à tester directement l’ensemble, que ce soit dans la console de debug ou depuis Slack:

image image

De cette manière, vous avez la possibilité d’avoir la maîtrise complète sur le code de votre Bot et du modèle associé. De plus, cela vous évite d’avoir à formatter vos résulats directement au niveau de votre API (comme c’est nécessaire dans la version utilisant uniquement LUIS): vous pouvez (et devez) garder votre API la plus simple possible et faire le rendu et la restitution des données au niveau du Bot.

Pour finir, et parce que le Bot Framework le permet, voici le résultat de l’utilisation du Bot, conjointement avec LUIS, dans Skype (possible après avoir eu accès à la preview de bots Skype via https://developer.microsoft.com/en-us/skype/bots/):

image

Plutôt sexy et intéressant en termes de scénarios, ne trouvez-vous pas ? Winking smile

 

Pour finir, quelques liens sur le sujet:

 

Happy coding! Smile

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus