Python
AWS
lambda
Alexa
スマートスピーカー
0

🔰Python少し知ってればつくれる! PythonとAWS Lambdaを使ったAlexaスキル開発チュートリアル (2018年8月版最新版)

0. Alexaスキル開発の概要

Amazon製のスマートスピーカーのAlexaがユーザとの対話で行える行動を"Alexaスキル"といいます。
例えばユーザがスピーカーに向かって"明日の天気を教えて"というと,"明日の○○の天気は○○です"と返すようなスキルがあります。:sunny:

AlexaスキルはAmazon以外にもユーザが無料で開発することができます。:moneybag:
この記事はAlexaスキル開発をプログラミング言語のPythonとクラウドサービスの1つのAWS Lambdaを使って開発するチュートリアルです。

Pythonの知識が3cmぐらいあれば簡単なものであれば開発できます。 🔰
以下の1つ目のリンクからPythonをウェブ環境で実行できます。また2つ目のリンクからPythonの公式チュートリアルを見ることができます。(3割ぐらい理解できてれば大丈夫なはず)

Amazonが出しているAlexaスキル開発トレーニングページもありますが、それよりもざっくりと説明していきます。またほんとうに簡単なものであればIFTTTを使ってプログラムを書かなくても開発が可能です。

スマートスピーカー

0.0. 概要をつかむ

Alexa のスキルでいったいなにができるの概要がつかめます。
途中"インテント"等よくわからない単語が出てきますが,とりあえず流し見することをお勧めします。:point_up_tone1:

また,時間がない人は倍速で見るのをお勧めします。

全部をみるとわりといろいろなことができることがわかると思います。

以下がAlexaの開発のための"Alexa Skills Kit"の公式リファレンスです。
Alexa Skills Kitによるスキルの作成 | ASK

0.1. Alexaスキル開発のため必要なもの

Alexaスキルはユーザから話しかけられたら以下のような流れで返答をします。

  1. ユーザがAlexaに話しかける
  2. 話しかけられた音声データをAlexaサーバに送る
  3. Alexaサーバ内で音声データを解釈して、ユーザがどのような意図を伝えたかったのか解釈し、その意図の内容を表すテキストデータ(json)を開発者が指定したサーバ(この場合ではLambda)に送る
  4. サーバでは受け取ったデータからAlexaの返答内容を作成し、テキストデータ(json)をAlexaサーバに戻す
  5. Alexaサーバは返答内容を読み取り、テキストデータをAlexaが発言する音声データを生成し、生成した音声データをAlexaに戻す
  6. Alexaは受け取った音声データを再生する

image.png

このような背景からスキル開発者はAlexaサーバの設定とAlexaサーバと通信するサーバの設定をする必要があります。
そのため以下のものが必要になります。

  • Amazon Developerアカウント (Alexaサーバの設定のため必要)
  • AWSアカウント (Lambda(Alexaサーバと通信するサーバ)を使うため)

Amazon Developerアカウントの作成

必要な個人情報を記入する必要があります。
Amazon Developerにはクレジットカード必要ありません。

以下のページから作成できます。
Amazon 開発者ポータル

作成が不安な方は以下のページを参考にすると良いかもしれません。
失敗しないAlexa開発者アカウントの作り方 | Developers.IO

AWSアカウント作成

以下のAmazonの公式のリファレンスからどのように作成すればよいかわかります。
AWS アカウント作成の流れ | AWS

※AWS Lambdaの関数が作成できる権限があるアカウントがあればよいです。

1. スキル開発を行う

今回はAlexaスキル開発のチュートリアルとして好きな動物を覚えさせるスキルを作成します。
非常に簡単で面白みのないスキルですが、基本的なスキル開発を学べます。

スキル開発では開発者は以下の部分の設定・コーディング等を行います。

image.png

やることは2点あります。

やること 作業する場所 必要なもの
対話モデルの作成 Alexaのウェブコンソール Alexa Developerアカウント
JSONサーバの作成 Lambdaの設定ページ等 Lambdaが使えるAWSアカウント

だいたい全体像が見えてきたでしょうか?

1.0. スキルの対話モデルを作成

スキルを実装するには現在のページから対話モデルを作成する必要があります。

対話モデルの作成にはAlexaに話しかける内容を定義して、どのように言われたらどの部分をどうとってくるかを設定します。

image.png
Alexaスキル開発トレーニングシリーズ 第2回 対話モデルとAlexa SDK : Alexa Blogsより

