mercredi 26 juillet 2017

Bot Framework Part 4 - Optimisez l'Interaction avec l'utilisateur avec les Prompts

Un des objectifs clés d'un Chat Bot est la contribution de valeur  vis à vis de l'utilisateur, et ceci va passer notamment par des interfaces de dialogue performantes dont l'effet sera le gain de temps. Le premier vecteur va bien évidemment consister à limiter les échanges par texte obligeant l'utilisateur à utiliser son clavier tactile sur un téléphone par exemple ce qui est clairement peu productif et fastidieux pour l'utilisateur. Pour pallier à cela on trouvera des solutions très interessantes dans le Bot Framework tel que les Prompts qui vont permettre d'accèlérer la collecte d'informations sous la forme d'options à cliquer sous différentes forme, l'idée étant de minimiser au maximum la frappe au clavier au profit de boutons ou d'options à cliquer, l'idée est également de cadrer l'utilisateur afin qu'il ne se perde pas dans des conversations infructueuses avec le Chat Bot, on aura également le fait d'apporter de l'aide à l'utilisaeur pour connaitre les possibilités du Chat Bot et donc optimiser le service rendu à l'utilisateur.
Parmi les différents types de Prompts on aura : 
  • Des formulaires pour la collecte de texte
  • La détection de type de données : Nombres, Date - Heure
  • Des listes d'options pour des choix guidés
  • Des Media : images, vidéos par exemple pour des propositions d'hotels ou restaurants
Un des scenarios les plus courant est l'implémentation de la recherche, avec de manière générique les  étapes suivantes : 
  1. Détecter que l'utilisateur est à la recherche de quelque chose : en lui posant la question :)
  2. Recueillir de la part de l'utilisateur une requéte en text Prompt
  3. Proposer des options de réponses cliquables
  4. Afficher le résultat, potentiellement un carousel d'images avec du texte
Nous allons donc mettre en oeuvre ce scénario avec un exemple dans lequel nous recherchons des Profils dans GitHub et affichons la liste de résultats sous la forme d'un carrousel d'options cliquables pour accèder au profil 

Le code du Message Controller qui collecte les messages et répond à l'utilisateur est toujours le même :


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;
        }
    }
}


Le code de RootDialog.cs ci dessous gère le déclenchement de la recherche dès que l'utilsateur aura entré le mot recherche au début de la conversation, si l'utilisateur tape une chaine commencant par le mot recherche suivi d'un espace ou de texte, on va gèrer le cas également comme montré ci dessous. Une fois la requète entrée, la recherche est lancée: 

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

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

            return Task.CompletedTask;
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var msg = await result as IMessageActivity;
            if (msg.Text.Equals(@"Recherche", StringComparison.OrdinalIgnoreCase))
            {
                PromptDialog.Text(context, QueryEntered, @"Que recherchez vous?");
            }
            else if (msg.Text.StartsWith(@"Recherche ", StringComparison.OrdinalIgnoreCase))
            {
                var query = msg.Text.Substring(7);
                await context.Forward<string, string>(new SearchDialog(), SearchComplete, query, default(CancellationToken));
            }
        }

        private async Task QueryEntered(IDialogContext context, IAwaitable<string> result)
        {
            await context.Forward<string, string>(new SearchDialog(), SearchComplete, await result, default(CancellationToken));
        }

        private async Task SearchComplete(IDialogContext context, IAwaitable<string> result)
        {
            var returnMessage = await result;
            if (!string.IsNullOrWhiteSpace(returnMessage)) await context.PostAsync(returnMessage);

            context.Wait(MessageReceivedAsync);
        }

    }
}

Nous avons également un client de recherche sommaire pour GitHub qui va nous retourner les informations de base d'un profil recherché sur GitHub, GitHubClient.cs: 


using Octokit;
using System;
using System.Threading.Tasks;
using gh = Octokit;

namespace Bot_Application
{
    public static class GitHubClient
    {
        private static readonly LazyGitHubClient> _client = new LazyGitHubClient>(() => new gh.GitHubClient(new ProductHeaderValue(@"MyBotApp")));

        public static TaskUser> LoadProfile(string username) => _client.Value.User.Get(username);

        public static TaskSearchUsersResult> ExecuteSearch(string query) => _client.Value.Search.SearchUsers(new SearchUsersRequest(query));
    }

}

Enfin, la classe SearchDialog.cs a pour responsabilité d'exécuter la recherche et de retourner une liste de cards sour la forme d'un carrousel de profils GitHub. On remarquera la facilité avec laquelle on crée la carte de profile avec la méthode CreateCard : 

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

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

            return Task.CompletedTask;
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<string> result)
        {
            var query = await result;

            var profiles = await GitHubClient.ExecuteSearch(query);

            var totalCount = profiles.TotalCount;

            if (totalCount == 0)
            {
                context.Done(@"Désolé, Pas de résultat trouvé.");
            }
            else if (totalCount > 10)
            {
                context.Done(@"Plus de 10 résultats trouvés. Pouvez vous filtrer plus précisément?");
            }
            else
            {
                // convert the results in to an array of cards for each user
                var userCards = profiles.Items.Select(item => CreateCard(item));

                var msg = context.MakeMessage();
                msg.AttachmentLayout = AttachmentLayoutTypes.Carousel;
                msg.Attachments = userCards.ToList();

                await context.PostAsync(msg);
                context.Done(default(string));
            }
        }

        private static Attachment CreateCard(Octokit.User profile) =>
            new ThumbnailCard()
            {
                Title = profile.Login,
                Images = new[] { new CardImage(url: profile.AvatarUrl) },
                Buttons = new[] { new CardAction(ActionTypes.OpenUrl, @"Cliquer pour consulter", value: profile.HtmlUrl) }
            }.ToAttachment();
    }
}

Voyons le résultat obtenu, on lance le Service Bot
Puis on lance l'émulateur Bot et on se connecte à l'URL : http://localhost:3979/api/messages
puis on entre le mot recherche pour déclencher la conversation avec le chat bot


On voit ici le résultat affiché sous la forme d'un carrousel de cartes, et si l'on clique sur consulter, on a accès à la fiche GitHub : 

Cet article nous a permis de montré que nous pouvons au travers d'un Chat Bot implémenter des scénarios ergonomiquement interessant et plus interactif en tirant parti des différents Prompts proposés par le Bot Framework.
Dans l'article suivant nous verrons comment déployer notre Bot afin de pouvoir le distribuer pour un usage en ligne

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: