Alexaスキル開発に入門してみた

Progateの@sy-tenchoです。この記事は Progate Advent Calendar 2021 の24日目です。このネタはもう何度も書き尽くされている気もしますが、はじめてAlexa開発に入門してみたという話です。

ことの発端

先日のAmazonブラックフライデーセールでスマートスピーカーのEchoシリーズが安くなっていたのでEcho showとEcho dotを購入し、元から持っていたEcho dotと合わせて計3台のEchoが家の中で稼働するという状態になりました。それぞれリビング、キッチン、寝室に配置したので家のどの場所から「アレクサ!」と叫んでも大体反応してくれる状態になりました。

ところが使い道はというと音楽やラジオを聴く、パスタを茹でるときにタイマーをかけるといった程度で全く有効活用できていませんでした。そこでもっとなにか便利な機能を試してみようと思い、手始めに買い物リストを使ってみることにしました。僕は週に2~3回スーパーに行って数日分の食材や日用品をまとめて買うのですが、メモを持っていかないので高い確率で買い忘れが発生していました。そこで家事をしていて食材や洗剤を使い果たしたタイミングや、次の買い物で買うべきものを思いついた時にAlexaを起動し、買い物リストにアイテムを追加しておけば買い忘れを削減できるだろうと考えました。

買い物リストとリマインダーの同期問題

Alexaには買い物リスト機能が用意されていて、音声で追加したアイテムをAlexaアプリかブラウザで確認することができます。しかしこの機能をそのまま使ってもあまりおもろくないだけではなく、リスト専用のネイティブアプリがないと買い物に行った時に使いづらかったので、IFTTTを使ってiOSリマインダーと連携することにしました。Echoが3台あるおかげで家の至るところから声で起動できるというインターフェースはなかなか都合が良く、家事をしていて手が離せない時でもリマインダーにアイテムを追加できるのは大変便利でした。

f:id:sy-tencho:20211223144635p:plain
構成図1

ところが、何回か使っているうちにある問題に気づきました。それは買い物を終えてリマインダーにチェックを入れてもAlexa側のリストからアイテムが削除されないということです。すごく些細なことなのですが、リストがどんどん溜まっていってしまうのは気持ち悪いし、定期的に手動で全部消すのも面倒なので以下のような解決方法を考えました。①リマインダー側にチェックが入ったら、リストのアイテムも削除する仕組みを入れる ②定期的にリストを掃除するバッチを書く また、この問題に苦しんでいるのは僕だけではなく ③「アレクサ、xxxを追加して」と言った直後に「アレクサ、xxxを削除して」と言うことでリストからアイテムを削除するという解決方法を紹介してる方もいました。

①についてはリマインダーがリスト側のメタデータ(item_id など)を持っていないので実現が難しく、②と③に関しても無駄な運用コストやオペレーションがコストがかかるのであまり良い解決方法だとは思えませんでした。しかし元を正すとデータがリストとリマインダーの二重管理になっているのが根本的な問題であり、Alexaとリマインダーが直接連携できれば良いのではないかと思い始めました。

解決方法

ものすごく前置きが長くなりましたがここからがやっと本題です。「アレクサ、洗剤を追加して」と言ったら、リストを経由せずにリマインダーに直接「洗剤」を追加する仕組みがあれば問題は解決します。そのようなツールが世の中になかったので自分でAlexaスキルを作ることにしました。構成は至極シンプルで、「アレクサ、洗剤を追加して」と言ったら「洗剤」の部分を抽出してIFTTTのAPIにリクエストを送り、IFTTTからリマインダーに「洗剤」を追加するというものです。

f:id:sy-tencho:20211223145121p:plain
構成図2

簡単なAlexaスキルを作ってみた

