Microsoft Bot Frameworkを使用して作成したボットをAzureにデプロイ、Bot Connectorに登録し、最終的にSlackで対話を行う。
前回は、Microsoft Bot Framework(以下、Bot Framework)を使用して、シンプルなボットを作成しながら、ダイアログ、コマンド、ウオーターフォールなどの基本要素について見た。今回は少し複雑なボットを作成して、Azureにデプロイ、これをSlackで使えるようにするまでを見てみよう。
今回は対話的に名前と電話番号のセットを登録し、そのデータを検索、一覧表示できるようなボットを作成する。ボットに「add」「find」などと呼びかけると、対話が始まり、名前と電話番号を入力したり、電話番号を検索したりできる。以下にSlackでボットと対話している様子を示す。
このボットとSlackで対話できるようにするには、おおよそ次のような段階を踏んでいく。
それではそれぞれのステップを順に見ていくことにしよう。
ボットの作成に当たっては前回と同様にbotbuilder/restifyの2つのパッケージをnpmからインストールしている(「npm init」コマンドと「npm install botbuilder restify --save」コマンドを実行している)。
実際のコードは次のようになる。
var restify = require('restify');
var builder = require('botbuilder');
var tmpdata = {};
var bot = new builder.BotConnectorBot(
{ appId: 'process.env.appid',
appSecret: 'process.env.appsecret' });
bot.use(function(session, next) {
if (!session.userData.addrbook)
session.userData.addrbook = [];
next();
});
bot.add('/', new builder.CommandDialog()
.matches('^(regist|add)', builder.DialogAction.beginDialog('/regist'))
.matches('^(find|search)', builder.DialogAction.beginDialog('/find'))
.matches('^list', showList)
.onDefault(function (session) {
var msg = 'you have ' + session.userData.addrbook.length + ' data.';
session.send('Hello, I am address book bot. ' + msg);
}));
function showList(session) {
var tmp = session.userData.addrbook.map(
current => 'name: ' + current.name + ' tel: ' + current.tel);
session.send(tmp.join(', '));
}
bot.add('/regist', [
function(session) {
builder.Prompts.text(session, 'what name do you want to add ?');
},
function(session, results) {
tmpdata.name = results.response;
builder.Prompts.text(session, 'telephone number? ');
},
function(session, results) {
tmpdata.tel = results.response;
session.userData.addrbook.push(tmpdata);
session.send('registerd!');
session.endDialog();
}
]);
bot.add('/find', [
function(session) {
builder.Prompts.text(session, 'find? ');
},
function(session, results) {
var target = results.response;
var res = session.userData.addrbook.filter(
item => item.name == target ? true : false
);
if (res[0]) {
session.send("name: " + res[0].name + " tel: " + res[0].tel);
} else {
session.send('no such data');
}
session.endDialog();
},
]);
var server = restify.createServer();
server.post('/api/messages', bot.verifyBotFramework(), bot.listen());
server.listen(process.env.port || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
コマンド、ウオーターフォールなど、コードを構成する要素は前回とほぼ変わらないので、前回と異なる点のみを簡単に説明しておこう。ただし、その前に1点。今回はボットのコードを記述するファイルの名前が「server.js」になっている。これは、Azure側の都合であり、「server.js」ファイルがある場合にはそのWebアプリがNode.jsを使って記述されていると判断するようになっているためだ。
このボットは以下の3種類のコマンドを受け付ける。サンプルなのでデータの修正と削除は実装していない。
コマンドのディスパッチを行っているのが以下のコードだ。
bot.add('/', new builder.CommandDialog()
.matches('^(regist|add)', builder.DialogAction.beginDialog('/regist'))
.matches('^(find|search)', builder.DialogAction.beginDialog('/find'))
.matches('^list', showList)
.onDefault(function (session) {
…… 省略 ……
}));
matchesメソッドでのコマンドのマッチ処理ではadd/registなど、同じ処理を行うコマンドを正規表現でひとまとめに記述している。登録と検索については前回と同様にウオーターフォールを使ってボットと対話しながら登録/検索を行う。一覧表示については、対話の必要がないので、showList関数を実行するように指定している(強調表示の部分)。シンプルな処理については、このように関数一発で処理を終わらせても構わない。showList関数のコードを以下に示す。特に説明の必要はないだろう。
function showList(session) {
var tmp = session.userData.addrbook.map(
current => 'name: ' + current.name + ' tel: ' + current.tel);
session.send(tmp.join(', '));
}
前回は特に気にしていなかったApp IdとApp Secretだが、今回はBot Connector(Bot Frameworkの構成要素の1つで、実際に対話を行う際に使われるWebページやSNSなどのチャンネルと、作成したボットとを接続するコミュニケーションサービス)にボットを登録して、設定を行う際にこれが必要になる。今回はAzureの[アプリケーション設定]で環境変数の形でこれを設定する(後述)。実行時にこれを取得するためのコードは次のようになる。
var bot = new builder.BotConnectorBot(
{ appId: 'process.env.appid',
appSecret: 'process.env.appsecret' });
プログラムに直接これらを記述するよりも、上のコードのように環境変数経由で取得した方がApp IdとApp Secretが他者にのぞかれる可能性が低く、安全な方法だろう。なお、Bot Emulatorでテストする場合には、これらの値はundefinedのままでも構わないようだ。
このボットではセッションごとに固有のユーザーデータ(session.userDataプロパティ)にaddrbookプロパティを追加し、そこに名前と電話番号のセットを保存している。が、ここで注意点が1つある。ボットとの対話がルート(ID)で分岐するので、そのままではボット全体で使い回すデータ(ここではaddrbookプロパティ)がきちんと初期化されているかをルートごとにチェックする必要があるのだ(少なくとも本稿のボットでは、どのコマンドが最初に実行されるかをプログラマーの側が強制できないため)。
さらにセッションデータはコールバック関数の引数として渡されるため、パッと見ではaddrbookプロパティを初期化する場所がない。そこでここでは以下のようにして、ミドルウェアを定義して、そこで初期化を行うようにしている。
bot.use(function(session, next) {
if (!session.userData.addrbook)
session.userData.addrbook = [];
next();
});
Bot Builder SDKではDialogCollectionクラス(前回見たTextBotクラスやBotConnectorBotクラスなどのボットクラスの基底クラス)でuseメソッドが定義されており、これを使ってミドルウェアを登録できる。ミドルウェアとして登録されたコードは、ボットがメッセージを受信するたびに呼び出され、そこで何か特別な処理を加えることができる。ここでは、「session.userData.addrbook」プロパティの有無を判定し、なければ初期化するようにしている。
毎回呼び出されるのでパフォーマンス的には気になるところだが、今回はこのようにしている(実際、Bot Builder SDKのサンプル「basics-firstRun」でもこの方法が例示されているので、これが一般的な初期化方法だと考えられる)。
これにより、コードの他の箇所ではsession.userData.addrbookプロパティが存在していることを前提としたコードを記述できるようになっている。
bot.add('/', new builder.CommandDialog()
…… 省略 ……
.onDefault(function (session) {
var msg = 'you have ' + session.userData.addrbook.length + ' data.';
session.send('Hello, I am address book bot. ' + msg);
}));
コードの説明はここまでとして、次ページではAzureへのデプロイ手順を見てみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.