Skip to content

Sauter le pas, créez votre Bot !

Posted on:5 novembre 2016 at 01:00

Envie de sauter le pas ? Les Bots où littéralement robots sont de plus en plus présents dans le numérique que ce soit par l’intermédiaire de Facebook, Twitter, Skype, et bien d’autres. Dans cet article nous allons donc voir comment réaliser un Bot “intelligent” qui comprendra l’utilisateur et pourra lui répondre. Pour cela je décide personnellement de partir sur le Microsoft Bot Framework disponible gratuitement.

Que peut faire un Bot ? À quoi ça sert ?

Je propose depuis quelques semaines la réalisation de Bot sur-mesure dans mes offres de réalisation dans mon activité de Freelance (Plus d’informations ici), mais pourquoi donc ? Le Bot a la capacité d’être interactif et surtout intuitif pour un client, plus pratique qu’un site internet, plus simple d’utilisation qu’une application : le langage naturel suffit pour effectuer n’importe quelle opération (prédéfini à l’avance par le développeur), plus besoin de chercher le bouton caché après un achat pour avoir le suivi du colis, pas besoin d’installer un logiciel tiers sur son ordinateur, pas de problème de compatibilité avec le navigateur internet. Bref, en résumé revoir la façon d’interagir avec le client !

En résumé

Comme vous pourrez le voir sur la page descriptive de ma prestation dans la réalisation de bot, voilà les arguments importants que j’avance :

Pourquoi opté pour un Bot : Automatisé votre système de réservation, de commande et le rendant plus vivant. Disponible 24h/24 et 7j/7 un bot ou robot peut accompagner vos clients dans leurs démarches. Relier les robots intelligents pour interagir avec vos utilisateurs naturellement là où ils sont, à partir d’email / SMS vers Skype, Slack, Office 365 mail et autres services populaires

Basé sur Microsoft Azure : Microsoft Azure permet de déployer rapidement votre infrastructure et vos services en fonction des besoins de votre entreprise. Les services Azure de paiement à l’utilisation peuvent rapidement évoluer pour correspondre à vos besoins, vous ne payez que ce que vous utilisez. Les Bots, basé sur le Microsoft Bot Framework ainsi que LUIS permettent une intégration fiable et robuste au cloud Microsoft.

Apprentissage : Un Bot apprend sur la durée, plus celui-ci sera utilisé, plus il sera cohérent et répondra efficacement. Indépendance : Une fois en ligne, le Bot est indépendant et autonome. Il n’a besoin d’entretien particulier sur la durée. Multiplate-forme : Le Bot est polyvalant et peut s’intégrer dans une multitude de services externes comme Facebook Messenger, Skype, SMS.

Réalisation de notre premier Bot

Les outils

D’abord, il vous faudra notre chère et bien aimer Visual Studio, dans son édition gratuite, dite community. Vous le trouverez ici si vous ne l’avez pas déjà sur votre ordinateur. Maintenant il va nous falloir installer les outils du botframework de Microsoft. Celui-ci est disponible pour .NET, NodeJs ainsi que via l’API REST. De mon côté je vais partir sur du développement en C# .NET, après à vous de faire votre choix, sachez que la démarche est similaire peu importe votre technologie.

Avant d’installer le Bot Builder SDK il va nous falloir un logiciel pour faire “l’interface” entre nous et le bot durant le développement. Deux possibilités s’offrent à vous :

Vous devriez (pour la version Windows) avoir le logiciel suivant : bot-emulator À gauche se trouve la zone de conversation entre vous et votre bot et à droite la requête JSON que va générer votre Bot, cela nous permettra d’avoir plusieurs informations comme le pourcentage de confiance du robot sur sa réponse, le type d’évènement qu’il a compris.

Maintenant, nous allons récupérer le template pour Visual Studio (uniquement disponible en C#) qui nous permettra de setup notre projet. Pour cela, télécharger le fichier suivant : Visual Studio Template - C#. Rendez-vous dans “Documents/Visual Studio 2015/Templates/ProjectTemplates/Visual C#/” puis déposer y le zip. Le Bot Builder est aussi disponible sous forme de Package sur NuGet via le nom suivant : Microsoft.Bot.Builder. Maintenant vous trouverez dans Visual Studio, lors de création de projet, le template Bot Application dans la section C#. (Voir image ci-dessous)

visualstudio

Pour les personnes utilisant NodeJs pas de soucis, vous trouverez aussi des exemples de code et de projet prédéfini ici., il vous suffit d’exécuter la commande npm suivante pour l’installation :

npm install botbuilder

Notre premier bot

Maintenant que nous avons les outils il ne nous reste plus qu’à développer notre fameux bot ! Je vous préviens, le premier que nous allons réaliser sera très basique et permettra surtout d’analyser l’architecture et le fonctionnement d’un projet, par la suite nous lui ajouterons la possibilité d’apprendre et surtout de comprendre les différentes tournures de phrases. Je vous invite donc à créer un projet avec le template dans Visual Studio, vous devriez avoir l’architecture de suivante (à droite de l’image). Le principal de notre programme se concentre dans le fichier “MessagesController.cs”, le reste du projet concerne la configuration du serveur Web IIS, littéralement notre “site internet”. Maintenant compiler votre programme avec la petite flèche verte ou simplement F5, une page web devrait s’ouvrir, vous pouvez la fermer, elle indique seulement que le bot est maintenant en fonction. Il ne vous reste plus qu’à ouvrir le logiciel que nous avons télécharger précédemment, le Microsoft Bot Framework Channel Emulator puis écrivez votre message. Par défaut votre bot vous retournera simplement le nombre de caractère de votre phrase !

botimg

Attention, parfois le port peut ne pas être le bon et vous verrez apparaitre un point d’exclamation rouge à côté de votre message, pour corriger le problème il vous faudra simplement regarder l’adresse IP à laquelle s’ouvre la page web sur notre navigateur et de rentrer la même dans le logiciel, dans la section “Bot Url”.

Bon, pour le moment notre bot n’est pas très bavard, nous allons lui rajouter la possibilité de réagir en fonction de certains mots-clefs. Là encore rien de très très extraordinaire ni de très compliquer, vous allez voir. Comme d’habitude voilà le code correspondant, je vais vous l’expliquer par la suite. Je vous invite à créer une classe à la racine de votre projet, de mon côté je l’ai nommé SimpleDialog.cs.

namespace BotTuto
{
    [Serializable]
    public class SimpleDialog : IDialog
    {
        public async Task StartAsync(IDialogContext context)
        {
            context.Wait(ActivityReceivedAsync);
        }

        private async Task ActivityReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var activity = await result as Activity;
            if(activity.Text.Contains("bonjour"))
            {
                await context.PostAsync("Bonjour, je suis un bot !");
            }
            else if(activity.Text.Contains("date"))
            {
                if(activity.Text.Contains("demain"))
                    await context.PostAsync($"Nous serrons demain le { DateTime.Now.AddDays(1) }");
                else if(activity.Text.Contains("hier"))
                    await context.PostAsync($"Nous étions hier le { DateTime.Now.AddDays(-1) }");
                else
                    await context.PostAsync($"Nous somme aujourd'hui le { DateTime.Now }");
            }

            context.Wait(ActivityReceivedAsync);
        }
    }
}

La logique de notre Bot se trouvera ici (pour cette version en tout cas). Rien de très intéressant pour le moment, notre Bot réagit simplement en fonction de mots-clefs présents dans la phrase de l’utilisateur. Ceci pause beaucoup de limite pour la formulation et le rend peu exploitable. Noté aussi que notre Bot est entièrement asynchrone ! Il peut donc répondre en parallèle à une multitude d’interlocuteurs en même temps, le programme ne reste pas bloquer. De plus, notre classe hérite de IDialog qui s’occupe pour nous du fonctionnement du bot.

La méthode ActivityReceivedAsync est appelée à chaque fois que notre Bot reçoit un message, celui-ci l’analyse par la suite à la recherche du mot-clef et retourne une réponse. Rien d’extraordinaire en soi, mais c’est un début ! Je vous invite à lancer le Bot Framework Channel Emulator pour le tester.

Cependant il ne faut pas oublier d’ajouter la ligne suivante dans le fichier MessagesController :

