カスタム・コンポーネントの実装

カスタム・コンポーネントを実装するには、Oracle Digital Assistant Node.js SDKを使用して、デジタル・アシスタントのカスタム・コンポーネント・サービスとインタフェースします。

次に、デジタル・アシスタントの埋込みコンテナ、Oracle Cloud Infrastructure Functions、モバイル・ハブ・バックエンドまたはNode.jsサーバーにデプロイ可能なカスタム・コンポーネントを実装する方法を示します:

  1. カスタム・コンポーネントをビルドするためのソフトウェアをインストールします。

  2. カスタム・コンポーネント・パッケージを作成します。

  3. カスタム・コンポーネントを作成およびビルドします。

ノート

カスタム・コンポーネント・パッケージを埋込みカスタム・コンポーネント・サービスにデプロイする場合、パッケージの追加先となる各スキルは別個のサービスとしてカウントされます。インスタンスに含めることができる埋込みカスタム・コンポーネント・サービスの数には制限があります。制限が不明な場合は、インフラストラクチャ・コンソールでのサービス制限の表示の説明に従って、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以前のバージョンをインストールする必要があります。
  1. Node.jsをhttps://nodejs.orgからダウンロードし、グローバル・アクセス用にインストールします(まだ行っていない場合)。Node Package Manager (npm)は、Node.jsとともに配布されます。

    Node.jsとnpmがインストールされているかどうかをテストするには、ターミナル・ウィンドウを開いて次のコマンドを入力します:

    node –v 
    npm –v
  2. Oracle Digital Assistant Bots Node.js SDKをグローバル・アクセス用にインストールするには、ターミナル・ウィンドウで次のコマンドを入力します:
    npm install -g @oracle/bots-node-sdk

    Macでは、sudoコマンドを使用します:

    sudo npm install -g @oracle/bots-node-sdk

    -g (グローバル)オプションを使用する場合、bots-node-sdkコマンド行インタフェースに直接アクセスできます。それ以外の場合は、npx @oracle/bots-node-sdkを使用します。

  3. Oracle Digital Assistant Bots Node.js SDKのインストールを確認するには、次のコマンドを入力します:
    bots-node-sdk -v
    このコマンドは、Oracle Digital Assistant Bots Node.js SDKのバージョンを出力します。

ステップ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>.jscomponentsフォルダに追加されます。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が暗黙的にkeepTurnfalseに設定するためです。

  • transition(action)は、応答がある場合は、それらがすべて送信された後にダイアログを次の状態に遷移させます。オプションのaction引数は、コンポーネントが返すアクション(結果)を指定します。

    transition()を呼び出さない場合、レスポンスは送信されますが、ダイアログは状態のままで、後続のユーザー入力はこのコンポーネントに戻ります。つまり、invoke()が再度コールされます。

invoke: async (context) ==> {
   ...
   context.reply(payload);
   context.keepTurn(true);
   context.transition ("success"); 
}

keepTurnおよびtransitionを使用してダイアログ・フローを制御する一般的なユースケースをいくつか次に示します:

使用例 keepTurnとtransitionに設定される値

先にユーザーに入力を要求せずに、別の状態に遷移するカスタム・コンポーネント。

  1. 該当する場合は、context.reply(<reply>)を使用して返信を送信します。

  2. context.keepTurn(true)を設定します。

  3. context.transitionを設定します。supportedActions文字列(context.transition("success")など)を指定するか、引数なし(context.transition()など)とします。

たとえば、このカスタム・コンポーネントは変数を値リストで更新し、ダイアログ・フローの次の状態によってすぐに表示されます。
invoke: async (context) => {
    const listVariableName = context.properties().variableName;
    ...
    // Write list of options to a context variable
    context.variable(listVariableName, list);
   // Navigate to next state without 
   // first prompting for user interaction.
   context.keepTurn(true);
   context.transition();
 }

