Actions on Google Node.js クライアント ライブラリ(Dialogflow)を使用してフルフィルメントを構築する

JavaScript でフルフィルメント Webhook を作成する場合は、Actions on Google プラットフォームにアクセスしてやり取りするために Actions on Google Node.js クライアント ライブラリを使用することをおすすめします。

はじめに

Node.js クライアント ライブラリは Actions on Google 用のフルフィルメント ライブラリであり、次の機能を提供します。

  • テキスト レスポンスやリッチなマルチメディア レスポンス、アカウントへのログイン、データの保存、取引など、Actions on Google のすべての機能をサポートします。
  • Conversation HTTP / JSON Webhook API をラップする、JavaScript の慣用的な抽象レイヤを提供します。
  • フルフィルメントと Actions on Google プラットフォーム間の低レベルの通信情報を処理します。
  • 使い慣れたパッケージ管理ツール(npmyarn など)を使用してインストールできます。
  • フルフィルメント Webhook を Cloud Functions for FirebaseAWS Lambda などのサーバーレス コンピューティング プラットフォームに簡単にデプロイできます。また、クラウド サービス プロバイダや自分で管理する自己ホスト環境でフルフィルメント Webhook をホストすることもできます。
  • Node.js v6.0.0 以降と互換性があります。

クライアント ライブラリは、Actions on Google 向けの Dialogflow 統合または Actions SDK と組み合わせて使用できます。

クライアント ライブラリを使用するための詳細なコードサンプルを見るには、サンプルページをご覧ください。

API リファレンスを見る

API リファレンスは、Actions on Google Node.js クライアント ライブラリの GitHub ページでホストされています。

また、クライアント ライブラリ コードをダウンロードしたディレクトリから次のコマンドを実行することで、リファレンスのローカルコピーを生成することもできます。

yarn docs

生成されたドキュメントは、クライアント ライブラリ コードをダウンロードしたディレクトリの docs フォルダにあります。

仕組みを理解する

クライアント ライブラリを使用する前に、Actions on Google からフルフィルメントに送信されたユーザー リクエストを処理するためにフルフィルメント Webhook がクライアント ライブラリをどのように使用するかを理解しておくことをおすすめします。

JavaScript でフルフィルメント Webhook を作成する場合は、作成したコードを Google の Cloud Functions for Firebase や AWS Lambda などのサーバーレス コンピューティング環境にデプロイしてホストできます。Express ウェブ フレームワークを使用すると、追加作業なしで自分でコードをホストすることもできます。

ランタイム環境内において、フルフィルメント Webhook はクライアント ライブラリの関数を呼び出してユーザー リクエストを処理し、ユーザーへの出力となるレスポンスを Actions on Google に送り返します。

フルフィルメント Webhook がクライアント ライブラリを使用して処理する主なタスクを以下に簡単にまとめます。