if (activity.Type == ActivityTypes.Message)
{
    await Conversation.SendAsync(activity, () => new SimpleDialog());
}

LUIS : Language Understanding Intelligent Service

Nous allons maintenant donner la compréhension à notre Bot, pour cela Microsoft propose un service du nom de LUIS pour Language Understanding Intelligent Service. Il va nous permettre de greffé une sorte de cerveau externe à notre bot, qui lui permettra de comprendre le langage naturel, en l’occurence ici le français. LUIS fait partie du toolkit Cognitive Services APIs qui permet l’analyse d’image, la compréhension de document audio, des émotions humaines, etc… Libre à vous de faire des combinaisons pour rendre votre Bot le plus intelligent possibles et répondre au mieux à vos besoins. Dans cet article nous ne nous attarderons que sur la compréhension via LUIS.

Les premières manipulations vont s’effectuer sur le site luis.ai, je vous invite à vous connecter avec votre compte Microsoft. Vous devriez être sur la page d’accueil. (Je précise que votre bot devra donc être connecté à internet pour qu’il fonctionne correctement). Cliqué sur “New app” -> “New application”, remplissez le formulaire, sachez que cela n’a absolument aucune importance excepté le choix de la culture (donc de la langue) qu’il vous faut définir sur “French”. Vous devriez maintenant avoir l’interface suivante :

luis

Dans le menu, à droite de l’espace central vous remarquerez différentes sections, je vais vous les décrire :

Bon, maintenant nous allons faire un Bot différent, cette fois en utilisant LUIS. Toujours sur le site, je vous invite à cliquer sur le ”+” à côté de Intents. Un popup s’ouvre, en premier nous devons définir un nom, je vais de mon côté le nommé Hello puis en dessous une phrase exemple, dans la langue de votre Bot, donc nous du français. Je vais simplement indiquer bonjour. Maintenant l’intention Hello apparaît en dessous de None (qui est l’évènement indiquant que le bot n’a pas compris la requête).

Au milieu de votre page vous devriez avoir cela (voir image ci-dessous), le Bot nous demande si bonjour correspond donc bien à l’événement Hello. Ceci et très important, car par la suite, plus vous utiliserez votre Bot, plus celui-ci recevra de requête différente et variée. S’il se trompe dans la compréhension le menu nous permettra de la corriger. Vous pouvez donc valider en appuyant sur le bouton “Submit”.

luishello

Maintenant il faut que notre Bot le mémorise et l’apprenne, pour cela en bas de la page cliqué sur Train, dans l’espace “Performance analysis” à droite, en cliquant sur le bouton de rafraichissement vous pourrez avoir des statistiques sur le taux d’erreur de notre Bot. Alors, maintenant, il ne faut pas oublier de faire un accès HTTP pour lui envoyer des requêtes par la suite via notre code C#. Pour cela dans le menu à droite, cliqué sur Publish. Dans le Popup répété l’opération en cliquant sur le bouton. Voilà, maintenant votre espace LUIS est accessible.

Je répète et j’insiste qu’à chaque modification et apprentissage du Bot il vous faut, faire un Train et un Publish -> Update published application.

Il ne nous reste plus que l’ajout de la partie concernant la récupération des dates d’ouvertures d’un lieu, ici nous allons supposer un salon de coiffure. Faisons la même opération que précédemment avec comme intention : OpeningSpecificDay et comme phrase d’exemple : Le salon est-il ouvert le lundi ?. Maintenant que cela est fait, il nous faut apprendre au bot les jours de la semaine. C’est ici que rentre en jeu le système “Entities”, d’entité. Je vous invite donc à en créer une, je la nommerais simplement DayOfTheWeek. Maintenant dans le menu principal, aller dans l’onglet “Review labels”, puis dans l’itinérance correspondante cliqué simplement sur “lundi”. Il doit devenir jaune et sélectionner notre entité DayOfTheWeek puis validé.

Notre bot peut donc savoir que jeudi correspond à un jour ou à une expression en lien avec les jours de la semaine. Nous allons lui en apprendre plus par la suite. Avant cela ajouté de nouveau une nouvelle intention sobrement appelée Opening, mon exemple est de mon côté quelles sont les heures d’ouverture .. Cet évènement sera appelé s’il ne correspond pas à un jour précis ou si le Bot n’a pas réussi à déterminer le jour correspondant, mais où il aura quand même compris qu’il était question d’horaire. Cela permet de lever des ambiguïtés pour le Bot. Les questions sur le présent étant séparées de ceux du passé ou futur.

