Building an Ordering Application with Watson AI and PostgreSQL: Part II

Published

Do you want to leverage Compose, Twilio, and IBM Watson to provide customers with a real-time, interactive experience? We'll show you how by building an IBM Bluemix application that lets Watson interact with users via Twilio's SMS service then sends their data over to Compose PostgreSQL for storage. Read Part I of this article here.

In the first part of this two-part series, we set up the foundation for our ordering application by creating a Cloud Foundry NodeJS runtime application in Bluemix then we added services to that application, namely Compose PostgreSQL, Watson Conversation, and Twilio. We then went over how to create a workspace and a dialog in Watson Conversation and then deployed it to get the workspace ID, which will be important when we start writing the code so that we use the right conversation for our application.

The second part of this article will look at writing the code, making a small change to the Twilio phone configuration, and deploying the application on Bluemix. The Github repository for this project can be found here.

Setting up the Code

To start setting up the code, we'll first have to download a few libraries from npm to get a NodeJS server up and running. We'll use ExpressJS for the server, the Watson Conversation library, the JavaScript SDK for Twilio, Cloud Foundry's library to parse environment variables stored on Bluemix, and SequelizeJS to create the database table and model to send over the customer orders to PostgreSQL.

const express = require('express');  
const app = express();  
const ConversationV1 = require('watson-developer-cloud/conversation/v1');  
const twilio = require('twilio');  
const cfenv = require("cfenv")  
const Sequelize = require("sequelize");  

Next, we'll set up the environment variables so that our authentication credentials will be called from VCAP_SERVICES in Bluemix. To do that, we'll use Cloud Foundry's cfenv library which will use the environment variables from Bluemix. If you want to take a look at your application's environment variables, go to the Bluemix console and click the Runtime menu on the left side.

The code we'll use to get the authentication credentials is the following when we have assigned to their own variables.

const appEnv = cfenv.getAppEnv();  
const vcap_services = JSON.parse(process.env.VCAP_SERVICES);  
const postgresUri = vcap_services['compose-for-postgresql'][0].credentials.uri;  
const conversationUsername = vcap_services.conversation[0].credentials.username;  
const conversationPassword = vcap_services.conversation[0].credentials.password;  
const twilioAccountSid = vcap_services['user-provided'][0].credentials.accountSID;  
const twilioAuthToken = vcap_services['user-provided'][0].credentials.authToken;  

Now let's set up our database model. Using SequelizeJS, we first create a new instance using our environment variable postgresUri. This gives Sequelize the URI of our PostgreSQL deployment on Bluemix. Then, we'll create a model called Order, which will set up a table orders (make sure those double quotes are there for the table name) with the column names and datatypes we'll store in PostgreSQL. After that, we'll call the sync() method on the Order model to create the table orders which will happen at the time the application is deployed on Bluemix.

const sequelize = new Sequelize(postgresUri);

const Order = sequelize.define("orders", {  
    id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
    quantity: Sequelize.INTEGER,
    lumber_type: Sequelize.TEXT,
    customer_id: Sequelize.INTEGER
}, { timestamps: false });

Order.sync();  

Setting up Watson Conversation and Twilio follows a similar pattern in that we pass the VCAP_SERVICE environment variables to them. In this case, we have the Watson Conversation username and password and Twilio's AccountSID and AuthToken taken from the environment variables. For your workspace in Watson Conversation, you'll have to either add your workspace ID into the path object, or you can create your own environment variable in your application to handle that.

// Watson Conversation set up
const conversation = new ConversationV1({  
    username: conversationUsername,
    password: conversationPassword,
    path: { workspace_id: 'you_workspace_id' },
    version: 'v1',
    version_date: '2017-05-26'
});

// Twilio set up
const client = new twilio(twilioAccountSid, twilioAuthToken);  

Now let's get the conversation going ...

An important part of the conversation is to allow multiple users to interact with your conversation application at the same time. It's also important to maintain state so each message sent back and forth from the application and Watson Conversation doesn't always start at the beginning of the dialog.

We'll use an array to hold information that we want to persist throughout the conversation. To do that, we'll create an array called contexts to store conversation responses from Watson and to maintain state.

let contexts = [];  

To get the responses from Twilio into Watson Conversation, we'll use a GET HTTP request. To set that up in ExpressJS we'll use app.get and point that to the endpoint message.

app.get('/message', (req, res) => {});  

Within app.get we have three variables we'll set up that will contain the Twilio message, the number the message is from, and the Twilio number we're sending responses back to. These will be important for storing information related to maintaining the conversation's context and for sending the Watson Conversations back to the user through Twilio.

let message = req.query.Body;  
let number = req.query.From;  
let twilioNum = req.query.To;  

The second set of variables and loop will make sure that we maintain the state of the application by retaining the customer's information. The information that we'll store in context is about the user from Twilio, our context values, and their position in the dialog. At each turn, the position of the customer in the dialog changes, and their context values may get updated. Here, we are making sure that we are keeping track of that.

let context = null;  
let index = 0;  
let indexForContext = 0;

contexts.forEach(val => {  
    console.log(val);
    if (val.from === number) {
        context = val.context;
        indexForContext = index;
    }
    index += 1;
 });

Next, we'll create a function that will contain all the logic to process messages sent to Watson Conversation. Here, is where we send Watson's responses back to the customer through Twilio. Also, it's where we check to see if a conversation is done to send their order information to PostgreSQL and delete their information from the contexts array.

We'll first start by running the message function from Watson Conversation and pull in the requested SMS message sent to Twilio. This has been stored earlier in our message variable above. The context is where we pass in our context variable, which is initially null to maintain the application's state.

