背景

  • Pollyで文字から音声を生成できる
  • Alexa Voice Serviceを使うとmp3が再生できる

つまり日本語を話せるAlexaが作れるのでは…?:thinking:

概要

日本語で話しかける :arrow_right: 日本語で答えてくれる

題材として、「挨拶」と「天気予報を」を選んだ。
「おはよう」
「今日の天気を教えて」
「明日の天気は?」
などに応答してくれる。

こんな感じで会話できます。
(Echo持ってないのでAVSで動かしてます…:cry:)

https://youtu.be/6GKvik7QOXA

Alexa Skills Kit

まず、自分で作ったアプリを登録し、AlexaからLambda関数を読み出せるための設定が必要です。
これらの作業は https://developer.amazon.com/ で行う必要があるため、アカウントを取得して下さい。
また、アカウント作成の際に、日本の通販のAmazonで使っているものと異なるメールアドレスで登録するようお願いします。

Skillの作成

トップページ[ALEXA]タブ :arrow_right: [Add a New Skill]
よりスキルを作成できます。
スキル作成にあたって、設定が必要な箇所があるので簡単に説明します。
(本題とは逸れるのであまりしっかりは説明しません)

・Skill Information

[Name]

表示される名前です(なんでもいい)

[Invocation Name]

Alexaに呼びかけてこのスキルを起動する時の名前です
自分は "nihon go" と入力しているので、 "ask nihon go" でスキルが起動します。

・Interaction Model

Lambda関数と同じくらい大切なところ。

[Intent Schema]

自分の例は以下。
type に入っているものが単語の集合で、この下の [Custom Slot Types] で設定します。 name はその単語をLambdaに渡す際の名前をここで決めています。

{
  "intents": [
    {
      "slots": [
        {
          "name": "greetings",
          "type": "JP_Greetings"
        }
      ],
      "intent": "GreetingWords"
    },
    {
      "slots": [
        {
          "name": "dates",
          "type": "JP_Dates"
        }
      ],
      "intent": "Questions"
    },
    {
      "intent": "AMAZON.HelpIntent"
    }
  ]
}

[Custom Slot Types]

こんなかんじ。(てきとう)

スクリーンショット 2017-07-11 15.50.15.png

[Sample Utterances]

上で定義したインテント名とスロット名を使います。
発話を受け取り、どのタイプの会話であるかを判断します。
<インテント名> <会話内容>という書き方で、"{xxx}"となっているものは、上で設定したスロット名のスロットタイプに含まれる単語の何かしらを受け取れます。
以下書き方の例。

GreetingWords {greetings}
Questions {dates} no tenki o oshiete
Questions {dates} no tenki wa

・Configuration

こんなかんじでLamnda指定。(てきとう)

スクリーンショット 2017-07-11 15.55.45.png

構成

Alexa Voice Service から Alexa Skill Kit(lambda関数) を呼び出して処理しています。
全てus-east-1 (バージニア北部) のリソースを使っています。

先日Lexも公開されたのですが、mp3の再生が出来ないようなので保留。
(Lexのほうがlambda関数を短く書けて楽な上に、Slotなどの管理も手軽なので使いたかった…)

スクリーンショット 2017-06-08 17.14.44.png

処理の流れ

処理としては上の図の3つのLambda関数が軸になっているので、それぞれの関数に沿って説明します。

AVSlink.js

AVSと連携しているlambda関数で、AVSからのリクエストを受け付けています。
音声の入力に応じて、S3に保存されているmp3を再生しています。

▼Lamnda関数

https://github.com/marceline-git/AlexaFunctions/blob/master/AVSlink.js

説明が面倒なので省略しますが コードを見ればなんとなく動きがわかると思います。
一応説明しておくべき場所を以下にまとめました。
cheerio-httpcliを使っているため、npm install cheerio-httpcliしてzipで固めてLambdaにアップロードして下さい。
function onIntentでインテントの名前からどの関数を実行するかを決めています。
<バケット名>/output以下のフォルダに、発話させたいmp3ファイルを置いてます。
・常にshouldEndSession = falseなので、特に終了させる会話などは決めていません。
repromptTextは一定時間発話が無かった際に送られるものです。(聞いたことないけど)

