Image: tabletop assistant by Matthew Hurst, used by CC BY 2.0
Background
SMARKIO is a suite of tools to help companies solve the puzzle of digital marketing. The suite includes integrated technologies that work on a website (such as trigger overlays, automated chats that replace web forms, and dynamic content). It also includes technologies like segments and workflows that trigger programmed actions (such as emails, SMSes, and integration with CRMs and external partners). In addition, it gathers online and offline conversion metrics to provide centralized reporting and tracking details.
SMARKIO developed its own web-based bot stack that provides a holistic solution for bots. It has a content management dashboard for authoring and managing dialogs, a UX that includes the controls (buttons, cards, carousels, etc.) and a web chat control for their partners to embed chats on their own websites.
SMARKIO’s primary goal was to bring their existing dialogs to other channels like Skype and Facebook. Their content management system stores the dialogs as JSON files, and they wanted to load the dialogs from these files to other channels. This approach would allow them to continue using their dashboard for authoring and managing dialogs.
Also, SMARKIO wanted to be able to update the dialogs at runtime, while the bot service is running and users might be chatting with it, without needing to redeploy or restart the bot service.
The Problem
Microsoft’s Bot Framework today does not support loading dialogs from external files. To implement a dialog now, a developer has to hard-code all of the dialog’s steps in the bot service. As a result, there is also no way to support a scenario where an updated dialog can be dynamically reloaded without needing to restart the bot service.
Also, we had to find a way to convert all of the dialogs written in SMARKIO’s specific format into a Bot Framework-compatible format.
The Engagement
We collaborated with SMARKIO to create a solution that would allow them to continue to use their current content management system while exposing those dialogs on different channels using Bot Framework.
The layout for our solution is based on the use of the bot-graph-dialog extension developed as part of our collaboration with Pager. Since Pager had similar problems, we were able to use the same approach to support loading configuration-based dialogs from SMARKIO’s backend, where they maintain their dialogs.
To support loading the dialogs on demand, we extended the bot-graph-dialog
library to create extra functionality, as detailed in the next section.
The Solution
The following list describes the sequence of the events in our flow. Next, we will focus on the key parts of the code that implement this sequence in our scenario.
- Bot starts and loads a dialog
- User starts talking to the bot, triggering the dialog
- Dialog is changed on the backend
- Backend triggers a load request to the bot
- Bot reloads dialog and replaces it with the new one
- User continues the chat
- Bot identifies dialog was changed and resets the dialog
- User gets a message that the dialog was updated and that he or she needs to restart the dialog
The full code that implements the above scenario can be found on GitHub.
Dialog Format Transformations
Since SMARKIO developed their whole stack and created their own language and format to author their dialogs, our first step was to convert their dialogs into a format that the bot-graph-dialog
extension can use. This task was straightforward and easily implemented in their dashboard. Now, every time someone adds or updates a dialog, a graph-bot-dialog
version is saved, ready to be fetched by the bot service.
Carousel control as it appears on Skype and Web Chat control, after transforming the dialog from SMARKIO’s format to the graph-bot-dialog
format:
REST API to Expose SMARKIO’s Dialogs
In Pager’s case, we loaded the dialogs from a database. Since the bot-graph-dialog
extension supports loading dialogs from any external data source by providing a handler implementation we just had to implement this handler to fetch the dialogs from SMARKIO’s backend. We did this by using a simple HTTP GET request to a REST API they exposed specifically for this purpose.
function loadScenario(scenario) {
return new Promise((resolve, reject) => {
var uri = `<SMARKIO's REST API endpoint>/${scenario}`;
console.log(`loading remote scenario ${scenario} from ${uri}`);
request(uri, { json: true }, (err, resp, body) => {
if (err) return reject(err);
resolve(body);
});
});
}
Reloading Dialogs On Demand
We modified the bot-graph-dialog
extension to support versions of dialogs. Each scenario file can now explicitly define its version. This feature can be used later on to check if a newer version of a scenario was loaded.
When a dialog is updated on the backend, we need to be able to notify the bot service about the change. Since the bot service is just a REST API service, it was easy to add another API call from the backend whenever a dialog was updated and saved. To do this, we call the new reload
method which was added to the GraphDialog
class for this purpose, which triggers an internal process that:
- Fetches the updated dialog by calling the
loadRemoteScenario
handler - Parses and replaces the existing dialog with the new one
- Calculates a dialog version if it wasn’t provided explicitly in the dialog’s JSON
// endpoint for reloading a scenario on demand
app.get('/api/load/:scenario', (req, res) => {
var scenario = req.params.scenario;
console.log(`reloading scenario: ${scenario}`);
var dialog = dialogsMapById[scenario];
return dialog.graphDialog.reload().then(() => {
var msg = `scenario id '${scenario}' reloaded`;
return res.end(msg);
})
.catch(err => res.end(`error loading dialog: ${err.message}`));
});
This solution will only work if there is only one instance of the bot service. In the case where a bot service is scaled out to multiple servers, you’ll need to use some sync mechanism to notify all instances about the change.
Identifying Dialog Version Changes
When a dialog is updated while users are in the middle of it, the state of the session and the dialog data is no longer valid. Our approach was to notify the users that the dialog was updated and that they will need to start over.
To support that notification, we added the option of providing onBeforeProcessingStep
and onAfterProcessingStep
handlers that will be executed before and after processing each step in the graph. Using these handlers we were able to hook into the dialog’s execution flow and check whether there was a version update before executing each step.
// Intercepts changes in a scenario version before processing each dialog step
// If there was a change in the version, restart the dialog
function onBeforeProcessingStep(session, args, next) {
// "this" is the GraphDialog instance that invoked this handler
// Gets the current dialog version
var dialogVersion = this.getDialogVersion();
// If we didn't keep the dialog version, store it in the privateConversationData
if (!session.privateConversationData._dialogVersion) {
session.privateConversationData._dialogVersion = dialogVersion;
}
// If the version we stored in the privateConversationData is different than the current one-
// we have a dialog update event. Restart the dialog.
if (session.privateConversationData._dialogVersion !== dialogVersion) {
session.send("Dialog updated. We'll have to start over.");
return this.restartDialog(session);
}
return next();
}
If the version
field in the root of the dialog object is specified, this will be considered the scenario version. If the version is not specified, the bot will assign it a version by hashing the content of the JSON file.
The Code
Please refer to the bot-graph-dialog extension that was used to support this solution, as well as to the bot-trees project that can be used as a reference for how to use this extension. The specific reference for this case study is the loadOnDemand.js
file.
Opportunities for Reuse
This code story demonstrates how to integrate an already existing bot platform with Microsoft’s Bot Framework to easily expose the bot’s dialogs on the various channels that the Bot Framework supports. This approach can be used as a reference to create solutions for similar scenarios. Also, scenarios that require the loading of bot dialogs from an external data source like files, databases or remote APIs can leverage the bot-graph-dialog
extension by using similar methods.
0 comments