Showing posts with label Bots. Show all posts
Showing posts with label Bots. Show all posts

Sunday, June 4, 2017

Azure Bot Service: A basic timer bot

So far in the process, we have created a bot that responds to user input via the prompt dialog process and then sends a request to azure automation via a webhook.  The next step to address in our bot is how we inform the user who made the request about the progress or, should the need arise, any errors.  The goal of this post is to build a very very basic timer bot that uses some concepts of proactive messages. I want to better understand the concepts in play, the data required, and the user experience.

The code I built below is derived from this example on github.   Here is the test code I wrote:


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

namespace TestBot.Dialogs
{
    [Serializable]
    public class TimerDialog : IDialog<object>
    {
        private string fromId;
        private string fromName;
        private string toId;
        private string toName;
        private string serviceUrl;
        private string channelId;
        private string conversationId;
        private string message;

        public Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);

            return Task.CompletedTask;
        }

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

            this.toId = activity.From.Id;
            this.toName = activity.From.Name;
            this.fromId = activity.Recipient.Id;
            this.fromName = activity.Recipient.Name;
            this.serviceUrl = activity.ServiceUrl;
            this.channelId = activity.ChannelId;
            this.conversationId = activity.Conversation.Id;

            PromptDialog.Number(context,this.AfterNumberGiven , "Set a timer for how many seconds?");
        }

        public async Task AfterNumberGiven(IDialogContext context, IAwaitable<long> result)
        {
            var amountOfTime = await result;
            var t = new Timer(new TimerCallback(TimerEvent));
            t.Change(amountOfTime*1000, Timeout.Infinite);
            this.message = $"Your {amountOfTime} second timer is up.  It started {DateTime.Now}";
            await context.PostAsync($"I will contact you in {amountOfTime} seconds");
            context.Done("");
        }

        public void TimerEvent(object target)
        {
            var userAccount = new ChannelAccount(this.toId, this.toName);
            var botAccount = new ChannelAccount(this.fromId, this.fromName);
            var connector = new ConnectorClient(new Uri(serviceUrl));

            var message = Activity.CreateMessageActivity();
            message.From = botAccount;
            message.Conversation = new ConversationAccount(id: this.conversationId);
            message.Text = this.message;
            message.Locale = "en-Us";
            connector.Conversations.SendToConversationAsync((Activity)message);
        }
    }
}

So the above code is actually quite simple.  Whereas the example on github went through the trouble of storing all data in a static object, I simply stored it in this dialog itself.  Based on my understanding of how state works, this dialog is essentially serialized into memory between each request.  Here is what the interaction looks like in the emulator.


You will note that I was able to kick off multiple timers and have them respond differently.  Okay, so here were some notes I made:

1)  I wanted to know more about the data required to continue a conversation.

Here is a snippit from the debugger of my interaction.  Please note that these were captured from the emulator.

  

The key pieces above are the channelId and the serviceURL.  These probably vary greatly based on the channel being selected and I wonder what they look like in slack, etc.

2)  What is the minimum set of information I need to issue a response?

In my use case, someone may send a request to my bot to kick off an automation job in a private message, but I might still want to broadcast that to the general channel in slack so that everyone knows the process of jobs.  You'll note in the code sample above, I actually don't use the toId/toName in my message response.  So it seems, at least in the emulator, you don't need to specify those fields.

When I tried removing the message.From property, the emulator generated a 400 error message.


When I decided to create a new direct conversation, the emulator essentially erased my previous history and replaced it with the new conversation.  This is good to know, so essentially, I do not NEED to keep the conversation id, although it  may lead to weird user behavior as state will be lost.

Ultimately, I guess that some of this behavior will be channel specific, and I'll need to invest further time with slack on all the different options.

This was a good first step and a very basic example on playing around with proactive messages.  Now that I have some understanding of what is involved,  I will now have to add some external storage, and a method for Azure Automation to communicate back to the user.  A future post for sure!

Friday, May 26, 2017

