Versioning a live bot is a little trickier. You want to get users onto the new bot as quickly as possible, but you probably don’t want to interrupt the current flow of a user already in the middle of a dialog. If nothing else, when you version your bot, you need to handle the fact that a user in the middle of a dialog might be returning to a dialog step that doesn’t exist, or a dialog that’s changed profoundly, meaning the message the user sent might not make sense in the flow you deployed.
Reloading an older version
If you do want to transition the user, you will need to pay a little bit of attention to how you update your bot. In theory, you could do this on a dialog by dialog basis, but the code becomes trickier as you need to figure out where the user is, and swap out the dialog. I find it much easier to simply swap out the entire bot. To help manage this, I’ll put the bot, and its associated dialogs, into a folder. When it comes time to update it, I’ll copy the entire folder structure, update the copy, and then update app.js
to call the updated (new) bot.
When you need to send a user to the old bot, you simply update the library
property of the session
, which sets the bot that will run for the incoming message. In the code snippet below, I have both an updated and original bot in the same file for simplicity’s sake. I hope you can see from here how you would implement this with multiple folders.
In the sample below, I’m going to transition the user once their current conversation completes. You could, if you so desire, update this code to always reset the conversation, with a message of course, which would cause the user to be rerouted to the new bot. Also, the code could be streamlined a little bit, but breaking it apart like I’ve done makes it (I think) a little easier to explain with comments.
const connector = new builder.ChatConnector();
const bot = new builder.UniversalBot(
// this is the updated bot
connector,
[
(session) => {
builder.Prompts.text(session, 'What is your name?');
},
(session, results) => {
session.endConversation(`Hello, ${results.response}, from the updated bot`);
}
]
);
// set a version number using duck typing
bot.version = 2;
const getOriginalBot = (connector) =>{
// this will return the original bot
const originalBot = new builder.UniversalBot(
connector,
[
(session) => {
builder.Prompts.text(session, 'What is your name?');
},
(session, results) => {
session.endConversation(`Hello, ${results.response}, from the original bot`);
}
]
);
// set a version number by using duck typing
originalBot.version = 1;
return originalBot;
};
bot.use(
{
botbuilder: (session, next) => {
if(!session.userData.version) {
// new user. set the version
session.userData.version = bot.version;
}
// check if user is in conversation and
// if the user is on the prior version
if(session.dialogStack().length > 0 && session.userData.version < bot.version) {
// load the original bot
session.library = getOriginalBot(connector);
// pass control to the original bot
// the correc step and dialog will be executed
next();
} else {
// update the version in userData
session.userData.version = bot.version;
// pass control to the new bot
next();
}
},
}
);
If you wanted to test this, start by making the new bot version 1 and sending a message to get yourself into the dialog. Then update the version to 2, rerun the application while not restarting the conversation in Bot Emulator, and sending the second message. You should see the original bot message.
Some thoughts on versioning
Versioning is tricky, especially if you’re in a CI/CD environment where you expect to make multiple updates to the bot. Bot Framework (unfortunately?) doesn’t automatically handle transitions for you, so you will need to decide what pattern will work best for you. This is also important when developing and testing your bot in a channel such as Skype or Slack, where how to reset the state from the client isn’t overly obvious. In dev you could certainly skip the transition, but in production you’ll likely want to implement a mechanism for doing so.