getData.js

天気予報APIを叩いて、発話させたい日本語文をDynamoDBに格納。
Dynamoにはこんな感じでデータが入っています。idを主キーとして、値はunixtimeを入れています。

スクリーンショット 2017-07-11 16.54.41.png

発話するためのmp3を生成する際はここのデータを引っ張ってきています。
データが増えすぎないように、天気の情報を取得するたびに古いものは削除しています。

▼Lamnda関数

https://github.com/marceline-git/AlexaFunctions/blob/master/getData.js

ちょっとした説明。
asyncを使っているため、npm install asyncしてzipで固めてLambdaにアップロードして下さい。
・livedoorの天気APIを使っています。住所などは適当にいじって下さい。
・CloudWatch Eventsをトリガーに、定期実行しています。

convert.js

Pollyの生成するmp3ファイルは、フォーマットの問題で直接AVSから再生できません。
以下のフォーマットに変換する必要があります。

コーデック: mp3
チャンネル数: 2
サンプリング周波数: 16000kHz
ビットレート: 48kbps
* Converting Audio Files to an Alexa-Friendly Format

ドキュメントではffmpegで変換しているのですが、lambdaにffmpegを乗せるのは結構面倒で…。
(ffmpegとlibmp3ame入れつつ軽量化するのが大変)
EC2インスタンス建てっぱなしにするのもスマートじゃないので悩んでいたら、ElasticTranscoderというサービスで解決しそうなので採用。

▼ElasticTranscoderの使い方

Pipelineの作成

[Elastic Transcoder]→[Create New Pipeline]より作成。
PipelineIdは後ほどLambdaで使います。

スクリーンショット 2017-06-08 17.58.18.png

Presetの作成

[Elastic Transcoder]→[Create New Preset]より作成。
PresetIdは後ほどLambdaで使います。

スクリーンショット 2017-06-08 18.15.48.png

Jobの作成

こちらはマネジメントコンソールで作成する必要がありません。Lambda関数内で生成します。

▼Lamnda関数

https://github.com/marceline-git/AlexaFunctions/blob/master/convert.js

ちょっとした説明。
asyncを使っているため、npm install asyncしてzipで固めてLambdaにアップロードして下さい。
・DynamoDBから文章を取ってきて、発話して、一旦mp3を生成して、変換して、古いmp3削除して、Dynamoからも削除して、みたいな流れです。
・発話時にpollyでSampleRate: "16000"を指定しているため、変換時に上のPresetでは[auto]でもうまく動きます。
PipelineIdPresetIdは先程作成したものを利用して下さい。
・CloudWatch Eventsをトリガーに、定期実行しています。

S3のフォルダ構成 :arrow_down:

  • <バケット名>/tmp
    pollyにより発話されたmp3ファイル(変換前)が一旦保存されるフォルダ
  • <バケット名>/output
    tmpフォルダからmp3ファイルを引っ張ってきて、ElasticTranscoderにより変換されたmp3ファイルが保存されるフォルダ

お疲れ様でした :sweat:

一部だけをかいつまんで説明したのですが、ElasticTranscoderなどの説明で結構長くなってしまいました。
大体がaws-sdkを利用しているので、リファレンスを読めばなんとなくわかると思います。
* AWS SDK for JavaScript
わからないことがあれば、コメント欄に書いてもらえれば回答します(多分(暇な時))。

余談

前もって定期実行して発話すべきmp3ファイルを持っておくのはどうなのかと思い、Alexaに問い合わせがあった際にLambda内で動的生成を行うものも作成しました。
が、返事まで30秒近く時間がかかってしまいスマートさが欠片も感じられなかったので使うのはやめました。