AmazonEcho/GoogleHome どちらにもサービスを提供する為のあれこれ
はじめに
Amazon Echo の一般販売が始まりましたね。皆さんもう手に入れましたか? Google Home も良いですよね。皆さんは何かサービス作られましたか?
どちらもお安く手に入るので Amazon Echo で動くサービスを作ったら、「あ、これ Google Home版も公開したい」ってなるのが人の性ですよね?
実際立ててみると結構似通ってる部分と多少違う部分があります。というわけで Amazon Echo と Google Home の間でサービスを移植、もしくは両視野に入れてサービスを作成するにあたって知っておいた方が良い事を比較しつつつらつらと書いてゆきます。
アプリケーションの作成・設定まわり
Alexa
- Amazon デベロッパーコンソール上でインテント・スロットを設定
- バックエンドのサービスは AWS Lambda で作成
- サービスのエンドポイントには Lambda の ARN(Amazon Resource Name) を指定。(要件を満たせば他のサーバも使えるけど触れません)
Google Home
- Actions on Google でプロジェクトを作成、音声認識に DialogFlow を選択します。
- DialogFlow コンソールでインテント・エンティティ(Alexaで言うところのスロット)を定義
- バックエンドのサービスは Cloud Functions for Firebase で作成
- DialogFlow コンソールでインテントごとに「Enable webhook call for intent」をチェック、DialogFlow の Fulfillment → Webhook → URL にサービスのURLを指定。
Alexaはカスタムスキル、Google Homeは Actions on Google の DialogFlowアプリケーションを作成します。
それぞれの作成方法はチュートリアルやらQiitaの先人の方の記事をご覧ください。
最低限の応答を返す実装
Alexa では Alexa Skill Kit、
Google Home では Cliant Library for Actions on Google がそれぞれ提供されているので利用します。
Alexa
const Alexa = require('alexa-sdk');
const handlers = {
'HelloIntent': function () {
this.emit(':tell', 'こんにちは');
},
};
exports.handler = function (event, context, callback) {
const alexa = Alexa.handler(event, context, callback);
alexa.registerHandlers(
handlers
);
alexa.execute();
};
Google Home
const { DialogflowApp } = require('actions-on-google');
const functions = require('firebase-functions');
exports.functionName = functions.https.onRequest((request, response) => {
const app = new DialogflowApp({ request, response });
function helloResponseHandler(app) {
app.tell('こんにちは');
};
const actionMap = new Map();
actionMap.set('HelloAction', helloResponseHandler);
app.handleRequest(actionMap);
}
見たまんまですがそっくりですね。ユーザからのインテント(意図)に対して対応するハンドラを作成し、tell(結果を返して終了)かask(ユーザに更なる発声を促す)かします。
Alexa では emit(':tell/:ask')、 Google Home では tell()/ask() で使い分けます。
Alexa では追加の引数でAlexaアプリに表示するカードの内容を指定できます。
GoogleHome は ask 時にカルーセルやリストを出してユーザに選択を促したりできるみたいです。
パラメータの取得
Alexa
const param = this.event.request.intent.slots.Param.value;
Google Home
const param = app.getArgument('Param');
インテントにパラメータを追加した場合の参照方法。
特に使用感に違いはありません。
ユーザのID取得
Alexa
const userId = this.event.context.System.user.userId;
Google Home
const userId = app.getUser().userId;
ユーザを特定する謎の文字列(匿名ユーザID)。
Alexa の UserId は別端末や一旦スキルを無効/有効化すると変わります。
Google の UserId はGoogleアカウントに対応しています。その為 Google Home とスマホの Google Assistantアプリ で同じ UserId からのアクセスが発生します。
30日以上使わないか、アカウントのリンクを解除するとリセットされるらしいです(参照)。
ユーザデータの永続化
Alexa
'xxxIntent': function () {
this.attributes['key'] = 'value';
},
...
alexa.dynamoDBTableName = process.env.ALEXA_DYNAMODB_TABLE;
ASKではdynamoDBTableName を設定しておくと this.attributes にユーザIDごとのデータが読み込まれ、更新すると自動的に DynamoDB に永続化されます。
Google Home
該当機能なし。
Alexa に寄せて UserId をキーに DynamoDB を読み書きするユーティリティを書くのが良いかと思われます。
文脈の遷移
「天気を教えて」「どこの天気を調べますか?」「東京」「東京の天気は晴れです」みたいな一連の会話の流れを実現する機能です。
Alexa
const handlers = {
'FirstIntent': function () {
this.handler.state = '_REQUEST_AREA';
this.emit(':ask', 'どこの天気を調べますか?');
},
};
const requestAreaHandlers = Alexa.CreateStateHandler('_REQUEST_AREA', {
'SecondIntent': function () {
....
this.emit(':tell', '〇〇の天気は晴れです。');
this.handler.state = null;
},
};
exports.handler = function (event, context, callback) {
const alexa = Alexa.handler(event, context, callback);
alexa.registerHandlers(
handlers,
requestAreaHandlers
);
alexa.execute();
};
this.handler.state にステートを設定しておくと次回インテントではステートに一致するStateHandlerがハンドラとして使用されます。
Google Home
フォローアップインテントという仕組みがあり、あるインテントに続くインテントを定義する事ができます。
ごめんなさい、使いませんでした。
私の場合↑のユーザデータの永続化を応用して Alexa でのステートにあたるものをユーザデータに持ち、自前でそれを見て分岐する事で Alexa での StateHandler っぽい動きを実装しました。
申請
Alexa/Google Home あまり違いはありません。
強いて言えば Alexaスキルの審査の方が厳しく感じました。
- AlexaビルトインのStopIntent/HelpIntentにちゃんと対応する事
- 無応答による終了時、おかしくならない事
- askでユーザの発話を促す場合、ちゃんと質問か発話を促すプロンプトを出力する事
このへんちゃんとしておかないとはねられます。
Google Home は申請時プライバシーポリシーのページが必要になります。ユーザごとのデータを持つ場合当然匿名ユーザーIDを収集する事になるので用途を記載する必要があります。
まとめ
応答を生成して返すVUI部分と応答の元となるデータを生成するロジックの部分は分けた設計にするのは第一です。
ユーザデータの管理や会話の文脈など少しややこしいものが入ってくる場合、個別に作るのではなく先に作った方の動きに寄せて移植するような感じでVUIを作っていくのが良いかなと思います。
気がついたら当たり前の事しか書いていませんがこれを機に皆様より良いスマートスピーカーライフを!