Sur votre interface, je vous invite dans l’onglet New utterances à entrer différentes phrases d’exemples, de lui indiquer les jours de la semaine pour qu’il puisse les connaître. N’oublier pas que lorsque votre Bot sera en ligne, vous verrez la liste des requêtes de vos clients, de façon à l’améliorer.

Bon, maintenant revenons sur notre Visual Sutdio, notre petit code. J’ai de mon côté créé une nouvelle classe du nom de LuisBotDialog.cs afin de contenir la nouvelle version de celui-ci. N’oublier pas non plus, si vous changez de classe de modifier le MessagesController, dans mon cas await Conversation.SendAsync(activity, () => new LuisBotDialog());. Comme d’habitude voilà le code correspondant !

using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BotTuto
{
    [LuisModel("ID Projet", "Code Secret")]
    [Serializable]
    public class LuisBotDialog : LuisDialog<object>
    {
        Dictionary<string, int[]> openingTime = new Dictionary<string, int[]>();
        public LuisBotDialog()
        {
            openingTime.Add("lundi", new[] { 8, 12, 13, 18 });
            openingTime.Add("mardi", new[] { 8, 12, 13, 18 });
            openingTime.Add("mercredi", new[] { 8, 12, 13, 18 });
            openingTime.Add("jeudi", new[] { 8, 12, 13, 18 });
            openingTime.Add("vendredi", new[] { 8, 12, 13, 18 });
            openingTime.Add("samedi", new[] { 8, 12 });
            openingTime.Add("dimanche", new[] { 0 });
        }

        [LuisIntent("")]
        public async Task None(IDialogContext context, LuisResult result)
        {
            await context.PostAsync("Désolé, je ne comprends pas votre demande.");
            context.Wait(MessageReceived);
        }

        [LuisIntent("Hello")]
        public async Task GetHello(IDialogContext context, LuisResult result)
        {
            await context.PostAsync("Bonjour, je suis un robot et mon objectif est de vous aider dans votre démarche ! Si vous le souhaitez je peux vous accompagner dans ses domaines :\n* Horaires d'ouverture\n\nQue puis-je faire pour vous ?");
            context.Wait(MessageReceived);
        }

        [LuisIntent("Opening")]
        public async Task GetOpening(IDialogContext context, LuisResult result)
        {
            // Create String
            string open = "Les heures d'ouverture sont les suivantes :\n";
            foreach (KeyValuePair<string, int[]> time in openingTime)
            {
                var hours = time.Value.ToList();

                if (hours.Capacity == 4)
                    open += $"* **{time.Key}** ouvert de { hours[0] }h à { hours[1] }h puis de { hours[2] }h à { hours[3] }h\n";
                else if (hours.Capacity == 2)
                    open += $"* **{time.Key}** ouvert de { hours[0] }h à { hours[1] }h\n";
                else
                    open += $"* **{time.Key}** Fermé\n";
            }

            //Speak
            await context.PostAsync(open);
            context.Wait(MessageReceived);
        }

        [LuisIntent("OpeningSpecificDay")]
        public async Task GetOpeningSpecificDay(IDialogContext context, LuisResult result)
        {
            string dayName;
            EntityRecommendation recommendation;

            if (result.TryFindEntity("DayOfTheWeek", out recommendation))
            {
                dayName = recommendation.Entity;

                int[] hours;
                if (openingTime.TryGetValue(dayName.ToLower(), out hours))
                {
                    if (hours.Length == 4)
                        await context.PostAsync($"Le salon est bien ouvert le { dayName.ToLower() } de { hours[0] }h à { hours[1] }h puis de { hours[2] }h à { hours[3] }h");
                    else if (hours.Length == 2)
                        await context.PostAsync($"Le salon est bien ouvert le { dayName.ToLower() } de { hours[0] }h à { hours[1] }h");
                    else
                        await context.PostAsync($"Le salon n'est pas ouvert le { dayName.ToLower() }.");
                }
                else
                    await GetOpening(context, result);

                context.Wait(MessageReceived);
            }
            else
                await GetOpening(context, result);
        }
    }
}

