Microsoft Bot Framework loves Node.js

Si has seguido mi blog estas últimas semanas, habrás visto que me he volcado mucho con Microsoft Bot Framework para la generación de bots multiplataforma. Para este post quiero compartir contigo un ejemplo en NodeJS de un bot que se conecta con LUIS, utiliza prompts en cascada además de Application Insights para monitorizar el servicio, para que te hagas una idea de cómo sería en este lenguaje:

var restify = require("restify"),
    builder = require("botbuilder");

//Configure Application Insights
var appInsights = require("applicationinsights");
appInsights.setup("app_insights_key").start();

//Create LUIS Dialog that points at our model and add it as the root / dialog 
var model = "https://api.projectoxford.ai/luis/v1/application?id=MODEL_ID&subscription-key=SUBSCRIPTION_KEY";
var dialog = new builder.LuisDialog(model);

var bot = new builder.BotConnectorBot({ appId: 'nodejsbotdemo', appSecret: 'SECRET_KEY' });

bot.add("/", dialog);

//Add intents handlers
dialog.on("OrderFood", [
    function (session, args, next) {

        builder.Prompts.choice(session, "Are you hungry? what do you want?", "burger|pizza|sandwich");
    },
    function (session, results) {

        switch (results.response.index) {
            case 0:
                session.endDialog("![Burger](http://www.publicdomainpictures.net/pictures/150000/velka/hamburger-1451665803uds.jpg)");
                break;
            case 1:
                session.endDialog("![Pizza](http://www.publicdomainpictures.net/pictures/60000/velka/pizza-1380213425Xby.jpg)");
                break;
            case 2:
                session.endDialog("![Sandwich](https://upload.wikimedia.org/wikipedia/commons/9/9d/Breakfast_sandwich.jpg)");
            default:
                break;
        }

    }]);

dialog.on("OrderSnacks", [
    function (session, args, next) {

        builder.Prompts.choice(session, "Do you want a snack? What type?", "sweet|salt");

    },
    function (session, results) {
        switch (results.response.index) {
            case 0:
                session.dialogData.tipoSnack = "sweet";
                builder.Prompts.choice(session, "Ok! Sweet. Choose what you like", ["KitKat", "M&Ms"]);
                break;
            case 1:
                session.dialogData.tipoSnack = "salt";
                builder.Prompts.choice(session, "Ok! Salt. Choose what you like", ["Lays", "Doritos"]);
                break;
            default:
                break;
        }
    },
    function (session, results) {

        switch (results.response.index) {
            case 0:
                if (session.dialogData.tipoSnack == "sweet") {
                    session.dialogData.snack = "KitKat";
                }
                else {
                    session.dialogData.snack = "Lays";
                }
                break;
            case 1:
                if (session.dialogData.tipoSnack == "salado") {
                    session.dialogData.snack = "M&Ms";
                }
                else {
                    session.dialogData.snack = "Doritos";
                }
                break;
            default:
                break;
        }
        session.endDialog("This is your order: %s > %s", session.dialogData.tipoSnack, session.dialogData.snack);
    }
]);

dialog.on("OrderDrinks", [function (session, args, next) {
    if (args.entities.length > 0) {
        var bebida = builder.EntityRecognizer.findEntity(args.entities, "Bebida");
        session.send("Do you want a  %s?", bebida.entity);
    }

    session.send("Here is what we have:");
    session.send("Coca-cola");
    session.send("Pepsi");

}]);

dialog.onDefault(builder.DialogAction.send("I am sorry. I did not understand you."));

var server = restify.createServer();
server.use(bot.verifyBotFramework({ appId: 'nodejsbotdemo', appSecret: 'SECRET_KEY' }));
server.post("/api/messages", bot.listen());

server.listen(process.env.port || 80, function () {
    console.log("%s listening to %s", server.name, server.url);
});

Lo primero que debes hacer es elegir un directorio para tu bot y lanzar el comando npm init para crear el archivo package.json que te ayudará a definir los metadatos del código que formará tu bot. Una vez hecho esto, debes instalar los modulos botbuilder y restify:

npm install --save botbuilder
npm install --save restify

De forma opcional, también puedes hacer uso de Application Insights con NodeJS para monitorizar tu bot. Para ello puedes instalar el módulo con el siguiente comando:

npm install applicationinsights

Para iniciarlizarlo necesitas el instrumentation key del servicio. La última parte de la configuración es para LUIS, donde deberás recuperar la URL de tu modelo en el apartado Publish del mismo. A partir de aquí ya puedes definir desde qué ruta del servicio se comienza el diálogo con tu bot y cuales son las intenciones que el mismo es capaz de gestionar, en este caso OrderFood, OrderSnacks OrderDrinks. En cada una de ellas he simulado un escenario diferente: en el primero de ellos, con OrderFood lo que tenemos es un prompt inicial que desemboca en un segundo donde a través de un switch le ofrece al usuario la comida seleccionada. Esto te da una idea de cómo se lanza un prompt y cómo, a través de un array de funciones, es capaz de retornar en la siguiente el resultado de la primera. En el caso de OrderSnacks voy un paso más allá guardando en la sesión del usuario las opciones que va eligiendo de los diferentes prompts. Por último, en OrderDrinks, identifico si el usuario ya ha decidido qué tipo de bebida quiere y de no ser así le lanzo diferentes mensajes indicando cuáles son las bebidas que tiene en un supuesto inventario.

Para que este bot quede a la escucha, utilizo restify para crear un servidor que use botbuilder y la URL y puerto por el que debe escuchar. Todo esto funciona perfectamente con Azure Continuous Integration asociado a App Service.

¡Saludos!