Webhook

アクションをより柔軟に作成するには、ロジックを HTTPS ウェブサービス(フルフィルメント)に委任します。アクションは、HTTPS エンドポイントにリクエストを行う Webhook をトリガーできます。フルフィルメントで可能なことの例は次のとおりです。

  • ユーザーから提供された情報に基づいて動的プロンプトを生成する。
  • 外部システムでの注文と成功の確認。
  • バックエンド データでスロットを検証する。
図 1. 呼び出しインテントとシーンは Webhook をトリガーできます。

Webhook トリガーとハンドラ

アクションは、呼び出しインテントまたはシーン内で Webhook をトリガーし、フルフィルメント エンドポイントにリクエストを送信します。フルフィルメントには、リクエストの JSON ペイロードを処理する Webhook ハンドラが含まれています。Webhook は、次のような状況でトリガーできます。

  • 呼び出しインテントが一致した後
  • シーンの開始ステージ
  • シーンの条件ステージで条件が true と評価された後
  • シーンのスロットフィリング ステージ
  • シーンの入力ステージでインテント マッチングが発生した後

アクションで Webhook をトリガーすると、Google アシスタントは JSON ペイロードを含むリクエストをフルフィルメントに送信します。ここには、イベントの処理に使用するハンドラ名が含まれます。フルフィルメント エンドポイントは、ロジックを実行するための適切なハンドラにイベントをルーティングし、対応する JSON ペイロードを含むレスポンスを返すことができます。

ペイロード

次のスニペットは、アクションがフルフィルメントに送信するリクエストとフルフィルメントから返されるレスポンスの例を示しています。詳しくは、リファレンス ドキュメントをご覧ください。

リクエストの例

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "actions.intent.MAIN",
    "params": {},
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {}
  },
  "session": {
    "id": "example_session_id",
    "params": {},
    "typeOverrides": []
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
    }
  },
  "home": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}

レスポンスの例

{
  "session": {
    "id": "example_session_id",
    "params": {}
  },
  "prompt": {
    "override": false,
    "firstSimple": {
      "speech": "Hello World.",
      "text": ""
    }
  },
  "scene": {
    "name": "SceneName",
    "slots": {},
    "next": {
      "name": "actions.scene.END_CONVERSATION"
    }
  }
}

ランタイム インタラクション

以下のセクションでは、Webhook ハンドラで実行できる一般的なタスクについて説明します。

メッセージを送る

シンプルなテキスト、リッチテキスト、カードを使用したプロンプトを作成できます。さらに、Interactive Canvas を使用して、ウェブアプリを基盤とした本格的な HTML プロンプトも作成できます。プロンプトのドキュメントには、Webhook イベントの処理時にプロンプトを作成する方法が記載されています。次のスニペットは、カード プロンプトを示しています。

Node.js

app.handle('rich_response', conv => {
  conv.add('This is a card rich response.');
  conv.add(new Card({
    title: 'Card Title',
    subtitle: 'Card Subtitle',
    text: 'Card Content',
    image: new Image({
      url: 'https://developers.google.com/assistant/assistant_96.png',
      alt: 'Google Assistant logo'
    })
  }));
});

レスポンス JSON

{
  "session": {
    "id": "example_session_id",
    "params": {}
  },
  "prompt": {
    "override": false,
    "content": {
      "card": {
        "title": "Card Title",
        "subtitle": "Card Subtitle",
        "text": "Card Content",
        "image": {
          "alt": "Google Assistant logo",
          "height": 0,
          "url": "https://developers.google.com/assistant/assistant_96.png",
          "width": 0
        }
      }
    },
    "firstSimple": {
      "speech": "This is a card rich response.",
      "text": ""
    }
  }
}

インテント パラメータを読み取る

アシスタントのランタイムはインテントを照合すると、定義済みのパラメータを抽出します。元のプロパティはユーザーが入力として指定したものであり、解決済みのプロパティは、型指定に基づいて NLU が入力を解決したものです。

Node.js

conv.intent.params['param_name'].original
conv.intent.params['param_name'].resolved

JSON をリクエストする

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "intent_name",
    "params": {
      "slot_name": {
        "original": "1",
        "resolved": 1
      }
    },
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {},
    "next": {
      "name": "actions.scene.END_CONVERSATION"
    }
  },
  "session": {
    "id": "session_id",
    "params": {},
    "typeOverrides": []
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
    }
  },
  "home": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}