今回のスキルでは以下のようなユーザとの対話を目指します。

:man_tone1:「Alexa, 動物覚えるくんを起動して」
:robot:「あなたの好きな動物はなんですか」
:man_tone1:「私の好きな動物はねこです」
:robot:「覚えました」
:man_tone1:「私の好きな動物は」
:robot:「あなたの好きな動物はねこです」
:man_tone1:「私の好きな動物はいぬです」
:robot:「覚えました」
:man_tone1:「私の好きな動物は」
:robot:「あなたの好きな動物はいぬです」
:man_tone1:「終了」

このような流れを考えた場合、
先ほどの図から「動物覚えるくん」が「呼び出し名」であることはわかります。

また、「ねこ」や「いぬ」がスロットに入ることが考えられます。
スロットは要は変数みたいなやつです。
「ねこ」や「いぬ」が入るスロットをAnimalスロットとします。(動物が入る変数みたいなものです)

するとユーザの発話が以下のようになることが推測できます。
このようなユーザの発話にはそれぞれ意図(インテント)があることがわかります。
そのため、以下のそれぞれをインテントと呼びます。

  • 「私の好きな動物は{Animal}です」→好きな動物を覚えされる意図
  • 「私の好きな動物は」→覚えされた動物を聞く意図

ここでユーザの発話は多少揺らぐことを考えると2つの文章は以下のような発話パターンが考えられます。

私の好きな動物は{Animal}ですの発話パターン
- 「私の好きな動物は {Animal} です」
- 「好きな動物は {Animal} 」
- 「ぼくは {Animal} は好きです」

私の好きな動物はの発話パターン
- 「私の好きな動物は」
- 「ぼくの好きな動物は」
- 「好きな動物は」
- 「覚えた動物は」

以上のような事柄を対話モデルとしてAlexaのコンソールより定義します。
そうすると対話モデルが完成し、インテントやスロットを理解できるようになります。

1.1. コンソールからスキルの追加をする

以下のウェブコンソールから"スキルの作成"をクリック。

Alexa Skills Kit Developer Console

Alexa開発コンソール画面

以下のような画面に遷移します。

今回は
スキル名は「動物覚えるくん」
デフォルトの言語は「日本語(日本)」
スキルに追加するモデルを選択では「カスタム」
を選択します。

そして画面右上の"スキルを作成"をクリック。

こんな画面に遷移します。

このページではAlexaの対話モデルに関する基本的にすべてのことを設定できます。

では実際に設定していきましょう。

まず以下のように呼び出し名を設定します。

image.png

次に変数のようなものであるスロットを設定します。

左のメニューからスロットタイプの追加からAnimalスロットを追加します。
image.png

そしてそのスロットに実際に入ることが想定されるものを追記していきます。
image.png

次に対話内容を追加していきます。
まず動物を覚えさせる意図の対話をRememberAnimalIntentとして新規作成します。

image.png

次に動物を覚えさせる意図のサンプル発話を設定します。
また、スロットタイプ(先ほど追加したAnimal)を指定するのを忘れずに。
{Animal}の前後に半角スペースを入れないとビルド時にエラーが発生するので半角スペースをいれてあげましょう

ちなみにAMAZON.Animalというスロットがあります。
こちらはAmazonがあらかじめ用意してくれた動物がいろいろ当てはまるスロットが使えます。(今回作ったAnimalスロットタイプより強力)
このようなAmazonがデフォルトでいろいろなスロットタイプを用意してくれています。(Amazonが用意してくれているインテントもあります)
詳しくはAlexa Skill KitでAmazonが用意したBuilt-In IntentとBuilt-In Slot Typeをひたすらまとめてみる | Developers.IO
image.png

image.png

同様に覚えさせた動物を聞くインテントをListenIntentとして以下のように設定します。

image.png

ここではスロットは登場しないので、スロットタイプの指定は必要ありません。

これで対話モデルの設定が終わりました。
画面上部メニューの「モデルの保存」をクリックしておくと良いでしょう。

1.2. スキルのサーバサイドを作成する

1.2.1. Lambdaとは? FaaSとは?

今回はAWSのサービスで、FaaSであるLambdaを使います。
FaaSは簡単に説明すると常時動いているサーバではなく、実行が要求された分だけ動くサーバみたいなものです。

