Alexa SkillとGoogle Assistantの両方を開発・リリースしてみて気付いた違い【まとめ】

## はじめに
Alexa SkillとGoogle Assistant両方のアプリを開発・リリースしてみて、気が付いた違いなどをまとめてみました。

流れとしては、まず、リリースされているスキル数の多いAlexaSkillは、開発自体の敷居が低いんだろうと考え、自分にもできるかも？と思い、[【Alexaスキル開発トレーニング】](https://developer.amazon.com/ja/alexa-skills-kit/training/building-a-skill)を参考にしながら、AWSのアカウント作成や、Amazon Developerのアカウント作成から始めてみました。

試行錯誤すること約１か月。2017/12/20に無事AlexaSkillをリリースすることができ、音声アシスタントに関するナレッジも溜まってきたので、次はGoogleAssistantにも挑戦することにしました！

なので、この記事は、**`AlexaSkill → GoogleAssistantではどうなるの？`** といった視点で進めていきます。

## 開発標準Webサービスの違い
Alexa・GoogleAssistantの開発で利用する標準的なWebサービス群の違いです。

|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.cloud.google.com/)|IntentやSlotType (Entity) を定義して、<br>自然言語処理と対話モデルを紐付ける。 <br> *NLP (Natural Language Processing)|
|Web Services|[AWS](https://aws.amazon.com/jp/)|[FireBase](https://console.firebase.google.com/)|クラウドコンピューティングサービス|
|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)|

### ＊[Cloud Functions Tips]
Cloud Functionsの関数デプロイは、DialogflowのFulfillmentタブ内にある機能「Inline Editor(Powered by Cloud Functions for Firebase)」からデプロイすることができます。
（AWS Lambdaでいうところの「関数コードをインラインで編集」の機能にあたります。）

上記の方法であれば、「Firebase ツールをインストール」 → 「ターミナルウィンドウを開く」 → 「デプロイ」といった、面倒な手順を踏まないで良いので、たいへん楽です。

