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 リファレンスをご覧ください。

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

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

ランタイム タイプを使用するには、フルフィルメントのハンドラを呼び出すアクションから Webhook をトリガーします。そこから、Action に戻すレスポンスの 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 ファイルを作成し、アクションのハンドラと、フルフィルメントに使用されるインライン Cloud Functions の関数を定義します。
    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 プロジェクトに設定する Firebase CLI 機能を選択するように求められます。Functions および他に使用する機能(Firestore など)を選択してから Enter キーを押して確定し、続行します。

    $ cd <ACTIONS_PROJECT_DIRECTORY>
    $ firebase init
    
  5. Firebase ツールを Actions プロジェクトに関連付けます。これには、矢印キーを使ってプロジェクト リストを移動して、該当する 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. 潜在的なバグの捕捉とスタイルの適用に ESLint を使用するかどうかを、「Y」または「N」を入力して選択します。

    ? 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 にアクセスするには、Function 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 ファイルを作成し、Action のハンドラと Webhook リクエストの URL を定義します。
    httpsEndpoint:
      baseUrl: https://my.web.hook/ActionsOnGoogleFulfillment
      endpointApiVersion: 2
    handlers:
    - name: questionOnEnterFunc
    - name: fruitSlotValidationFunc