まずはAmazon Developer Servicesにログインします。この時にEchoを購入したアカウントでログインするとスキルをテストするときに自分のデバイスと自動で連携されるのおすすめです。Alexaスキルには「カスタムスキル」、「スマートホームスキル」、「音楽スキル」など様々な種類がありますが今回は「カスタムスキル」を選択します。適当な呼び出し名を選択したら、インテントを作成します。インテントはAlexaで実行される特定のアクションの定義です。今回だと音声入力されたアイテムをリクエストに含めて、外部APIを叩くという処理です。インテントを追加したらサンプル発話とスロットを追加します。サンプル発話は「アレクサ、xxx」の「xxx」の定義で、呼びかけの文言が多少ブレても起動するように複数登録しておきます。スロットはインテントに渡せる変数です。「アレクサ、洗剤を追加して」と言ったときに「洗剤」の部分だけを抽出できるよにスロットを登録しておきます。

f:id:sy-tencho:20211223145815p:plain
インテントとスロットのイメージ

裏側ではAWS Lambdaが起動するので適当なブループリントを見つけてきて、追加したインテント処理を書きます。色々省略して雑なコードですがイメージはこんな感じです。

import ask_sdk_core.utils as ask_utils
import requests
import ast

from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler

...

class IntentReflectorHandler(AbstractRequestHandler):
  def can_handle(self, handler_input):
    return ask_utils.is_request_type("IntentRequest")(handler_input)

  def handle(self, handler_input):
    item = ask_utils.get_slot_value(handler_input=handler_input, slot_name="item")

    secret = ast.literal_eval(get_secret())
    url = 'url_to_ifttt/{}'.format(secret['api_key'])
    payload = {"Value1": item}

    res = requests.post(url, json=payload)
    speak_output = "買い物リストに " + item + "を追加しました。"

    try:
      res.raise_for_status()
    except RequestException as e:
      logger.error(e.response.text)
      speak_output = item + "の追加に失敗しました。"

    return (
      handler_input.response_builder
        .speak(speak_output)
        .response
      )

...

def get_secret():
  ...

...

sb = SkillBuilder()
sb.add_request_handler(IntentReflectorHandler())

lambda_handler = sb.lambda_handler()

スキル開発用に使えるAWSの機能はLambda, S3, DynamoDB, CloudWatch Logsに制限されているので、それ以外のサービスを使いたい場合は個人で所有しているAWSアカウントを使います。今回はAPIを叩くときのキーを個人で所有しているAWSアカウントのKey Management Serviceに配置して使うことにしました。

次にIFTTTでアップレットを作成します「If This」でWebhookを選択し、「Receive a web request」を選択します。ハマりポイントは「Receive a web request with a JSON payload」を選択すると好きなJSONスキーマでリクエストを受けることができますが、連携先で値だけ抽出して使うことができないことです。逆に「Receive a web request」ではJSONのキーはValue1, Value2, Value3しか利用できませんが、値だけを抽出して使うことができます。今回は{Value1: "洗剤"}のようなJSONスキーマでリクエストを受け取って「洗剤」だけをリマインダーに送信したいので、値をだけを抽出できる「Receive a web request」を使います。

f:id:sy-tencho:20211223150432p:plain
Webhookを選択

最後に「Then」でiOS Remindersの「Add reminder to list」を選択し、IngredientとしてValue1を追加すると受け取ったJSONのValue1キーの値だけをリマインダーに追加することができます。

f:id:sy-tencho:20211223150638p:plain
リマインダーとの連携

コード等の修正が済んだら開発用コンソールからデプロイボタンを押し、数秒でデプロイが完了すると手持ちのEchoでスキルが利用可能になります。Alexaスキルは一般公開もできますが今回は個人で使う目的なのでこれで実装は完了です。

まとめ

今回は結果的に簡単なAlexaスキルを作って、日常をほんの少しだけ快適にすることができました。世の中にはとても便利なツールがたくさん存在しますが、自分の環境やニーズにピタッとはまるものを見つけることができない時も度々あります。プログラミングの良いところはそんな時に自分で手を動かして、カスタマイズして自分にとって本当に必要なものを作ることができる点だと思います。このようなプログラミングの楽しさを伝えるプロダクトの開発に興味がある方をProgateでは募集しています!

https://prog-8.com/about/careers