Tuesday, August 16, 2016

Automating creation of ChatBot dialog using the new IBM Watson Conversation service

In my last blog post, I took you through how to create a ChatBot using IBM's new Bluemix Watson Conversation service.  This capability allows you to add a Natural Language interface to your applications to simulate conversations with your end users using Watson's cognitive capabilities.  In that post, I showed step-by-step how you could integrate these capabilities into a chat client, such as Slack.  This blog post is a continuation of that topic, so if you haven't read it yet, please do so first: http://mydevbits.blogspot.com/2016/08/create-slack-chatbot-using-new-ibm.html

As I showed last time, you can use the Conversation service visual Dialog Builder to create your conversation flow using the Intents and Entities you define.  But what if you have a very large conversation you are trying to model?  Let's say hundreds, or maybe thousands of potential questions that the user can ask with various different ways to ask the same question.  Trying to model that with the visual Dialog Builder without errors would be quite a challenge.  So in this blog post, I'm going to show you how to automate the creation of your dialog flow programmatically without needing a person to create the flow visually.

In order to demonstrate this capability, I am going to use a currently undocumented method.  The Conversation service definition (all your Intents, Entities, and Dialog flow in a Workspace) are exportable in JSON format.  Similarly, you can also take a previously exported JSON Conversation definition and import it into a new Workspace to create an instance of that definition.  While the import/export function is documented in the official APIs, the details of that JSON format are not (at least not yet, it is anticipated that it will be at some point).  In this post, I am going to leverage this capability to show how you can programmatically create this JSON format of a Conversation Workspace to import and create your Conversation instance.  Doing it this automated way will reduce time to create your Dialog flow, as well as reduce errors, and make it a repeatable process for testing purposes, especially when we are talking about scaling to hundreds or thousands of Dialog entries.

For this demo, I'm going to keep the Dialog flow relatively simple and just concentrate on an Conversation app that will mimic a Question and Answer forum where the Dialog flow is only limited to a depth of one.  This means, in this case I don't have to concern myself with defining Entities, but instead create a series of Intents with sample user input that represent the questions, and map that directly to the expected answer.  In Watson terms, this represents our ground truth which is the gold standard for the learning algorithm in what the answer should be for a set of questions.

In order to create the ground truth, I need to define a corpus (all the questions, and their resulting answer) in a way that it can be programmatic read to build our Conversation model.  Keeping things simple for this demo, I will go ahead and define the corpus in a CSV (Comma Separate Value) file which for each row will contain in the first column the answer, and the remaining columns the various ways of asking the question.  Doing it this way will allow me to enter this information using any one of my favorite spreadsheet editors (e.g. OpenOffice) and export the contents to a CSV which has the added benefit of automatically encoding special characters so they can be imported directly to create a valid JSON document.  For example, the corpus would take the format:

A.1, Q.1-1, Q.1-2, Q.1-3, Q.1-4, Q.1-5
A.2, Q.2-1, Q.2-2, Q.2-3, Q.2-4, Q.2-5, Q.2-6, Q.2-7, Q.2-8
A.3, Q.3-1, Q.3-2, Q.3-3, Q.3-4, Q.3-5, Q.3-6
...

Where A.N is the answer to questions Q.N-M.  For example:

A.1 = On target earnings is a term often seen in job advertisements.  The typical pay structure may be composed...
Q.1-1 = What is on target earnings?
Q.1-2 = What is target earnings?
Q.1-3 = What are the elements of on target earnings?
Q.1-4 = How is on target earnings calculated?
Q.1-5 = Is on target earnings part of a reference salary?

This is just a very small sampling of questions, a more typical example would have many more to more accurately train Watson.

Once the corpus is defined, I need to build the JSON definition for the Conversation Workspace based on this corpus.  So first, let's look at what this JSON format looks like.  To obtain one, just go to a Conversation Workspace instance that you have previously created and select the menu item to export as shown:


Here is an example downloaded JSON format.  Note that I've beautified the output using a simple online tool ( http://jsonprettyprint.com ) as well as color coded it to highlight 4 distinct parts of the JSON that I will explain below.  I also removed some text that was repetitive in terms of the explanation and replace it with "..." to make the size of this example manageable.  Lastly, I inserted the corpus elements (questions and answer) in how I intend to model this Q&A model and will explain that below.  Note, if you are unfamiliar with some of the sections and terms in use here, please go back to the original blog post I mentioned above to first become familiar with the Conversation service.