FaaS(Function as a Service)は関数が実行した分の請求のみ発生します、常時動いているサーバよりも圧倒的にコストが低い利点があります。またサーバレスアーキテクチャを考えた場合に、重要な役割を担っています。

AWSのLambdaでは毎月初回の1,000,000回のリクエストは無料で、さらにそれ以降1,000,000回のリクエストにつき0.20USDの請求が発生します。仮にVPSでJSONサーバを立てたら毎月約1000円以上かかるのと比較すると圧倒的に安い!

詳しくは以下の記事をご覧ください。
- FaaS(Function as a Service)とは | 意味・メリット - サーバいらずのクラウドサービス - 開発 | ボクシルマガジン
- 料金 - AWS Lambda(サーバーレスでコードを実行)|AWS

1.2.2. Lambdaを使ってサーバサイドを構築

実際にLambdaを使ってサーバサイドを構築していきましょう。

以下のページから新規にLambdaの関数を作成できます。
Lambda Management Console

キャプチャ.PNG

:raised_hand_tone1:Alexaのスキル開発は現在以下のリージョンでしか作成できないので、作成するリージョン(画面右上に表示されている地域)を確認しましょう。

  • アジアパシフィック(東京)
  • EU(アイルランド)
  • 米国東部(バージニア北部)
  • 米国西部(オレゴン)

実際に"関数の作成"ボタンをクリックします。

そして関数の設定をします。
今回はPythonでサーバをコーディングしていくので以下のようになります。

image.png

またロールの設定はカスタムロールの作成→以下の画面→許可で設定ができます。
image.png

もろもろできたら関数の作成をクリックします。

画面下部に行くとエディタが出てきます。

image.png

ここにサーバが呼ばれた際の処理を書きましょう。

以下のように記述します。

lambda_function.py
# coding: utf-8

def lambda_handler(event, context):
    """
    始めにここが実行される
    eventの中にユーザの発話内容がdictで格納されている
    returnでdictを返すとそれがサーバ自体のjsonの返り値になる
    """

    if event['session']['new']:
        on_session_started({'requestId': event['request']['requestId']}, event['session'])

    if event['request']['type'] == "LaunchRequest":
        return on_launch(event['request'], event['session'])
    elif event['request']['type'] == "IntentRequest":
        return on_intent(event['request'], event['session'])
    elif event['request']['type'] == "SessionEndedRequest":
        return on_session_ended(event['request'], event['session'])

def on_session_started(session_started_request, session):
    """ セッションが開始したときに呼び出される """
    print("requestId=" + session_started_request['requestId'] + ", sessionId=" + session['sessionId'])

def on_launch(launch_request, session):
    """ ユーザーが必要なものを指定せずにスキルを起動したときに呼び出される """
    print("on_launch requestId=" + launch_request['requestId'] + ", sessionId=" + session['sessionId'])
    return get_welcome_response()

def on_intent(intent_request, session):
    """ ユーザがインテントのある発言をしたら呼び出される """

    intent = intent_request['intent']
    intent_name = intent_request['intent']['name']

    if intent_name == "RememberAnimalIntent":
        return set_animal_in_session(intent, session)
    elif intent_name == "ListenIntent":
        return get_animal_from_session(intent, session)

    elif intent_name == "AMAZON.HelpIntent" or intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":
        return handle_session_end_request()
    else:
        raise ValueError("Invalid intent")

def on_session_ended(session_ended_request, session):
    """ セッションが終わったら呼び出される """
    print("on_session_ended requestId=" + session_ended_request['requestId'] + ", sessionId=" + session['sessionId'])

def build_speechlet_response(title, output, reprompt_text, should_end_session):
    """ 返す発話内容のdictを返す関数 """
    return {
        'outputSpeech': {
            'type': 'PlainText',
            'text': output
        },
        'card': {
            'type': 'Simple',
            'title': "SessionSpeechlet - " + title,
            'content': "SessionSpeechlet - " + output
        },
        'reprompt': {
            'outputSpeech': {
                'type': 'PlainText',
                'text': reprompt_text
            }
        },
        'shouldEndSession': should_end_session
    }

def build_response(session_attributes, speechlet_response):
    """ 返す全体のjsonのdictを返す関数 """
    return {
        'version': '1.0',
        'sessionAttributes': session_attributes,
        'response': speechlet_response
    }