Azure Bot Service: Automation Integration via WebHooks

If you have been following along, we now have a bot that can chat on a slack channel and interact with them via prompt dialogs.  The goal of this post is to briefly discuss one method of integration with Azure Automation, which is using webhooks.

Webhooks are an effective way of starting Azure automation runbooks remotely (without the need of accessing the portal).  Essentially, using webhooks you create a magic URL that points specifically to an Azure Automation runbook.  You can configure certain run settings on the automation side, but other than that, everything else is parsed via the POST body of the request.

On the client side, one would just need to make use of the HTTPClient class to create a post request and send it to the magic link.  In some cases, you may want to augment the code below to pass in an object that is then converted to JSON.  This would be useful when trying to pass in parameters on the fly.


                    case SnapshotAllOption:
                        await context.PostAsync("Snapshotting all!");
                        using (var client = new HttpClient())
                        {
                            client.BaseAddress = new Uri(BotWebhookAddress);
                            client.DefaultRequestHeaders.Accept.Clear();
                            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                            var response = await client.PostAsJsonAsync(BotWebhookToken,new object());
                            if (response.IsSuccessStatusCode)
                            {
                                await context.PostAsync("Successfully sent request");
                            }
                        }
                            break;


The above example simply calls out to the automation runbook when a user selects the "Snapshot All" option via the bot prompt dialog.  It inspects the result and lets the user know that the automation runbook was kicked off successfully.

This integration is quite simple, and that is the beauty of it.  The more challenging part will be how the runbook talks back to the bot to provide status.  I plan to tackle that in an upcoming post.  The following image shows what this interaction looks like in the webchat interface.


Sunday, May 21, 2017

Azure Bot Service: Basic Slack Integration

If you have been following along so far we have created a basic bot and then we have fooled around with prompt dialog as it renders in different channels.  The goal of this post is to chat a bit about basic slack integration.

Bots use the concept of bot connectors to connect with various channels.  A channel can be considered a "line of communication" with a particular client.  Configuration for the channel is done via the bot developer portal.  Here is what my dev portal looks like:

As you can see, there are several options that are built in.  One of the above is a "direct line" which could probably be used to connect with many more channels via a generic interface.  We will probably talk about that one in an upcoming post.

There are a couple of different steps when enabling slack integration.  The official documentation for this process can be found here.

Step 1: Create a Slack Application

Slack already has a built-in concept of an "application" that essentially creates a user and funnels messages to and from that user to the backend that you configure.  Visit this link to create an app.

Step 2: Add the "bot feature" to your Slack Application

Once you have your app created, you need to add the "bot feature" to this application.  This is found under the basic information tab and then the add features and functionality section.


When you create a bot, you are simply asked for the default username for the bot.  Pick anything you'd like!

Step 3: Set the Redirect URI

 This step is required in order to assist with authentication between the slack bot and the Azure bot service.  This option can be found under the OAuth & Permissions tab and the Redirect URLs.

It should look something like this:


Step 4: Configure Channel

Here, you need to basically pass in the OAuth information from slack to the bot dev portal channel configuration. 

The client id, client secret information, and verification token can be found on the Basic Information tab under the App Credentials 

From the Microsoft bot framework portal, click the slack icon to add slack to the list of channels.  Then click "edit" to be taken to the submit your credentials step of the slack integration.


Enter in the required information, and confirm the slack credentials.  After that, mark the checkbox at the bottom that will enable the bot on slack.

Step 5: Install App to your team and test

After you have enabled the bot, you can now distribute it to your team.  Do this in the slack app configuration in the Basic Information tab and the Install your app to your team section.  Once this is done, you should be able to message and interact with your app in your slack channel.

At this point, you should now be chatting with your bot via Slack.  Pretty cool stuff!

Monday, May 15, 2017

Azure Bot Service: Fooling around with PromptDialog

