カスタム・コンポーネントの実装
カスタム・コンポーネントを実装するには、Oracle Digital Assistant Node.js SDKを使用して、Digital Assistantカスタム・コンポーネント・サービスとインタフェースします。
Digital Assistant埋込みコンテナ、「Oracle Cloud Infrastructure関数」、「モバイル・ハブ」バックエンドまたはNode.jsサーバーにデプロイできるカスタム・コンポーネントを実装する方法を次に示します:
ノート:
カスタム・コンポーネント・パッケージを埋込みカスタム・コンポーネント・サービスにデプロイする場合、パッケージを追加する各スキルは個別のサービスとしてカウントされます。 インスタンスに含めることができる埋込みカスタム・コンポーネント・サービスの数に制限があります。 この制限がわからない場合は、「インフラストラクチャ・コンソールでのサービス制限の表示」の説明に従ってembedded-custom-component-service-count
を取得するようにサービス管理者に依頼してください。 使用する埋込みコンポーネント・サービスの数を最小限に抑えるために、パッケージごとに複数のコンポーネントをパッケージ化することを検討してください。 この制限を満たした後にコンポーネント・サービスを追加しようとすると、サービスの作成に失敗します。
ステップ1: カスタム・コンポーネントをビルドするためのソフトウェアのインストール
カスタム・コンポーネント・パッケージをビルドするには、Node.js、ノード・パッケージ・マネージャ、およびOracle Digital Assistant Bots Node.js SDKが必要です。
ノート:
Windowsでは、Node.jsでの下位互換性のない変更のためにノード・インストールがバージョン20.12.2以上である場合、Bots Node SDKはWindowsで機能しません。 すでにノード・バージョン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ファイルを配置する場所です。 -
package.json
ファイルを追加します。このファイルでは、main.js
をメイン・エントリ・ポイントとして指定し、@oracle/bots-node-sdk
をdevDependency
としてリストします。 パッケージ・ファイルには、一部の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ファイルのサイズが2倍になります。
埋込みコンテナにデプロイする場合、パッケージはノード14.17.0と互換性がある必要があります。
ステップ3: カスタム・コンポーネントの作成およびビルド
各カスタム・コンポーネントをパッケージにビルドするステップを次に示します:
コンポーネント・ファイルの作成
SDK CLIのinit component
コマンドを使用して、Oracle Digital Assistant Node.js SDKを操作してカスタム・コンポーネントを書き込むためのフレームワークを備えたJavaScriptまたはTypeScriptファイルを作成します。 init
コマンドを実行してコンポーネント・パッケージを作成するときに指定した言語によって、JavaScriptファイルまたはTypeScriptファイルが作成されるかどうかが決まります。
たとえば、カスタム・コンポーネントのファイルを作成するには、ターミナル・ウィンドウからCDをパッケージの最上位フォルダまで作成し、次のコマンドを入力します。<component name>
をコンポーネント名で置き換えます:
bots-node-sdk init component <component name> c components
JavaScriptの場合、このコマンドは<component name>.js
をcomponents folder
に追加します。 TypeScriptの場合、ファイルはsrc/components
フォルダに追加されます。 c
引数は、ファイルがカスタム・コンポーネント用であることを示します。
コンポーネント名は100文字を超えることはできません。 名前に使用できるのは、英数字とアンダースコアのみです。 ハイフンは使用できません。 また、System.
プレフィクスを持つ名前も使用できません。 Oracle Digital Assistantでは、無効なコンポーネント名を含むカスタム・コンポーネント・サービスを追加することはできません。
詳細は、https://github.com/oracle/bots-node-sdk/blob/master/bin/CLI.md
を参照してください。
メタデータへのコードの追加および関数の起動
カスタム・コンポーネントは、次の2つのオブジェクトをエクスポートする必要があります:
metadata
: これにより、スキルに次のコンポーネント情報が提供されます。- コンポーネント名
- サポートされるプロパティ
- サポートされている遷移アクション
「YAMLベースの場合」ダイアログ・フローでは、カスタム・コンポーネントはデフォルトで次のプロパティをサポートしています。 これらのプロパティは、ビジュアル・ダイアログ・モードで設計されたスキルでは使用できません。
autoNumberPostbackActions
: Boolean. 不要true
の場合、ボタンおよびリスト・オプションには自動的に番号が付けられます。 デフォルトは、false
です。 「YAMLダイアログ・フローでのテキストのみのチャネルの自動採番」を参照してください。insightsEndConversation
: Boolean. 不要true
の場合、セッションはインサイト・レポートの会話の記録を停止します。 デフォルトは、false
です。 「ダイアログ・フローのモデル化」を参照してください。insightsInclude
: Boolean. 不要true
の場合、状態はインサイト・レポートに含まれます。 デフォルトはtrue
です。 「ダイアログ・フローのモデル化」を参照してください。translate
: Boolean. 不要true
の場合、このコンポーネントで自動翻訳が有効になります。 デフォルトは、autotranslation
コンテキスト変数の値です。 「スキルの翻訳サービス」を参照してください。
invoke
: これには実行するロジックが含まれます。 このメソッドでは、スキル・コンテキスト変数の読取りおよび書込み、会話メッセージの作成、状態遷移の設定、RESTコールの作成などを実行できます。 通常は、この関数とともにasync
キーワードを使用して約束を処理します。invoke
関数は、次の引数を取ります:context
: Digital Assistant Node.js SDKのCustomComponentContext
オブジェクトへの参照を指定します。 このクラスは、SDKのドキュメント(https://oracle.github.io/bots-node-sdk/)で説明されています。 以前のバージョンのSDKでは、名前はconversation
でした。 どちらの名前も使用できます。
ノート:
Promisesをサポートしていない(したがって、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および遷移を使用したフローの制御
Bots Node SDKのkeepTurn
関数とtransition
関数の様々な組合せを使用して、カスタム・コンポーネントがユーザーと対話する方法、およびコンポーネントがフロー制御をスキルに戻した後に会話を続行する方法を定義します。
-
keepTurn(boolean)
は、最初にユーザー入力を要求せずに会話を別の状態に遷移するかどうかを指定します。keepTurn
をtrueに設定する場合、reply
はkeepTurn
をfalse
に暗黙的に設定するため、reply
のコール後にkeepTurn
をコールする必要があります。 -
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で導入されたinvoke
メソッドのasync
バージョンを利用し、await
キーワードを使用してRESTコールを同時に記述することをお薦めします。
1つのオプションは、Bots Node SDKで事前インストールされている「ノード・フェッチ」 APIです。 Bots Node SDKドキュメントの「HTTP RESTコールを使用したバックエンドへのアクセス」には、いくつかのコード例が含まれています。
SDKを使用したリクエストおよびレスポンス・ペイロードへのアクセス
CustomComponentContext
インスタンス・メソッドを使用して、起動のコンテキストを取得し、変数にアクセスして変更し、結果をダイアログ・エンジンに送り返します。
これらのメソッドの使用については、ボット・ノード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に理解されていない場合は、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;
}
この場合、ユーザーが2つの出力メッセージを取得するように、データ・フローをaskage
に戻す必要があります。 - "年齢入力が理解されていません。 年齢はいくつですか?"に続けて再試行してください。 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"