非WebエンジニアがAlexaスキル開発をしていてハマったこと


Web系に全く疎い組み込みエンジニアの私が、この度Alexaスキルを作ってみたのですが、
当然のように色々と躓いたので、試したことと解決した方法を、メモがてら綴っておきたいと思います。

# 手順書通りにやってもうまくいかない。つまづく。

こういったクラウドサービスは日々変化してるので、手順書の内容が古かったりするとそこで躓きます。
手順書作る大変さは職業柄良く知ってます・・・（汗）

私もいくつかの手順書を参考にしたのですが、lambdaのトリガー設定がうまくいかず、設計図の所で`alexa-skills-kit`も出てこない！
読みかえて試しても見たのですが、エラーに次ぐエラーで何が悪いのか途方に暮れ・・・
以上から「ユーザー定義のプロビジョニング」は諦めました。（素人は手を出すなっ！）

ということで結局、今回私が作ろうとするスキルは「Alexaがホスト」で十分なので、そちらを使いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267148/63a76059-c875-3910-56e9-18293d74bcb2.png)

19/06/25 追記
@nagao-masashi 様より、alexa関係の設計図は、Serverless Application Repositoryの参照に移動したとの情報を頂きました。ありがとうございました！

19/07/12 追記
その後、ユーザ定義のプロビジョニングでもできるようになりました。
こちらのメリットとしては、JS以外の言語（Pythonなど）が使える点ですね。
一度覚えてしまえば、ビルドも早いのでLambda直接の方が便利かもです。

# 「You just triggered～」と言われる

これは、Alexaが入力を認識して、それに対応するintent handlerを呼ぼうとしますが、存在しない場合に発生します。

## 1. Handlerが存在しない

 Intentが正しく呼ばれているが、対応するHandlerが無いケースです。
 下記サンプルで言う所の Intent_nameに、正しくintentの名前が設定されているか確認しましょう。（綴りも含め）

```javascript:index.js
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'Intent_name';
    }
```
## 2. コードがデプロイされていない
  ちゃんとコードは書かれているが、それでも上手くいかない場合、コードをブラウザで作成して保存したは良いがデプロイされていない可能性があります。
  保存しただけでは、変更したコードは実行されないので、必ずデプロイしましょう。


# 「スキルがリクエストに正しく応答できませんでした」と言われる
これには、いくつかケースがあり、それぞれ原因が異なっていました。最終的にどれが正しかったのかは分からないこともありましたが、やってみたことを綴ります。

## ブラウザ上のテスト（シミュレータ）でNGの場合

### 1. そもそもコードにバグがある
  文法は合っていても、コード自体にバグがある場合です。
  こればっかりは、バグを特定して潰すしかないのですが、CloudWatchのログを見るのも手です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267148/3c1f6e2f-9b7e-ab47-0667-3623ac1d22d7.png)
  ですが、恥ずかしながら私はログを見てもさっぱりでしたので、最終的にはHello worldサンプルを使って最初から組み直しました・・・
### 2.名前解決がうまくいかない(Python)
インテントスロットを設定したは良いが、呼び出し方が分からない！
そこでインテントのデータ構造をネットで検索してみました。

```json:intent.json
{
  "type": "IntentRequest",
  "intent": {
    "name": "GetMediaIntent",
    "slots": {
      "MediaType": {
        "name": "MediaType",
        "value": "トラック",
        "resolutions": {
          "resolutionsPerAuthority": [
            {
              "authority": "amzn1.er-authority.echo-sdk.<skill_id>.MEDIA_TYPE",
              "status": {
                "code": "ER_SUCCESS_MATCH"
              },
              "values": [
                {
                  "value": {
                    "name": "曲",
                    "id": "SONG"
                  }
                }
              ]
            }
          ]
        }
      }
    }
  }
} 
``` 
上記の構造になっているのは分かりました。しかし、`id`を取りだしたいのですが、名前解決の方法が分からない！という壁にぶつかりました。
jsonの扱いなんて慣れてないよ！！


結論として、下記のようにして引っぱれました。1時間ぐらい悩んみました・・・
ただのPythonの文法の問題な気がしました。

``` Python:lambda.py
#"list_number"はSlot名
slot_id = intent['slots']['list_number']['resolutions']['resolutionsPerAuthority'][0]['values'][0]['value']['id']
```

Alexa skillのヘルプドキュメントは比較的わかりやすいものの、もっと例を書いてくれると有り難いんですがね・・・

### 3. 記法が古いSDKのものになっている（未確定情報）
  Hello worldサンプルと見比べて気づいたのですが、NodeJs SDKのバージョンが変わって、以前のものからは記法が変わってるところがあります。
  巷にあふれているサンプルは、古い記法のものが多いので、これが何か原因してるのかもしれません。
  実は以前の記法でも動くのかもしれませんが、Hello worldに合わせたところで動いたので、根本原因は分かりません。（サンプル自体が悪かったのかもしれませんし）
  どうしても動かない場合、Hello worldサンプルと見比べて違いに注目してみてください。

## シミュレータでは上手くいくが、実機にしたらNGの場合

