Open7

PythonでAlexaスキルを開発する

TannyTanny

Goal

Alexa公式のチュートリアルスキルをベースとして、以下のような要素を追加したAlexaスキルを開発する。

  1. pipenvでPython仮想環境を構築する
  2. Github ActionsでAWS LambdaのCI/CD環境を構築する
  3. Alexaのタイマー機能を利用する
TannyTanny

pipenvでPython仮想環境を構築

pipenv

$ export PIPENV_VENV_IN_PROJECT=1                                      
$ pipenv install --dev flake8 black isort mypy
$ pipenv install ask-sdk 
  • PIPENV_VENV_IN_PROJECT=1 を設定することで、プロジェクトルートに.venvフォルダを作成する
    • あとでlambda用のzipファイルを作成するときのための設定
  • Pythonのバージョンは明示的に3.9に設定した方がベターかも

リンター・フォーマッター

リンター・フォーマッターの設定はこちらを参照。
https://zenn.dev/tanny/articles/cdb555d6124a2a

TannyTanny

AWS Lambdaのデプロイパッケージ作成

AWS公式のチュートリアルでは、lambdaデプロイ用のzipファイル作成の具体的な方法は記載されていない。
https://developer.amazon.com/ja-JP/docs/alexa/alexa-skills-kit-sdk-for-python/develop-your-first-skill.html#preparing-your-code-for-aws-lambda

こちらの記事の内容を参考にして、手動でデプロイパッケージを作成。
https://pyteyon.hatenablog.com/entry/2019/08/04/204704

zipファイルを手動でlambdaにアップロードする場合は、以下のコマンドを実行。

$ aws lambda update-function-code --function-name {YOUR FUNCTION NAME} --zip-file fileb://lambda.zip --publish
  • --publishでlambdaのバージョニングを行う

AWS CLIの設定について

AWSのクレデンシャル情報は、こちらのページの方法に基づいて管理する。
https://zenn.dev/wakkunn/articles/be748e71d405d1

TannyTanny

Github ActionsでAWS Lambdaへのアップロードを自動化

このサイトの手順を参考にして実装。
https://dev.classmethod.jp/articles/lambda-github-actions/
ただし、「IAMロールの登録」の箇所で、「信頼されたエンティティのサンプル」を以下のように書き換える必要があった。

-  "vstoken.actions.githubusercontent.com:sub": "repo:{GITHUB_ORGANIZATION_NAME}/{GITHUB_REPO_NAME}:*"
+  "token.actions.githubusercontent.com:sub": "repo:{GITHUB_ORGANIZATION_NAME}/{GITHUB_REPO_NAME}:*"
  • vstokentokenに変更

zipファイルの作成も自動化

上記ページの手順に加え、zipファイルの作成部分も自動化する。以下はdeploy.ymlの一部抜粋。

      - name: install pipenv, awscli
        run: |
          pip3 install pipenv
          pip3 install awscli

      - name: run pipenv sync
        run: |
          export PIPENV_VENV_IN_PROJECT=1                                      
          pipenv sync

      - name: make lambda.zip
        run: |
          PROJECT_DIR=$(pwd)
          SITE_PACKAGES_DIR=$(pipenv --venv)/lib/python3.9/site-packages
          echo "Project Location: $PROJECT_DIR"
          echo "Library Location: $SITE_PACKAGES_DIR"
          cd $SITE_PACKAGES_DIR
          rm -rf __pycache__  
          zip -r $PROJECT_DIR/lambda.zip *
          cd $PROJECT_DIR/src
          zip -g ../lambda.zip lambda_function.py
          cd $PROJECT_DIR

      - name: lambda update (DEV)
        if: contains(toJSON(github.ref), 'dev')
        run: |
          aws lambda update-function-code --function-name lambda-alexa-curry-udon-timer --zip-file fileb://lambda.zip

      - name: lambda update (STG)
        if: contains(toJSON(github.ref), 'main')
        run: |
          aws lambda update-function-code --function-name lambda-alexa-curry-udon-timer --zip-file fileb://lambda.zip --publish
  • pipenv syncでローカル環境と同じ環境を構築する。
  • pushするブランチでlambdaへのアップロード処理を分岐する。mainブランチへのpushの場合は、--publishを付与してバージョン管理を行うようにしてみた。(後述)
TannyTanny

AWS Lambdaのバージョン管理

こちらのページの方法を参考に、バージョン管理とエイリアス設定を実施する。

  • 本番Alexaスキル:エンドポイントARNはエイリアス設定した修飾ARNを指定し、利用するlambdaのバージョンを固定
  • 検証Alexaスキル:エンドポイントARNは非修飾ARN($latest)を指定し、devブランチにプッシュした最新のlambdaを利用
    https://zenn.dev/amarelo_n24/articles/158a166d832487
TannyTanny

Alexaスキルにタイマーを追加する

実装方法はこちらを参照した。
https://developer.amazon.com/ja-JP/docs/alexa/smapi/alexa-timers-overview.html
https://kun432.hatenablog.com/entry/alexa-timer-api-1
https://developer.amazon.com/ja-JP/blogs/alexa/alexa-skills-kit/2021/08/add-timer-to-alexa-skill

上記のサンプルはNode.jsで実装されている。Pythonで書き換えた実装は以下の通り。

タイマーの実装サンプル
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def launch_request_handler(handler_input: HandlerInput) -> Response:

    request_envelope = handler_input.request_envelope
    service_client_factory = handler_input.service_client_factory
    permissions = request_envelope.context.system.user.permissions

    if not (permissions and permissions.consent_token):
        # パーミッションがない場合の処理
        pass
    else:
        # タイマーをセットする
        timer1 = {
            "duration": "PT3M",
            "timerLabel": "タイマー1",
            "creationBehavior": {"displayExperience": {"visibility": "VISIBLE"}},
            "triggeringBehavior": {
                "operation": {
                    "type": "ANNOUNCE",
                    "textToAnnounce": [
                        {"locale": "ja-JP", "text": "タイマー終了時の発話"}
                    ],
                },
                "notificationConfig": {"playAudible": True},
            },
        }
        try:
            timer_service_client = service_client_factory.get_timer_management_service()
            timer_response = timer_service_client.create_timer(timer1)
        except Exception as e:
            logger.error("タイマー作成エラー", e)

    speech_text = "スピーチテキスト"
    return (
        handler_input.response_builder.speak(speech_text)
        .set_card(SimpleCard("カードのタイトル", speech_text))
        .set_should_end_session(False)
        .response
    )

StandardSkillBuilder

get_timer_management_service()を実行するために、チュートリアルに記載されているSkillBuilderStandardSkillBuilderに変更する必要がある。

from ask_sdk.standard import StandardSkillBuilder
sb = StandardSkillBuilder()