制御がスキルに戻った後、スキルが別の状態に遷移する前に、スキルが入力を待機できるようにするカスタム・コンポーネント。

  1. 該当する場合は、context.reply(<reply>)を使用して返信を送信します。

  2. context.keepTurn(false)を設定します。

  3. context.transitionを設定します。supportedActions文字列(context.transition("success"))を指定するか、引数なし(context.transition())とします。

例:
context.keepTurn(false);
context.transition("success");
フロー制御をスキルに戻さずにユーザー入力を取得するカスタム・コンポーネント。例:
  • コンポーネントは、バックエンド検索エンジンを問い合せるためにユーザー入力を渡します。スキルが1つの結果のみ対応可能で、問合せが複数のヒットを返す場合、コンポーネントは結果をフィルタリングするためにユーザーにさらに入力を求めます。この場合、カスタム・コンポーネントはユーザー入力の処理を継続し、検索エンジンが単一のヒットを返すまで対話を保持します。単一の結果が得られると、コンポーネントはcontext.transition()をコールし、ダイアログ・フロー定義に定義されている次の状態に遷移します。

  • コンポーネントは質問リストを処理し、すべての質問に回答されている場合にのみ別の次の状態に遷移します。

  1. transitionをコールしないでください。

  2. keepTurn(false)を設定します。

たとえば、このカスタム・コンポーネントは引用符を出力し、YesおよびNoボタンを表示して別の引用符を要求します。ユーザーがNoをクリックすると、スキルに戻ります。
  invoke: async (context) => {
    // Perform conversation tasks.
    const tracking_token = "a2VlcHR1cm4gZXhhbXBsZQ==";    
    const quotes = require("./json/Quotes.json");
    const quote = quotes[Math.floor(Math.random() * quotes.length)];
    
    // Check if postback action is issued. If postback action is issued, 
    // check if postback is from this component rendering. This ensures
    // that the component only responds to its own postback actions.     
    if (context.postback() && context.postback().token == tracking_token && context.postback().isNo) {
      context.keepTurn(true);
      context.transition();
    } else {
      // Show the quote of the day.
      context.reply("'" + quote.quote + "'");
      context.reply(" Quote by: " + quote.origin);
      // Create a single message with two buttons to 
      // request another quote or not.
      const mf = context.getMessageFactory();
      const message = mf.createTextMessage('Do you want another quote?')
        .addAction(mf.createPostbackAction('Yes', { isNo: false, token: tracking_token }))
        .addAction(mf.createPostbackAction('No', { isNo: true, token: tracking_token })); 
      context.reply(message);
      // Although reply() automatically sets keepTurn to false, 
      // it's good practice to explicitly set it so that it's
      // easier to see how you intend the component to behave.
      context.keepTurn(false);
    };
  }

コンポーネントが別の状態に遷移しない場合は、次の例に示すように、独自の状態をトラッキングする必要があります。

データ取得に時間がかかりすぎる場合に取り消すオプションをユーザーに提供するなど、より複雑な状態処理の場合は、コンテキスト変数を作成して使用できます。例: context.variable("InternalComponentWaitTime", time)。コンテキスト変数を使用する場合は、context.transitionをコールする前に、必ず再設定するかnullに設定します。

遷移しないと、コンポーネント・プロパティとして渡される値はすべて使用可能になります。

コンポーネントの起動は、ユーザー入力なしで繰り返されます。例:

  • コンポーネントは、ステータスがacceptedとして返されるか、コンポーネントがタイムアウトするまで、オーダーのステータスをリモート・サービスにpingします。5回目のpingの後にacceptedステータスが返されない場合、コンポーネントはfailedOrderステータスで遷移します。

  • カスタム・コンポーネントは、ユーザーをライブ・エージェントに渡します。この場合、ユーザーの入力とレスポンスがエージェントにディスパッチされます。コンポーネントは、ユーザーまたはエージェントがセッションを終了すると、別の状態に遷移します。

  • transitionをコールしないでください。

  • context.keepTurn(true)を設定します。