def set_animal_in_session(intent, session):
    card_title = intent['name']
    session_attributes = {}
    should_end_session = False

    if 'Animal' in intent['slots']:
        favorite_animal = intent['slots']['Animal']['value']
        session_attributes = {"favoriteAnimal": favorite_animal}
        speech_output = "あなたの好きな動物は " + \
                        favorite_animal + \
                        "ですね。 "
        reprompt_text = speech_output
    else:
        speech_output = "動物が分からなかったよ。もういちど言って " 
        reprompt_text = speech_output
    return build_response(session_attributes, build_speechlet_response(
        card_title, speech_output, reprompt_text, should_end_session))

def get_animal_from_session(intent, session):
    session_attributes = {}
    reprompt_text = None

    if session.get('attributes', {}) and "favoriteAnimal" in session.get('attributes', {}):
        favorite_animal = session['attributes']['favoriteAnimal']
        speech_output = "あなたの好きな動物は " + favorite_animal + \
                        "です。終わります。"
        should_end_session = True
    else:
        speech_output = "私はあなたが好きな動物が分からないです。" \
                        "好きな動物はいぬですとかって言ってください。"
        should_end_session = False

    return build_response(session_attributes, build_speechlet_response(
        intent['name'], speech_output, reprompt_text, should_end_session))

def get_welcome_response():
    session_attributes = {}
    card_title = "あなたの好きな動物はなんですか"
    speech_output = "あなたの好きな動物はなんですか"
    reprompt_text = "あなたの好きな動物はなんですか"
    should_end_session = False
    return build_response(session_attributes, build_speechlet_response(
        card_title, speech_output, reprompt_text, should_end_session))

def handle_session_end_request():
    card_title = "おわります"
    speech_output = "おわります" 
    should_end_session = True
    return build_response({}, build_speechlet_response(
        card_title, speech_output, None, should_end_session))

コードの保存はウェブ上から行えます。

image.png

また、Lambda自体も保存しないと反映されないので画面右上から保存しましょう。(オレンジ色になっていれば保存できます)

image.png

1.3. 対話モデルとサーバサイドをつなげる

スキルを動かすために以下の2点の設定が必要です。

  • 対話モデルからサーバサイドの設定
  • サーバサイドから呼ばれる元の設定

途中エラーが出る場合はモデルの保存やビルド,Lambdaの保存をしてから再度設定してみてください。

1.3.1. サーバサイドから呼ばれる元の設定

関数がどこから呼ばれるのか設定します。
LambdaのDesignerの項目の左部から"Alexa Skills Kit"をクリックして追加します。(以下のよう)

image.png

Alexa Skills Kitが左部に出ない場合はリージョンの設定が問題であることが考えられます。
関数を削除して、以下のリージョンであらたに作成しましょう。

  • アジアパシフィック(東京)
  • EU(アイルランド)
  • 米国東部(バージニア北部)
  • 米国西部(オレゴン)

追加するとトリガーの設定の項目がページ下部に出現します。
そこでAlexaのスキルIDを指定する必要があります。

image.png

追加ボタンを押して完了。

1.3.2. 対話モデルからサーバサイドの設定をする

Alexaのコンソールから呼び出すサーバの設定をする必要があります。
コンソールのエンドポイントから以下の画面に行きます。

image.png

デフォルトの地域にLambdaのコンソールの画面右上(以下の画像)にあるARNを入力します。

image.png

入力した後に、対話モデルを保存→ビルドします。

1.4. テストをしてみる

alexa developer consoleからスキルのテストが行えます。
ここでは、実際にAlexaに話しかける内容を入力して、テストが行えます。
また、どのようなjsonがサーバに送られ、どのようなjsonがサーバから返ってくるのか確認できます。

image.png

1.5. デバッグ方法

主なデバッグ方法として、1.4.のalexa developer consoleからスキル(対話モデル)のテストが行えます。

また、JSONサーバへのテストとしてalexa developer consoleのJSONエディタからJSONを送信してテストが行えます。

また後述のSSMLのテストも行えます。

またLambdaのログ監視を以下の画面の"モニタリング"→"CloudWatchのログ表示"→"(任意のログストリーム)"より行えます。

image.png

log.PNG

以上のようにLambda上のPythonのエラーやprint()したものの中身を見ることができます。

またLambdaの画面上部よりテストイベントが設定できます。(もちろんそれをテストできます)
デフォルトでAlexaのセッションスタート時のテストイベントが入ってたりしています。それをテストイベントとして設定をしておくとワンクリックでセッションスタート時のテストができてかなり便利です。

image.png

