Alexa Skill のための AWS Lambda 関数を Python で書く


はじめに

先日 4/27 に、僕が開発した Alexa Skill「声で買い物!シミュレーション」が Amazon Skill Store にて公開されました。


声で買い物!シミュレーション

Amazon Alexa を使って VUI1 で軽食デリバリーを頼む過程をシミュレーションできる(実際に買い物を行えるわけではない)Skill です。VUI による買い物をシミュレーションするだけの何の役にも立たない Skill ですが、ユーザを迷わせない対話フローの設計、想定外のユーザ発話へのエラーハンドリングなど、作る側が学べることは多かったかと思います。
そこで今回は学んだことの中でも特に AWS Lambda 関数を Python で書く方法をまとめたいと思います。Amazon 公式のスキル開発トレーニングなどでは JavaScript の解説が多く、Python での開発方法を記したドキュメントは少ないのが現状です。そのため少しでも Python での AWS Lambda 関数解説記事が増えることを願い、この記事を執筆します。
なお、今回開発した Skill のソースコードはすべて僕の GitHub レポジトリ に公開しています。以降の解説もこのソースコードをもとに作成しているので、詳細を知りたい方はご参照ください。

Alexa から送られてくる情報の取得

Lambda の main 関数

Alexa Skill から AWS Lambda 関数が呼び出させるとき、実際には lambda_function.py という Python スクリプトが呼び出されます。このファイル名は変更できません。また lambda_function.py の中で、main 関数のように振る舞うのは

def lambda_handler(event, context):

という関数です。この関数の引数のうち、event の中に Alexa Skill から渡される JSON データが階層を持ったディクショナリとして入っています。
例えば Lambda 関数を呼び出した Application ID を知りたいときは

event['session']['application']['applicationId']

のようにすれば Application ID を取得できます。

呼び出しの振り分け

さて、まずこの Lambda 関数の呼び出しがセッションの開始(スキル起動)時なのか、それとも何かインテントが送られてきたのかを知りたいとします。その場合

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'])

のようにセッション情報やリクエストタイプを判定することで、各呼び出し毎の処理へと分岐させることができます。セッションとはスキルが呼び出されてから終了するまでの一連の流れを指します。分岐後の各関数の詳細については、レポジトリをご参照ください。

インテントの振り分け

インテントとは、その名の通りユーザ発話の意図を表す情報です。これは開発者が Alexa コンソールで作成するもので、今回僕が開発したスキルの場合、例えば AddItemIntent はカートへの商品の追加を表し、SendOrderIntent であれば注文の送信を表す、といった具合になっています。
この情報へは

intent = event['request']['intent']
intent_name = intent['name']

という手順でアクセスできます。これにより各インテントを処理する分岐を作成できます。

スロットへのアクセス

スロットとは、各インテントを処理するために必要な値です。例えば AddItemIntent の場合商品 Item, 注文数 Number といったスロットが必要です。これもインテント同様開発者が定義します。
この情報には

item = intent['slots']['Item']['value']

のようにアクセスできます。['Item'] の部分がスロット名になっています。

セッション情報の取得

セッション情報は

session = event['session']

のように取得できます。これにより

session_attributes = session.get('attributes', {})

のように SessionAttributes へとアクセスできます。SessionAttributes とはセッションを通じて保持したい値(カートの中身など)を保持するためのディクショナリです。詳細は後述します。
SessionAttributes はセッション開始時には存在しないので、get でエラーハンドリングする必要があります。
SessionAttributes を取得すれば、例えばカートの中身を取得したい場合

cart = session_attributes.get('cart', {})

のように取得できます。

Alexa へ送る情報の追加

SessionAttributes へ情報を追加

前述の通り、SessionAttributes とはセッションを通じて保持したい値を保持するためのディクショナリです。通常のディクショナリなので

session_attributes['cart'] = cart

のようにすればキーとバリューを追加できます。ここで追加する情報には上述の cart の他にも、現在セッションがどの状態にいるのかを示す state などがあるでしょう。すなわち

session_attributes['state'] = "_SHOPPINGMODE"

のようにすれば、現在セッションは買い物モードであることを保持できます。

Alexa へのレスポンス生成

SessionAttributes など、Alexa へ送信したい情報を含むレスポンスは、決まった形式のディクショナリ型である必要があります。すなわち

def build_speechlet_response(title, output, reprompt_text, should_end_session):
    """Build general speech response.

    Args:
        title: The card title.
        output: The text Alexa will read and loud.
        reprompt_text: The text for reprompt.
        should_end_session: The bool value indicates the session will end or
                            not.

    Returns:
        The speech response dictionary will be converted into JSON.

    """
    return {
        'outputSpeech': {
            'type': 'PlainText',
            'text': output
        },
        'card': {
            'type': 'Simple',
            'title': title,
            'content': output
        },
        'reprompt': {
            'outputSpeech': {
                'type': 'PlainText',
                'text': reprompt_text
            }
        },
        'shouldEndSession': should_end_session
    }


def build_response(session_attributes, speechlet_response):
    """Build response will be sent to Alexa.

    Args:
        session_attributes: The session attributes.
        speechlet_response: The speech response.

    Returns:
        The response dictionary will be converted into JSON.

    """
    return {
        'version': '1.0',
        'sessionAttributes': session_attributes,
        'response': speechlet_response
    }

のようなディクショナリを返す必要があります。ここで title はホームカード2output は Alexa が発話する内容、reprompt は Alexa の発話後ユーザが一定時間発話しなかった場合、 Alexa がユーザの発話を促すために発話する内容、should_end_session はセッションを終了するかどうかを示す bool 値です。

まとめ

今回は Alexa Skill のための AWS Lambda 関数を Python で書く方法、具体的には Alexa Skill との情報のやり取りの方法をまとめてみました。
ここで挙げた情報はあくまでごく一部であり、Lambda 関数には他にも様々な機能があるようです。公式の GitHub レポジトリなどが参考になると思います。
次は実際に Alexa Skill を開発した際に問題となったエラーハンドリングについてまとめたいと思います。


  1. Voice User Interface の略  

  2. Alexa アプリや Amazon Echo Show など画面のある Alexa 対応デバイスに表示されるレスポンスのタイトル