ユーザー ロケールの読み取り

この値は、Google アシスタントにおけるユーザーの言語 / 地域の設定に対応します。

Node.js

conv.user.locale

JSON

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "actions.intent.MAIN",
    "params": {},
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {}
  },
  "session": {
    "id": "session_id",
    "params": {},
    "typeOverrides": []
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
    }
  },
  "home": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}

読み取りと書き込みのストレージ

さまざまなストレージ機能の使用方法については、ストレージのドキュメントをご覧ください。

Node.js

//read
conv.session.params.key
conv.user.params.key
conv.home.params.key

// write
conv.session.params.key = value
conv.user.params.key = value
conv.home.params.key = value 

JSON をリクエストする

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "actions.intent.MAIN",
    "params": {},
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {}
  },
  "session": {
    "id": "session_id",
    "params": {
      "key": "value"
    },
    "typeOverrides": [],
    "languageCode": ""
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED",
      "key": "value"
    }
  },
  "home": {
    "params": {
      "key": "value"
    }
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}

レスポンス JSON

{
  "session": {
    "id": "session_id",
    "params": {
      "key": "value"
    }
  },
  "prompt": {
    "override": false,
    "firstSimple": {
      "speech": "Hello world.",
      "text": ""
    }
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED",
      "key": "value"
    }
  },
  "home": {
    "params": {
      "key": "value"
    }
  }
}

デバイスの機能を確認する

さまざまなエクスペリエンスや会話フローを提供するデバイスの機能を確認できます。

Node.js

const supportsRichResponse = conv.device.capabilities.includes("RICH_RESPONSE");
const supportsLongFormAudio = conv.device.capabilities.includes("LONG_FORM_AUDIO");
const supportsSpeech = conv.device.capabilities.includes("SPEECH");
const supportsInteractiveCanvas = conv.device.capabilities.includes("INTERACTIVE_CANVAS");

JSON をリクエストする

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "actions.intent.MAIN",
    "params": {},
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {}
  },
  "session": {
    "id": "session_id",
    "params": {},
    "typeOverrides": [],
    "languageCode": ""
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
    }
  },
  "home": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO",
      "INTERACTIVE_CANVAS"
    ]
  }
}

サーフェス機能の一覧については、Capability のリファレンスをご覧ください。

ランタイム タイプのオーバーライド

ランタイム タイプを使用すると、実行時にタイプ仕様を変更できます。この機能を使用すると、他のソースからデータを読み込んで、型の有効な値を入力できます。たとえば、ランタイム タイプのオーバーライドを使用して、アンケートの質問に動的オプションを追加したり、メニューに 1 日の項目を追加したりできます。

ランタイム タイプを使用するには、フルフィルメントのハンドラを呼び出す Webhook をアクションからトリガーします。そこから、アクションへのレスポンスの session.typeOverrides パラメータを入力できます。使用可能なモードには、既存のタイプエントリを保持する TYPE_MERGE と、既存のエントリをオーバーライドに置き換える TYPE_REPLACE があります。

Node.js

conv.session.typeOverrides = [{
    name: type_name,
    mode: 'TYPE_REPLACE',
    synonym: {
      entries: [
        {
          name: 'ITEM_1',
          synonyms: ['Item 1', 'First item']
        },
        {
          name: 'ITEM_2',
          synonyms: ['Item 2', 'Second item']
       },
       {
          name: 'ITEM_3',
          synonyms: ['Item 3', 'Third item']
        },
        {
          name: 'ITEM_4',
          synonyms: ['Item 4', 'Fourth item']
        },
    ]
  }
}];

レスポンス JSON

{
  "session": {
    "id": "session_id",
    "params": {},
    "typeOverrides": [
      {
        "name": "type_name",
        "synonym": {
          "entries": [
            {
              "name": "ITEM_1",
              "synonyms": [
                "Item 1",
                "First item"
              ]
            },
            {
              "name": "ITEM_2",
              "synonyms": [
                "Item 2",
                "Second item"
              ]
            },
            {
              "name": "ITEM_3",
              "synonyms": [
                "Item 3",
                "Third item"
              ]
            },
            {
              "name": "ITEM_4",
              "synonyms": [
                "Item 4",
                "Fourth item"
              ]
            }
          ]
        },
        "typeOverrideMode": "TYPE_REPLACE"
      }
    ]
  },
  "prompt": {
    "override": false,
    "firstSimple": {
      "speech": "This is an example prompt.",
      "text": "This is an example prompt."
    }
  }
}