ユーザー入力を待たずに起動を繰り返す方法と、完了時に遷移する方法を示すやや不自然な例を次に示します:
invoke: async (context) => {

  const quotes = require("./json/Quotes.json");
  const quote = quotes[Math.floor(Math.random() * quotes.length)];
  
  // Check if postback action is issued and postback is from this component rendering. 
  // This ensures that the component only responds to its own postback actions.     
  const um = context.getUserMessage()
  if (um instanceof PostbackMessage && um.getPostback() && um.getPostback()['system.state'] === context.getRequest().state && um.getPostback().isNo) {
    context.keepTurn(true);
    context.transition();
  } else {
    // Show the quote of the day.
    context.reply(`'${quote.quote}'`);
    context.reply(`Quote by: ${quote.origin}`);
    // Create a single message with two buttons to request another quote or not.
    let actions = [];

    const mf = context.getMessageFactory();
    const message = mf.createTextMessage('Do you want another quote?')
      .addAction(mf.createPostbackAction('Yes', { isNo: false }))
      .addAction(mf.createPostbackAction('No', { isNo: true }));
    context.reply(message);
    // Although reply() automatically sets keepTurn to false, it's good practice to explicitly set it so that it's
    // easier to see how you intend the component to behave.
    context.keepTurn(false);
  }
}

バックエンドにアクセスします

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にあります。

多言語スキルのカスタム・コンポーネント

カスタム・コンポーネントを設計する際には、そのコンポーネントを複数の言語をサポートするスキルで使用するかどうかを考慮する必要があります。

カスタム・コンポーネントで多言語スキルをサポートする必要がある場合は、スキルがネイティブ言語サポートまたは翻訳サービス用に構成されているかどうかを知る必要があります。

翻訳サービスを使用すると、スキルからテキストを翻訳できます。次のオプションがあります。

母国語スキルには、次のオプションがあります。

  • データを変数内のスキルに戻し、変数の値をリソース・バンドル・キーに渡してシステム・コンポーネントからテキストを出力します(「システム・コンポーネントを使用したリソース・バンドルの参照」を参照)。このオプションでは、データを格納する変数の名前を渡すには、カスタム・コンポーネントにスキルのメタデータ・プロパティが必要です。

  • 「カスタム・コンポーネントからのリソース・バンドルの参照」の説明に従って、カスタム・コンポーネントのリソース・バンドルを使用してカスタム・コンポーネントの応答を構成します。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の説明が続きます
図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"

開発環境でのコンポーネント・サービスの実行

開発フェーズ中に、ローカル・サービスを開始してカスタム・コンポーネント・パッケージを公開できます。

  1. 最上位フォルダから、ターミナル・ウィンドウを開き、次のコマンドを実行してサービスを開始します:
    npm install
    npm start
  2. サービスが実行されていることを確認するには、ブラウザで次のURLを入力します:
    localhost:3000/components

    ブラウザにコンポーネント・メタデータが表示されます。

  3. インターネットに直接アクセスできる場合は、スキルから開発環境にアクセスできます:
    1. ngrokやLocaltunnelなどのトンネルをインストールします。
    2. プロキシの背後にある場合、http://www.whatismyproxy.com/に移動してプロキシの外部IPアドレスを取得した後、トンネルの起動に使用するターミナル・ウィンドウで次のコマンドを入力します:
      export https_proxy=http://<external ip>:80
      export http_proxy=http://<external ip>:80
    3. トンネルを起動し、ポート3000を公開するように構成します。
    4. Oracle Digital Assistantで、スキルの「コンポーネント」「コンポーネント」アイコンタブに移動し、メタデータURLをhttps://<tunnel-url>/componentsに設定して「外部」コンポーネント・サービスを追加します。
      ユーザー名とパスワードには任意の値を使用できます。
これで、サービスのコンポーネントの状態をダイアログ・フローに追加し、スキルの「プレビュー」ページでテストできます。