note_記事見出し_googlehome_1280_670___6

Google Home, Nest Hub向けにアクションを作ろう - 第6話: Webhookの作成

前回までで、Google Homeが会話を受ける部分を全部作ってきました、このこの回からいよいよ、返答を作る部分にはいります。

第2回で説明しましたが

Webhook ≒ Firebase Functions

ですので、Firebase Functionsの開発環境をセットアップするところから始めたいと思いますが、まず後々の回のためにIntereactive Canvasの機能を有効にします。

Interactive Canvas機能を有効にする

Interactive Canvasとは、Google Nest Hub以降のディスプレイを持ったスマートスピーカーで、ディスプレイにグラフィカルユーザーインターフェースを表示するための仕組みです。

この機能を使うと、ちょうどソフトバンクロボティクスのPepperが話しながら胸についたタッチパネルに絵やボタンを出したりするように、
Google Assitantも話しながら、ディスプレイに写真やボタンなどのグラフィカルなユーザーインターフェースを表示することができます。

このマガジンでは、後の回でこのInteractive Canvasの開発方法を紹介するため、まず、この時点でInteractive Canvasが使えるプロジェクトを作っておきましょう。

まず、Actions on Google コンソールに行きプロジェクトを選択します。

https://console.actions.google.com

DeployタブからDirectory Informationに移動し、

スクリーンショット 2020-01-31 11.07.30

Categoryから、Games & funを選びます。

そして、このページを下にスクロールしていき、Interactive Canvasの下のYesのところのチェックマークをマークします。

スクリーンショット 2020-01-31 11.07.50

(2020年1月現在、Interactive CanvasはまだGame & funカテゴリーのアクションしか認められていません。他のカテゴリーでもInteractive Canvasが使えるようになるまでもう少し待つ必要があるかもしれません。)

Firebase Command-line Interfaceのインストール

次にFirebaseのコマンドが使えるように、下のコマンドを実行してFirebase Command-line Interfaceをインストールします。

npm -g install firebase-tools

(npmのパーミション設定によっては、sudoをつける場合もあります)
npm(Node.js)のインストールがされていない方は、こちらからインストールしてから上のコマンドを実行してみてください。)

Firebase Functionsプロジェクトの作成

まず、自分のコンピューター上にプロジェクトのディレクトリを作成し、そのディレクトリの下に移動します(以下Mac OSのターミナル(bash)でのコマンド例です)

$ mkdir parrot
$ cd parrot

次に、firebase initを実行して、プロジェクトを作成します。

$ firebase init