{ 
  "workspace_id": "fede7b10-f035-44d8-9fcd-80cddbcf08db"
  "name": "Workspace Test Name",
  "description": "Workspace Test Description",
  "language": "en",
  "metadata": null,
  "created": "2016-08-16T02:10:04.048Z",
  "created_by": null,
  "modified": "2016-08-16T02:10:04.048Z",
  "modified_by": null,
  "intents": [
    {
      "intent": "my_intent_0",
      "created": "2016-08-16T02:10:04.048Z",
      "description": null,
      "examples": [
        {
          "text": "Q.1-1",
          "created": "2016-08-16T02:10:04.048Z"
        },
        {
          "text": "Q.1-2",
          "created": "2016-08-16T02:10:04.048Z"
        },
        {
          ...
        }
      ]
    },
    { 
      "intent": "my_intent_1",
      ...
    }
  ],
  "entities": [
    ...
  ],
  "dialog_nodes": [
    {
      "go_to": null,
      "output": {
        "text": "A-1",
      },
      "parent": null,
      "context": null,
      "created": "2016-08-16T02:10:04.048Z"
      "metadata": null,
      "conditions": "#my_intent_0",
      "description": null,
      "dialog_node": "node_1_1471313404048,
      "previous_sibling": null
    },
    {
      ...
    },
    ... 
  ]
}

The first section (highlighted in green) is the general information regarding the Workspace definition.  Keep in mind, depending on how you set up your instance, the values might be different.  In this case, the fields that we are most interested in are "workspace_id", "name", "description", and "language".  These are pretty self explanatory, where the "workspace_id" is a Universal Unique Identifier (UUID).