* ＜参考リンク＞ [Dialogflow(旧:API.AI)の新機能Inline Editorを試してみた #dialogflow](https://qiita.com/flatfisher/items/c772fada35add9dd67c9)

### ＊[Cloud Storage・Hosting]
アプリ内で.mp3のaudioと.pngのimageコンテンツを返しているのですが、同じものを「Cloud Storage・Hosting」に配置してもあまり意味が無いので、今回は、「S3・Cloud Front」に配置してあるコンテンツをそのまま利用しました。
なので、上記表内の「~~Cloud Storage・Hosting~~」は利用していないので、取り消し線にしています。

## Web Services 無料利用枠（月間）
月間1000人のユーザが訪れ、20回のGetRequestがあったと想定すると、以下のような計算になります。

`1,000 Users x 20 Requests of Monthly = 20,000 Requests`

|Service|実リクエスト数|無料利用枠|利用率|-|
|:--|:--|--:|--:|:--:|
|<font color="Red">AWS - S3 - Gets</font>|20,000 Requests|20,000 Requests|<font color="Red">100.00%</font>|<font color="Red">×</font>|
|<font color="Blue">AWS - CloudFront</font>|20,000 Requests|2,000,000 Requests|<font color="Blue">1.00%</font>|<font color="Blue">○</font>|
|AWS - Lambda|20,000 Requests|1,000,000 Requests|2.00%|○|
|FireBase - Cloud Functions|20,000 Requests|125,000 Requests|16.00%|△|

このままでは、「AWS - S3 - Gets」が100%を超え、課金されてしまいますので、S3から直接コンテンツを配信するのではなく、AWS - CloudFrontを経由して、コンテンツを配信することで、月間の使用量をAWS無料利用枠内の1%に抑えることができます。

```js
// AWS - S3
// const audioHost = "https://s3-ap-northeast-1.amazonaws.com/xxxx/audio/";
// AWS - CloudFront
const audioHost = "https://xxxxxxxx.cloudfront.net/audio/";
```

* ＜参考リンク＞ [CloudFront経由でS3からコンテンツ配信をする](https://qiita.com/ishikuro_yuuki/items/33b77510dfab14449a78)
<br><br>

同様に、アプリの人気が出すぎて「FireBase - Cloud Functions - Requests」が 100%を超えてきてしまう場合は、バックエンドの関数を FireBaseのCloud Functionsから、AWSのLambdaに引っ越しする必要がありそうです。

* ＜参考リンク＞ [Google Home と Lambda で「"あ"のつくポケモンなーんだ」](https://qiita.com/hotatesan/items/1350c1c8d08ccff22dc4)

## バックエンドソースの違い
### LaunchRequest
特定のインテントが指定されずにユーザーから呼び出される最初のイベントは、
Alexaでは「LaunchRequest」、GoogleAssistantではDialogflowの`Default Welcome Intent`に設定されたEvent（初期値:`input.welcome`)となります。

**Alexa SDK v1 (2018/04以前)**

```js
const handlers = {
  'LaunchRequest': function () {
    var speechOutput = 'Hello!';
    var reprompt = 'Reprompt Message';
    this.emit(':ask', speechOutput, reprompt);
  }
};
```

**Alexa SDK v2**

```js
const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = 'Hello!';
    const repromptText = 'Reprompt Message';

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(repromptText)
      .getResponse();
  }
};
```

**GoogleAssistant**

```js
const actionHandlers = {
  'input.welcome': () => {
      let responseToUser = {
        speech: 'Hello!(VoiceMessage)',
        displayText: 'Hello!(TextMessage)'
      };
      let googleResponse = app.buildRichResponse()
            .addSimpleResponse(responseToUser);
      app.ask(googleResponse);
  }
};
```
GoogleAssistantでは、スマートフォンで呼び出されることもあるため、
VoiceMessageだけでなく、TextMessage用のdisplayTextも用意されています。


### TellWithCard
アプリの応答にカードを表示して終了する場合、
AlexaとGoogleAssistantでは、以下のような違いになります。

**Alexa SDK v1 (2018/04以前)**

```js
const handlers = {
  'ByebyeIntent': function () {
    var speechOutput = 'Byebye!';
    var cardTitle = 'Card Title Text';
    var cardContents = 'Card Contents Text';
    var imageObj = {
        smallImageUrl: 'https://example.com/image/card_small.png',
        largeImageUrl: 'https://example.com/image/card_large.png',
    };
    this.emit(':tellWithCard', speechOutput, cardTitle, cardContents, imageObj);
  }
};
```

**Alexa SDK v2**

```js
const ByebyeIntent= {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'ByebyeIntent';
    },
    handle(handlerInput) {
        const speechOutput = 'Byebye!';
        const cardTitle = 'Card Title Text';
        const cardContents = 'Card Contents Text';
        const imageObj = {
            smallImageUrl: 'https://example.com/image/card_small.png',
            largeImageUrl: 'https://example.com/image/card_large.png',
        };

        return handlerInput.responseBuilder
            .speak(speechOutput)
            .withStandardCard(cardTitle , cardContents, imageObj.smallImageUrl, imageObj.largeImageUrl)
            .getResponse();
    },
};
```

* ＜参考リンク＞ [スキルの応答にカードを追加する](https://developer.amazon.com/ja-JP/docs/alexa/custom-skills/include-a-card-in-your-skills-response.html)

**GoogleAssistant**

```js
const actionHandlers = {
  'ByebyeIntent': () => {
    let responseToUser = {
      speech: 'Byebye!(VoiceMessage)',
      displayText: 'Byebye!(TextMessage)'
    };
    let googleResponse = app.buildRichResponse()
          .addSimpleResponse(responseToUser)
          .addBasicCard(app.buildBasicCard('Card Contents Text')
            .setSubtitle('Card Subtitle Text')
            .setTitle('Card Title Text')
            .addButton('Read more', 'https://example.com/index.html')
            .setImage('https://example.com/image/card_small.png', 'Image alternate text'));
    app.tell(googleResponse);
  }
};
```

* ＜参考リンク＞ [Responses | Actions on Google | Google Developers](https://developers.google.com/actions/assistant/responses)

## リリース後のソース修正の違い
リリース後のIntent(Frontend)と、Function(Backend)のソース修正の違いについては、以下の表のようになります。

|Service|Alexa|GoogleAssistant|備考|
|:--|:--|:--|:--|
|Intentの追加・変更 <br> (Frontend)|<font color="Red">× (Skill Builder*)</font>|○ (Dialogflow)|*[Go to development]で修正可|
|Functionの修正 <br> (Backend)|○ (Lambda)|○ (Cloud Functions)|-|

Alexaの場合、**`Intentを追加・変更`**するには **`「Go to development」`**で開発モードにして、再申請する必要があります。

GoogleAssistantの場合は、「Dialogflow」や「Training」機能の発話ログから、**`動的にIntentの追加・変更`**ができます。

また、Backend側の**`Functionの修正`**は、Alexa・GoogleAssistantのどちらも動的に行えます。

* ＜参考リンク＞ [Training | Dialogflow](https://dialogflow.com/docs/training)

## 参考ソース
開発のベースに利用したGitHubの参考ソースです。

**Alexa**

* [GitHub - alexa/skill-sample-nodejs-fact at ja-JP](https://github.com/alexa/skill-sample-nodejs-fact/blob/ja-JP/lambda/custom/index.js)

**GoogleAssistant**

* [GitHub - dialogflow/fulfillment-webhook-nodejs](https://github.com/dialogflow/fulfillment-webhook-nodejs/blob/master/functions/index.js)

## 参考リンク
実装・申請にあたって参考にしたQiita記事のリンクです。

**Alexa**

* [Amazon Echo (Alexa) のSkillの開発に必要な基本概念を押さえる](https://qiita.com/chroju/items/3f30fe424df36e4b4d80)
* [【保存版】開発部長が102時間かけてまとめたAlexaの落とし穴と習得方法](https://qiita.com/tsutorm/items/3b2d0f048c3916c04a4b)

**GoogleAssistant**

* [Google Homeアプリをリリースしてみた(実装編)](https://qiita.com/miso_develop/items/bc22d55788c025c32c39)
* [Google Homeアプリをリリースしてみた(申請編)](https://qiita.com/miso_develop/items/c56fd334f333160a9130)

## おわりに
1,000 Users を達成するのに、Alexaでは１か月掛かりましたが、GoogleAssistantでは、３日で達成しました！

やはり、Alexaに対して、GoogleAssistantは、Android、iPhone、GoogleHomeと間口が広いのが有利です。

日本語対応のGoogleAssistantアプリも少ないので、露出が増える分、今がチャンスかもしれないですね！

今回が初めてのQiita投稿となりましたが、最後まで見てくださって、本当にありがとうございます。
ぜひ、今回リリースした[GoogleAssistantアプリ【ピピトーク】](https://sites.google.com/view/pipi-talk/home)でも遊んでみてください！

<img width="512" alt="pipi-talk.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233066/cdec0db2-ffa5-8e2b-39a8-8be0680a7e3d.jpeg">

* [【ピピトーク】 (pipi-channel) おしゃべりインコのピピちゃんとトークしよう！ ｜ Googleアシスタント](https://assistant.google.com/services/a/uid/0000009b9f1a16e6?hl=ja-JP&source=web)

* [【ピピトーク】 (pipi-channel) おしゃべりインコのピピちゃんとトークしよう！ ｜ Amazonアレクサ](https://www.amazon.co.jp/pipi-channel-%E3%83%94%E3%83%94%E3%83%88%E3%83%BC%E3%82%AF/dp/B078HB9F87)

## 添付
アプリの応答カード画像

**Alexa**

|App|Card|
|:-:|:-:|
|<img width="375" alt="Alexa.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/233066/1879fe0d-27af-fa84-872b-e6b77ead3245.jpeg">|<img width="375" alt="AlexaCard.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/233066/eb22d405-abf3-02b1-22c4-0a3129ff5aa5.jpeg">|

**GoogleAssistant**

|App|Card|
|:-:|:-:|
|<img width="375" alt="GoogleAssistant.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/233066/c9cdf560-f272-0b68-6143-252a5db34891.jpeg">|<img width="375" alt="GoogleAssistantCard.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/233066/a1cc4bc8-e649-bdef-551b-73d5fad8b0ed.jpeg">|