図 1. Node.js クライアント ライブラリのアーキテクチャの概要
  1. ユーザー リクエストの受信: ユーザーが Google アシスタントに対してクエリを行うと、Actions on Google プラットフォームからフルフィルメント Webhook に HTTP リクエストが送信されます。このリクエストには、インテント、ユーザー入力の未加工テキスト、ユーザーのデバイスのサーフェス機能などのデータを含む JSON ペイロードが含まれます。JSON ペイロード コンテンツのその他の例については、Dialogflow Webhook 形式会話 Webhook 形式のガイドをご覧ください。
  2. フレームワークの呼び出し形式の検出: サポートされているフレームワークの場合、クライアント ライブラリはフレームワークの呼び出し形式を自動的に検出し(たとえば、リクエストの送信元が Express ウェブ フレームワークか、AWS Lambda かを検出する)、Actions on Google プラットフォームとの通信をシームレスに処理することができます。
  3. サービス ハンドラ処理: クライアント ライブラリでは、Dialogflow と Actions SDK 用の Conversation HTTP / JSON Webhook API がサービス関数として表されています。フルフィルメント Webhook は、適切なサービスを使用してグローバル app インスタンスを作成します。app インスタンスは HTTP リクエストのハンドラとして機能し、サービスの特定のプロトコルを理解します。
  4. 会話処理: クライアント ライブラリでは、会話ごとの情報を Conversation オブジェクトとして表し、app インスタンスに接続されます。フルフィルメント Webhook は Conversation オブジェクトを使用して、複数の会話にわたって保存されたデータまたは状態情報の取得、ユーザーへのレスポンスの送信、マイクを閉じる操作を行うことができます。
  5. ミドルウェア処理: クライアント ライブラリを使用して独自の会話サービス ミドルウェアを作成できます。これはデベロッパーが定義する関数で構成され、クライアント ライブラリによってインテント ハンドラが呼び出される前に自動的に実行されます。フルフィルメント Webhook はこのミドルウェアを使用して、プロパティやヘルパークラスを Conversation オブジェクトに追加できます。
  6. インテント ハンドラ処理: クライアント ライブラリを使用して、フルフィルメント Webhook によって認識されるインテント用のハンドラを定義できます。Dialogflow の場合、クライアント ライブラリは Dialogflow コンソールで定義されたインテント名の正確な文字列にマッピングすることで、リクエストを正しいインテント ハンドラに転送します。Actions SDK の場合は、Actions on Google から送信された intent プロパティに基づいてルーティングされます。
  7. ユーザーにレスポンスを送信する: レスポンスを作成するために、フルフィルメント Webhook で Conversation#ask() 関数を呼び出します。ask() 関数を複数回呼び出して、レスポンスを段階的に作成できます。レスポンスは、JSON ペイロードを含む HTTP リクエストにシリアル化されて Actions on Google に送信されます。close() 関数の動作は ask() と同様ですが、会話は終了します。

ローカル開発環境の設定

フルフィルメント Webhook を実装する前に、必ずクライアント ライブラリを最初にインストールしてください。

クライアント ライブラリをインストールする

クライアント ライブラリをローカル開発環境にインストールする最も簡単な方法は、npmyarn などのパッケージ マネージャーを使用することです。

インストールするには、ターミナルから次のいずれかのコマンドを実行します。

  • npm を使用する場合: npm install actions-on-google
  • yarn を使用する場合: yarn add actions-on-google

プロジェクト フォルダを準備する

フルフィルメント Webhook をデプロイする場所(Google の Cloud Functions for Firebase、AWS Lambda、自己ホスト型 Express)によっては、特定のプロジェクト フォルダ構造を作成してファイルを保存する必要があります。

たとえば、Cloud Functions for Firebase を使用している場合は、Node.js と Firebase CLI を設定する Firebase for Cloud Functions を初期化するで説明されている手順に沿って、必要なプロジェクト フォルダを設定できます。Cloud Functions for Firebase では通常、フルフィルメント Webhook を /functions/index.js ファイルに記述します。

app インスタンスを作成する

Actions on Google は、Dialogflow または Actions SDK を使用して会話型アクションを構築するか、スマートホーム アクションを構築するかに応じて、フルフィルメント Webhook とリクエストとレスポンスを交換するために特定のメッセージ形式を使用します。

これらの異なるリクエスト / レスポンス プロトコルを表すために、クライアント ライブラリには次の 3 つのサービス関数が用意されています。

会話 Webhook プロトコルは Dialogflow と Actions SDK の両方の会話サービスによって使用されますが、メッセージのラップ方法はそれぞれ異なります。

サービスを使用して app インスタンスを作成します。app インスタンスは、グローバルな状態と Webhook のフルフィルメント ロジックをカプセル化し、サービス固有のプロトコルを使用して Actions on Google とフルフィルメント間の通信を処理します。

app インスタンスのプロパティを構成し、そのメソッドを呼び出して、フルフィルメント Webhook の動作を指示できます。app インスタンスは、HTTP リクエストのハンドラとして JavaScript 関数を受け入れる Cloud Functions for Firebase などのサーバーレス コンピューティング環境に簡単に接続することもできます。