音声のバイアスを設定する

音声バイアスを使用すると、NLU にヒントを指定してインテント マッチングを改善できます。最大 1,000 個のエントリを指定できます。

Node.js

conv.expected.speech = ['value_1', 'value_2']
conv.expected.language = 'locale_string'

レスポンス JSON

{
  "session": {
    "id": "session_id",
    "params": {}
  },
  "prompt": {
    "override": false,
    "firstSimple": {
      "speech": "This is an example prompt.",
      "text": "This is an example prompt."
    }
  },
  "expected": {
    "speech": "['value_1', 'value_2']",
    "language": "locale_string"
  }
}

転換シーン

Actions プロジェクトで静的遷移を定義するだけでなく、実行時にシーン遷移を発生させることもできます。

Node.js

app.handle('transition_to_hidden_scene', conv => {
  // Dynamic transition
  conv.scene.next.name = "HiddenScene";
});

レスポンス JSON

{
  "session": {
    "id": "session_id",
    "params": {}
  },
  "prompt": {
    "override": false,
    "firstSimple": {
      "speech": "This is an example prompt.",
      "text": ""
    }
  },
  "scene": {
    "name": "SceneName",
    "slots": {},
    "next": {
      "name": "HiddenScene"
    }
  }
}

シーンスロットの読み取り

スロット入力の際は、フルフィルメントを使用して、スロットの検証やスロットフィルのステータスの確認(SlotFillingStatus)を行うことができます。

Node.js

conv.scene.slotFillingStatus  // FINAL means all slots are filled
conv.scene.slots  // Object that contains all the slots
conv.scene.slots['slot_name'].<property_name> // Accessing a specific slot's properties

たとえば、レスポンスからタイムゾーンを抽出するとします。この例のスロット名は datetime1 です。タイムゾーンを取得するには、次のコマンドを使用します。

conv.scene.slots['datetime1'].value.time_zone.id

JSON をリクエストする

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "",
    "params": {
      "slot_name": {
        "original": "1",
        "resolved": 1
      }
    },
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "FINAL",
    "slots": {
      "slot_name": {
        "mode": "REQUIRED",
        "status": "SLOT_UNSPECIFIED",
        "updated": true,
        "value": 1
      }
    },
    "next": {
      "name": "actions.scene.END_CONVERSATION"
    }
  },
  "session": {
    "id": "session_id",
    "params": {
      "slot_name": 1
    },
    "typeOverrides": []
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
    }
  },
  "home": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}

シーンスロットを無効にする

スロットを無効にして、ユーザーに新しい値を提供してもらうことができます。

Node.js

conv.scene.slots['slot_name'].status = 'INVALID'

レスポンス JSON

{
  "session": {
    "id": "session_id",
    "params": {
      "slot_name": 1
    }
  },
  "prompt": {
    "override": false,
    "firstSimple": {
      "speech": "This is an example prompt.",
      "text": ""
    }
  },
  "scene": {
    "name": "SceneName",
    "slots": {
      "slot_name": {
        "mode": "REQUIRED",
        "status": "INVALID",
        "updated": true,
        "value": 1
      }
    },
    "next": {
      "name": "actions.scene.END_CONVERSATION"
    }
  }
}

開発オプション

Actions Builder には、Cloud Functions エディタというインライン エディタが用意されています。このエディタを使用すると、コンソールで直接 Cloud Functions for Firebase をビルドしてデプロイできます。フルフィルメントをビルドして任意のホスティングにデプロイし、HTTPS フルフィルメント エンドポイントを Webhook ハンドラとして登録することもできます。

インライン エディタ