すると下のようどのFirebaseの機能を使ったプロジェクトを作るのか聞かれるので、

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm you
r choices. (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◯ Database: Deploy Firebase Realtime Database Rules
◯ Firestore: Deploy rules and create indexes for Firestore
◯ Functions: Configure and deploy Cloud Functions
◯ Hosting: Configure and deploy Firebase Hosting sites
◯ Storage: Deploy Cloud Storage security rules
◯ Emulators: Set up local emulators for Firebase features

FunctionsHostingのところでSpaceキーを押して選択し、Enterキーで決定をします。

スクリーンショット 2020-01-31 11.36.48

非常にざっくり説明すると、

Functions:  node.jsのweb APIを作る機能
Hosting:  htmlやcssなどのWebページのアセットをホスティングする機能

です。

次に

? Please select an option: (Use arrow keys)

と聞かれるので、Use an existing project を選択します。

スクリーンショット 2020-01-31 11.41.09

次に、

? Select a default Firebase project for this directory:

と聞かれるので、Actions on Googleコンソールで作ったプロジェクト名(私の場合はparrot)を選択してください。その後、

? What language would you like to use to write Cloud Functions?

と聞かれるので、Java Scriptを選択します。

スクリーンショット 2020-01-31 11.54.38

? Do you want to use ESLint to catch probable bugs and enforce style?

は"N"(NO)を選択します。

? Do you want to install dependencies with npm now? 

は"Y"(YES)を選択します。

これで、Firebase FunctionsとFirebase Hostingのプロジェクトの構成が作られ、必要なライブラリ(node_modules)がインストールされます。

? What do you want to use as your public directory?

にはpublicと入力します(publicディレクトリをFirebase Hostingにディプロイするもの全部を含んだフォルダとして使うという意味。)

Configure as a single-page app (rewrite all urls to /index.html)?

には"N" を選択します。

最終的には下のようなフォルダ構成ができました。

parrot
├── firebase.json
├── functions
│   ├── index.js
│   ├── node_modules
│   ├── package-lock.json
│   └── package.json
└── public
   ├── 404.html
   └── index.html

次に下のコマンドで、ライブラリをインストールします。

$ cd functions/
$ npm install actions-on-google --save

ここまででWebhookを作成するためのプロジェクトの構成ができましたので、いよいよコーディングに移ります。

index.jsのコーディング

index.jsを下のコードで置き換えて見てください。(今後のコーディングは全部このindex.js上で行います。)

// Actions on Google client libraryから、Dialogflowモジュールをインポートする
const {dialogflow} = require('actions-on-google');

// firebase-functions packageをインポートする
const functions = require('firebase-functions');

// Dialogflow clientインスタンスを作る
const app = dialogflow({debug: true});

// 'what_is_this'というIntentのハンドラーの実装
app.intent('what_is_this', (conv, {animal, fish, any}) => {

 

});

// DialogflowApp オブジェクトをHTTPS POSTリクエストのハンドラーとして登録
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

これがWebhookを作成する上での雛形になります。
このコードの中程に、下のような部分があります。

// 'what_is_this'というIntentのハンドラーの実装
app.intent('what_is_this', (conv, {animal, fish, any}) => {

 

});

ここは、"what_is_this"というインテントからのリクエストに対応するハンドラー(処理を請け負うコード)を登録する部分です。

app.intent('what_is_this',という部分でwhat_is_thisインテントに対してハンドラーを登録しているのがわかります。このインテントの名前のスペルを間違えると上手く動作しないので注意しましょう。

次に、このハンドラーが受け取るパラメターに注目してください。

(conv, {animal, fish, any}

convはconversationの略で会話に関する情報、関数が詰まったオブジェクトです。

{animal, fish, any}はDialogFlowでEntityとして定義した部分の値が入ってくるパラメーターです。

前回以下のように、Training phrasesにEntityを割り付けた際、その下のAction and parametersに、各Entityに対応したパラメーターが生成されていたのに気づいた人がいるかもしれません。

スクリーンショット 2020-01-26 10.54.11

Action and parametersの下に、fish, animal, anyというパラメーターができているのがわかると思います。

これらのパラメーターがWebhookに送られてくるので、それをキャッチするのが、

(conv, {animal, fish, any}

{animal, fish, any}の部分です。

パラメーターを使って返答を作る

それでは、これらのパラメータを使ってハンドラーの中身を実装していきます。

以下のようなコードを書いてみてください。

app.intent('what_is_this', (conv, {animal, fish, any}) => {
   if (animal) {
       conv.close(animal + 'は動物ですね');
   }else if (fish) {
       conv.close(fish + 'は魚ですね');
   }else {
       conv.close(any + 'は動物でも魚でもありません');
   }
});

Entity = 知りたいキーワード

と説明しましたが、Entityはユーザーの会話の中身からこちらが知りたいキーワードを抽出するためのものです。

前回作成したEntityとTraining phrasesによって、

ユーガーが動物の名前(犬、ネコ、熊)を言ったらanimalに、
魚の名前(マグロ、鯵、サメ)を言っていたら、fishに、
そうでないもの(バケツなど)を言ったら、anyに
ユーザーの言った単語が入ってくるようになっています。

ハンドラーでは、これらのパラメメーターを1個1個見ていき、動物が入っていたら、「(animal)は動物ですね。」
魚が入っていたら「(fish)は魚ですね。」
それ以外が入っていたら「(any)は動物でも魚でもありません。」

という返事を返すようにします。

返事は、

conv.close(animal + 'は動物ですね');

という部分で、convオブジェクトのcloseという関数を呼び出して行っています。

このcloseという関数は、パラメーターで渡された文字列を読み上げてアプリをクローズするという関数です。

この関数を呼び出すとGoogle Homeは「犬は動物ですね。」と喋った後アプリを終了し、通常の状態にもどります。

(今回は返事をしたあとにアプリが終了するシンプルな例を説明しますが、次回以降は会話を続けるための返答conv.ask()関数を使った説明をします。)

Firebase Functionsのディプロイ

これでfunctionsが完成しましたので、ディプロイ作業にはいります。

functionsディレクトリの中に、移動します。

cd functions

functionsディレクトリの中でいかのコマンドを実行して、functionsをFirebaseのサーバーにディプロイします。

 firebase deploy

これで今書いたコードがサーバー(Firebase)に反映されました。

ディプロイしたFirebase FunctionsのURLを取得する。

Firebase Consoleをひらきます。

先程ディプロイしたparrotプロジェクトがあるはずなので、開きます。

左の開発メニューのなかに、Functionsというメニューがあので、そこをクリックします。
ダッシュボードタブの下に"dialogflowFirebaseFulfillment"という項目があり、URLが表示されていますので、このURLをコピーします。

画像3

このURLがWebhook URLです。

Webhook URLをDialogflowにセットする

先程まで作業していたDialogflowコンソール を再び開きます。

メニューからFulfillmentに移動し、Webhookの横のスイッチをDISABLEDから、ENABLEDにし、下のようにURLの欄に、先程Firebase FunctionsからコピーしてきたWebhook URLをペーストし、SAVEボタンを押して保存しましょう。

画像4

(SAVEボタンを押し忘れないように!)

第4話のテストの章にしたがって、DialogflowのIntegrationsのページからシミュレーターを起動してテストしてみましょう。

下のように「テスト用アプリにつないで」と呼びかけ、そのあとに、

「犬はなんですか?」

と呼びかけて見てください。下のように「イヌは動物ですね」という返答が返ってきたら、成功です!

スクリーンショット 2020-01-24 20.38.12

ほかにも

イヌって何?

サメってなんですか?

マグロって何?

熊ってなんですか?

などTraining phrasesEntityで受け皿を作ったパターンを試して返答を確認してみましょう。

fishに登録した魚について聞くと下のような回答が返ってくると思います。

スクリーンショット 2020-01-24 20.45.59

fish Entityにも、animal Entityにも登録しなかったものについて聞くと、下のような返答が返ってくると思います。

スクリーンショット 2020-01-24 20.45.30

これで、

Intent -> Entity -> Training phrasesで定義したものが最終的にどのように返答として返ってくるのか一連の流れがわかったと思います。

(まだ???な人も焦らずに。私は、アクションを3個くらい自分で完成さえてみるまで、Intent, Entity, Training phrasesの関係性がわかりませんでした。ここはActions on Googleを理解する上で最も難解な部分だと思います。今の時点でIntent -> Entity -> Training phrasesに定義したものと、Firebase Functionsでの返答の作成のフローが1つの道として見えなくても、この後に会話を改良して行く過程で何度もこのフローの間を行き来している間にだんだん見えてくると思います。)

Webhook開発のフロー

ここまでの過程では、

DialogFlowでIntentの作成 → 手元でコーディング → FirebaseでURLの確認 → DialogFlowにWebhook URLを登録 → Actions on Googleのシミュレーターでテスト

といろいろな場所をぐるぐると循環するので混乱した方もいると思います。しかし、一度Webhook URLをDialog Flowに登録してしまえば、

1. 手元でコーディング -> Firebaseにディプロイ

2. DialogFlowのIntegrationsからActions On Googleのシミュレーターを起動して動作確認

という作業ワークフローだけになるので、以降の作業はシンプルになって行きます。

本日も非常に長いセッション、お疲れさまでした。

こちらが最終的なindex.jsのコードです。、答え合わせとしてお使いください。

// Actions on Google client libraryから、Dialogflowモジュールをインポートする
const {dialogflow} = require('actions-on-google');

// firebase-functions packageをインポートする
const functions = require('firebase-functions');

// Dialogflow clientインスタンスを作る
const app = dialogflow({debug: true});

// 'what_is_this'というIntentのハンドラーの実装
app.intent('what_is_this', (conv, {animal, fish, any}) => {
   if (animal) {
       conv.close(animal + 'は動物ですね');
   }else if (fish) {
       conv.close(fish + 'は魚ですね');
   }else {
       conv.close(any + 'は動物でも魚でもありません');
   }
});

// DialogflowApp オブジェクトをHTTPS POSTリクエストのハンドラーとして登録
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);


本日やったステップ

Firebase Command Line Interfaceのインストール

Firebase Functionsプロジェクトの作成

npm installでnodeモジュールのインストール

index.jsで、what_is_thisインテントのハンドラーの実装

インテントのパラメーターに応じた返答を作る

返答はconvオブジェクトに対しておこなう。

conv.closeは返答+アプリのクローズ

Firebase Functionsをfirebase deployを使ってFirebaseにディプロイ

ディプロイ後に、Webhook URLを取得

Webhook URLをDialog Flowに登録

Dialog FlowからActions On Googleシミュレーターを起動して動作確認

ここからは体力が残っている人が、参考までに読んでいただければと思います。

[おまけ] Dialog FlowとFirebase Functionsはどこで動いているのか?

Dialog Flowで定義したIntent, Entitiesなどは、Dialog Flowの自分のアカウントのプロジェクトの中にJSONなどの何らかのデータの形で存在しています。

一方ディプロイされたFirebase Functionsは、Firebaseの中の自分のアカウントの中のプロジェクトの中にnode.jsのプログラムとして展開されています。言い方を変えるとnode.jsの上でうごくJavascriptのAPIとして存在しています。

画像9

この2つの実行体は、概念的にも、サービス的にも、物理的にも違う場所、違うサーバー上で動いており、Webhook URLを介して相互にやり取りしていると考えてください。

Dialog Flowから、Firebase Functionsを見つけるためのURLがWebhook URLなのです。


[おまけ2]

このマガジンの最後にInteractive Canvasという仕組みを使って、Google Nest Hub向けにグラフィカルユーザーインターフェースをもったアクションを作るため、今回はfirebase initのあと、Firebase Hostingの機能にチェックマークをつけ、Firebase Hostingにウェブページを上げるためのプロジェクト構造を作成しました。

Interactive Canvasを使わず、音声のみのアクションを作るだけで十分な場合は、firebase initでプロジェクトを作る際に、Firebase Hostingにチェックせず、Firebase Functionsだけを選択してプロジェクトを作るだけでも十分です。その場合はディレクトリ構成は以下のようになります。

parrot
├── firebase.json
└── functions
   ├── index.js
   ├── node_modules
   ├── package-lock.json
   └── package.json


このブログに関する質問やActions on Googleの開発の相談はこちらから↓↓↓

@mizutory
mizutori@goldrushcomputing.com

次回から本格的なアクションのプロジェクトの制作を始めます!↓↓↓





この記事が気に入ったらサポートをしてみませんか?