Google Assistantアプリ開発経験者向けAlexaスキルの作り方

# はじめに
3月中にAlexaスキルをリリースするとTシャツ貰えるそうなのでいっちょやってみました。
[スキルを開発して、特典をもらおう](https://developer.amazon.com/ja/alexa-skills-kit/alexa-developer-skill-promotion)

その際Google Assistantアプリ開発経験者がAlexaスキルを作るうえでポイントになりそうなところをまとめてみました。

あと[DialogflowのIntentとEntityをAlexaスキルの形式に変換かけるツール](#移行ツールを使って移行)作ったので、Google Assistantアプリリリース済みの方はこれ使ってさくっとAlexaスキル作ってTシャツゲットしましょう！



# Google AssistantアプリとAlexaスキルの開発要素比較表
まずGoogle Assistantアプリ開発に関わる要素がAlexaスキルのどの要素に相当するか把握すべく調べてみました。

今回のケースと逆方向で、Alexaスキル > Google Assistantアプリの流れで違いを比較した記事がありました。
[AlexaSkillとGoogleAssistantの両方を開発・リリースしてみて気付いた違い【まとめ】](https://qiita.com/murakei/items/f82f326e319cc1368daa#_reference-38edb2935e0885ae8be5)

> |Service|Alexa|GoogleAssistant|備考|
> |:--|:--|:--|:--|
> |Developer Service|[Amazon Developer](https://developer.amazon.com/ja/)|[Actions on google](https://developers.google.com/actions/)|開発ポータルでプロジェクトの作成・定義を行う。|
> |NLP Service <br> (Frontend)|Skill Builder <br> (Alexa Skill Kit)|[Dialogflow](https://dialogflow.com/)|IntentやSlotType (Entity) を定義して、<br>自然言語処理と対話モデルを紐付ける。 <br> *NLP (Natural Language Processing)|
> |Web Services|[AWS](https://aws.amazon.com/jp/)|[FireBase](https://firebase.google.com/?hl=ja)|クラウドコンピューティングサービス|
> |Computing Service <br> (Backend)|- Lambda|- Cloud Functions|Node.jsなどのBackend Functionを配置 <br> *[Cloud Functions Tips] 参照|
> |Storage Service|- S3|~~- Cloud Storage~~|audio/imageコンテンツ格納用 <br> *[Cloud Storage・Hosting] 参照|
> |CDN Service|- Cloud Front|~~- Hosting~~|コンテンツ配信用 <br> (GetRequestが少なければ無くてもよい) <br> *CDN (Contents Delivery Network)|

なるほど。
あとはDialogflowの中身(Intent、Entity)が何に対応するか把握出来ればおっけーそうですね。



# チュートリアルをやってみる
次にとりあえずチュートリアルですね。
[Alexaスキル開発トレーニング](https://developer.amazon.com/ja/alexa-skills-kit/training/building-a-skill)

私は最初の2つをやってみました。
分かりやすくまとめられているのでこれだけで基本的な流れは把握できます。
セッション（DialogflowでいうContext）も扱う場合は第3回までやってみるといいかもしれません。

* [第1回 初めてのスキル開発](https://developer.amazon.com/ja/blogs/alexa/post/6e716e5c-55b0-445b-b936-9cfac4712e7b/training-1)
    * Alexaスキルコンソール画面の概要
    * Intentの書き方
    * AWS Lambdaのコンソール画面の概要
    * サービスシミュレーター（テストするやつ）の使い方


* [第2回 対話モデルとAlexa SDK](https://developer.amazon.com/ja/blogs/alexa/post/07377568-2815-4028-8c21-409dd8e84fa2/alexa-training-jp-2nd)
    * Slot（変数ワード、DialogflowでいうParameter）について
    * サンプル発話（DialogflowでいうTraining phrases）
    * Lambda（DialogflowでいうFulfillment）の詳しい書き方
    * デフォルトIntentについて

なお2018/3/10現在、Amazon開発者コンソールの新コンソールbeta版が使えるようになってます。
チュートリアルは旧コンソールでの手順なので注意して下さい。
LambdaのUIもちょっと変わっちゃってるぽいですが、大体わかるかなと思います。



# リリース済みのGoogle AssistantアプリをもとにAlexaスキルを作ってみる
「[クロマチックチューナー](https://assistant.google.com/services/a/uid/000000c79cd8901d?hl=ja)」というGoogle Assistantアプリを既にリリースしているので、同じ実装を踏襲します。
なおGoogle Assistant版の実装は以下の記事にまとめています。
[Google Homeアプリをリリースしてみた(実装編)](https://qiita.com/miso_develop/items/bc22d55788c025c32c39)

## 移行ツールを使って移行
上記のアプリでEnttityに色々打ち込んでおり、あれをコピペするのはちょっと気が重かったのでツールを作って移行しました。
GitHubに上げています。

[Dialogflow2AlexaSkillJson](https://github.com/miso-develop/dialogflow2alexa-skill-json)

Dialogflowからエクスポートしたzipをツール(Node.js)に通すとAlexaスキルのJSONが出力され、新コンソールにJSONをポイっと投げ込めば簡単に移行できます。
移行出来るのはIntentとSlot Type（Entity）です。
なおGoogle AssistantアプリでContextとかは使ってなかったのでそのへん未対応です。

導入、使い方は[README](https://github.com/miso-develop/dialogflow2alexa-skill-json#readme)を参照下さい。

またAlexaスキルはDialogflowに比べて色々制約がきつく、ちょこちょこ修正していく必要があります。
以下に移行後いじった内容をピックアップしてみます。

### Intent
* Intent名とSlot名が同じだと怒られます
    * Intent名はマジメに「XxxIntent」みたく名付けましょう
* サンプル発話（Training phrases）に同じSlot（Entity）を繰り返し使えません
* Welcome IntentはAlexaスキルではLaunchRequestとしてLambdaのコード上で管理されます
* HelpIntentがBuilt-In Intentであり、必須です
    * 適当に書くとリジェクト食らいます

### Slot Type(Entity)
* Slotの値は基本的に日本語じゃないといけません（頭文字、イニシャリズム、アクロニムを除く）
    * 「a flat」みたいのはエラーは出ませんが申請時にリジェクト食らいます
    * 数字もダメです。カタカナか漢数字にしましょう

### SaveとBuild
一通り修正できたら「Save Model」 > 「Build Model」と上部のボタンをポチポチしてって下さい。



## EndPoint(Fulfillment)
AWS Lambdaでコーディングを行います。
HTTPSって選択肢もあったから他のWebhookでもいけそう。

### 静的応答
ここがDialogflowとの一番の違いだと思うんですが、固定フレーズ（DialogflowでいうText response）はここで定義するっぽいです。
なので、スキル開始時や終了時、ヘルプにFallbackだったりは文字列直打ちして対象Intentのハンドラーで応答かけます。

### 動的応答
Fulfillmentみたいな動的処理もここに一緒に書きます。
言語は色々選べて、Dialogflowと同じNode.jsを選択したのでコアコードをコピペしてAlexaスキルの仕様に微調整（`sendGoogleResponse` > `this.emit`とか、SSMLからspeakタグを除外したりとか）したらおっけー。

ちょっとハマったのはSlot Type(Entity)の値を一意で取得するときでした。
Slot Typeの値を一意に定める際は、`this.event.request.intent.slots.xxx.value`ではなく、`this.event.request.intent.slots.xxx.resolutions.resolutionsPerAuthority[0].values[0].value.id`でidを取得するっぽいです。
ここもDialogflowと違ってSlot Type(Entity)に完全一致ではなく部分一致した結果が配列で返ってくるっぽいので注意が必要です。

あとは今回作ったスキルでは静的ファイル(mp3)を読んでるのですが、置き場をGitHubにしてRawで参照かけることにしました。

### Source
以上を踏まえて作成したのが以下になります。
`handlers`配下の`Tune`が動的応答、他は静的応答です。

```js:index.js
"use strict";

const Alexa = require("alexa-sdk");

const APP_ID = "amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

const START_MESSAGE = "欲しい音を教えて下さい。";
const END_MESSAGE = "終了する場合は「終了」と話しかけて下さい。";
const HELP_MESSAGE = `楽器のチューニングスキルです。ドからシまでの12音階の音を鳴らします。「ドの音」のように話しかけて下さい。それでは、${START_MESSAGE}`;
const STOP_MESSAGE = "さようなら";
const FALLBACK_MESSAGE = `すみません、よく分かりません。もう一度${START_MESSAGE}`;

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context);
    alexa.appId = APP_ID; 
    alexa.registerHandlers(handlers);
    alexa.execute();
};

const handlers = {
    "LaunchRequest": function () {
        this.emit(":ask", START_MESSAGE);
    },
    "Tune": function () {
        try {
            const note = this.event.request.intent.slots.note.resolutions.resolutionsPerAuthority[0].values[0].value.id;
            const url = `https://raw.githubusercontent.com/miso-develop/tuner/master/mp3/alexa/${note}.mp3`;
            const speak = `<audio src="${url}"></audio><break time="1s" />${END_MESSAGE}`;
            this.emit(":ask", speak);
        } catch(e) {
            this.emit(":ask", FALLBACK_MESSAGE);
        }
    },
    "AMAZON.HelpIntent": function () {
        this.emit(":ask", HELP_MESSAGE);
    },
    "AMAZON.CancelIntent": function () {
        this.emit(":tell", STOP_MESSAGE);
    },
    "AMAZON.StopIntent": function () {
        this.emit(":tell", STOP_MESSAGE);
    },
    "SessionEndedRequest": function () {
        this.emit(":tell", STOP_MESSAGE);
    }
};
```



### EndPoint登録とappId登録
AlexaスキルとLambdaはそれぞれ別のコンソールで管理を行うので、互いに紐付けを行う必要があります。
[チュートリアル](https://developer.amazon.com/ja/blogs/alexa/post/6e716e5c-55b0-445b-b936-9cfac4712e7b/training-1)参照。
最後に「Save EndPoints」ボタンもポチって下さい。

以上でAlexaスキルが完成です。



# テスト

## コンソールから
Actions on GoogleのSimulatorみたいのがあります。
デバッグはこちらで。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/297e13a4-f1ab-eb7f-d003-18be4ed9c63d.png)

## 自動テスト
先日行われた[スマートスピーカー勉強会](https://kotodama.connpass.com/event/78137/)で自動テストに関するLTがありました。
そのLTではGoogle HomeからAssistantを利用して喋らせてAlexaのテストを行っており面白かったです。

私の環境ではFirebaseのRealtime Databaseに言葉を書き込めばGoogle Homeが喋りだすようになってるので、Excelに書かれた言葉リストを上から順にFirebaseに書き込むマクロ組んでパターン網羅テストをしてみました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">先日の勉強会LTを参考にExcelからスマートスピーカー自動テスト実行。<br>Excel(VBA) &gt; Firebase &gt; Node.js &gt; google-home-notifierの流れ。<br>Firebase以降の仕組みは既にあったのでVBAからFirebaseへ書き込みしただけ。 <a href="https://t.co/TKUGQaYTua">pic.twitter.com/TKUGQaYTua</a></p>&mdash; 田中みそ (@miso_develop) <a href="https://twitter.com/miso_develop/status/972506944341557249?ref_src=twsrc%5Etfw">2018年3月10日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### Node.js版
一応Node.jsでも直接google-home-notifierを叩くスクリプトを作ってGitHubに上げました。
[google-home-auto-tester](https://github.com/miso-develop/google-home-auto-tester)

`data.csv`に喋らせたい言葉と待ち時間（msec）を以下のように記載して`node index.js`することで順番に指定間隔で読み上げてくれます。
ポイントとしてgoogle-home-notifierは喋り始めに「ﾃﾞｭﾘﾝ!」って音が鳴り、これをAlexaが誤認識してしまうのでAlexaの応答中にうまいこと「ﾃﾞｭﾘﾝ!」って鳴るように待ち時間を調整するといいです。

```csv:data.csv
test1,5000
test2,5000
test3,0
```

# リリース申請
ここも大体Google Assistantアプリと一緒です。
違いとしては、

* 画像は108x108と512x512が必要です
* プライバシーポリシーは特になければ不要です

ぐらいです。


# リジェクト
当然ながら(?)リジェクトされました。2回。
内容としては、

* ドキュメントちゃんと読め的なこと
    * サンプルフレーズの書き方とかで先頭に「アレクサ、」と書いてないとか
    * Slotの中身は日本語で、数字もカタカナか漢数字にしてねとか
* ヘルプの内容しっかり書いてね
* セッションオープン時は必ずAlexaスキル側でユーザーに問いかけるように
* サンプル発話でSlotの重複ダメよ

てな感じでした。

ちなみに審査は結構早くて、朝申請だしたら夕方には返ってきました。



# リリース
そして無事リリースされました。
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/4cb0092a-bc22-a560-dc2b-125a1cc3580d.png)

参考動画
<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr">Google Assistantアプリリリース時、使い方がよく分からないという声もあったので参考動画もどうぞ。 <a href="https://t.co/J4Db9y4kMJ">pic.twitter.com/J4Db9y4kMJ</a></p>&mdash; 田中みそ (@miso_develop) <a href="https://twitter.com/miso_develop/status/974204801322860544?ref_src=twsrc%5Etfw">2018年3月15日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

なおTシャツゲットキャンペーンはちゃんとエントリーしておかないとダメです。
[スキルを開発して、特典をもらおう](https://developer.amazon.com/ja/alexa-skills-kit/alexa-developer-skill-promotion)



# おわりに
とりあえずGoogle Assistantアプリ作ったことある方は、

* チュートリアルやる
* 移行ツール使ってDialogflowからIntent、Entity移行
* AWS Lambdaで静的応答と動的応答仕込む

でさくっといけるはず。
所々呼び方は違えど仕組みはほぼほぼ一緒です。
キャンペーンは残りあと半月ですが、頑張ってTシャツゲットしましょう！