LoginSignup
1
2

More than 3 years have passed since last update.

Amazon PersonalizeをAlexaから呼び出してみる

Last updated at Posted at 2019-09-01

AWS Personalizeのチュートリアルをやってみたついでに、作成したキャンペーンをAlexaから呼び出してみました。

チュートリアルでは、ユーザIDを指定すると映画のレコメンドを返すキャンペーンを作成しますが
これを呼び出すAlexaスキルを作成します。

Amazon Personalizeとは

アプリケーションを使用するユーザー向けにレコメンデーションを簡単に追加できるサービスです。
https://aws.amazon.com/jp/personalize/

必要なもの

  • AWSアカウント
  • Alexa開発者アカウント

手順

  1. Personalizeのチュートリアルを実施する
  2. DynamoDBにメタデータを登録する
  3. Lambdaを作成する
    1. サンプルアプリケーションをデプロイする
    2. ロールを修正する
    3. boto3のLayerを作成・追加する
    4. サンプルアプリケーションのコードを修正する
  4. Alexaスキルを作成する
  5. 完成

1. Personalizeのチュートリアルを実施する

以下を用意します。
https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/getting-started.html#gs-prerequisites

  • S3 Bucketの作成とBucket Policyの設定
  • IAM Roleの作成
  • CSVファイルのダウンロード・加工・アップロード

バケットポリシーは以下の手順で適用する必要があります。

  1. Block public accessを一旦OFFにする
  2. バケットポリシーを適用する
  3. Block public accessをONに戻す

あとはマニュアルの手順通りに作成します。
https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/getting-started-console.html

こんな感じでレコメンデーションが取得できるようになります。
小一時間もあればできると思います。

1_recommend.png

2. DynamoDBにメタデータを登録する

次に、Alexaのレスポンスが映画のIDだけでは寂しいので、Alexaから映画のタイトルとジャンルをレスポンスできるようにDynamoDBに登録します。

DynamoDBに以下のテーブルを作成後、チュートリアルでダウンロードしたzipファイル中にあるmovies.csvをDynamoDBに登録します。

  • table name: movie
  • key: movieId
dynamodb_import.py
import boto3
import csv
import time

dynamodb = boto3.client('dynamodb')


with open('movies.csv') as f:
    moviereader = csv.reader(f, delimiter=',', quotechar='"')
    next(moviereader, None)
    for line in moviereader:
        movieId, title, genres = line
        dynamodb.put_item (
            TableName='movie',
            Item={
                'movieId': {
                    'S': movieId
                },
                'title': {
                    'S': title
                },
                'genres': {
                    'S': genres
                }
            }
        )
        time.sleep(0.2)

3. Lambdaを作成する

3-1. サンプルアプリケーションをデプロイする

まずは、Serverless Application Repositoryからalexa-skills-kit-python36-factskillをデプロイしてLambdaを作成します。

3-2. ロールを修正する

LambdaからPersonalizeとDynamoDBを呼び出すので、上記で指定したIAM RoleにPersonalizeとDynamoDBのポリシーを付与します。

ポリシーの追加

3_2_1role.png

3-3. boto3のLayerを作成・追加する

Lambdaに標準でインストールされているboto3はPersonalizeに対応していないため(2019-08-31時点)、最新のboto3のLayerを作成してLambdaに追加します。

アップロード用のLayerパッケージの作成

mkdir -p boto3/python
cd boto3
echo "boto3==1.9.220" > requrements.txt
pip install -r requrements.txt -t python
zip -r ../boto3.zip .

Layerの作成

3_3_1boto3.png

Layerの追加

3_3_2layer.png

3-4. コードを修正する

get_recommendationsのレスポンスからitemId(映画ID)を取得して、DymamoDBからitemIdをキーにtitleとgenres(ジャンル)を取得しています。

※campaignArnは書き換えてください。
※チュートリアルを流用している関係でレコメンドとAlexaのユーザIDはマッチ出来ないので、レコメンド取得時のユーザIDはランダムで渡しています。

import random
import json
import boto3

from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import (
    AbstractRequestHandler, AbstractExceptionHandler,
    AbstractRequestInterceptor, AbstractResponseInterceptor)
from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response

personalizeRt = boto3.client('personalize-runtime')
dynamodb = boto3.client('dynamodb')
campaignArn = 'arn:aws:personalize:us-east-1:XXXXXXXXXXXX:campaign/ratings-campaign'
sb = SkillBuilder()


def get_recommendations(userId):
    try:
        res = personalizeRt.get_recommendations(
            campaignArn=campaignArn,
            userId=userId
        )
        itemId = res['itemList'][0]['itemId']
        res = dynamodb.get_item(
            TableName='movie',
            Key={
                'movieId': {
                    'S': itemId
                }
            }
        )
        genres = res['Item']['genres']['S'].split('|')
        title = res['Item']['title']['S']
        return (title, genres)
    except Exception as e:
        print(e)
        raise e


class GetMovieHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return (is_request_type("LaunchRequest")(handler_input) or
                is_intent_name("GetMovieIntent")(handler_input))

    def handle(self, handler_input):
        userId = str(random.randrange(611))
        title, genres = get_recommendations(userId)

        speech = 'おすすめの映画は' \
             + title \
             + 'です。' \
             + 'ジャンルは' \
             + 'と'.join(genres) \
             + 'です。'

        handler_input.response_builder.speak(speech).set_should_end_session(
            False)
        return handler_input.response_builder.response


class HelpIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_intent_name("AMAZON.HelpIntent")(handler_input)

    def handle(self, handler_input):
        message = '何かお困りですか。'
        handler_input.response_builder.speak(message).ask(message)
        return handler_input.response_builder.response


class CancelOrStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return (is_intent_name("AMAZON.CancelIntent")(handler_input) or
                is_intent_name("AMAZON.StopIntent")(handler_input))

    def handle(self, handler_input):
        handler_input.response_builder.speak('停止します。')
        return handler_input.response_builder.response


class FallbackIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_intent_name("AMAZON.FallbackIntent")(handler_input)

    def handle(self, handler_input):
        handler_input.response_builder.speak('わかりません。')
        return handler_input.response_builder.response


class SessionEndedRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_request_type("SessionEndedRequest")(handler_input)

    def handle(self, handler_input):
        return handler_input.response_builder.response


class CatchAllExceptionHandler(AbstractExceptionHandler):
    def can_handle(self, handler_input, exception):
        return True

    def handle(self, handler_input, exception):
        handler_input.response_builder.speak('システムエラーが発生しました。')
        return handler_input.response_builder.response


sb.add_request_handler(GetMovieHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelOrStopIntentHandler())
sb.add_request_handler(FallbackIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_exception_handler(CatchAllExceptionHandler())

lambda_handler = sb.lambda_handler()

4. Alexaスキルを作成する

スキルを作成後、以下を指定・追加します。

  • 呼び出し名
  • カスタムインテント
  • エンドポイント(lambdaのARN)

呼び出し名の指定

4_1_invokename.png

カスタムインテントの追加

4_2_intent.png

エンドポイント(lambdaのARN)の指定

4_3_endpoint.png

lambdaのトリガーの修正

エンドポイントの画面に表示されているスキルIDをコピーして、lambdaのトリガーに指定します。
こうすると特定のスキルIDからのみ受け付けるようになります。
トリガーは画面から修正できないので、Alexa Skills Kitトリガーを追加してから古いトリガーを削除します。

4_4_trigger.png

完成

シミュレータはこんな感じになります。

5_1_fin.png

感想

レコメンドがサクッと実装できるって、いい世の中だなぁ。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2