Designing NinjaChat

Dexter Fong
Ninja Van Tech
Published in
5 min readOct 27, 2020

This article is part one in a series of articles about how we set up a chatbot — NinjaChat — to serve our shippers and consignees here at Ninja Van. As an introduction, I will focus on painting a broad picture of our chatbot from its design and architecture, and at the same time, the strokes of ingenuity by the people in my team that led to its rapid prototyping and ultimately deployment.

Early this year, my company, Ninja Van, launched its first iteration of a chatbot catered towards consignees who use our delivery services to receive parcels and shippers who — well — ship with us. And it seemed like the best time to be launching one, too, just as consumer trends and attitudes towards e-commerce were on the rise against the backdrop of our current global health pandemic.

But business g o a l s and strategic alignment aside, as engineers, my team (or I rather) quite simply leapt at the idea and opportunity to build our very own conversational chatbot. As a junior engineer, I was more than grateful for that opportunity to work on something novel with the support of some very talented seniors in my team.

Primarily, we had to:

  • Provide a consumer-facing chatbot that would allow customers to track the status of their parcels, obtain answers to commonly asked questions (or be redirected to a live agent), and reschedule their upcoming deliveries
  • Provide a shipper-facing chatbot that would allow shippers to manage and create pickups for their parcels, track the status of the parcels they have sent out, and likewise obtain answers to commonly asked questions or speak to a human agent.

Towards Implementation

On one hand, we knew what we wanted to build, courtesy of the amazing UX designers on our team who had already produced a coherent set of mockups and prototypes for how the conversation tree and its paths would map out (i.e. flowcharts and Botsociety). On the other, we did not know how. So we explored various solutions, one of which was Google’s Dialogflow and the other being the open-source solution Rasa.

Long story short, we settled on Dialogflow primarily due to its intuitive console, available support for languages in our target markets, and its analytics/validation tools.

Briefly, Dialogflow (formerly API.AI) is a service running on Google Cloud Platform that facilitates natural language processing and management of conversational flows. It is used to build ‘conversational interfaces’, and comes with an arsenal of features such as intents, contexts and responses that allow you to craft and build natural conversations.

But these features are perhaps best explained alongside examples of how our team has adopted them to model the conversation tree for both our consumers and shippers, which I’ll do so in subsequent parts.

An Architectural Overview

The chatbot integration with Dialogflow was built on top of our existing Social Network Service (SNS), which is a microservice that originally spawned from a side project during one of Ninja Van’s regular hackathons. As its namesake suggests, SNS handles all notifications sent and messages received on social media platforms, such as informing customers about their parcels being on the way to their doorstep, for example. Our language of choice was Java, and like many of our microservices here, ran on the Play framework.

Source: Google, “Dialogflow Basics”

SNS sits between the various social media platforms and Dialogflow. Requests reach our service, are processed and sent to Dialogflow, and the resulting response is sent back to the user.

An alternative, of course, was to enable fulfillment for platform integrations and have Dialogflow directly handling the user’s input and calling our webhook on SNS for certain matched intents. But this approach was abandoned for a couple of reasons:

  • Parking SNS in the middle allows us to process user input as is e.g. being able to allow Whatsapp (a purely text-based chatbot) to accept numbers as input which would be translated into a corresponding option that was shown to the user in the previous interaction
  • We can ignore certain requests, such as duplicated requests, requests fired from buttons in the same interaction which were clicked twice, etc.
  • We have finer control over responses sent back to the various platforms and how it’s formatted

Although the initial Proof of Concept launch was geared towards Facebook Messenger users, we architected the chatbot integration with the aim of eventually incorporating other platforms in mind — which we did, eventually, one month later with Whatsapp.

Here’s a straightforward diagram of our initial implementation:

  • Each platform would be configured with a webhook on SNS that receives a request whenever a user interacts with the bot. In this case, Messenger notifies us of events whenever a user types an input, clicks on a menu button, or reacts to our responses with an emoticon (but we ignore that last one, although admittedly it would be cool if our bot didn’t).
  • A platform-specific parser, initialized based on the identified platform, is used to parse the platform-specific request into a platform-neutral request for Dialogflow. The Dialogflow request looks something like this:
public class DialogflowQueryRequestBean {    private String platformMessageRef; 
//Unique message identifier
private String queryInput;
//Actual user input
private DialogflowEventTrigger eventTrigger;
//Used for constructing event requests
private String sessionId;
//Identifier for the user's current conversation
private DialogflowUser user;
//Information such as type (e.g. shipper), phone number, etc
}
  • The Dialogflow request bean is passed to a service class that calls Dialogflow via a provided Java API client, processes its output and performs side effects such as hitting databases, calling other microservices within Ninja Van, or simply retriggering another call to Dialogflow (as a form of manually directing the flow of the conversation). At the end, a Dialogflow response bean is returned:
public class DialogflowQueryResponseBean {    private String response; 
//Main text response from the bot
private Optional<DialogflowOptionContainer> optionContainer;
//Holds option info and describes how it is to be displayed
private DialogflowQueryResponseAction responseAction;
//Allows processing to be diverted or specially handled
private Optional<DialogflowEventTrigger> eventTrigger;
//Describes whether the response is to trigger another event

private boolean showBackToMain;
//Flag for whether to display an option to return to main menu

private boolean reprocess;
//Flag for whether the response is to be piped back as input
}
  • The platform-neutral response from our internal service class is translated back into a platform-specific response bean by a parser. The parser would be in charge of deciding how text and options are presented i.e. in Messenger’s case, processing options using carousel buttons, menu buttons, etc. This is then posted back to the platform, and the user finally sees that response from our bot.

In the next article, I will dive into the specifics of how we utilized Dialogflow’s wide range of features to produce a responsive and intuitive bot. Till next time!

--

--