In a previous post I started on my journey to build a bot to handle some IT automation tasks I have been working on.  If you have been following along, you should now have a basic bot that you can test locally.  The goal of this post is to play around a bit with PromptDialog.

The prompt dialog seems like an effective and easy way to get some basic information from a user.  There are several samples that show how to use this.  At it's basic, it seems like you can use a prompt dialog to get a pick from a list of choices, a long, an int, a boolean, etc.  Here is some sample code to work with that prompts the user for a choice.

                    PromptDialog.Choice(
                        context,
                        this.AfterChoiceSelected,
                        new[] { Option1, Option2, Option3, Quit },
                        "How can I help you today?",
                        $"I'm sorry {activity.From.Name}, but I didn't understand that, can you try again?",
                        attempts: 2,
                        promptStyle: PromptStyle.PerLine,
                        descriptions: new[] { Option1, Option2, Option3, Quit }
                        );

Most of the above is pretty standard.  The second parameter passed in is a callback that should be called when the prompt is finished.  Here is a sample of my code.


           
        private async Task AfterChoiceSelected(IDialogContext context, IAwaitable<string> result)
        {
            try
            {
                var selection = await result;
                this.selectedChoice = selection;

                if (this.selectedChoice.Equals(Quit))
                {
                    await context.PostAsync("Quitting... Have a nice day!");
                    context.Done(this);
                    return;
                }
                
                PromptDialog.Confirm(
                    context,
                    this.AfterConfirm,
                    prompt: $"Are you sure you want to {selection}",
                    retry:$"Sorry {context.Activity.From.Name}, I didn't understand that",
                    attempts: 2,
                    promptStyle: PromptStyle.PerLine
                );
            }
            catch (Exception)
            {

                throw;
            }
        }

The other thing worth noting is the promptStyle parameter.

Okay, so now that you have the code, lets chat about a couple of things.

There is no way to quit a prompt dialog, at least that I could find....

When you kick off a prompt dialog as part of a bot interaction, it takes over control of the conversation.  As the state is carried forward automatically in the bot, the only way to exit the prompt dialog is to either pick a valid selection or exhaust the number of attempts.  While playing around, I added a "quit" option that allows the user to type quit to exit the conversation.

You should play around with promptstyle in your different channels

The prompt style parameter is a pretty interesting and neat feature.  Essentially, you are telling the channel abstraction that you are dealing with to render the choice list.  Auto doesn't always work in all cases.  Here is an example of my options in slack:

 
 As you can see, it isn't really usable since the options are cut off.  When I switched it to "PerLine" it showed up like this:

That is much more usable.  I also took a look at the "keyboard" option.  While this didn't seem to do anything material in slack, there was a cool experience in the emulator where "buttons" would show up as the responses.


Pretty cool.  Ultimately, I would think a "per channel" approach to rendering might be a better way to go if you get into complex channel visual requirements.

There is no default "confirm" in PromptDialog.Choice

While it completely makes sense, there is no "confirm" option in the prompt dialog choice object.  So essentially, once you select the option, it fires your call back.  It is up to you then to process the logic as required.  From a usability perspective, you might want to add this step.  It is pretty simple and requires two key pieces.

The first is the PromptDialog.Confirm object which allows you to prompt the user for a yes/no.  The second is the fact that you need to maintain state between interactions with the bot.  The former is shown in the code above, and the latter is easy given that the entire dialog object is serialized into state automatically.  Here is what it looks like in slack.

There is fuzzy matching on picking a number for the option, but not on the words

One thing that is kind of cool (and shown in the image above) is the user can be a little "loose" with how to reference the choices by number.  So they can use "the first one" or "first" or "one" and it will all map to the same option.  This doesn't work, at least in my testing, when trying to match the display of the option.  You have to type out the entire option if you want to select it by name.

Well, prompt dialog seems to be doing it's thing.  I know that I could tie this into LUIS and do some cool things with the natural language processing, but for my use case, this simple prompt dialog does the trick.

Sunday, May 14, 2017

Building my first Azure Bot Service