The second section (highlighted in blue) defines all the Intents for this Workspace.  This section of the JSON contains an array containing each intent.  For each intent, there is an intent "name", "created" date and "description" (that I didn't use).  Then there is an array of "examples" for each intent.  Remembering how I defined the corpus, I can map the questions the user might ask for a particular answer each as an "example" under the parent intent.  In this example you can see I used the symbols (i.e. "Q.1-1") but in the real example this is the actual text the user would say.

The third section (highlighted in red) defines all the Entities for this Workspace.  Since I previously said that this Q&A example was not going to model a back and forth dialog for this particular demo with synonyms for objects, I don't need to use entities and thus this section is blank.  If you did use entities, it would be modeled very similar to how the intents are modeled except using the synonyms instead of examples.  So we can ignore this section for the remainder of this demonstration.

The fourth section (highlighted in yellow) defines all the Dialog nodes that you would normally model in the visual Dialog Builder.  There is an array that contains for each element a definition of a node.  For this example, the most important attributes to worry about are "output", "conditions", "dialog_node" and "previous_sibling".  The "output" attribute defines a child "text" attribute that contains the text output that Watson would say for this particular node.  Going back to how we modeled our corpus, this would be an answer (i.e. A-1) for a set of questions.  In order to map this answer to the original questions defined by the intents, I need to set the "conditions" attribute.  In this case, our condition is simply our intent name that then maps our questions directly to the ground truth answer.  Lastly, there are two attributes "dialog_node" and "previous_sibling" which need to be defined correctly.  For "dialog_node" this is a name that starts with the prefix "node_" and then has a count number (1-N) for each node defined, followed by a time stamp (milliseconds from 1970).  For "previous_sibling", this points back to the name of the node previously defined at this depth of the node tree branch.  In the example above, it shows null because this is the first node at that branch, but this would point to the previous sibling node for any subsequent nodes.

So now that I have defined the Q&A corpus model, and explained how we intend to insert that information in the JSON definition for a Conversation Workspace instance, I will turn attention to how we code up this solution in a generic way.  I've decided to use Node.js because it will allow me to quickly create a CLI (Command Line Interface) tool that I can publicly share on npmjs.org.  This CLI will import the CSV corpus file, create the required Conversation JSON object based on that corpus, and export it to a JSON file that can be imported into a Conversation Workspace.  I will also set up the project so that the source code is available on GitHub.

So I start by picking a Node.js npm name for this project (e.g. bluemix-watson-conversation).  You can use the following command to test to see if it's already in use:

$ npm view bluemix-watson-conversation
npm ERR! 404 'bluemix-watson-conversation' is not in the npm registry.

Since it's not in use, I can select that name.  Now, I want to initialize a new npm project locally so I can create my CLI tool as a new Node.js module.  I can do this with the following commands:

$ mkdir bluemix-watson-converation
$ cd bluemix-watson-conversation
$ npm init

This will ask you a series of questions, most of which are self explanatory, or you can take the defaults for.  If you will be managing your code in GitHub source control as I do, you will need to create a new Git repository for your project, and then provide the "git repository" information when prompted so it can be included in your project descriptor.

The result of this command and questions will be a new directory for your Node.js project containing the package.json descriptor for your project.  Assuming you took the default index.js file for your main project code file, you will need to create this empty file in your directory first which will be where all the following code I demonstrate will go.

So let's think a bit again about our CSV corpus model and the resulting Conversation JSON format.  The modeling of these two artifacts are different in how the Questions and Answers are exposed in each document.  In the CSV, the resulting answer and the possible questions are all contained in each row of the spreadsheet.  However, in the JSON document, these elements are broken apart across the two different sections "intents" (for the questions) and "dialog_nodes" (for the answers) with the dependency established using the "conditions" attribute in the "dialog_node" as intent name reference.  With that in mind, I will structure the code so that we can build our JSON document in one pass through without having to create an in between in-memory model to build that linkage.  Here is the outline of the code:

// REQUIRE STATEMENTS ...

module.exports = function() {
  main();
};

// CONSTANTS ...

function buildDialog( ... ) { ... }

function buildWorkspace( ... ) { ... )

function promptUserForInput(action) { ... }

function main() {
  promptUserForInput(function() {
    buildWorkspace(FILE_CSV_INPUT);
  });
}

In this incomplete code snippet, I define the basic structure for our Node.js code. 
  • First, I will list the Node.js module dependencies that will be used.
  • Second, I export the function so that anyone downloading this module will be able to run the CLI tool.
  • Third, I have a place holder section for constants I will define that will represent the default values for various inputs that I might want the user of the tool to customize.
  • Fourth, I define 2 functions.  The first function buildDialog will be used to build the sections of JSON responsible for defining our dialog nodes.  The second function buildWorkspace will be used to build the overall JSON document, calling out to buildDialog where necessary to build those sections.
  • Fifth, is a function promptUserForInput which will be used to allow the CLI to get input from the user to customize the generation of the output JSON.
  • Lastly, is the main function that the module exports and will first prompt the user for input, and then call the building of the Workspace function.

It's always best to reuse code, rather then build from scratch, so I chose a couple existing Node.js modules ( http://npmjs.org ) to help me build this program.  First, fs which is the standard Node.js module for interacting with the file system. Second, csv-parse which is a module that can be used to parse our input CSV to ultimately create our JSON output.  Third, prompt which is a slick module for gathering input from the user for our CLI tool.  So in order to create these dependencies, first we need to install them locally as well as add those dependencies to our project descriptor package.json with the following commands inside our project directory:

$ npm install fs --save
$ npm install csv-parse --save
$ npm install prompt --save

Now I need to add those dependencies to the very top of our code index.js file:

var fs = require('fs');
var csv = require('csv-parse');
var prompt = require('prompt');



Next I will define the constants that the code will use placing these right under the //CONSTANTS comment:

var FILE_CSV_INPUT = './corpus.csv';
var FILE_JSON_OUTPUT = './output.json';

var WORKSPACE_ID = 'fede7b10-f035-44d8-9fcd-80cddbcf08db';
var WORKSPACE_NAME = 'Workspace Test Name';
var WORKSPACE_DESC = 'Workspace Test Description';
var WORKSPACE_LANG = 'en';
var WORKSPACE_ANYTHING_ELSE = 'I do not understand what you are asking.';
var WORKSPACE_INTENT_NAME = 'my_intent';
 

var WORKSPACE_DATE = new Date();
var WORKSPACE_DATE_JSON = WORKSPACE_DATE.toJSON();


The constants are defined as follows:
  • The first 2 constants starting with FILE_ are used to designate the input corpus CSV file to use, and the output JSON file to generate.
  • The next 4 constants WORKSPACE_<ID, NAME, DESC, LANG> are pretty self explanatory, but note that the UUID used for the ID is arbitrary as it will be replaced on import.
  • The constant WORKSPACE_ANYTHING_ELSE will be used to define a dialog node text to generically respond if none of the corpus questions are recognized and matched.
  • The constant WORKSPACE_INTENT_NAME will be used as the prefix for our intent names in the JSON.
  • Lastly, the two WORKSPACE_DATE constants are used to populate dates in the JSON document as we build it in the code.

Now I will write the code for how to modify these default constants to customize the CLI using the prompt module.  It would be repetitive to do this for each constant shown above, so I will just show one here in the promptUserForInput function.  For more details on how to use the prompt module, see it's accompanying documentation ( https://www.npmjs.com/package/prompt ).

function promptUserForInput(action) {

  var promptSchema = {
    properties: {
      fileInput: {
        description: 'Enter the File Input (CSV Corpus)',
        pattern: /^[a-zA-Z0-9\_\.\/]+$/,
        message: 'CSV file name must be a valid file name.',
        required: true,
        default: FILE_CSV_INPUT
      },

      ...
    }
  };
  
  prompt.start();

  // Prompt user with defaults, then override with input from user.
  prompt.get(promptSchema, function (err, result) {
 

    FILE_CSV_INPUT = result.fileInput;
    ...

    action();
  });
}


First thing I do is create the schema for the prompts we want for each constant setting the default value for the prompt to what we already defined as the default value in the code.  Notice that for each prompt you can provide validation using a regular expression which is very flexible and useful for providing fool proof user prompts.

After all prompts have been defined, I call the start method to start the module and the get method to prompt the user for input on the command line for each prompt defined in the schema.

Lastly, I set the constant variables to the input from the user and when all variables are set, I call the provided action function that was passed in to kick off the creation of the JSON object.

The next step is to build the heart of the code in the buildWorkspace function which is defined to take the CSV file name as input.  This is a lot of code with string manipulation to create the JSON which I will describe in more detail below.  It also uses a few helper functions that I leave out of this code snippet to reduce the size (the complete code can be downloaded at the links at the end of the article).

function buildWorkspace(csvFile) {

  var parser = csv({delimiter: ','}, function(err, data) {

    var intents = '[', dialogs = '[';
    var nodeName = null, nodeNamePrev = null;

    var i = 0;
    for (; i < data.length; i++) {

      var intentName = WORKSPACE_INTENT_NAME + '_' + i;

      var intent =
        '{"intent":"' + intentName + '",' +
         '"created":"' + WORKSPACE_DATE_JSON + '",' +
         '"description":null,';

      var answer = '', questions = '[';

      for (j = 0; j < data[i].length; j++) {

        if (data[i][j]) {

          if (j == 0) {

            answer = data[i][j];
          }
          else {

            questions +=
              '{"text":"' + data[i][j] +
              '","created":"' + WORKSPACE_DATE_JSON + '"},';
      }}}

      questions = removeComma(questions) + ']';

      intent += '"examples":' + questions + '}';
      intents += intent + ',';

      nodeNamePrev = nodeName;
      nodeName = createNodeName(i);

      dialogs += buildDialog('#' + intentName, nodeName, nodeNamePrev, answer)
        + ',';
    }

    intents = removeComma(intents) + ']';

    dialogs += buildDialog(
      'anything_else', createNodeName(i), nodeName, WORKSPACE_ANYTHING_ELSE)
      + ']';

    var workspace =
      '{"name":"' + WORKSPACE_NAME + '",' +
      '"created":"' + WORKSPACE_DATE_JSON + '",' +
      '"intents":' + intents + ',' +
      '"entities": [],' +
      '"language":"' + WORKSPACE_LANG + '",' +
      '"metadata":null,' +
      '"modified":"' + WORKSPACE_DATE_JSON + '",' +
      '"created_by":null,' +
      '"description":"' + WORKSPACE_DESC + '",' +
      '"modified_by":null,' +
      '"dialog_nodes":' + dialogs + ',' +
      '"workspace_id":"' + WORKSPACE_ID + '"}';

    fs.writeFile(FILE_JSON_OUTPUT, workspace, function (err) {

      if (err) {
        return console.log(err);
      }

      console.log('Worksplace JSON file saved to "' + FILE_JSON_OUTPUT + '".');
    });
  });

  fs.createReadStream(csvFile).pipe(parser);
}






Let me break this down a bit:
  • First I use the csv Node.js module to parse the CSV resulting in a data object 2x2 array containing the columns and rows of the input spreadsheet.
  • Next I use a for loop to go through each row of this CSV (remember a row represents the answer followed by a list of potential questions).
  • Within each pass of this for loop is another loop to iterate through each column entry in the row until there are no more questions (there might be blank entries depending on the amount of questions, so we ignore those).
  • For the inner loop I extract the questions from the CSV that will be used to create the child "examples" attribute that will be contained in the "intent" attribute of the JSON object.
  • For the outer loop I create the "intents" JSON attribute which contains all the intents and their examples (questions).  For each pass, I also build up the dialog nodes definitions that will be needed later.  I use the helper function buildDialog passing the intent name (used as the "condition"), along with the node name, previous node name and the answer text.
  • After the loops are completed, I do one more call to buildDialog to generate the "anything else" condition node that will be selected if Watson can't find a matching answer to a question asked.
  • And lastly I build the overall Workspace JSON inserting the "intents" and "dialog_nodes" that I created in the for loop passes and save that resulting JSON object representing the Conversation Workspace to the local file.

Lastly, I define the creation of the dialog JSON representing a dialog node:

function buildDialog(conditions, nodeName, nodeNamePrev, textOutput) {

  return dialogJSON =
    '{"go_to":null,' +
      '"output":{"text":"' + textOutput + '"},'+
      '"parent":null,' +
      '"context":null,' +
      '"created":"' + WORKSPACE_DATE_JSON + '",' +
      '"metadata":null,' +
      '"conditions":"' + conditions + '",' +
      '"description":null,' +
      '"dialog_node":"' + nodeName + '",' +
      '"previous_sibling":' +
        (nodeNamePrev ? '"' + nodeNamePrev + '"' : null) + '}';
}


And that's it!  This code will now take as input our CSV corpus file, ask the user a series of questions to customize the output, and then generate our Conversation Workspace JSON file that can be imported into a new, empty Workspace you create on Bluemix.  Just select import as shown below:


 You can run the CLI with the following command inside your project directory:

$ node
$ > var run = require('./index.js');
$ > run();

Before I finish this blog post, let me show you how to publish this new CLI tool as a module on http://npmjs.org as a module you can share, as well as check in the code to source control.

If you are a new user to npm, you first need to create your account:

$ npm adduser <your username>

Now we can check in the code and publish it with the following commands:

$ git add package.json index.js
$ git commit -m "Check in 1.0.0"
$ npm version 1.0.0
$ git push && git push --tags
$ npm publish

That's it, now all the world can see and download your work!  You can download the full code for this project from my GitHub repository here:

https://github.com/mc30ski/bluemix-watson-conversation

And download the Node.js module here and include it in your own projects as a required module:

https://www.npmjs.com/package/bluemix-watson-conversation

To try it out locally:

$ node install bluemix-watson-conversation
$ node
$ > var run = require('bluemix-watson-conversation');
$ > run();

Monday, August 1, 2016

Create a Slack ChatBot using the new IBM Watson Conversation service

IBM has just released a new Watson service called Conversation, available on IBM Bluemix.  Conversation is a service that allows you to add a Natural Language interfaces to your applications to simulate conversations with your end users using Watson's cognitive capabilities.  There are a lot of uses for this service, but one I thought would be fun to prototype is a chat bot integrated with an instant messaging client.  You can find out more about the new IBM Watson Conversation service here: https://www.ibm.com/watson/developercloud/conversation.html

For the chat client, I decided on Slack because it provides a very rich set of integration APIs.  Specifically, Slack has the built in ability to create a custom bot that can be used within your team channels.  It also has the ability to distribute that bot to other teams, but that is a more advanced topic I will not cover in this post.  Instead of describing all the technical aspects of the Slack APIs, I will instead demonstrate how to use them with a practical example of building a Slack bot that your team can converse with using the Watson Conversation service as the backend logic for conducting the conversation with the bot.

So what type of bot to create?  Well, there is no shortage of ideas out there that's for sure.  Even Taco Bell has gotten into the game by creating their own Slack bot for ordering their food (https://www.tacobell.com/feed/tacobot).  What a great idea for those developers so busy that they just don't have any time to stop coding to go order food :)  So in the spirit of this, I will create a very simple bot to simulate a conversation about ordering food to demonstrate one potential way of using the Conversation service.