フルフィルメント Webhook で app インスタンスを作成する手順は次のとおりです。

  1. require() 関数を呼び出して actions-on-google モジュールをインポートし、必要なサービスを読み込みます。たとえば、次のスニペットは、dialogflow サービスと、レスポンスの作成に使用する一部の要素を読み込み、dialogflow という名前の定数に代入する方法を示しています。

    // Import the service function and various response classes
    const {
      dialogflow,
      actionssdk,
      Image,
      Table,
      Carousel,
    } = require('actions-on-google');

    ここで、actions-on-google はプロジェクト フォルダ内の package.json ファイルで指定されている依存関係を指します(この package.json ファイルの例をご覧ください)。

    app インスタンスを取得する際、必要に応じてリッチ レスポンス、ヘルパー インテント、使用する Actions on Google の機能を表すクラスを指定できます。読み込める有効なクラスの一覧については、会話レスポンス モジュールとヘルパー インテント モジュールのリファレンス ドキュメントをご覧ください。

  2. 読み込んだサービスを呼び出して、app インスタンスを作成します。たとえば、

    const app = dialogflow();
    です。

  3. 初期化時に app インスタンスを構成するには、サービスを呼び出すときに最初の引数として options オブジェクトを指定します。(詳しくは DialogflowOptions をご覧ください)。たとえば、次のスニペットは、{ debug: true } フラグを設定して、ユーザー リクエストまたはレスポンスからの未加工の JSON ペイロードをログに記録する方法を示しています。

const app = dialogflow({
  debug: true
});

イベントのハンドラを設定する

アクションとのユーザー インタラクションのライフサイクル中にクライアント ライブラリによって作成された Actions on Google 関連のイベントを処理するには、クライアント ライブラリを使用して、ユーザー リクエストを処理してレスポンスを返すハンドラを作成します。

クライアント ライブラリが認識する以下の主な種類のイベントについて、そのハンドラとして機能する関数を作成できます。

  • インテント イベント: インテントは、ユーザーが特定の機能を要求したときに Actions on Google がフルフィルメントに送信する一意の識別子です。Dialogflow を使用している場合、これは Dialogflow がユーザークエリを Dialogflow エージェント内のインテントと照合する処理に対応します。
  • エラーイベント: JavaScript またはクライアント ライブラリでエラーが発生した場合、app インスタンスの catch 関数を使用して、エラー例外を適切に処理できます。フルフィルメントに関係するすべてのエラーを処理するには、単一の catch 関数を実装する必要があります。
  • フォールバック イベント: フォールバック イベントは、Actions on Google が認識できないクエリをユーザーが送信したときに発生します。app インスタンスの fallback 関数を使用して、受信フルフィルメント リクエストに一致するインテント ハンドラがない場合にトリガーされる汎用のフォールバック ハンドラを登録できます。すべてのフォールバック イベントを処理する単一の fallback 関数を実装する必要があります。Dialogflow は他のどのインテントとも一致しない場合に特定のフォールバック インテントをトリガーすることができます。Dialogflow を使用している場合、そのフォールバック インテントに対応するインテント ハンドラを作成する必要があります。

ユーザーがアクションにリクエストを送信するたびに、app インスタンスは、その会話セッションを表す Conversation オブジェクトを作成します。このオブジェクトには、インテント ハンドラ関数で最初の関数引数として渡された conv 変数名を介してアクセスします。通常は、ハンドラ内で conv オブジェクトを使用してユーザーにレスポンスを送信します。

ユーザークエリには、アクションで抽出してレスポンスを絞り込むために使用できるパラメータを含めることもできます。

インテントのハンドラを設定する

インテントのハンドラを設定するには、app インスタンスの intent() 関数を呼び出します。たとえば、Dialogflow を使用している場合は、DialogflowApp#intent() 関数がこれに該当します。引数にインテント名を指定し、ハンドラ関数を提供します。

Dialogflow を使用している場合、エージェント内のすべてのインテントに対してハンドラを設定する必要はありません。その代わりに、Dialogflow に組み込まれたレスポンス ハンドラを利用して、独自のハンドラ関数を実装せずに自動的にインテントを処理できます。たとえば、デフォルトのウェルカム インテントは、この方法で Dialogflow に処理を委任できます。

次の例は、「greeting」インテントと「bye」インテントのインテント ハンドラを示します。これらの匿名ハンドラ関数は、conv 引数を受け取り、conv.ask() 関数を介して単純な文字列レスポンスをユーザーに返します。

app.intent('Default Welcome Intent', (conv) => {
  conv.ask('How are you?');
});

app.intent('bye', (conv) => {
  conv.close('See you later!');
});

close() 関数は、マイクを閉じて会話を終了する点を除き、ask() と似ています。

インテントのハンドラを作成する方法の詳細については、インテント ハンドラを作成するをご覧ください。

エラーイベントのハンドラを設定する

エラーのハンドラを設定するには、app インスタンスの catch() 関数を呼び出します。(たとえば、Dialogflow を使用している場合は、DialogflowApp#catch() 関数です)。

次の例は、エラーをコンソール出力に送信し、conv.ask() 関数によって単純な文字列レスポンスを返してユーザーにプロンプトを表示する単純な catch エラーハンドラを示しています。

app.catch((conv, error) => {
  console.error(error);
  conv.ask('I encountered a glitch. Can you say that again?');
});

フォールバック イベントのハンドラを設定する

フルフィルメントの受信リクエストに対してインテントが一致しない場合に汎用のフォールバック ハンドラを設定するには、app インスタンスの fallback() 関数を呼び出します。(たとえば、Dialogflow を使用している場合は、DialogflowApp#fallback() 関数です)。

次の例は、conv.ask() 関数で単純な文字列レスポンスを返し、ユーザーにプロンプトを表示するシンプルなフォールバック ハンドラです。

app.fallback((conv) => {
  conv.ask(`I couldn't understand. Can you say that again?`);
});

インテント ハンドラを作成する

このセクションでは、クライアント ライブラリを使用してインテント ハンドラを実装するときの一般的なユースケースを取り上げます。クライアント ライブラリがインテントをどのように照合するかについては、仕組みを理解するの「インテント ハンドラ処理」セクションをご覧ください。

パラメータとコンテキストにアクセスする

Dialogflow を使用している場合は、Dialogflow エージェントでパラメータコンテキストを定義して、状態情報を維持し、会話フローを制御できます。

パラメータは、重要な単語、フレーズ、またはユーザークエリの値を捕捉するのに役立ちます。Dialogflow は実行時にユーザークエリから対応するパラメータを抽出します。これらのパラメータ値をフルフィルメント Webhook で処理して、ユーザーに応答する方法を決定できます。

ユーザーがアクションにリクエストを送信するたびに、DialogflowApp インスタンスは、Dialogflow がリクエストから抽出したパラメータ値を表す parameters オブジェクトを作成します。このオブジェクトには params 変数名を介してアクセスします。

次のスニペットは、ユーザーがリクエストを送信したときに params オブジェクトの name プロパティにアクセスする方法を示しています。

app.intent('Default Welcome Intent', (conv, params) => {
  conv.ask(`How are you, ${params.name}?`);
});

同じことを行う別のスニペットを次に示します。中かっこ({})は JavaScript の分離を実行して、parameters オブジェクトから name プロパティを取得し、それをローカル変数として使用します。

app.intent('Default Welcome Intent', (conv, {name}) => {
  conv.ask(`How are you, ${name}?`);
});

次のスニペットでは、パラメータ名は full-name ですが、分割されて name というローカル変数に代入されています。

app.intent('Default Welcome Intent', (conv, {'full-name': name}) => {
  conv.ask(`How are you, ${name}?`);
});

コンテキストは Dialogflow の高度な機能です。コンテキストを使用すると、会話の状態、フロー、分岐を管理できます。クライアント ライブラリは、DialogflowConversation#contexts オブジェクトを介してコンテキストへのアクセスを提供します。次のスニペットは、フルフィルメント Webhook でプログラムでコンテキストを設定する方法と、コンテキスト オブジェクトを取得する方法を示しています。

app.intent('intent1', (conv) => {
  const lifespan = 5;
  const contextParameters = {
    color: 'red',
  };
  conv.contexts.set('context1', lifespan, contextParameters);
  // ...
  conv.ask('...');
});

app.intent('intent2', (conv) => {
  const context1 = conv.contexts.get('context1');
  const contextParameters = context1.parameters;
  // ...
  conv.ask('...');
});

app.intent('intent3', (conv) => {
  conv.contexts.delete('context1');
  // ...
  conv.ask('...');
});

ヘルパー インテントの結果にアクセスする

利便性のため、クライアント ライブラリには、アクションが頻繁にリクエストする一般的なタイプのユーザーデータをラップするヘルパー インテント クラスが用意されています。これには、Actions on Google のさまざまなヘルパー インテントの結果を表すクラスが含まれます。ヘルパー インテントは、会話を続けるためにユーザーからの入力が必要な場合に、「ユーザーに入力を求める」部分の処理を Google アシスタントに任せるときに使用します。

例: 確認ヘルパーの結果

確認ヘルパー インテントを使用すると、ユーザーにはい/いいえの確認を依頼し、その結果を得ることができます。次のスニペットは、確認ヘルパー インテントから返された結果に基づいて Webhook でレスポンスをカスタマイズする方法を示しています。より詳細な例については、Confirmation クラスのリファレンス ドキュメントをご覧ください。

// Create Dialogflow intent with `actions_intent_CONFIRMATION` event
app.intent('get_confirmation', (conv, input, confirmation) => {
  if (confirmation) {
    conv.close(`Great! I'm glad you want to do it!`);
  } else {
    conv.close(`That's okay. Let's not do it now.`);
  }
});

次のスニペットは、カルーセルに対するユーザーの入力に基づいてフルフィルメント Webhook がレスポンスをカスタマイズする方法を示しています。カルーセル コンポーネントを使用すると、ユーザーが選択できるオプションを提示できます。より詳細な例については、Carousel クラスのリファレンス ドキュメントをご覧ください。

app.intent('carousel', (conv) => {
  conv.ask('Which of these looks good?');
  conv.ask(new Carousel({
    items: {
      car: {
        title: 'Car',
        description: 'A four wheel vehicle',
        synonyms: ['automobile', 'vehicle'],
      },
      plane: {
        title: 'Plane',
        description: 'A flying machine',
        synonyms: ['aeroplane', 'jet'],
      }
    }
  }));
});

// Create Dialogflow intent with `actions_intent_OPTION` event
app.intent('get_carousel_option', (conv, input, option) => {
  if (option === 'one') {
    conv.close(`Number one is a great choice!`);
  } else {
    conv.close(`Number ${option} is a great choice!`);
  }
});

会話レスポンス オブジェクトを構成する

クライアント ライブラリには、アクションから送信できるリッチ レスポンスやマルチメディア要素を表す会話レスポンス クラスが用意されています。通常、これらのレスポンスや要素は、ユーザーが入力する必要がない場合に会話を続けることができます。

例: 画像

次のスニペットは、フルフィルメント Webhook がレスポンスで Image を送信する方法を示しています。これは、ライブラリによって自動的に BasicCard レスポンスに添付されます。

app.intent('Default Welcome Intent', (conv) => {
  conv.ask('Hi, how is it going?');
  conv.ask(`Here's a picture of a cat`);
  conv.ask(new Image({
    url: '/web/fundamentals/accessibility/semantics-builtin/imgs/160204193356-01-cat-500.jpg',
    alt: 'A cat',
  }));
});

非同期関数呼び出しを行う

Actions on Google Node.js クライアント ライブラリは、非同期プログラミング用に設計されています。インテント ハンドラは、フルフィルメント Webhook がレスポンスの生成を完了したときに解決される Promise を返すことができます。

次のスニペットは、フルフィルメント Webhook が「greeting」インテントを受け取った場合に非同期関数呼び出しを行い、Promise オブジェクトが返されてからメッセージで応答する方法を示します。このスニペットでは、外部 API 呼び出しの Promise が解決されて初めて、フルフィルメント Webhook から会話レスポンスが返されることが Promise によって保証されます。

この例では、天気データを取得するために実在しない API を使用しています。

/**
 * Make an external API call to get weather data.
 * @return {Promise<string>}
 */
const forecast = () => {
  // ...
};

app.intent('Default Welcome Intent', (conv) => {
  return forecast().then((weather) => {
    conv.ask('How are you?');
    conv.ask(`Today's weather is ${weather}.`);
  });
});

次の合理化されたコード スニペットも効果は同じですが、ECMA 2017(Node.js バージョン 8)で導入された async await 機能を使用しています。このコードを Cloud Functions for Firebase で使用するには、適切なバージョンの firebase-tools を使用して、正しい構成であることを確認してください。

app.intent('Default Welcome Intent', async (conv) => {
  const weather = await forecast();
  conv.ask('How are you?');
  conv.ask(`Today's weather is ${weather}.`);
});

会話データを保存する

クライアント ライブラリを使用すると、フルフィルメント Webhook で今後使用できるように会話のデータを保存できます。データ ストレージに使用できる主なオブジェクトは次のとおりです。

次のスニペットは、フルフィルメント Webhook が、定義した任意のプロパティ(someProperty)にデータを保存し、それを Conversation#user.storage オブジェクトに追加する方法を示しています。より詳細な例については、Conversation#user.storage クラスのリファレンス ドキュメントをご覧ください。

app.intent('Default Welcome Intent', (conv) => {
  conv.user.storage.someProperty = 'someValue';
  conv.ask('...');
});

Conversation#user オブジェクトを使用すると、文字列 ID や個人情報などのユーザーに関する情報を取得できます。conv.user.name.displayconv.user.email などの特定のフィールドでは、conv.ask(new Permission) を NAME に、conv.ask(new SignIn) を Google ログイン用にリクエストする必要があります。

const {Permission} = require('actions-on-google');
app.intent('Default Welcome Intent', (conv) => {
  if (conv.user.last.seen) {
    conv.ask('Welcome back! How are you?');
  } else {
    conv.ask('Nice to meet you! How are you doing?');
  }
});

app.intent('permission', (conv) => {
  conv.ask(new Permission({
    context: 'To greet you personally',
    permissions: 'NAME',
  }));
});

// Create Dialogflow intent with `actions_intent_PERMISSION` event
app.intent('get_permission', (conv, input, granted) => {
  if (granted) {
    conv.close(`Hi ${conv.user.name.display}!`);
  } else {
    // User did not grant permission
    conv.close(`Hello!`);
  }
});

ミドルウェアによるスケーリング

ミドルウェアを介してクライアント ライブラリを拡張できます。

ミドルウェア層はデベロッパーが定義する関数で構成され、クライアント ライブラリによってインテント ハンドラが呼び出される前に自動的に実行されます。ミドルウェア レイヤを使用すると、Conversation インスタンスを変更して機能を追加できます。

Dialogflow サービスと Actions SDK サービスは、Conversation インスタンスにプロパティまたはヘルパークラスを追加できる app.middleware() 関数を公開しています。

次のスニペットは、ミドルウェアの使用方法の例を示します。

class Helper {
  constructor(conv) {
    this.conv = conv;
  }

  func1() {
    this.conv.ask(`What's up?`);
  }
}

app.middleware((conv) => {
  conv.helper = new Helper(conv);
});

app.intent('Default Welcome Intent', (conv) => {
  conv.helper.func1();
});

アプリをエクスポートする

ウェブ フレームワークまたはサーバーレス コンピューティング プラットフォームにフルフィルメント Webhook を公開するには、app オブジェクトを一般公開されている Webhook としてエクスポートする必要があります。クライアント ライブラリは、難しい設定なしに、さまざまな環境へのデプロイをサポートしています。

次のスニペットは、さまざまなランタイム内で app をエクスポートする方法を示しています。

例: Cloud Functions for Firebase

const functions = require('firebase-functions');
// ... app code here
exports.fulfillment = functions.https.onRequest(app);

例: Dialogflow インライン エディタ

const functions = require('firebase-functions');

// ... app code here

// Exported function name must be 'dialogflowFirebaseFulfillment'
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

例: 自己ホスト型 Express サーバー(単純)

const express = require('express');
const bodyParser = require('body-parser');  

// ... app code here

express().use(bodyParser.json(), app).listen(3000);

例: 自己ホスト型 Express サーバー(複数のルート)

const express = require('express');
const bodyParser = require('body-parser');

// ... app code here

const expressApp = express().use(bodyParser.json());

expressApp.post('/fulfillment', app);

expressApp.listen(3000);

例: AWS Lambda API ゲートウェイ

// ... app code here

exports.fulfillment = app;