Bots are all the rage these days (or so it seems).  We used to do a similar thing way back in the day when we built IRC bots to help us manage channels, etc.  The technology has come a long way since then, and integration with LUIS make for some more interesting use cases over and above the standard "pick from this menu" type of implementation.

I think what had been holding me back from building one was a lack of a good use case to build a "real/live service".  The opportunity presented itself at one of my clients, and I decided to take it upon myself to build a working model.  Essentially the problem case is the following:

- I have built a lot of IT automation (via Azure Automation) to simplify the execution of various tasks in the environment (refreshing environments, turning environments on/off, etc)
- The team consists of technical and non-technical folks
- We are looking for an easy way to surface this automation to the end users who need it

The original thought was to build a small web page that uses post forms and integrates with Azure Automation via webhooks.  This would obviously work, but currently we use github for our internal documentation which doesn't lend itself well to this pattern.  One of the other tools in the environment is slack.  Everyone on the team is in the slack channel to facilitate discussions (we have teams in various areas around the world).  So naturally, the idea of a slack bot built on Azure Bot Service with hooks into Azure automation came to mind.  I'm hoping over the next several blog posts to detail out how I'm building this bot to satisfy this need.  Full disclaimer, this is my first bot on Azure Bot Service, so hopefully you are willing to learn with me!

With the preamble out of the way, the goal of this post is talk about getting started on the Azure Bot Framework.  The documentation is actually quite good, and this post follows closely the instructions in the bot builder quickstart for .NET.

There are three main tasks to accomplish here

  1. Install prerequisites
  2. Build/Test the standard bot template
  3. Publish/Register the bot
 Okay, lets get started.

Install prerequisites

First thing you will need is Visual Studio 2017.  You can download it from here. If you already have it, take a moment to ensure that everything is updated.  If you do not, set it to download and take the rest of the day off as it installs.

Next, you can download the bot template.  It is interesting that this isn't distributed via the online templates in VS2017.  Once you have it downloaded, you need to put the zip file in the following location %USERPROFILE%\Documents\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\

Lastly, you will want to download the bot framework emulator. This emulator will allow you to test your bot locally before publishing it to the cloud.

Build/Test the standard bot template

If you did everything correctly in the previous step, you should now be able to open VS2017 and create a "bot application" project type.


Once created, have a look at the code.  Bots are built upon the WebAPI framework, so if you have some experience with that it will be pretty easy to navigate.  The root logic for the bot is in the MessagesController.cs.



     await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());


This line of code above in the Post method essentially creates a new conversation, and calls a dialog to handle it.  Feel free to explore the code further.

You can execute a build in a similar way to any other project, which should start up the debugger and launch Edge which is pointed at the correct endpoint.  There isn't much you can do on that page, which is where the bot framework emulator comes in.

Launch that application, and then configure it to hit your local endpoint.  Type a message and see the response.


Be sure to update it with the port you are working on and click connect

Publish/Register the bot

Now that you have tested your bot, you may want to publish it to the cloud in order to use it in more channels than the bot emulator locally.  There are a couple of steps here, most of which are documented  here.

The first thing you are going to want to do is register your bot.  In this step, you will need to provide a bot name, be assigned an app id, create an app password and provide a bot url.  The detailed instructions were fairly accurate.  It is important to note that you might not know the URL of your bot before you register it.  You can leave it blank and come back to it.

Once you have registered your bot, copy the bot name, app-id, and app password (that you created) and populate them in the web.config file of your bot application.

  

Please note that the botid is simply the name of your bot.  After you have done this, right click on your project and click on publish.  The wizard will walk you through connecting to your Azure account and selecting a resource group, etc for placement. 




After this is published and created, you will need to update your messaging endpoint configuration in your registration page.  If you are using the bot template, do not forget to add the "/api/messages/" to your URL path.


Once you have that done, click the large "test" button in the top right hand corner of the bot framework page to interact with your bot. 

Hopefully you enjoyed that walk through!