カスタム・コンポーネントの実装
カスタム・コンポーネントを実装するには、Oracle Digital Assistant Node.js SDKを使用して、デジタル・アシスタントのカスタム・コンポーネント・サービスとインタフェースします。
次に、デジタル・アシスタントの埋込みコンテナ、Oracle Cloud Infrastructure Functions、モバイル・ハブ・バックエンドまたはNode.jsサーバーにデプロイ可能なカスタム・コンポーネントを実装する方法を示します:
カスタム・コンポーネント・パッケージを埋込みカスタム・コンポーネント・サービスにデプロイする場合、パッケージの追加先となる各スキルは別個のサービスとしてカウントされます。インスタンスに含めることができる埋込みカスタム・コンポーネント・サービスの数には制限があります。制限が不明な場合は、インフラストラクチャ・コンソールでのサービス制限の表示の説明に従って、
embedded-custom-component-service-count
の取得をサービス管理者に依頼してください。使用する埋込みコンポーネント・サービスの数を最小限に抑えるために、パッケージごとに複数のコンポーネントをパッケージ化することを検討してください。この制限に達した後にコンポーネント・サービスを追加しようとすると、サービスの作成に失敗します。
ステップ1: カスタム・コンポーネントをビルドするためのソフトウェアのインストール
カスタム・コンポーネント・パッケージをビルドするには、Node.js、Node Package ManagerおよびOracle Digital Assistant Bots Node.js SDKが必要です。
Windowsでは、Node.jsでの下位互換性のない変更のためにノード・インストールがバージョン20.12.2以上である場合、Windowsでボット・ノードSDKが機能しません。すでにノード・バージョン20.12.2以上がインストールされている場合は、それをアンインストールしてから、Bots Node SDKが機能するようにバージョン20.12.1以前のバージョンをインストールする必要があります。
ステップ2: カスタム・コンポーネント・パッケージの作成
プロジェクトを開始するには、SDKのコマンドライン・インタフェース(CLI)からbots-node-sdk init
コマンドを使用して、コンポーネント構造に必要なファイルおよびディレクトリ構造を作成します。
init
コマンドには、JavaScript (デフォルト)とTypeScriptのどちらを使用するか、初期コンポーネントのJavaScriptファイルにどのような名前を付けるかなど、いくつかのオプションがあります。これらのオプションについては、CLI開発者ツールで説明しています。JavaScriptプロジェクトを開始するための基本コマンドは次のとおりです:
bots-node-sdk init <top-level folder path> --name <component service name>
このコマンドは、JavaScriptパッケージに対して次のアクションを完了します:
-
最上位フォルダを作成します。
-
components
フォルダを作成し、hello.world.js
というサンプル・コンポーネントJavaScriptファイルを追加します。ここにコンポーネントのJavaScriptファイルを配置します。 -
メイン・エントリ・ポイントとして
main.js
を指定し、devDependency
として@oracle/bots-node-sdk
をリストするpackage.json
ファイルを追加します。パッケージ・ファイルは、いくつかのbots-node-sdk
スクリプトも指します。{ "name": "myCustomComponentService", "version": "1.0.0", "description": "Oracle Bots Custom Component Package", "main": "main.js", "scripts": { "bots-node-sdk": "bots-node-sdk", "help": "npm run bots-node-sdk -- --help", "prepack": "npm run bots-node-sdk -- pack --dry-run", "start": "npm run bots-node-sdk -- service ." }, "repository": {}, "dependencies": {}, "devDependencies": { "@oracle/bots-node-sdk": "^2.2.2", "express": "^4.16.3" } }
-
パッケージ設定をエクスポートし、コンポーネントの場所のコンポーネント・フォルダを指す
main.js
ファイルを最上位フォルダに追加します。 -
.npmignore
ファイルを最上位フォルダに追加します。このファイルは、コンポーネント・パッケージをエクスポートするときに使用されます。これは、.tgz
ファイルをパッケージから除外する必要があります。例:*.tgz
。 -
npmの一部のバージョンについては、
package-lock.json
ファイルを作成します。 - すべてのパッケージ依存関係を
node_modules
サブフォルダにインストールします。
bots-node-sdk init
コマンドを使用してパッケージ・フォルダを作成しない場合は、*.tgz
エントリを含む.npmignore
ファイルが最上位フォルダに格納されていることを確認してください。例:*.tgz
spec
service-*
そうでない場合、ファイルをTGZファイルにパックするたびに、最上位フォルダにすでに存在するTGZファイルを含めるので、TGZファイルのサイズが倍増し続けます。
埋込みコンテナにデプロイする場合は、パッケージにノード14.17.0との互換性が必要です。
ステップ3: カスタム・コンポーネントの作成とビルド
パッケージ内の各カスタム・コンポーネントをビルドするステップは、次のとおりです:
コンポーネント・ファイルの作成
SDKのCLI init component
コマンドを使用し、Oracle Digital Assistant Node.js SDKと連携するためのフレームワークを含むJavaScriptまたはTypeScriptファイルを作成し、カスタム・コンポーネントを記述します。init
コマンドを実行してコンポーネント・パッケージを作成したときに指定した言語によって、JavaScriptファイルとTypeScriptファイルのどちらが作成されるかが決まります。
たとえば、カスタム・コンポーネントのファイルを作成するには、ターミナル・ウィンドウでパッケージの最上位フォルダに移動して、<component name>
をコンポーネント名で置き換えて次のコマンドを入力します:
bots-node-sdk init component <component name> c components
JavaScriptの場合、このコマンドによって<component name>.js
がcomponents
フォルダに追加されます。TypeScriptの場合、ファイルはsrc/components
フォルダに追加されます。c
引数は、ファイルがカスタム・コンポーネント用であることを示します。
コンポーネント名は100文字を超えることはできません。名前に使用できるのは、英数字とアンダースコアのみです。ハイフンは使用できません。また、System.
接頭辞を持つ名前も使用できません。Oracle Digital Assistantでは、無効なコンポーネント名を含むカスタム・コンポーネント・サービスの追加は許可されません。
詳細は、https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md
を参照してください。
metadata関数とinvoke関数にコードを追加する
カスタム・コンポーネントは、2つのオブジェクトをエクスポートする必要があります:
metadata
: 次のコンポーネント情報をスキルに提供します。- コンポーネント名
- サポートされるプロパティ
- サポートされる遷移アクション
YAMLベースのダイアログ・フローの場合、カスタム・コンポーネントではデフォルトで次のプロパティがサポートされます。これらのプロパティは、ビジュアル・ダイアログ・モードで設計されたスキルには使用できません。
autoNumberPostbackActions
: ブール。必須ではありません。true
の場合、ボタンおよびリストのオプションに自動的に番号が付けられます。デフォルトはfalse
です。「YAMLダイアログ・フローのテキストのみのチャネルの自動番号付け」を参照してください。insightsEndConversation
: ブール。必須ではありません。true
の場合、セッションがインサイト・レポート用の会話の記録を停止します。デフォルトはfalse
です。ダイアログ・フローのモデリングを参照してください。insightsInclude
: ブール。必須ではありません。true
の場合、状態がインサイト・レポートに含まれます。デフォルトはtrue
です。ダイアログ・フローのモデリングを参照してください。translate
: ブール。必須ではありません。true
の場合、このコンポーネントで自動翻訳が有効になります。デフォルトは、autotranslation
コンテキスト変数の値です。スキルでの翻訳サービスを参照してください。
invoke
: 実行するロジックが含まれます。このメソッドで、スキル・コンテキスト変数の読取りと書込み、会話メッセージの作成、状態遷移の設定、RESTコールの実行などを行うことができます。通常は、この関数でasync
キーワードを使用してpromiseを処理します。invoke
関数は、次の引数を取ります。context
: デジタル・アシスタントNode.js SDKのCustomComponentContext
オブジェクトへの参照を指定します。このクラスについては、SDKのドキュメント(https://oracle.github.io/bots-node-sdk/)で説明しています。SDKの以前のバージョンでは、名前はconversation
でした。いずれの名前も使用できます。
ノート
Promiseをサポートしていない(したがってasync
キーワードを使用していない)JavaScriptライブラリを使用している場合は、処理が終了したときにコンポーネントが起動するコールバックとしてdone
引数を追加することもできます。
次に例を示します:
'use strict';
module.exports = {
metadata: {
name: 'helloWorld',
properties: {
human: { required: true, type: 'string' }
},
supportedActions: ['weekday', 'weekend']
},
invoke: async(context) => {
// Retrieve the value of the 'human' component property.
const { human } = context.properties();
// determine date
const now = new Date();
const dayOfWeek = now.toLocaleDateString('en-US', { weekday: 'long' });
const isWeekend = [0, 6].indexOf(now.getDay()) > -1;
// Send two messages, and transition based on the day of the week
context.reply(`Greetings ${human}`)
.reply(`Today is ${now.toLocaleDateString()}, a ${dayOfWeek}`)
.transition(isWeekend ? 'weekend' : 'weekday');
}
}
詳細およびいくつかのコード例は、Bots Node SDKドキュメントのカスタム・コンポーネントの記述を参照してください。
keepTurnおよびtransitionを使用したフローの制御
Bots Node SDKのkeepTurn
関数とtransition
関数の様々な組合せを使用して、カスタム・コンポーネントがユーザーとやり取りする方法およびコンポーネントがフロー制御をスキルに返した後の会話の続行方法を定義します。
-
keepTurn(boolean)
は、先にユーザー入力を要求せずに会話を別の状態に遷移するかどうかを指定します。keepTurn
をtrueに設定する場合は、reply
のコール後にkeepTurn
をコールする必要があります。これは、reply
が暗黙的にkeepTurn
をfalse
に設定するためです。 -
transition(action)
は、応答がある場合は、それらがすべて送信された後にダイアログを次の状態に遷移させます。オプションのaction
引数は、コンポーネントが返すアクション(結果)を指定します。transition()
を呼び出さない場合、レスポンスは送信されますが、ダイアログは状態のままで、後続のユーザー入力はこのコンポーネントに戻ります。つまり、invoke()
が再度コールされます。
invoke: async (context) ==> {
...
context.reply(payload);
context.keepTurn(true);
context.transition ("success");
}
keepTurn
およびtransition
を使用してダイアログ・フローを制御する一般的なユースケースをいくつか次に示します:
使用例 | keepTurnとtransitionに設定される値 |
---|---|
先にユーザーに入力を要求せずに、別の状態に遷移するカスタム・コンポーネント。 |
たとえば、このカスタム・コンポーネントは変数を値リストで更新し、ダイアログ・フローの次の状態によってすぐに表示されます。
|
制御がスキルに戻った後、スキルが別の状態に遷移する前に、スキルが入力を待機できるようにするカスタム・コンポーネント。 |
例:
|
フロー制御をスキルに戻さずにユーザー入力を取得するカスタム・コンポーネント。例:
|
たとえば、このカスタム・コンポーネントは引用符を出力し、
Yes およびNo ボタンを表示して別の引用符を要求します。ユーザーがNo をクリックすると、スキルに戻ります。
コンポーネントが別の状態に遷移しない場合は、次の例に示すように、独自の状態をトラッキングする必要があります。 データ取得に時間がかかりすぎる場合に取り消すオプションをユーザーに提供するなど、より複雑な状態処理の場合は、コンテキスト変数を作成して使用できます。例: 遷移しないと、コンポーネント・プロパティとして渡される値はすべて使用可能になります。 |
コンポーネントの起動は、ユーザー入力なしで繰り返されます。例:
|
ユーザー入力を待たずに起動を繰り返す方法と、完了時に遷移する方法を示すやや不自然な例を次に示します:
|
バックエンドにアクセスします
HTTPリクエストを簡単にするために構築されたNode.jsライブラリがいくつかあり、リストは頻繁に変更されることがわかります。現在使用可能なライブラリの長所と短所を確認し、最適なものを判断する必要があります。バージョン2.5.1で導入されたasync
バージョンのinvoke
メソッドを利用できるように、プロミスをサポートするライブラリを使用することと、await
キーワードを使用してRESTコールを同期的に記述することをお薦めします。
1つの方法は、Bots Node SDKとともに事前にインストールされているnode-fetchAPIです。Bots Node SDKドキュメントのHTTP RESTコールを使用したバックエンドへのアクセスに、いくつかのコード例が含まれています。
SDKを使用してリクエスト・ペイロードおよびレスポンス・ペイロードにアクセスする
CustomComponentContext
インスタンス・メソッドを使用して、起動のコンテキストを取得し、変数にアクセスして変更し、結果をダイアログ・エンジンに返します。
これらのメソッドを使用するためのコード例は、Bots Node SDKドキュメントのカスタム・コンポーネントの作成および会話メッセージングを参照してください
SDKの参照ドキュメントは、https://github.com/oracle/bots-node-sdk
にあります。
多言語スキルのカスタム・コンポーネント
カスタム・コンポーネントを設計する際には、そのコンポーネントを複数の言語をサポートするスキルで使用するかどうかを考慮する必要があります。
カスタム・コンポーネントで多言語スキルをサポートする必要がある場合は、スキルがネイティブ言語サポートまたは翻訳サービス用に構成されているかどうかを知る必要があります。
翻訳サービスを使用すると、スキルからテキストを翻訳できます。次のオプションがあります。
-
「レスポンスを翻訳サービスに直接送信」の説明に従って、カスタム・コンポーネントの状態の
translate
プロパティをtrueに設定して、コンポーネントの応答を翻訳します。 -
RAWデータを変数内のスキルに戻し、出力を構成するシステム・コンポーネントで変数の値を使用します。そのコンポーネントの
translate
プロパティをtrueに設定します。「翻訳サービスにメッセージを渡すためのシステム・コンポーネントの使用」を参照してください。 -
RAWデータを変数でスキルに戻し、その言語にリソース・バンドル・キーを使用するシステム・コンポーネントで変数の値を使用します。システム・コンポーネントを使用したリソース・バンドルの参照を参照してください。
母国語スキルには、次のオプションがあります。
-
データを変数内のスキルに戻し、変数の値をリソース・バンドル・キーに渡してシステム・コンポーネントからテキストを出力します(「システム・コンポーネントを使用したリソース・バンドルの参照」を参照)。このオプションでは、データを格納する変数の名前を渡すには、カスタム・コンポーネントにスキルのメタデータ・プロパティが必要です。
-
「カスタム・コンポーネントからのリソース・バンドルの参照」の説明に従って、カスタム・コンポーネントのリソース・バンドルを使用してカスタム・コンポーネントの応答を構成します。
conversation.translate()
メソッドを使用して、context.reply()
へのコールに使用するリソース・バンドル文字列を取得します。このオプションは、位置(番号付き)パラメータを使用するリソースバンドル定義でのみ有効です。名前付きパラメータでは機能しません。このオプションでは、カスタム・コンポーネントにリソース・バンドル・キーの名前のメタデータ・プロパティが必要であり、名前付きリソース・バンドル・キーのパラメータは、context.reply()
のコールで使用されるパラメータと一致する必要があります。
カスタム・コンポーネントのリソース・バンドルを使用する例を次に示します。この例では、fmTemplate
は${rb('date.dayOfWeekMessage', 'lundi', '19 juillet 2021')}
のように設定されます。
'use strict';
var IntlPolyfill = require('intl');
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
module.exports = {
metadata: () => ({
name: 'Date.DayOfWeek',
properties: {
rbKey: { required: true, type: 'string' }
},
supportedActions: []
}),
invoke: (context, done) => {
const { rbKey } = context.properties();
if (!rbKey || rbKey.startsWith('${')){
context.transition();
done(new Error('The state is missing the rbKey property or it uses an invalid expression to pass the value.'));
}
//detect user locale. If not set, define a default
const locale = context.getVariable('profile.locale') ?
context.getVariable('profile.locale') : 'en-AU';
const jsLocale = locale.replace('_','-');
//when profile languageTag is set, use it. If not, use profile.locale
const languageTag = context.getVariable('profile.languageTag')?
context.getVariable('profile.languageTag') : jslocale;
/* =============================================================
Determine the current date in local format and
the day name for the locale
============================================================= */
var now = new Date();
var dayTemplate = new Intl.DateTimeFormat(languageTag,
{ weekday: 'long' });
var dayOfWeek = dayTemplate.format(now);
var dateTemplate = new Intl.DateTimeFormat(languageTag,
{ year: 'numeric', month: 'long', day: 'numeric'});
var dateToday = dateTemplate.format(now);
/* =============================================================
Use the context.translate() method to create the ${Freemarker}
template that's evaluated when the reply() is flushed to the
client.
============================================================= */
const fmTemplate = context.translate(rbKey, dateToday, dayOfWeek );
context.reply(fmTemplate)
.transition()
.logger().info('INFO : Generated FreeMarker => '
+ fmTemplate);
done();
}
};
コンポーネントがデジタル・アシスタントで機能することを確認する
デジタル・アシスタントの会話では、ユーザーは主題を変更することで会話フローを中断できます。たとえば、ユーザーが購入を行うためのフローを開始した場合、そのフローを中断してギフト・カードの残高を確認できます。これは無関係な発言と呼ばれます。デジタル・アシスタントが無関係な発言を識別して処理できるようにするには、ユーザーの発話のレスポンスがコンポーネントのコンテキストで認識されない場合にcontext.invalidInput(payload)
メソッドをコールします。
デジタル会話では、ランタイムは、すべてのスキルでレスポンスの一致を検索することにより、無効な入力が無関係な発言であるかどうかを判断します。一致するものが見つかった場合は、フローを再ルーティングします。見つからなかった場合は、メッセージを表示し、指定されていれば、ユーザーに入力を求めた後、コンポーネントを再度実行します。新しい入力は、text
プロパティでコンポーネントに渡されます。
スタンドアロン・スキル会話では、ランタイムはメッセージを表示し、指定されていれば、ユーザーに入力を求めた後、コンポーネントを再度実行します。新しい入力は、text
プロパティでコンポーネントに渡されます。
次のサンプル・コードでは、入力が数値に変換されない場合は常にcontext.invalidInput(payload)
がコールされます。
"use strict"
module.exports = {
metadata: () => ({
"name": "AgeChecker",
"properties": {
"minAge": { "type": "integer", "required": true }
},
"supportedActions": [
"allow",
"block",
"unsupportedPayload"
]
}),
invoke: (context, done) => {
// Parse a number out of the incoming message
const text = context.text();
var age = 0;
if (text){
const matches = text.match(/\d+/);
if (matches) {
age = matches[0];
} else {
context.invalidUserInput("Age input not understood. Please try again");
done();
return;
}
} else {
context.transition('unsupportedPayload");
done();
return;
}
context.logger().info('AgeChecker: using age=' + age);
// Set action based on age check
let minAge = context.properties().minAge || 18;
context.transition( age >= minAge ? 'allow' : 'block' );
done();
}
};
次に、デジタル・アシスタントが実行時に無効な入力をどのように処理するかの例を示します。年齢に関する最初のレスポンス(twentyfive
)については、デジタル・アシスタントに登録されているどのスキルにも一致するものがないため、指定されたcontext.invalidUserInput
メッセージが会話に表示されます。年齢に関する2番目のレスポンス(send money
)では、デジタル・アシスタントは一致するものを見つけたため、そのフローに再ルーティングするかどうかを尋ねます。

図components-nonsequitur-conversation.pngの説明
context.invalidInput()
またはcontext.transition()
をコールする必要があります。両方の操作をコールする場合は、追加メッセージが送信される場合にsystem.invalidUserInput
変数が引き続き設定された状態になるようにしてください。また、ユーザー入力コンポーネント(共通レスポンス・コンポーネントやエンティティの解決コンポーネントなど)がsystem.invalidUserInput
をリセットすることに注意してください。
たとえば、次に示すようにAgeCheckerコンポーネントを変更し、context.invalidInput()
の後にcontext.transition()
をコールするとします。
if (matches) { age = matches[0]; } else {
context.invalidUserInput("Age input not understood. Please try again");
context.transition("invalid");
context.keepTurn(true);
done();
return;
}
この場合は、データ・フローが遷移してaskage
に戻り、ユーザーが2つの出力メッセージを取得するようにする必要があります – 「Age input not understood.Please try again」と、その後に続く「How old are you?」。YAMLモードのダイアログ・フローで処理する方法を次に示します。
askage:
component: "System.Output"
properties:
text: "How old are you?"
transitions:
next: "checkage"
checkage:
component: "AgeChecker"
properties:
minAge: 18
transitions:
actions:
allow: "crust"
block: "underage"
invalid: "askage"