### 1. コード変更してデプロイだけやった
  デプロイだけだとシミュレータによるテストの場合は良いみたいですが、
  実機はビルドしないと適用されないので、

### 2. ビルド後に適用されるまでタイムラグがある
  インテントの発話関係を諸々変更し、ビルドしてすぐに試そうと思っても、エラーで上手くいかないことがありました。
  その時は5分ぐらい置いてから試すと通りました。（最近はクイックビルドができたようなので、あまりこの不具合は出ないかも？）
  果たして、これは時間によるものなのか？そこまでは突き詰めていないので、
  どうしてもうまくいかないときは、まずビルド後に少し待ってみるのも手です。

  但し、lambdaコンソールでコーディングしたコードはこの限りではありません。保存するとすぐに適用されます。

### 3. βテスト中の場合、一旦終了して再度始めてみる
  βテスト中はコードの修正についてはデプロイすれば適用されるのかなと思っておりましたが、インテントの設定は？
  丁度いろいろな不具合に見舞われていたところだったので、この手順が効いていたのかは謎ですが、
  いろいろ試してもダメだったら、やってみる価値はあります。

### 4. 開発中スキル一覧に同名の起動ワードを持つスキルがある
  私の場合はこれにハマりました。
  どうしても動かなくて、新しくプロジェクトを作った時に、前のものはリファレンスとして残しておいたのです。
  いざ実機で試す時に、古い側が呼ばれてしまっていました。
  これはAlexaアプリのアクティビティログに、エラーが起きるとカードとして残るので、そこで確認できます。
  古いスキルを削除、もしくは起動ワードを変更しましょう。

### 5. 画面付きデバイス向けコードを、画面がないデバイスで実行しようとしている
Echo spotなど、画面デザインを適用できるデバイスがありますが、このコードをEcho dotなどの画面が無いデバイスで実行しようとするとエラーになります。
画面が付いている場合と、付いていない場合で条件を分けられるようにコードを書き直す必要があります。
これに該当しているかどうかは、Alexaアプリのアクティビティログのカードからエラー内容を見ればわかります。

これを防ぐ方法は、下記が参考になりますよ！
https://developer.amazon.com/ja/blogs/alexa/post/a3712152-cd02-4fda-9d33-3b64ef3a13af/jp-alexa-presentation-language-part1

## 良くわからないなら、ユニットテスト！(AWS Lambda)
Lambdaを使っている場合になりますが、いちいちAlexa developer consoleを使わなくてもテストが出来ますよ。
発話されたように見せかけるJSONを簡単に作り出せちゃいます。

まずは、Lambda Consoleの「テスト」をクリックして「テストイベントの設定」を開きましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267148/6bef027c-1052-f233-32a7-e929005ae1ca.png)

そしたら、新しいテストイベントを設定します。今回は起動時を想定して「Start setssion」テンプレートを選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267148/b97fa5ab-3f41-e0bb-cdde-dbafa74ef70e.png)

選んだら、「作成」をクリックすれば準備完了。あとは「テスト」ボタンを押すだけでいつでもテストできます。
また、テンプレートにはインテント起動したい場合のものもありますし、JSON編集すれば柔軟にデバッグできるかと思います。

出力結果はコンソールか、Lambdaのページの一番上に出てきますので、簡単ですね。結果はCloud watchでも見れますよ。

# APL(画面表示）関係

APLはまだ日が浅い機能ということで、さらに情報が少ないです。
そのため、色々と躓くことが多いのが現状・・・

## 画面有無のチェックについて
画面が無いモデルもあるので、APLのコードを実行するかどうかの判断についてをチェックする必要がありますが、
Alexaの公式ドキュメントを見ると、下記のように書かれています。

```js:apl.js
module.exports.isAPLSupported = function isAPLSupported(request) {
  return request
    && request.context
    && request.context.System
    && request.context.System.device
    && request.context.System.device.supportedInterfaces
    && request.context.System.device.supportedInterfaces['Alexa.Presentation.APL'];
}
```
出典：https://developer.amazon.com/ja/blogs/alexa/post/a3712152-cd02-4fda-9d33-3b64ef3a13af/jp-alexa-presentation-language-part1

しかしながら、Alexa Developer ConsoleからテストをしてみてもsupportedInterfacesが空白のまま！
そういった場合は、APLを使う設定になっていないかもしれません。
Alexa Developer Consoleの画面表示メニューから、APLをONしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267148/e1b2c607-03be-8798-0696-d4c2cf51ca57.png)


### Pythonで画面有無をチェックする
Pythonで書く場合は、上記とはちょっと違った文法になりますので、下記を参考にしてください。
キーを1つ1つチェックするのが冗長なので、Tryを使っちゃいました。(Python初心者のコードなので、あまり良い書き方では無いかもしれません)

```python:apl.py
    display = False
    try:
        event_context = event['context']['System']['device']['supportedInterfaces']
        if 'Alexa.Presentation.APL' in event_context :
            display = True
    except:
        display = False

```

## APLがうまく起動しない(Python)

APLのコードを追加して、Lambda側ではテストするときちんとJsonが出力されるのに、
テスト画面で「Json出力」の所を見ると`addDirective`の部分が出てこない！

これ、結構悩みましたが、Pythonでキーを直接指定する場合は、`addDirective`では無かったのです。

正しくは、こうです。

```python:apl.py
APL_dict = {'directives': [
        {
            'type' : 'Alexa.Presentation.APL.RenderDocument',
            'version': '1.0',
            'document': apl_json['document'],
            'datasources': apl_json['datasources']
        }
    ]
}
```

そう。`directives`だったのです。やっぱりjsで書けばこの辺りも悩まず書けたのかなぁ・・・

# 認定関係
ここから、認定における各種指摘の対応方法をまとめます。

## スキルの画面要素が終了していないと言われた
下記のようなフィードバックを受ける場合があります。
>画面に表示される操作要素は音声による対話が終了すると共に表示されなくなる必要があります。スキルとのインタラクションが終了したあとにインタラクティブ要素が選択されるとナビゲーションの問題が発生する可能があります。

要は、画面表示デバイス向けに、意図的にセッションを終わらせる必要があるようです。
その場合は、終了処理用のインテントに、下記の記述を追加します。エラーで強制終了をキャッチするインテント`SessionEndedRequestHandler`にも忘れずに追加しましょう。

```javascript:index.js
        return handlerInput.responseBuilder
            .speak(speechText)
            .withShouldEndSession(true) //この行を追加
            .getResponse();
```
これで、画面表示処理を終わらせることが出来ます。

##ありがちな指摘
ここでは私が受けた指摘を参考に、ありがちな指摘をまとめます。事前にこちらをチェックしておくと認定がスムーズかと思います。

### 次の発話を促す言葉が無い

対話型などRepromptを使って、続けて発話できるスキルの場合、

アレクサが発話を終わった後、次の発話を促す言葉が無いと指摘を受けます。

例としては

>ユーザ「アレクサ、お天気を開いて」
>アレクサ「お天気へようこそ。明日の天気は？というように話しかけてみて下さい。」
>ユーザ「明日の天気は？」
>アレクサ「明日は雨のち晴れでしょう。」 ← **(次は何を聞きますか？ というような発話が必要)**
>ユーザ「明後日の天気は？」
>アレクサ「晴れでしょう。」

対話型スキルは多いのですが、意外と忘れがちなので、注意しましょう。



### サンプルに「アレクサ、〇〇を開いて」が無い

「公開」のタブから、公開時にスキル紹介画面で出る公開用発話サンプルを３つ登録できます。

そこで落とし穴があって、必ず最初の１つは「アレクサ、〇〇を開いて」という具合に、「アレクサ、」を含めたサンプルにする必要があります。

これを忘れると必ず指摘を受けますので、忘れずに登録しておきましょう。

もう、固定文字列入れておいてよ！って感じですね。


### サンプル通りに発音してもインテントのトリガが起動しない

前述したサンプルを登録したら、それらを必ず発音したテストをしましょう。

公開用発話サンプル「〇〇に翻訳をかけて」

インテントの発話サンプル「〇〇を翻訳にかけて」

という具合に、意外と細かいところが違っていたりして、指摘を受ける場合があります。

公開用発話サンプル通りに発音して、意図したトリガが発生するかを確認しましょう。


### インテントの発話サンプルが足りない

各インテントに登録する発話サンプルは十分な数を用意しましょう。少ないと指摘を受けます。

具体的にいくつという規定は無いですが、最低でも10個は欲しいところです。

前述した翻訳のケースを例にとると

「〇〇を翻訳して」を派生させ、ユーザが発話しそうなケースをありったけ登録します。

- 〇〇を翻訳
- 〇〇の翻訳
- 〇〇を翻訳する
- 〇〇の翻訳をする
- 〇〇の翻訳をして
- 〇〇を翻訳すると
- 〇〇の翻訳をすると
- 〇〇を翻訳にかける
- 〇〇を翻訳にかけて
- 〇〇に翻訳をかける
- 〇〇に翻訳をかけると

という具合です。

いかなる場合でも正しく動作するように気を配りましょう。

### 発話サンプルの重複
複数のインテントに同じ発話サンプルが含まれているとNGです。
ありがちなのが、StopインテントとCancelインテントに同じ発話サンプルが含まれているというケース。
サンプルコードだとこの2つのインテントは同じ処理をさせることが多いので、エラーにはならず気づかず申請してしまうことがあります。

重複してしまうと、その発話を受けた時に、どのインテントを起動して良いか分かりませんからね。
一度確認してみて下さい。

# まとめ
Web系エンジニアだったら、サーバ通信系アプリケーションの勘所が分かっているので、難なく出来ると思いますが、私みたいな素人は全く手探りの状態で、思わぬところで躓きました。
普通の人が躓かないところだったのか、ネットを探してもそれっぽい情報が見当たらなかったので、今回まとめました。

また、壁に当たって分かったことがあれば、本記事を更新していきたいと思います。

とはいえ、AlexaスキルはWebに疎くても作れるよ！という後押しになれば嬉しいです。