Cloud Functions エディタを使用して開発するには:

  1. sdk/webhooks/ActionsOnGoogleFulfillment.yaml ファイルを作成し、アクションのハンドラと、フルフィルメントに使用するインライン クラウド関数を定義します。
    handlers:
    - name: questionOnEnterFunc
    - name: fruitSlotValidationFunc
    inlineCloudFunction:
      executeFunction: ActionsOnGoogleFulfillment
        
  2. sdk/webhooks/ActionsOnGoogleFulfillment フォルダを作成し、以前に定義したハンドラを実装する index.js ファイルと、コードの npm 要件を定義する package.json ファイルを追加します。
    // index.js
    const {conversation} = require('@assistant/conversation');
    const functions = require('firebase-functions');
    
    const app = conversation();
    
    app.handle('questionOnEnterFunc', conv => {
      conv.add('questionOnEnterFunc triggered on webhook');
    });
    
    app.handle('fruitSlotValidationFunc', conv => {
      conv.add('fruitSlotValidationFunc triggered on webhook');
    });
    
    exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
        
    // package.json
    {
      "name": "ActionsOnGoogleFulfillment",
      "version": "0.1.0",
      "description": "Actions on Google fulfillment",
      "main": "index.js",
      "dependencies": {
        "@assistant/conversation": "^3.0.0",
        "firebase-admin": "^5.4.3",
        "firebase-functions": "^0.7.1"
      }
    }
        

外部 HTTPS エンドポイント

このセクションでは、会話型アクションのフルフィルメント サービスとして Cloud Functions for Firebase を設定する方法について説明します。フルフィルメントは、任意のホスティング サービスにデプロイできます。

環境を設定する

Cloud Functions for Firebase をフルフィルメント サービスとして使用する場合は、次のプロジェクト構造を使用することをおすすめします。

ProjectFolder        - Root folder for the project
  sdk                - Actions project configuration files
  functions          - Cloud functions for Firebase files

環境の設定手順は次のとおりです。

  1. Node.js をダウンロードしてインストールします
  2. Firebase CLI を設定して初期化します。次のコマンドが EACCES エラーで失敗した場合は、npm アクセス権の変更が必要になることがあります。

    npm install -g firebase-tools
    
  3. Google アカウントを使用して Firebase ツールを認証します。

    firebase login
    
  4. Actions プロジェクトを保存したプロジェクト ディレクトリを起動します。Actions プロジェクトに設定する Firebase CLI 機能を選択するよう求められます。Functions と、Firestore などの他の使用する機能を選択し、Enter キーを押して確定して続行します。

    $ cd <ACTIONS_PROJECT_DIRECTORY>
    $ firebase init
    
  5. 矢印キーを使用してプロジェクト リストを移動し、Firebase ツールを Actions プロジェクトに関連付けます。

  6. プロジェクトを選択すると、Firebase ツールによって Functions の設定が開始され、使用する言語を尋ねられます。矢印キーを使用して選択し、Enter キーを押して続行します。

    === Functions Setup
    A functions directory will be created in your project with a Node.js
    package pre-configured. Functions can be deployed with firebase deploy.
    
    ? What language would you like to use to write Cloud Functions? (Use arrow keys)
    > JavaScript
    TypeScript
    
  7. Y」または「N」を入力して、ESLint を使用してバグの疑いを捕捉し、スタイルを適用するかどうかを選択します。

    ? Do you want to use ESLint to catch probable bugs and enforce style? (Y/n)
  8. プロンプトに「Y」と入力して、プロジェクトの依存関係を取得します。

    ? Do you want to install dependencies with npm now? (Y/n)

    設定が完了すると、次のような出力が表示されます。

    ✔  Firebase initialization complete!
    
  9. @assistant/conversation 依存関係をインストールします。

    $ cd <ACTIONS_PROJECT_DIRECTORY>/functions
    $ npm install @assistant/conversation --save
    
  10. フルフィルメント依存関係を取得し、フルフィルメント関数をデプロイします。

    $ npm install
    $ firebase deploy --only functions
    

    デプロイには数分かかります。完了すると、次のような出力が表示されます。Dialogflow にアクセスするには、関数の URL が必要です。

    ✔  Deploy complete!
    Project Console: https://console.firebase.google.com/project/<PROJECT_ID>/overview Function URL (<FUNCTION_NAME>): https://us-central1-<PROJECT_ID>.cloudfunctions.net/<FUNCTION_NAME>
  11. フルフィルメント URL をコピーして、次のセクションで使用します。

Webhook ハンドラを登録

  1. ファイル sdk/webhooks/ActionsOnGoogleFulfillment.yaml を作成し、アクションのハンドラと Webhook リクエストの URL を定義します。
    httpsEndpoint:
      baseUrl: https://my.web.hook/ActionsOnGoogleFulfillment
      endpointApiVersion: 2
    handlers:
    - name: questionOnEnterFunc
    - name: fruitSlotValidationFunc