Chatbots in JS
Stacks and implementation


Yann LEFLOUR
Deputy CTO @ BAM


@yleflour
yleflour

slides.com/yleflour

A wee bit about chatbots first

What chatbots are not
- Artificial Intelligence
- A must have solution
- A replacement for apps
What chatbots are
- A new platform to engage with users
- A way to engage more personaly with users
- A marketing tool
- A frontend in disguise
A wee more about chabot services

What we won't be talking about






























Why stick with JS?
Answer: It's code
Chatbot
Architecture

The 3 components
- Recognizer
- Connector
- Bot Builder

Universal Chatbot Architecture


Bot Builder
Bot Builder
- Handles the dialog flow
- Persists user context
- Akin to a Controler in a MVC architecture
Some bot builder librairies (JS only)

Botkit

Microsoft
Botbuilder

Our choice:
- Open Source (MIT)
- Fully typed and heavily documented
- Includes I18N
- Unit testable
- Easy to implement custom logic
Microsoft Bot Builder

Connector
Connector service (input)
- Listens to messages through a webhook
- Takes a channel specific message format
- Converts it from to a standardized format
Connector service (ouput)
- Takes a standardized message format
- Converts it to the specified channel message format
- Sends the message to the platform
Some connector services




Our choice:
export default class MessengerConnector {
Pure JS (for the win)
/** onInvoke, onEvent, startConversation, update, delete, isInvoke */
}
listenMessengerEvent() {
// Express listener
return async (req, res) => {
// Respond immediately to messenger to to prevent it from getting stuck
res.send();
const events = await mapMessengerPostToEvents(req.body);
this.onEventHandler(events);
};
}
async send(messages, done) {
const messengerMessages = mapBotBuilderToMessengerMessages(messages);
await sendMessengerMessages(messengerMessages);
done(null, []);
}

Recognizer
Recognizer
- Map user sentences to actionable inputs
- Uses machine learning (NLU)
- Needs to be trained
- Ouputs intents and entities
Example: "I want to book a restaurant in Verona"
- Intent: "restaurant_booking"
- Entities: [{type: "city", value: "verona"}]
Some NLU Services




Our choice
- Plug & Play SaaS API
- They were very available
- Handles multiple languages well (EN, FR, ...)
export default class RecastRecognizer {
async recognize(context, callback) {
try {
const recastResults = await recast.analyseText(context.message.text);
const output = mapRecastToBotBuilderIntent(recastResults);
callback(null, output);
} catch(err) {
callback(err);
}
}
}
Recast.ai + JS mapper
A typical conversation track with Bot Builder


Example
- User says: "I would like to book a restaurant"
- Structure:

Conversation
A root dialog which resets the bot to its initial state at the end
export const bookRestaurantConversation = [
];
(session, results) => {
session.dialogData.restaurantId = results.response.restaurantId;
session.beginDialog('/getDateTimeDialog', session);
},
(session, results, next) => {
session.dialogData.dateTime = results.response.dateTime;
next();
},
async (session) => {
const { restaurantId, dateTime } = session.dialogData;
await bookingAPI.bookRestaurant(restaurantId, dateTime);
session.endConversation();
}}
(session, args) => {
session.beginDialog('/getRestaurantDialog', session);
},

Dialog
A branch in your conversation tree
export const getRestaurantDialog = [
];
(session, args, next) => {
session.beginDialog(
session'/locationPrompt',
{ prompt: "Where do you want to book?" }
);
},
(session, results, next) => {
const location = results.response;
session.beginDialog(
session'/locationPrompt',
{ prompt: "Which restaurant?", location }
);
},
(session, results) => {
const restaurantId = results.response;
const output = { response: restaurantId };
session.endDialogWithResults(output);
},

Prompt
A single step dialog which handles user input
export const restaurantPrompt = new Prompt()
// Used to display the prompt message
.onFormatMessage(async (session, text, _speak, callback) => {
const location = session.dialogData.options.location;
const restaurants = await getRestaurantsFromAPI(location)
const message = formatRestaurantCarouselMessage(text, restaurants);
return callback(null, message);
})
// Used to handle the user input
.onRecognize((context, callback) => {
const entities = context.intent.entities;
const restaurantId = EntityRecognizer.findEntity(entities, 'restaurant_id');
if (!restaurantId) return callback(null, 0);
return callback(null, 1, { restaurantId });
})

Server
Setup

const connector = new MessengerConnector();
const bot = new UniversalBot(connector).recognizer(new RecastRecognizer());
// Root Dialog
bot.dialog('/', /** ... */)
.beginDialogAction(
'bookRestaurantFromIntentAction',
'/bookRestaurantConversation',
{ matches: 'book_restaurant' }
);
// Setup Conversations
bot.dialog('/bookRestaurantConversation', bookRestaurantConversation);
// Setup Dialogs
bot.dialog('/getRestaurantDialog', getRestaurantDialog);
bot.dialog('/getDateTimeDialog', getDateTimeDialog);
// Setup Prompts
bot.dialog('/locationPrompt', LocationPrompt);
bot.dialog('/restaurantPrompt', restaurantPrompt);
bot.dialog('/dateTimePrompt', dateTimePrompt);
Bot setup
const app = express();
const server = http.createServer(app);
const port = process.env.PORT || 80;
/**
* Declare the bot and its connector
*/
// Listen to messages
app.use(bodyParser.json());
app.post('/api/messages', connector.listenMessengerEvent());
// start the server
server.listen(port, () => {
console.log('Listening on %s', port);
});
Express setup
Quick warning
- Type everything
- Unit test everything
- Yes, that includes the NLU
- Plan out your conversation flow
- Implementing a bot takes time
Happy bot building!
Want to checkout some more?






Chatbots in JS
By Yann Leflour
Chatbots in JS
- 1,980