Que du beau monde dans cette classe. Je vais l’expliquer de façon linéaire afin que cela soit le plus compréhensible possible.

[LuisModel("ID Projet", "Code Secret")]

Dès la première ligne vous remarquerez cette étrange instruction, celle-ci permet vous permet d’indiquer vos codes d’accès. Sur l’interface de LUIS, votre ID de projet est visible dans “App Settings” dans le menu latéral gauche. Pour votre code secret il vous suffit de simplement cliquer sur votre nom/prénom dans le menu en haut du site, identifiable avec le rouage. Une fois ses informations rentrées votre Bot se connecteront connectera à votre “cerveau” LUIS.

    Dictionary<string, int[]> openingTime = new Dictionary<string, int[]>();
    public LuisBotDialog()
    {
        openingTime.Add("lundi", new[] { 8, 12, 13, 18 });
        openingTime.Add("mardi", new[] { 8, 12, 13, 18 });
        openingTime.Add("mercredi", new[] { 8, 12, 13, 18 });
        openingTime.Add("jeudi", new[] { 8, 12, 13, 18 });
        openingTime.Add("vendredi", new[] { 8, 12, 13, 18 });
        openingTime.Add("samedi", new[] { 8, 12 });
        openingTime.Add("dimanche", new[] { 0 });
}

Ici rien d’extraordinaire, j’utilise simplement un Dictionnaire pour stocker les heures d’ouverture ainsi que le jour correspondant. Je précise que cela n’est pas très fonctionnel, et pour un vrai Bot une connexion à une base de données SQL ou SQLite semble beaucoup plus approprié car vous permettra de modifier les données à distance sans devoir éditer le code.

    [LuisIntent("")]
    public async Task None(IDialogContext context, LuisResult result)
    {
        await context.PostAsync("Désolé, je ne comprends pas votre demande.");
        context.Wait(MessageReceived);
    }

    [LuisIntent("Hello")]
    public async Task GetHello(IDialogContext context, LuisResult result)
    {
        await context.PostAsync("Bonjour, je suis un robot et mon objectif est de vous aider dans votre démarche ! Si vous le souhaitez je peux vous accompagner dans ses domaines :\n* Horaires d'ouverture\n\nQue puis-je faire pour vous ?");
        context.Wait(MessageReceived);
    }

            [LuisIntent("Opening")]
    public async Task GetOpening(IDialogContext context, LuisResult result)
    {
        // Create String
        string open = "Les heures d'ouverture sont les suivantes :\n";
        foreach (KeyValuePair<string, int[]> time in openingTime)
        {
            var hours = time.Value.ToList();

            if (hours.Capacity == 4)
                open += $"* **{time.Key}** ouvert de { hours[0] }h à { hours[1] }h puis de { hours[2] }h à { hours[3] }h\n";
            else if (hours.Capacity == 2)
                open += $"* **{time.Key}** ouvert de { hours[0] }h à { hours[1] }h\n";
            else
                open += $"* **{time.Key}** Fermé\n";
        }

        //Speak
        await context.PostAsync(open);
        context.Wait(MessageReceived);
    }

Voilà 2 événements très simples, le [Luis Intent("")] doit contenir les noms des intents défini dans notre interface LUIS. Par conséquent quand le cerveau LUIS comprend l’événement, il l’envoie à notre programme, qui lui actionne la bonne action. Le reste du code est simple et sans difficulté, je ne vais donc pas le détaillé plus que cela.

    [LuisIntent("OpeningSpecificDay")]
    public async Task GetOpeningSpecificDay(IDialogContext context, LuisResult result)
    {
        string dayName;
        EntityRecommendation recommendation;

        if (result.TryFindEntity("DayOfTheWeek", out recommendation))
        {
            dayName = recommendation.Entity;

            int[] hours;
            if (openingTime.TryGetValue(dayName.ToLower(), out hours))
            {
                if (hours.Length == 4)
                    await context.PostAsync($"Le salon est bien ouvert le { dayName.ToLower() } de { hours[0] }h à { hours[1] }h puis de { hours[2] }h à { hours[3] }h");
                else if (hours.Length == 2)
                    await context.PostAsync($"Le salon est bien ouvert le { dayName.ToLower() } de { hours[0] }h à { hours[1] }h");
                else
                    await context.PostAsync($"Le salon n'est pas ouvert le { dayName.ToLower() }.");
            }
            else
                await GetOpening(context, result);

            context.Wait(MessageReceived);
        }
        else
            await GetOpening(context, result);
    }