Let's first start simple, and build a very rudimentary Slack bot that does only one thing by responding generically to any message that is sent to it.  Once we've laid out how to integrate a bot with Slack, then we can move on to integrating that bot into the Conversation service to simulate food ordering.

Our first step in creating our own team bot is to go to the Slack team's setting page and make sure you are logged into the correct team (especially if you belong to multiple domains), and then create a new bot user by going to this URL and selecting a name in all lowercase: https://my.slack.com/services/new/bot


This will result in an integration settings page where you can optionally customize more aspects to your bot, including giving the bot an icon, first and last name, etc.  Most notably though is the API Token that you will need to keep private and use later for integration.



Integration with Slack and giving the bot the ability to communicate with other users is accomplished through the Real Time Messaging (RTM) API (https://api.slack.com/rtm).  This WebSocket-based API is able to authenticate with Slack, receive events, and send messages to users in real time.  The previous link shows the many types of events that your bot can respond to.  There is some API gymnastics required if we coded this integration from scratch.  But since others have already done a lot of the hard work associated with the API integration, let's use that and focus our attention on the overall integrated example.  In this case, we will use a node.js based toolkit for integrating with the Slack APIs.  I choose Botkit (https://howdy.ai/botkit/) since it is very well done and complete with a lot of examples and good instructions (https://github.com/howdyai/botkit/blob/master/readme.md).

Let's first start by installing Botkit locally with the following command (note, if you don't already have node.js installed, then hop on over to http://nodejs.org to download a package that will install both node and npm):

$ npm install --save botkit

Now using Botkit, we need to create our bot logic in node.js.  Let's start with something extraordinarily simple, the classic hello world.  First, we need to add the necessary setup code by requiring the Botkit toolkit, and then we create a controller that we will use to interact with Slack:

var Botkit = require('botkit');
var controller = Botkit.slackbot({ debug: false });

Once we have the controller, we can spawn our bot and connect the bot to the stream of messages coming from Slack (via the Real Time Messaging API).  Replace the token with the token you generated earlier so that it can be authenticated with the RTM API:

var bot = controller.spawn({ token: '<your_token>' }).startRTM();

Now, we simply want the bot to listen for the word "hello" by a direct message and reply back "Hello World".  This is accomplished by registering a listener as follows:

controller.hears(
   'hello',
   ['direct_message'],
   function(bot, message) {
      bot.reply(message, 'Hello World');
});

Save this Javascript code to a file called "foodbot.js" and run it in node on the command line:

$ node foodbot.js

Assuming you do not get any errors, you should be able to navigate over to your Slack client and direct message your new bot (note, if you do have errors, you can enable debugging by changing the parameter passed to the controller to true):


In this example, we are instructing the controller to listen for the word "hello" and only respond to that text while ignoring other messages.  We can use any regular expression pattern we want for the listener, so this can be very useful in building a bot where all the business logic is contained in this one service.  However, since we will be connecting this bot to the Watson Conversation service, we would like to just provide a pass through of any messages.  Let's change the line to the following which will allow the bot to respond to any message sent to it:

controller.hears(
   '(.*)',
   ...

Now let's turn our attention to how we can use the Conversation service on IBM Bluemix.  Go ahead and log into your Bluemix (http://bluemix.net) account and navigate to the dashboard and filter down to Watson services.  Then select the Conversation service and create it with all the defaults:


Before we get started, let's talk a bit about what the Conversation service can do.  As I mentioned earlier, this service allows you to add natural language interfaces to your applications to automate interactions with end users, exactly what we want to do with our Slack food bot.  It does this through 3 very simple concepts:

1. Intents.  You first need to train Watson to understand your user's input with example utterances.  You do that by declaring an "Intent" and giving examples of that intent.  For example, in our food bot, an intent can be to "place an order".

2. Entities.  You then need to define what are the things that the user will be trying to interact with.  You do that by declaring an "Entity" and provide terms that may vary in your users' input.  For example, in your food bot, an entity can be "pizza" that you want to order.

3. Dialog.  Now that you have the basic building blocks of a conversation, you can use the provided dialog builder to build a flow of how the service will create responses to your user's input.

This is all best understood through an example, so let's go ahead and get started.  Of course a dialog flow could get pretty complicated, but let's just do something very simple to demonstrate the basics.  Let's say that a user can ask to order one of two food items for now, a "pizza" or a "sandwich".

The basic flow looks like this.  User sends the food bot the message "Place an order for food".  The bot responds to the user clarifying food options "You can order a pizza or a sandwich".  The user can now respond with either option, e.g. "Pizza" or "Sandwich".  If the user asks for something not available, then we want the response to again ask for either the pizza or sandwich.  Very simple, but enough to show the integration we intended.

Once you launch the Conversation tool in Bluemix, the first step is to create a Workspace.  This is a way to maintain separate intents, entities, and dialogs for each application.  For this example, I will call my Workspace "FoodBot".  Once the Workspace is created, there will be a "Get Started" button to launch the tool to start creating Intents.

Go ahead and create your first Intent, give it the name "#order_food".  Then it will ask you for some User examples.  These are example of how you expect users to interact with this intent.  For example, a user might say "I want to order food", or "Place an order for a pizza".  Let's add more examples to this intent:



A more typical intent would have a much broader amount of examples, but this is good enough for now to train Watson on the basic things a user might say.

Now, let's create our Entities.  Since we have just this simple example, where the user is only ordering food, we only need one.  Let's name it "@food" and give it the two options: Pizza and Sandwich.  For each option, we will add a couple synonyms, so that for example if the user asks for a Sandwich or a Sub, either will be recognized.


And lastly, let's create the dialog flow.  In this simple example, we first add the intent "#order_food".  If that isn't recognized, then "Anything else" will kick in and ask the user what they want.  Then we add a child dialog and make it recognize any food, and respond with confirmation that their food has been order repeating the food item.  If the food entity is not recognized, then we default to the "true" condition, and which instructs the user as to which food items can be ordered.  Lastly, since we want to give the user the option to say the whole order in one sentence, verses needing to prompt what type of food, we have to add the "Continue from..." condition as show in the dialog flow here:



And you can see from the "Try it out" panel on the right side, that we get the answers we were expecting based on the dialog flow we created.

Now that we have a Conversation instance set up and ready to take commands, let's turn our attention back to integrating our original Slack Food bot with this conversation instance.  The API documentation for the Watson Conversation service can be found here ( http://www.ibm.com/watson/developercloud/conversation/api/v1/ ) although note that we are using V1 of the API and that will obviously change sometime soon.

To make sure we can interact with the Conversation API, let's first start simple and use CURL ( https://en.wikipedia.org/wiki/CURL ) from the command line to send our API commands.  The first command we will make is simply to verify we get an appropriate response.  So we need to issue the following from the command line:

$ curl -X POST -u "<username>":"<password>" -H "Content-Type:application/json" -d "{}" "https://gateway.watsonplatform.net/conversation/api/v1/workspaces/<workspace_id>/message?version=2016-07-11"

A couple things to note.  You first have to replace the username and password highlighted above with the credentials you use for your Conversation instance.  These are not your Bluemix credentials.  Instead, go to your Conversation instance, and select "Service Credentials" from the left hand navigation as shown here:


Secondly, you need to replace the workspace_id with your actual workspace ID for your Conversation instance.  You can find that by by first launching the Conversation tool.  Then select the "..." ellipsis from the Workspace and select "View details"


Notice that in the CURL command you are issuing, there is empty brackets as input for the POST.  This is because the Conversation service expects a JSON formatted input that we will provide later.  For now issue this command, and you should get back a JSON response like this:

{"context":{"conversation_id":"<conversation_id>","system":{"dialog_stack":["root"],"dialog_turn_counter":1,"dialog_request_counter":1}},"intents":[],"entities":[],"input":{},"output":{"log_messages":[],"text":["Would you like to order a pizza or a sandwich?"],"nodes_visited":["<node_id>"]}}

There are a couple important things to note in this response.  First, the service responds with a "context" which has a conversation ID that you can use to continue the conversation if you desire, and a "system" object which provides information about the dialog.  Because we provided no input, the service just responds with our catch all phrase "Would you like to order a pizza or a sandwich?".

Ok, let's take this and see if we can hook together our node.js code integrated with the Slack API to make this request to the Conversation service.  So first to make this easier, let's use the third party node module "request" which was built to make it very simple to make HTTP(S) calls.  Install it on your command line with the following command to the package manager:

$ npm install request

Now add the following require code to your node.js file:

var request = require('request');

And replace the line we created earlier:

bot.reply(message, 'Hello World');

With these lines of code:

function(bot, message) {
  request({
    auth: {
      username: '<username>',
      password: '<password>'
    },
    method: 'post',
    json: true,
    url: '<service_URL>',
    headers: {
      'Content-Type': 'application/json'
    }
  },
  function (error, response, body) {
    if (!error && response.statusCode == 200) {
      console.log('BODY: ', body);
    }
  }).end('{}');
});

What this code does is use the request module to do the POST method to the Conversation API.  First we need to specify the options for our request, including the "username" and "password" from before, as well as the URL we used in the CURL command (which included your workspace ID).

First stop your node.js service and start it again (you will need to do this any time you make a code change).  Now, when you message your bot in Slack, you should see a response in your node command window from the Conversation service like we did when we used CURL directly.  For example:

$ BODY: {
    context: { 
      conversation_id: '<conversation_id>',
      system: {
        dialog_stack: [Object],
        dialog_turn_counter: 1,
        dialog_request_counter: 1 } },
    intents: [],
    entities: [],
    input: {},
    output: { log_messages: [],
     text: [ 'Would you like to order a pizza or a sandwich?' ],
     nodes_visited: [ '<node_id>' ] } }

Now we want to extract the text from the output so we can have our bot reply with that text response in Slack.  So let's replace the code snippet:

console.log('BODY: ', body);

With the following which takes the first text response from the output:

bot.reply(message, body.output.text[0]);

Go ahead and message your Slack bot (after restarting the node.js service), and it should respond with the appropriate message from the Watson Conversation service:


Looks good so far, but upon further inspection this is not exactly what we want.  Because our node code doesn't actually simulate a conversation, so you will get this response no matter what you type in.  What we really need is for the code to first call the Conversation service to get the generic response as well as provide us a conversation id, so that we can actually continue the dialog with the service.  All the details for the format of the request expected, and the response given,  by the Conversation API are detailed in the documentation link I provided earlier.  So for now I will just show you the code that we need to 1) start the dialog with Conversation service, and then 2) make a second request to the Conversation service with the input from the user as well as the conversation id and some other information regarding the request counter, etc.  Given the need to make 2 Conversation API calls now, this is an opportunity for me to modularize the code into reusable functions.  Here is the completed code example:

var Botkit = require('botkit');
var request = require('request');
var controller = Botkit.slackbot({ debug: false });
var bot = controller.spawn({ token: '<your_token>' }).startRTM();

var username = '<username>';
var password = '<password>';
var workspce = '<workspace_id>';

var url = 
  'https://gateway.watsonplatform.net/conversation/api/v1/workspaces/' + 
  workspce + 
  '/message?version=2016-07-11';

// Gets the first text from an array of potential responses.
function getResponseText(params) {
  for (i = 0; i < params.length; i++) {
    if (params[i]) return params[i];
  }
  return "";
}

// Calls the Watson Conversation service with provided request JSON.
// On response, calls the action function with response from Watson.
function callConversationService(json, action) {
  request({
    auth: {
      username: username,
      password: password
    },
    method: 'post',
    json: true,
    url: url,
      headers: {
        'Content-Type': 'application/json'
      }
    },
    function (error, response, body) {
      if (!error && response.statusCode == 200) {
        action(body);
      }
  }).end(json);
}

// Register to listen for any user communication with bot.
controller.hears(
  '(.*)',
  ['direct_message'],
  function(bot, message) {      
    callConversationService('{}', function (resp) { 
      
      var conv_id = resp.context.conversation_id;
      
      var req_json = 
        '{\"input\":{\"text\":\"' + 
        message.match[1] + 
        '\"},\"context\":{\"conversation_id\":\"' + 
        conv_id + 
        '\",\"system\":{\"dialog_stack\":[\"root\"],\"dialog_turn_counter\": 1,\"dialog_request_counter\": 1}}}';
            
      callConversationService(req_json, function (resp2) {
        
        var txt = getResponseText(resp2.output.text);
        bot.reply(message, txt);
      });
  });
});

Let's break this code down a bit and describe what it's doing.  I register to listen for any user communication though Slack to the Food bot.  Then the code calls the Conversation service with an empty "{}" JSON request just to start the dialog and get back a conversation ID.  Then a new JSON request is constructed, this time providing the message the user gave to the bot, as well as context information needed to pass to the Conversation service (see details in documentation for each parameter).  Then the Conversation service is called once again passing this JSON request, and as a result, the Conversation service reply is located in the response and then sent to Slack to present to the user.  Note, that the Conversation service returns an array of responses, some of which might be empty due to the nature of the conversation, thus a function was created to get the first non-empty textual response.

This is a pretty simple example, and if you wanted to have an example that had more dialog with the user and the Conversation service, than this code example would need to be enhanced to make that possible since this code simulates the conversation with the Conversation service in a single message to the bot, instead of saving state and allowing the user to go back and forth in a dialog with the Conversation service.  But I'll leave that to you as a homework assignment ;-)

Lastly, we can see our code in action by interacting with the Food bot in Slack as shown here: