Amazon Echo (Alexa) のSkillの開発に必要な基本概念を押さえる

Amazon Echo Dotを買ったので、早速Skillを自分で作るなどしてみたのだが、そのとき調べたことを書き記しておく。だいたい入門としてはこれぐらいの内容があれば足りるはず。

Alexaの開発についてはわりと資料が充実していて、公式のドキュメントもすでに日本語になっているし、ビデオを交えたトレーニング資料も存在している（現時点ではまだ途中までの公開）。

* [Alexa Skills Kitによるスキルの作成 | ASK](https://developer.amazon.com/ja/docs/ask-overviews/build-skills-with-the-alexa-skills-kit.html)
* [Alexa | Alexaスキル開発トレーニング](https://developer.amazon.com/ja/alexa-skills-kit/training/building-a-skill)

また国内販売開始からは日が浅いものの、それ以前にすでに開発を試している方々がちらほらいらっしゃるので、参考にさせていただいた。少し画面についてはこれらの記事当時から変わっていたりするので注意。

* [【Alexa初心者向け】Alexa Skill Kitを噛み砕いて解説してみる ｜ Developers.IO](https://dev.classmethod.jp/cloud/introduction-of-alexa-skill-kit/)
* [Alexa Skillsを公開する流れ - Qiita](https://qiita.com/sparkgene/items/ac3d14f1e8815ce8802a#sample-utterances)
* [AWS Lambdaを使ってAmazon Echoに機能追加してみた ｜ Developers.IO](https://dev.classmethod.jp/etc/alexa-skill-with-lambda-sample/)

Alexa Skillの開発
-----------------

まず概念的な話から。

Alexaはデフォルトで多様な機能を持っているわけではなく、「Skill」と呼ばれるデベロッパーや各企業提供の機能をプラガブルに有効化することで機能が増える。日本版では[Amazon.co.jp: : Alexa Skills Guide](https://www.amazon.co.jp/b?node=5262653051)に公開Skillの詳細があるけれど、まだSkill一覧を動的に確認できるページはなくて、pdfでしか公開されていないっぽい（amazon.comだとAppStoreのようなSkill一覧ページがブラウザで見られる）。

で、Skillはスマートフォンアプリと同様、個人のDeveloperが開発して公開することができるようになっている。Skillの作り方はいろいろあるけれど、最もシンプルな開発として、AWS Lambdaを直接叩くことができる。Lambdaを叩けるということは、そこを起点に各種AWSリソースが自在に使えるわけで、すでにAWSを習熟していればかなり開発難易度は低いと思う。

Alexa側のSkillの作成、受け取る発話パターンの定義などは、[Alexa Skills Kit](https://developer.amazon.com/ja/alexa-skills-kit) から実施する。Amazon.co.jpのアカウントがあれば誰でも使うことができる（通常はAmazon Echoのセットアップに使ったアカウントを使う）。開発イメージとしては、要はフロントエンドをAlexa Skills Kitで定義し、実際の処理を行うバックエンドをLambdaで作成する形になる。

Alexa Skills Kitで作成したSkillを、実際にAmazon Echo端末から使う方法は2種類ある。1つはいわゆる「Skillの公開」で、スマートフォンアプリのように審査を経てパブリックに公開されることになる。パブリックに公開したくない場合はもう1つ手段があって、Alexa Skills Kit上でSkillのテストを有効可すると、そのSkillを作成したユーザーのAmazon Echoと、招待メールを送った相手だけがSkillを使える。自分は手始めにかなりプライベートなものを作ったので、テスト実行で試してみている。

![スクリーンショット 2017-11-18 22.51.30.png](https://qiita-image-store.s3.amazonaws.com/0/19465/3d87e68c-f83d-67d1-4c17-d7b426495996.png)


Skillのアーキテクチャー
-----------------------

Alexa Skillの「フロントエンド」は、次のような概念で構成される。

* **Invocation name**: そのSkillを呼び出す名前。
* **Intent**: そのSkillが実行できるアクションの定義。
* **Sample utterance**: 各Intentに対して定義する、それを呼び出す発話サンプルの文字列。
* **Slot**: Intentが受け取る引数。

SkillはInvocation nameをトリガーとして呼び出される。例えば現時点のAlexaは運行情報のSkillをデフォルトでは持っていないので、「運行情報を調べて」と言っても反応してくれないのだけど、「JRの運行情報を調べて」と言うと、「JR」がJR東日本のInvocation nameになっていて呼び出すことができる。

たいていのSkillは複数の機能を持っているので、機能ごとにIntentを作成し、それを呼び出すための発話をSample utteranceとして定義する。Sample utteranceが "Sample" を名乗っているのは本当にサンプルだからで、一字一句ここで定義した通りの発話を必要としない。Amazon Echoがディープラーニングでそれなりに上手いこと文章を捉えてくれるらしく、Sample utteranceに近い表現であれば拾ってくれるようになっている。Sample utteranceはできるだけ多くのバリエーションを書いておくと、それだけAmazon Echoによる解釈も利きやすくなる。また、肯定の受け答え（YesIntent）や、会話のキャンセル（CancelIntent）といった、汎用的に使われると想定されるIntentについては、標準で用意されており、Sample utteranceも適当なものがすでに埋め込まれた形になっている。

* [一般的標準インテント | Custom Skills](https://developer.amazon.com/ja/docs/custom-skills/standard-built-in-intents.html)

SlotはSample utteranceに埋め込む引数。例えばfoodというslotを定義して、Sample utteranceに「`{food}` が食べたい」というようにブレースで囲んで埋め込む。Slotにどんな単語が当てはまるのかは、リストアップして定義しておく必要があるが、[Built-inで用意されているSlots](https://developer.amazon.com/ja/docs/custom-skills/slot-type-reference.html)（日付、数字、都市の名前など）も活用できる。

ここまで書いた内容は、 Alexa Skills Kit の中で、 Skills Builder というGUIで設定ができる。サンプルを貼っておくけど、ここでは「GetBillingIntent」というIntentを作成し、「service」というslotを交える形でいくつかSample utteranceを書いている。ちなみにまだ Skills Builder はベータ版で、不具合なのかわからないが、「slotのブレース（{}）の前後はスペースが必要」ということに気付かずハマったりした（参考：[Alexa Skill Builder | A sample utterance is invalid – DeviantDev Journal](https://www.journal.deviantdev.com/alexa-skill-builder-a-sample-utterance-is-invalid/)）。英語圏ではスペースがあるのは自然だけど、日本語だとつい忘れてしまうので、これはローカライズしてもらえたら嬉しいかも。

![スクリーンショット 2017-11-18 23.09.01.png](https://qiita-image-store.s3.amazonaws.com/0/19465/46515b9e-c6eb-f747-d5e6-32618b8a1f0c.png)

### Request type

まとめると、Invocation nameとSample utteranceを的確に発話したとき、それに紐付いたIntentを呼び出すということになる。しかし、Invocation nameには該当しても、Sample utteranceには適合しない場合もありえるわけで、発話はその処理結果に応じて以下に分類される。

* **LaunchRequest**: invocation nameのみに適合した場合。Skillを開くだけのアクション。
* **IntentRequest**: invocation name + sample utteranceに適合した場合。特定のIntentを呼び出すアクション。
* **SessionEndedRequest**: エラーが発生した場合。Skillを終了、キャンセルするアクション。

Alexaは発話を受け取り、Invocation nameがトリガーされると、発話が上記3種類のリクエストのいずれに該当するかを判断し、さらにIntentRequestに該当する場合は、どのIntentを呼び出したのかも判断する。それらの情報が一定の構造（後述）にまとめられて、Lambdaへのrequestとしてスローされる。



Lambdaでの処理
--------------

Lambdaは先のAlexaによる発話処理結果を受け取り、その内容に基いて処理を行うことになる。LambdaにはAlexa用のblueprintがいくつか用意されているので、それを参考にすれば難しくはないと思う。

基本的には継続Sessionなのかという分岐、どのRequest typeが呼ばれたかによる分岐、どのIntentが呼ばれたかによる分岐が必要となる。Request typeであれば、先に書いたLaunchRequest、IntentRequest、SessionEndedRequestのそれぞれについて処理を書く必要があるし、Intentについても当然ながらすべて処理を満たさなければならない。

### Request

AlexaからLambdaへ渡されるRequestのサンプルは以下。自分が作成したサンプルアプリから持ってきたものなので、詳細は気にしないでほしい。

```json
{
  "session": {
    "new": true,
    "sessionId": "SessionId.XXXXXXXX",
    "application": {
      "applicationId": "amzn1.ask.skill.XXXXXXXX"
    },
    "attributes": {},
    "user": {
      "userId": "amzn1.ask.account.XXXXXXXX"
    }
  },
  "request": {
    "type": "IntentRequest",
    "requestId": "EdwRequestId.XXXXXXXX",
    "intent": {
      "name": "GetBillingIntent",
      "slots": {
        "service": {
          "name": "service",
          "value": "イーシーツー"
        }
      }
    },
    "locale": "ja-JP",
    "timestamp": "2017-11-18T13:24:27Z"
  },
  "context": {
    "AudioPlayer": {
      "playerActivity": "IDLE"
    },
    "System": {
      "application": {
        "applicationId": "amzn1.ask.skill.XXXXXXXX"
      },
      "user": {
        "userId": "amzn1.ask.account.XXXXXXXX"
      },
      "device": {
        "supportedInterfaces": {}
      }
    }
  },
  "version": "1.0"
}
```

先程書いたIntentやRequest typeの判別結果は、 `request` の中にdictionaryで格納されている。これを元にして処理を分岐したり、 `slots` を処理の引数として使ったりすることができる。

また一番最初に `session` というdictionaryがある通り、AlexaにはSessionの概念がある。Skillには1回の会話の往復で終わらないものがあって、例えば [NAVITIME 乗換案内](http://corporate.navitime.co.jp/topics/pr/201711/08_4292.html) は、起動→到着駅指定→出発駅指定という3回の会話で成立する検索機能を持っている。このとき、到着駅、出発駅という、会話をまたいで保持する必要がある情報を、SessionAttributesとして `request` と `response` に含める形で管理する。またRequest typeのうち`LaunchRequest`については、Skillを起動したけどまだ具体的なIntentは呼ばれていない状態なわけで、通常はSessionを維持したまま、追加の発話を促すような実装になる。

もう一点、 `applicationId` という項目があるが、これはAlexaのskillごとに一意のものが割り当てられている。Lambdaは権限さえあれば他のskillから呼び出すことも可能なわけで、ある特定のskillからの呼び出しである場合にのみ反応したいのであれば、この `applicationId` を使って判定を行う形になる。

### response

レスポンスのサンプルは以下。こちらも内容の詳細は気にせず。

```json
{
  "version": "1.0",
  "response": {
    "outputSpeech": {
      "text": "イーシーツーの利用金額はXX.Xだらーです",
      "type": "PlainText"
    },
    "card": {
      "content": "イーシーツーの利用金額はXX.Xだらーです",
      "title": "Get Billing"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "PlainText"
      }
    },
    "speechletResponse": {
      "outputSpeech": {
        "text": "イーシーツーの利用金額はXX.Xだらーです"
      },
      "card": {
        "content": "イーシーツーの利用金額はXX.Xだらーです",
        "title": "Get Billing"
      },
      "reprompt": {
        "outputSpeech": {}
      },
      "shouldEndSession": true
    }
  },
  "sessionAttributes": {}
}
```

`response` に `card` という項目があるが、これはAlexaでのやり取りはすべてアプリの中にカード形式で履歴が残るので、そこに載せる情報を意味する。例えばボイスでは長文を吐けないので、詳細はカードに書くとか、画像やリンクはカードに書く、といった使われ方をしている。

<img src="https://qiita-image-store.s3.amazonaws.com/0/19465/807d7fcf-8f02-16fb-68eb-0beb9f81925f.png" width=50%>

 `reprompt` はSessionが継続する場合に、次の発話を促す（到着駅はどこですか？とか）テキストを指定する部分になる。Sessionを継続させたい場合には、 `shouldEndSession` を `false` にし、保存したい値を `sessionAttributes` に代入して返す形になる。


テスト
------

先に書いた通り、作成したSkillは自分のアカウントだけでテスト公開が可能なので、その状態で実際にAmazon Echoからテスト実行ができる。Alexa Skills Kitの中で、文字列で発話を指定してテストすることもできるけど、実際に音声での発話がどう読み取られるか？というところが重要なので、実機で試した方がいいと思う。

当たり前だけど自動テストも何もあったものではないので、ここだけはちょっとしんどい。

まとめ
------

だいたいここに書いた内容があれば、取りあえず何かしらSkillを作ることはできるんじゃないかと思う。というか自分はできた。概念さえ理解してしまえば、Alexa Skills KitはGUIで簡単に使えるし、バックエンドはLambdaを書くだけなので、悩む要素は少ない。まだ発売から日が浅く、できないこともちょいちょいあるとは思うけど、だったら自分で作っちまえぐらいの精神でガンガン遊べるガジェットだと思う。