La méthode suivante est la plus complexe de notre Bot (mais elle reste tout de même assez simple). Ici, cela correspond à l’événement qui demande les horaires d’un jour précis. Dans la première étape l’on vérifie, par sécurité, qu’il y a bien une entité : jour de la semaine dans la phrase. Normalement si l’intention est appelée il n’y a pas de raison, mais au cas ou, s’il n’y en a pas on enverra simplement la liste générale. Après c’est encore plus simple, l’on récupère le jour, on le transforme en minuscule pour correspondre au dictionnaire et enfin on récupère les horaires et le Bot les affiches !

Alors, je suis d’accord, les exemples vus ici sont très très simples et n’ont finalement que peu d’intérêt. Nous n’avons cependant pas tout vue concernant le dialogue, mais je vais m’arrêter ici. Bien entendu, l’ajout de la reconnaissance visuelle, audio ou même connecté le Bot à la base de données de Bing permettrait à celui-ci peut faire beaucoup plus de choses. La seule limite est vraiment votre imagination, tout ce qu’un site, une application, des lignes de commandes peuvent faire, un Bot peut le faire de façon plus naturelle et avec un résultat identique.

Mise en ligne de votre Bot

Bonne nouvelle ! La mise en ligne d’un Bot est gratuite, il vous faudra vous munir d’un compte Microsoft Azure (carte bancaire obligatoire, mais vous ne payerez rien). Azure nous propose jusqu’à 6 espaces d’hébergements gratuits pour nos sites et application, la seule contrainte étant le nom de domaine qui se termine en .azurewebsites.net. Les manipulations vont être très simples, sur le dahsboard Azure : Nouveau ->Application web->Choisir votre plan gratuit->Créer. Voilà vous avez maintenant votre espace web configurer et en ligne !

Maintenant revenons sur Microsoft Visual Studio, clique droit sur votre projet puis Publier, choisir Microsoft Azure App Service et enfin sélectionner votre application Web créer précédemment. Valider, votre bot est maintenant en ligne ! Il ne nous reste plus qu’une seule étape sur le site Bot Framework. Dans le menu aller dans Register a bot et rentrées les informations nécessaires. Pour Messaging endpoint je vous invite à rentrer : votrenomapp.azurewebsites.net/api/messages. Générer ensuite un ID et un mot de passe via Create Microsoft App ID and password et rentré ses informations dans le Web.conf de votre projet, ici :

<appSettings>
<!-- update these with your BotId, Microsoft App Id and your Microsoft App Password-->
<add key="BotId" value="YourBotId" /> <!-- Nom de votre Bot (pas obligatoire) -->
<add key="MicrosoftAppId" value="" /> <!-- ID -->
<add key="MicrosoftAppPassword" value="" /> <!-- Mot de passe -->

N’oubliez pas de mettre à jour votre bot sur Azure, de la même façon que vous l’avez publié précédemment. Une fois cela validé, votre Bot est par défaut disponible sur Skype et par Chat Web. À vous de configurer avec les API nécessaires pour Facebook ou encore Microsoft Teams / slack.

Bravo à vous ! Voilà votre premier Bot très basique mais fonctionnel. Je précise que vous pouvez héberger votre Bot n’importe où, mais pour des questions d’unification j’ai choisi Azure. N’hésitez pas à partager vos créations dans les commentaires. Si certains points ne sont pas non plus bien clairs je peux vous aider. Vous retrouverez ci-dessous un fichier zip contenant le code du projet, ainsi que le modèle LUIS que vous pouvez importer si vous le souhaitez.

Je rappelle que le code source ainsi que l’article en lui-même est libre de droits, sans licence particulière. Téléchargement du projet