lundi 24 juillet 2017

Bot Framework Part 3 - Utilisation du Bot Workflow pour gérer la persistence du contexte d'un dialogue

lors des précédents articles, nous avons vu la mise en oeuvre simple d'un Chat Bot avec des fonctionnalités extrêmement limitées, par exemple notre Bot ne gérait aucune persistence de la conversation, donc incapacité à maintenir une conversation sur un sujet ou bien encore de se souvenir de vous dès le début d'une nouvelle conversation. Nous allons voir dans cet article que tout ceci est possible au travers de différents niveaux de persistence des données avec dans l'idée, la capactité à maintenir l'état d'une conversation. Par exemple si vous démarrez une conversation avec un Chat Bot en lui demandant quel est le monument le plus haut de Paris, et que le Bot vous répond La Tour Eiffel, si vous lui demandez qui l'a construite, sans persistence, il ne saura répondre car il aura perdu l'information précédente, donc avec ce système, nous allons pouvoir développer des expériences bien plus proches du réel.
Donc le besoin de maintenir des états est quelque chose de très important, cette fonctionnalité sera fournie par le Bot Connector dont le contrat est de router les messages et gérer la persistence et l'état d'un dialogue. Le Bot fonctionne en tant que Web Service et a donc une communication sans état (stateless) et c'est donc bien la responsabilité du Bot Connector d'assurer cette persistence d'état, notamment par Bot et par utilisateur. Le stockage de l'état se fait en base de données, et on aura un UserID, un ConversationID etc.
Dans l'exemple ci dessous, nous allons donner à notre robot notre prénom et notre ville, grace à la gestion de persistence, il sera capable de restituer toutes ces informations stockées sur notre profil
La première chose à faire est de créer une classe sérialisable décrivant nos informations de profil : UserProfile : 
namespace Bot_Application.Dialogs
{
    [Serializable]
    public class UserProfile
    {
       public string Name { get; set; }
       public string Country { get; set; }
    }
}

Puis nous allons créer la classe EnsureProfileDialog.cs dont la responsabilité va être de s'assurer de la capture des informations de profil de l'utilisateur, dans notre cas, le prénom et la ville

using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Threading.Tasks;

namespace Bot_Application.Dialogs
{
    [Serializable]
    public class EnsureProfileDialog : IDialog<UserProfile>
    {
        public Task StartAsync(IDialogContext context)
        {
            EnsureProfileName(context);

            return Task.CompletedTask;
        }

        UserProfile _profile;
        private void EnsureProfileName(IDialogContext context)
        {
            if (!context.UserData.TryGetValue(@"profile", out _profile))
            {
                _profile = new UserProfile();
            }

            if (string.IsNullOrWhiteSpace(_profile.Name))
            {
                PromptDialog.Text(context, NameEntered, @"Bonjour, quel est votre nom?");
            }
            else
            {
                EnsureCountryName(context);
            }
        }

        private void EnsureCountryName(IDialogContext context)
        {
            if (string.IsNullOrWhiteSpace(_profile.Country))
            {
                PromptDialog.Text(context, CountryEntered, @"Ou habitez vous?");
            }
            else
            {
                context.Done(_profile);
            }
        }

        private async Task CountryEntered(IDialogContext context, IAwaitable<string> result)
        {
            _profile.Country = await result;

            context.Done(_profile);
        }

        private async Task NameEntered(IDialogContext context, IAwaitable<string> result)
        {
            _profile.Name = await result;

            EnsureCountryName(context);
        }
    }
}

La classe RootDialog va permettre le stockage des données de profil dans le User Data Bag, cette tache est effectuée par la tache ProfileEnsured

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace Bot_Application.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog<object>
    {
        public Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);

            return Task.CompletedTask;
        }

        private Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            context.Call<UserProfile>(new EnsureProfileDialog(), ProfileEnsured);

            return Task.CompletedTask;
        }

        private async Task ProfileEnsured(IDialogContext context, IAwaitable<UserProfile> result)
        {
            var profile = await result;

            context.UserData.SetValue(@"profile", profile);

            await context.PostAsync($@"Bonjour {profile.Name}, j'adore {profile.Country}!");

            context.Wait(MessageReceivedAsync);
        }
    }

 
}

Enfin la classe  MessageController.cs

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace Bot_Application
{
    [BotAuthentication]
    public class MessagesController : ApiController
    {
        ///


        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        ///

        public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity.Type == ActivityTypes.Message)
            {
                await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
            }

            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }
    }
}

Regardons maintenant le résultat obtenu en lançant le Bot avec F5 : 
On lance alors l'émulateur et on se connecte sur le port 3979/api/messages, on voit bien les 2 questions posées par le robot, qui termine par la restitution de l'ensemble de l'information en requêtant le User Data Bag

Cette gestion d'état et de persistence va permettre des interactions beaucoup plus riche avec le Chat Bot.
Vous retrouverez le code de cet exemple à l'adresse : Code Chatbot

Aucun commentaire: