Skip to content

develohpanda/Bot-Hackfest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hackfest!

Table of Contents generated with DocToc

Introduction

Microsoft has a barebones C# starter project for bots, however it can require some plumbing to get running properly. By all means, if you wish to use one of those example projects, you can. For the purposes of the Hackfest, I have extracted patterns and helpers developed and used in a very recent bot.

I have also added a conversation to demonstrate how a conversation may flow, employing some of the helpers included in the HackfestBotBase.

Demo

The purpose is to identify the users name and persist it. If the name is already saved, the user is not asked and it loads from memory. It then prompts the user to type a phrase, provides suggested responses, and uses the response to determine the next step. This demo implemented in dialogs/DemoDialog.cs of the HackfestBotBase incorporates all of the bot concepts and features described before, and serves as an example. You can experiment with it here.

Prerequisites

  • Visual Studio 2017 (optional: Azure SDK)
  • Azure Bot Emulator (v3.5.35) (Install Instructions)
  • Azure subscription (Sign up for a trial here)
  • Access the code on the VSO project (https://intergen.visualstudio.com/FHF%20Bot%20Hackfest/Attendees/_git/HackfestBotBase). Fork the HackfestBotBase repository for your team and start hacking! This repository needs to be kept private as it is internal IP. For Auckland, the MA developer in your team will have admin access and can add you as a member. Alternatively, get the code from here
  • Join the Hackfest group on Yammer. It will be the primary channel to post questions, suggestions, or to show off ideas. These grants visibility to all tech leads to pitch in when appropriate.
  • Microsoft Edge (use other browsers at your own risk, they can give issues in Azure or while running the demo)

Get started

  1. Ensure you have all prerequisites above.
  2. Extract and open the Hackfest bot solution.
  3. Right click on the solution > Restore nuget packages.
  4. Build solution.
  5. Start debugging the API project (F5).
  6. Open the Bot Emulator.
  7. Navigate to http://localhost:3978/api/messages and press connect.

One of the fastest ways to get something working is looking through examples and making changes. Play around with the demo project, using the docs below and on MSDN to supplement.

Test locally with the Bot Emulator

  1. Run the Hackfest bot solution.
  2. Open the bot emulator.
  3. Navigate to http://localhost:3978/api/messages and press connect.

Setup-3

  1. If successful, you should see 200 in the bot emulator logs.

Setup-4

  1. SAY HI!

Setup-5

Bot Concepts

This section will primarily link to the Bot Service and Bot Builder SDK documentation on MSDN as the main source of information. At the very least, I suggest reading through the following short documents to understand the key concepts.

Designing/Wireframing

Use Bot Society to design and visually 'see' your conversation before you start building, if you wish. It simplifies the building process significantly.

First interaction: Language vs Menus

MSDN

Starting the bot with an open-ended question such as "How can I help you?" is generally not recommended. If your bot has a hundred different things it can do, chances are users won’t be able to guess most of them. Your bot didn’t tell them what it can do, so how can they possibly know? Menus provide a simple solution to that problem.

Dialogs, conversation flow

Design, Implementation

Dialogs enable the bot developer to logically separate areas of bot functionality and guide conversation flow.

When one dialog invokes another, the Bot Builder adds the new dialog to the top of the dialog stack. The dialog that is on top of the stack is in control of the conversation. Every new message sent by the user will be subject to processing by that dialog until it either closes or redirects to another dialog. When a dialog closes, it's removed from the stack, and the previous dialog in the stack assumes control of the conversation.

dialogs-screens

Dialog lifecycle

MSDN

When a dialog is invoked, it takes control of the conversation flow. Every new message will be subject to processing by that dialog until it either closes or redirects to another dialog.

In C#, you can use context.Wait() to specify the callback to invoke the next time the user sends a message. To close a dialog and remove it from the stack (thereby sending the user back to the prior dialog in the stack), use context.Done(). You must end every dialog method with context.Wait(), context.Fail(), context.Done(), or some redirection directive such as context.Forward() or context.Call(). A dialog method that does not end with one of these will result in an error (because the framework does not know what action to take the next time the user sends a message).

Channel inspector

MSDN

This will allow you to see all the different types of cards that can be attached to messages to add rich data to your conversation.

Additional links

Features of the HackfestBotBase

All of the features below, posting and receiving messages, and handling basic conversation flow, are implemented in the DemoDialog.cs example class. When you first run the project, this is the dialog that will power the conversation (as configured in MessageControler.cs).

This project is built on top of the base bot project by Microsoft, so all of the documentation on MSDN is still relevant. However some patterns and helpers have been developed while building another prototype and developing internal IP, which are included within this project.

This is one of the first iterations of the internal base project, and it will improve over time.

Data storage/state helpers

The bot framework has three data stores. (MSDN)

  • User: data associated with a specific user (across all channels and conversations)
  • Conversation: data associated with a specific conversation with a specific user
  • PrivateConversation: data associated with a specific user within the context of a specific conversation

These are stored in an in-memory store, but if you set the AzureWebJobsStorage config value, it will use Table Storage. This is automatically configured once you deploy to Azure, but locally the in-memory store will be used.

These are key-value stores and can get unwieldy to manage when data is written to one store with a certain key, and read from a different store with a different key. This can especially happen when there are many key-value pairs being stored. It is welcoming bugs and is a trigger for rip-your-hair-out syndrome.

There are two main classes that enable a cleaner method of managing the data stores.

  • models/DataStoreKey.cs is an enum, defining keys for the key-value pairs. Each enum value has an attribute identifying the data store to use (User, Conversation, PrivateConversation)
  • services/BotDataService.cs contains logic pertaining to reading and writing from the data store

The implementation in the BotDataService.cs and SetValue()/GetValueOrDefault() extension methods ensure data is read/written against the correct key in the correct store, and is used as per the examples below. In order to use this bot service, inject the interface IBotDataService to your class.

public enum DataStoreKey
{
    [DataStoreEntry("Preferred name", DataStore.User)]
    PreferredFirstName
}
// Snippet from class BotDataService
...
public void SetPreferredName(IBotData botData, string name) 
{
    botData.SetValue(DataStoreKey.PreferredFirstName, name);
}

public string GetPreferredName(IBotData botData) 
{
    return botData.GetValueOrDefault<string>(DataStoreKey.PreferredFirstName);
}
...

Autofac/IoC

A conversation and all of its instances/resources will get serialized and saved to the data store by the bot framework, so when creating services to integrate with the bot, we need to ensure they don't get serialized and are instantiated each time. Serialization can cause unnecessary issues.

When registering a service with Autofac, use the FiberModule.Key_DoNotSerialize key.

builder.RegisterType<MessageService>()
    .Keyed<IMessageService>(FiberModule.Key_DoNotSerialize)
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

Use the examples in the IoC folder for registrations.

Dialog builder

The dialog builder simplifies resolution of a dialog through an Autofac registration. Because of the way Autofac requires the creation of a lifetimescope each time a registered services needs to be resolved, the code can get cluttered with unnecessary plumbing.

For this reason, all logic pertaining to dialog creation/resolution should be contained within the dialogs/DialogBuilder.cs class.

Note: A conversation, its dialog stack and its state gets serialized and saved into the bot data store. For this reason, all dialogs must have the [Serializable] attribute applied to the class.

Use the example implementations and notes in the files below.

  • IoC registration: IoC/ApplicationDialogsModule.cs
  • IoC resolution: dialogs/DialogBuilder.cs
  • Usage: dialogs/DemoDialog.cs

As in the examples below, the CreateDialog method handles all the lifetime scope creation/deletion duties when resolving an instance.

Example: Register and resolve dialog without no custom parameters

// Snippet from DialogBuilder.cs
public NameDialog BuildNameDialog(IMessageActivity message)
{
    return CreateDialog(message, scope => scope.Resolve<NameDialog>());
}
// Snippet from NameDialog.cs
...
private readonly IBotDataService _botDataService;

public NameDialog(IBotDataService botDataService)
{
    SetField.NotNull(out _botDataService, nameof(botDataService), botDataService);
}
...
// Snippet from ApplicationDialogsModule.cs
...
builder.RegisterType<NameDialog>().AsSelf().InstancePerDependency();
...

Example: Register and resolve dialog with custom parameters

// Snippet from DialogBuilder.cs
public ShowSuggestedActionsDialog BuildShowSuggestedActionsDialog(IMessageActivity message, string prompt, List<string> options)
{
    return CreateDialog(message, scope => scope.Resolve<ShowSuggestedActionsDialog>(TypedParameter.From(prompt), TypedParameter.From(options)));
}
// Snippet from ShowSuggestedActionsDialog.cs
...
private readonly IMessageService _messageService;

public ShowSuggestedActionsDialog(string prompt, List<string> options, IMessageService messageService)
{
    SetField.NotNull(out _messageService, nameof(messageService), messageService);
    SetField.NotNull(out _options, nameof(options), options);
    SetField.NotNull(out _prompt, nameof(prompt), prompt);
}
...
// Snippet from ApplicationDialogsModule.cs
...
builder.Register((c, p) =>
    new ShowSuggestedActionsDialog(
        p.TypedAs<string>(),
        p.TypedAs<List<string>>(),
        c.Resolve<IMessageService>()))
    .AsSelf()
    .InstancePerDependency();
...

Message service

The purpose of this service is to send multiple messages from the bot to the user. This helper service will split a string into separate chat messages, using newline characters \n in the original string.

For example, _messageService.PostAsync("Hello!\nI'm on a new line!"); will send messages as shown in the following image.

MessageService

Reading separate concise messages is nicer than reading a big paragraph, in a conversational context. Think about how to effectively communicate using shorter messages.

User persistence

When you use any channel other than a webchat, such as Messenger, a user id is returned unique to each channel. When using a webchat, if the web app has a concept of user accounts, then that account id is used to identify an existing user and load their data from state.

In a web app without the concept of a user account, there is no way of automatically identifying a return user. The BotFramework-WebChat control by Microsoft is open-source, so on my fork I have added a new boolean property named persistUser. The compiled version of this fork exists here, and can be used to embed an app using the CDN links (https://cdn.rawgit.com/develohpanda/Bot-Hackfest/master/botchat.css, https://cdn.rawgit.com/develohpanda/Bot-Hackfest/master/botchat.js).

If this flag is set, the modification is enabled. On launching the chat, it will generate a new user id and persist it to localstorage. If an id already exists, that id will be used to identify a return user. A timeout can be added in the future, but at the moment there is no timeout. This means in the demo above, if you have already provided your name, because the bot identifies a return user with the saved id, it knows your name and doesn't ask you again.

Set up Azure Environment

Make sure to use Microsoft Edge to browse the Azure Portal if you come across any issues. I have faced bugs with creating a Web App Bot using Chrome and Firefox.

  1. Log into the Azure Portal.
  2. Select Create a Resource from the menu.
  3. We will set up a Web App Bot as a base. It's found under AI + Cognitive Services.

Setup-1

  1. Enter a bot name, and the remaining fields should automatically populate. Use the Basic C# bot template if using the Hackfest starter project. (MSDN)

Setup-2

Publish

Follow the steps below to deploy to Azure.

Publish-1

Publish-2

Select the app service created when you created the bot resource.

Publish-3

Publish-4

Publish-5

Make sure to republish after changing settings. Publish-6

Configure Channels

MSDN documentation for configuring channels, speech priming etc here.

DirectLine

Publish-7

Publish-8

Republish, and then you can navigate to https://your-bot-name.azurewebsites.net/ to use the chatbot.

You can also navigate to http://localhost:3979/default.htm to view the same page, although the bot will be connected to the deployed version in Azure. To debug a local version of the bot, use the bot emulator.

Submit

Submit your entry at bit.ly/bot-hackfest-submit

You can either publish your bot and let us play with it, or create and upload a video to share with us. Be sure to include a description of the features you have added, the drive behind the bot and why it an improvement in the user experience. Tell us about anything unique you did.

Basically, submit anything that screams "we should win"!

Having issues?

  • Is each dialog class tagged with the [Serializable] attribute?
  • Do the service registrations have the FiberModule.Key_DoNotSerialize key added?
builder.RegisterType<MessageService>()
    .Keyed<IMessageService>(FiberModule.Key_DoNotSerialize)
    .AsImplementedInterfaces()
    .InstancePerLifetimeScope();

About

Intergen Bot HackFest documentation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published