ここで作るもの
こちらの実装 では、最初のステップとしてボットサーバ自体 (Azure Web Apps) のエンドポイント情報を .bot
ファイルから取得する実装を行いました (config.js
)。
ここでは、さらに、LUIS サービスや QnA Maker サービスを利用することを想定し、これらの情報も .bot
ファイルから取得できるように拡張します。
使用イメージとしては、下記のようにしてそれぞれの接続情報を簡単に読み込めるようにします。
const config = require('./config.js');
const LUIS_APP_NAME = 'maku-luis-sample';
const QNA_APP_NAME = 'maku-qna-sample';
const botEndpoint = config.loadBotEndpoint(); // ボット自体への接続情報
const luisEndpoint = config.loadLuisEndpoint(LUIS_APP_NAME); // LUIS への接続情報
const qnaEndpoint = config.loadQnaEndpoint(QNA_APP_NAME); // QnA Maker への接続情報
単一のオブジェクトとしてまとめて取得するように実装することもできるのですが、分かりやすさのために、3 つの情報に分けて取得するようにしています。 LUIS や QnA Maker は、複数のアプリ(ナレッジベース)を同時に使用する可能性があるので、アプリ名を指定して接続情報を取得できるようにしています。
取得した情報は、次のように BotBuilder SDK が提供するクラスへの入力として使用することを想定しています。
botEndpoint
オブジェクトは、botbuilder
パッケージのBotFrameworkAdapter
クラスのコンストラクタに渡されます。luisEndpoint
オブジェクトは、botbuilder-ai
パッケージのLuisRecognizer
クラスのコンストラクタに渡されます。qnaEndpoint
オブジェクトは、botbuilder-ai
パッケージのQnAMaker
クラスのコンストラクタに渡されます。
下記は、実際に取得されるオブジェクトの内容の例です。 これらのオブジェクトは、上記のように SDK のクラスへの入力用に使用するので、各プロパティの値を直接参照することはないと思います。
botEndpoint = {
appId: '921c6a01-1948-0f4d-9a82-75daf6a6d43c',
appPassword: '16lgoF+phvG:MPeg1eIDma*fcNU#!5jv',
...
}
luisEndpoint = {
applicationId: 'c5514855-fa6b-8f4c-c695-9ac7d3519412',
endpointKey: '9ccff1530f4214fd8e319434a1408fa2',
endpoint: 'https://westus.api.cognitive.microsoft.com'
}
qnaEndpoint = {
knowledgeBaseId: 'c78ec3e7-a58d-ca44-dbb6-e7805d8130c6',
endpointKey: 'c9153f6c-980c-bd41-4ba6-370392cda0e8',
host: 'https://maku-qna-sample.azurewebsites.net/qnamaker'
}
botframework-config パッケージのインストール
.bot
ファイルのロードには、Node の botframework-config
パッケージが提供する BotConfiguration
クラスを使用します。
必要があれば、下記のようにインストールして、package.json
に依存関係を追記します。
$ npm install botframework-config --save
実装
config.js 全体のコード
class Config {
/**
* コンストラクタ。
* 環境変数や .env ファイルに書かれた botFilePath や botFileSecret の
* 情報を基に、.bot ファイルを読み込みます。
*/
constructor() {
const env = this._loadEnvFile();
this.botEnv = env.botEnv;
this.botConfig = this._loadBotFile(env.botFilePath, env.botFileSecret);
}
/**
* 指定された Bot Endpoint (developement や production) への
* 接続情報を取得します。
*/
loadBotEndpoint() {
// Get bot endpoint configuration by service name
// Bot configuration as defined in .bot file
const endpointConfig = this.botConfig.findServiceByNameOrId(this.botEnv);
return {
appId: endpointConfig.appId || process.env.microsoftAppID,
appPassword: endpointConfig.appPassword || process.env.microsoftAppPassword,
channelService: process.env.ChannelService,
openIdMetadata: process.env.BotOpenIdMetadata,
}
}
/**
* 指定した LUIS アプリへの接続情報を取得します。
* botbuilder-ai パッケージの LuisRecognizer クラスの
* 初期化情報として使用することを想定しています。
* 設定が見つからない場合は空のオブジェクトを返します。
*/
loadLuisEndpoint(luisAppName) {
const luisConfig = this.botConfig.findServiceByNameOrId(luisAppName);
if (luisConfig == null) {
return {};
}
// Map the contents to the required format for `LuisRecognizer`.
return {
applicationId: luisConfig.appId,
endpointKey: luisConfig.subscriptionKey,
endpoint: luisConfig.getEndpoint()
};
}
/**
* 指定した QnA Maker アプリへの接続情報を取得します。
* botbuilder-ai パッケージの QnAMaker クラスの初期化情報として
* 使用することを想定しています。
* 設定が見つからない場合は空のオブジェクトを返します。
*/
loadQnaEndpoint(qnaAppName) {
const qnaConfig = this.botConfig.findServiceByNameOrId(qnaAppName);
if (qnaConfig == null) {
return {};
}
// Map the contents to the required format for `QnAMaker`.
return {
knowledgeBaseId: qnaConfig.kbId,
endpointKey: qnaConfig.endpointKey,
host: qnaConfig.hostname
};
}
/**
* .env ファイルの設定を環境変数 (process.env) とマージし、
* .bot ファイルを読み込むための情報(パスなど)を返します。
*/
_loadEnvFile() {
const dotenv = require('dotenv');
const path = require('path');
const ROOT_DIR = __dirname;
// Merge .env info to process.env
dotenv.config({ path: path.join(ROOT_DIR, '.env') });
return {
// Absolute path of .bot file
botFilePath: path.join(ROOT_DIR, (process.env.botFilePath || '')),
// Secret key of .bot file
botFileSecret: process.env.botFileSecret,
// Name of the endpoint configuration section from the .bot file
botEnv: process.env.NODE_ENV || 'development',
}
}
/**
* 暗号化された .bot ファイルを読み込みます。
*/
_loadBotFile(botFilePath, botFileSecret) {
const { BotConfiguration } = require('botframework-config');
try {
return BotConfiguration.loadSync(botFilePath, botFileSecret);
} catch (err) {
console.error('Error reading bot file.');
console.error(err.message + '\n');
console.error('Please ensure you have valid botFilePath' +
' and botFileSecret set for your environment.');
console.error('You can find the botFilePath and botFileSecret' +
' in the Azure App Service application settings.');
console.error('If you are running this bot locally, consider adding' +
' a .env file with botFilePath and botFileSecret.');
process.exit(1);
}
}
}
module.exports = new Config();
BotConfiguration.loadSync() のエラーについてのメモ
BotConfiguration.loadSync()
でエラーが発生して .bot
ファイルがうまく読み込めなかった場合、Microsoft 公式のサンプルコードでは「botFilePath
と botFileSecret
をちゃんと設定してください」といった感じのメッセージだけ表示して済ませています。
しかし、実際には .bot
ファイルを読み込めても記述内容がおかしい場合にはエラーが発生します(URL が求められているプロパティなのに URL の形式になっていない場合など)。
このチグハグなエラーメッセージのせいで結構ハマりました。。。
.bot
ファイルの読み込みがエラーになった場合に、その原因を明確にするためには、最低限 Error オブジェクトの内容(err.message
など)を出力してあげた方がよいです。
BotConfiguration#findServiceByNameOrId() で返される接続情報についてのメモ
BotConfiguration#findServiceByNameOrId()
で返される各サービスの接続情報に何が含まれるかは、下記の TypeScript のクラスとして定義されています。
- EndpointService class
appId
、appPassword
、endpoint
プロパティなどを持つ。
- LuisService class
appId
、authoringKey
、region
、subscriptionKey
、version
プロパティ、getEndpoint()
メソッドなどを持つ。getEndpoint()
メソッドの戻り値を使用すれば、通常はregion
やversion
プロパティの値を個別に参照する必要はない。authoringKey
は管理用のキーなので、実運用ではsubscriptionKey
を LUIS のエンドポイントキーとして使用すること。
- QnaMakerService class
endpointKey
、hostname
、kbId
、subscriptionKey
プロパティなどを持つ。subscriptionKey
は管理用のキーなので、実運用ではendpointKey
を QnA Maker のエンドポイントキーとして使用すること。
LUIS と QnAMaker のキーはそれぞれ 2 種類ずつあって、どちらを実際のエンドポイントキーとして使用するかは注意すべきポイントです。 キーの区別がよくわからなくなってきた場合は、こちらの記事「LUIS と QnA Maker でキーの管理方法が異なるのはなぜか?」を参照してください。
findServiceByNameOrId()
の返すオブジェクトには、各種サービスの API を利用するために必要十分な情報が含まれていますが、.bot
ファイルに記述した内容をすべて取得できるというわけではないことに注意してください。
他にも、DispatchService、FileService、CosmosDbService、BlobStorageService など、サービスタイプに応じたクラスが定義されています。 サービスタイプの一覧は、ServiceType enum の定義を見れば分かるでしょう。
使用例
上記の config.js
で取得した接続情報の使用例です。
ボットコード全体を示すと長大になってしまうので、ここでは、BotFrameworkAdapter
、LuisRecgonizer
、QnAMaker
クラスのインスタンスを作成する部分を抜粋して示します。
BotFrameworkAdapter の初期化
const { BotFrameworkAdapter } = require('botbuilder');
const config = require('./config.js');
const adapter = new BotFrameworkAdapter(config.loadBotEndpoint());
LuisRecognizer の初期化
const { LuisRecognizer } = require('botbuilder-ai');
const config = require('./config.js');
const LUIS_APP_NAME = 'your-luis-app-name';
const luisEndpoint = config.loadLuisEndpoint(LUIS_APP_NAME);
const luisOptions = {
includeAllIntents: true,
log: true,
staging: false
};
const luisRecognizer = new LuisRecognizer(luisEndpoint, luisOptions, true);
QnAMaker の初期化
const { QnAMaker } = require('botbuilder-ai');
const config = require('./config.js');
const QNA_APP_NAME = 'your-qna-app-name';
const qnaEndpoint = config.loadQnaEndpoint(QNA_APP_NAME);
const qnaOptions = {};
const qnaMaker = new QnAMaker(qnaEndpoint, qnaOptions);
関連記事
- チャットボット: MS Bot Framework の .bot ファイルで接続情報を管理する
- チャットボット: Azure ポータルで生成されるボットのテンプレートコードを解読&リファクタしてみる
- チャットボット (2-2) Web App Bot で生成されたボットのコードを編集する
- チャットボット (2-3) Azure の Web App Bot リソースにボットをデプロイする
- チャットボット (2-1) Azure でボットをホストするための Web App Bot リソースを作成する
- チャットボット (1-1) Bot Builder SDK とは
- チャットボット (1-2) Bot Builder SDK で Echo ボットを作成する