function processResponse(err, data) {  
        if (err) {
            console.error(err);
            return;
        }

    conversation.message({
        input: { text: message },
        context: context
    }, function(err, resp)             
        if (err) {
            console.error(err);
        }

        if (context === null) {
               contexts.push({ 'from': number, 'context': resp.context });
        } else {
               contexts[indexForContext].context = resp.context;
        }
        ...

 }, processResponse);

}

Once Watson processes this input, we're given a response that looks something like this:

{"intents":[{"intent":"ask_oak","confidence":1}],"entities":[{"entity":"kind_of_wood","location":[14,17],"value":"oak","confidence":1}],"input":{"text":"I'd like some oak"},"output":{"text":["How much would you like? You can choose from 10, 50, 100, and 150 boards"],"nodes_visited":["Ask for pine or oak"],"log_messages":[]},"context":{"conversation_id":"8823737a-324f-9g3j-9999-90s9929g","system":{"dialog_stack":[{"dialog_node":"root"}],"dialog_turn_counter":3,"dialog_request_counter":3,"_node_output_map":{"node_6_1497233079260":[0],"node_7_1497233116909":[0],"Ask for pine or oak":[0]},"branch_exited":true,"branch_exited_reason":"completed"},"accountId":7276363,"lumber_type":"oak","quantity":null}}

Within the message function, we'll check to see if the context is null and if it is, we'll pass in the customer's phone number that they sent the SMS from and it will include the response context that we get from Watson Conversation. If the context isn't null, then we'll update the context object. With each update to our context, we'll replace that with the new context reponse given by Watson. The response context object looks something like this:

{"from":"+1206999999","context":{"conversation_id":"8823737a-324f-9g3j-9999-90s9929g","system":{"dialog_sta
ck":[{"dialog_node":"root"}],"dialog_turn_counter":1,"dialog_request_counter":1,"_node_output_map":{"node_6_1497233079260":[0]},"branch_exited":true,"branch_exited_reason":"completed"},"accountId":  
7276363,"lumber_type":null,"quantity":null}}  

Next, we'll add another piece of logic that will tell us when an order should be processed. In the following code example, we check if the nodes_visited from the response output is equal to "Order Processing", which is the name of the last dialog box we created in our Watson Conversation workspace.

if (resp.output.nodes_visited[0] === "Order Processing") {  
     Order.create({
        customer_id: resp.context.accountId,
        lumber_type: resp.context.lumber_type,
        quantity: resp.context.quantity
     }).then(data => {
        console.log("Success!");
     });
     contexts.splice(indexForContext, 1);
}

Therefore, we're checking whether we end up at "Order Processing", and if we do, we send the values from the accountId, lumber_type, and quantity over to our PostgreSQL database and we take that customer's information out of the contexts array.

Sending messages back to Twilio each time is done using the following:

client.messages.create({  
    body: resp.output.text[0],
    to: number,
    from: twilioNum
}, function(err) {
    if (err) {
       console.error(err.message);
    }
});

Basically, each returned response from Watson is sent to Twilio to send back to the customer. Since we already stored the customer's and our Twilio number, we just send those back as well.

After creating the processResponse function, we invoke it inside our GET route like:

conversation.message({}, processResponse);  

Again, the entire code can be found on the Github repository here.

Pushing to Bluemix

So, your Bluemix environment has been set up with the Cloud Foundry Node.js SDK runtime, Compose PostgreSQL, Twilio, and Watson Conversation. You've set up your Watson Conversation dialog and have written the JavaScript code that will tie everything together. Now, let's push this application to Bluemix.

If we logged in successfully when we set up Bluemix, we just have to make sure that the manifest.yml file has your application's information name, route, and memory allocation information stored.

---
applications:  
 - name: prolumber-sms-application
   random-route: true
   memory: 128M

Next, you just have to run bluemix app push "ProLumber SMS Application" in your terminal and wait for the application to be deployed. Any errors that you encounter will be visible in your application's log, which is visible your application's Bluemix console within the menu option Logs.

There is one more step that we need to do after deploying the application to Bluemix. We'll have to set Twilio up to send messages to the message route of our Bluemix application. To do that, log back into your Twilio console and go into the Manage Numbers view. Select your number and that will take you to another page with configuration settings.

Within the Messaging section at the bottom, in the input box next to "A message comes in", insert your Bluemix application's URL with the /message route attached. That's the ExpressJS route we created. You'll want to set the input to "HTTP GET" as well. Once that's done, just save and test out your application using the SMS on your phone.

Start a conversation

Both articles have shown you how to set up a simple ordering system using IBM Bluemix, Compose PostgreSQL, Watson Conversation, and Twilio. Using Bluemix to keep track of your credentials and all the services you have tied to the application is convenient, especially if you have a lot of services tied to it. We also covered Watson Conversation, which is a really interesting tool that gives you the ability to create automated and interactive dialogs between users, and in this case a dialog with customers to order goods. Try out Watson, Compose, and Bluemix next time you want to create something fun and interactive.


If you have any feedback about this or any other Compose article, drop the Compose Articles team a line at articles@compose.com. We're happy to hear from you.

attribution Hudson Hintze

Abdullah Alger
Abdullah Alger is a former University lecturer who likes to dig into code, show people how to use and abuse technology, talk about GIS, and fish when the conditions are right. Coffee is in his DNA. Love this article? Head over to Abdullah Alger’s author page to keep reading.

Conquer the Data Layer

Spend your time developing apps, not managing databases.