1.6. その後...

以上で解説は終了です。
これからAlexaのスキルを実際にストアにあげる(誰でも使えるインストールできるものにする)には以下の手順を踏む必要があります。

  1. 公開
    コンソールの"公開"から公開申請を出す。
    image.png

  2. Amazonから認定をもらい、ストアに出る。

その他

APIを使ってみる

外部のAPIを使ってみるといろいろなことができます。

全体的なシステムは以下のようになります。

image.png

AlexaのUI/UX

Alexaはインプットが音声のみ(Spot,Showは除く)なので使いづらくなりがちです。
それを解決するためにAlexaのセリフのベストプラクティスが公式にあります。

Alexaのセリフについて | Amazon Alexa Voice Design Guide

たとえば以下のようなことも

一息テストでセリフの長さをチェックしよう
Alexaのセリフを書く場合、書いた内容を声に出して読み上げてみてください。普通に会話する速度で一息にそのセリフを読み上げることができたら、適切なセリフの長さと考えてよいでしょう。息継ぎが必要な場合はセリフを短くすることを検討してください。

Alexaスキル開発時には一読しといたほうがいいかもしれません。

Alexaの返答

Alexaの声色を変えたりすることができます。

音声合成マークアップ言語(SSML)のリファレンス | Custom Skills

SSMLというマークアップ言語でどのように発言すればいいか記述するとそのように発音してくれるようです。
例えば以下のようなSSMLを返すと"I am not a real human."の部分だけ囁いてくれます。

<speak>
    I want to tell you a secret.
    <amazon:effect name="whispered">I am not a real human.</amazon:effect>.
    Can you believe it?
</speak>

またSSMLのaudioタグを使うと、音声を返すこともできます。
チャットボットの技術と初音ミクさんを組み合わせれば、初音ミクさんと会話ができるようなスキル開発が可能です。

<speak>
    Welcome to Car-Fu.
    <audio src="https://carfu.com/audio/carfu-welcome.mp3" />
    You can order a ride, or request a fare estimate.
    Which will it be?
</speak>

ただリファレンスにあるようにさまざまな制約があるので注意が必要です。

その他いろいろなタグがあります。

Alexaのプッシュ通知

「雨が降りそうになってきたら洗濯物をとりこんで!」といきなりAlexaからしゃべってくれたら便利ですよね。
そんなデバイスから能動的に通知が実装できたら開発の幅が広がっていいですよね!

結論から言うと基本的に実装できないです。

以下の記事の通りです。

通知の機能が日本語環境でも利用可能になりました : Alexa Blogs

通知の機能をあなたのスキルに実装したい場合
通知の機能を持ったスキルを開発するための開発環境は現在デベロッパー・プレビューとして一部の開発者のみに公開しています。ご興味のある方はデベロッパー・プレビューの申請フォームから登録を行ってください。申請フォームでは、どのようなユースケースを想定しているか、配信の頻度、配信しようと考えているメッセージの例についてお伺いします。申請内容のレビュー後、一部の開発者の方には、さらに詳しい情報が提供されます。

なので、申請をして通ると使えるようになるようです。

以下のスキルがプッシュ通知の機能があるようです。(2018年2月26日現在)

JR東日本 列車運行情報案内
登録している路線の運行情報に遅れが発生または見込まれる場合に、通知でお知らせします。
Yahoo!天気・災害
デバイスに登録している所在地に雨や雪の予報がある日は、朝7時ごろ通知でお知らせします。
japan taxi
全国タクシー
配車が確定した時に通知でお知らせします。

AlexaとIFTTT

IFTTTを使えば簡単なAlexaスキル(?)が作れちゃったりします。

IFTTTはさまざまなウェブサービスをつなげるサービスです。
例えばAlexaに「~」と話しかけたら、「???」とつぶやくみたいなことがウェブ上でできちゃいます。

簡単なサービスが(ほしい|つくりたい)だけならIFTTTを使うのをおすすめします

IFTTTはAが起きたらBをするを設定することができます。

AとBには既存のさまざまなウェブサービス(Facebook, Twitter, Instagram, LINE等の200以上のサービス)のイベントをとることができます。以下で使えるサービス一覧が公開されています。

See all services - IFTTT

例えば、AをWebhooksにしてBを任意のウェブサービスにするとWEB APIっぽいものがつくれます。
アカウント連携が必要なものも簡単に使えるので、個人使用で使うならおすすめです。