Edited at
ClojureDay 11

ClojureScriptでAlexaスキルを書いてみた

More than 1 year has passed since last update.

みなさま、おはようございます。

人生初のコミュニティのアドベントカレンダー(= 会社のじゃないやつ)でドギマギしている toku345 です。

この記事は Clojure Advent Calendar 2017 の 11日目の記事です。

さて今回は、近頃盛り上がりを見せている Amazon Echo と好きなように会話するために ClojureScript (以降cljs) で Alexa Skill を書いて、公開するまでをご紹介します。

どこからか「素のJSで書いたほうが楽だよー」みたいな声も聞こえて来そうですが、完全に私の趣味なので悪しからず。。。

では、始まり始まり〜


何を作ったの?

せっかくcljsで書くのだから、cljsに関係するものがいいなということで...

問いかけると最新のcljsのバージョンを教えてくれるスキルを作ってみました。


Alexa って何? Echoと何が違うの?


Amazon Alexa

クラスメソッドさんのこちらの記事の説明がすごくわかりやすく、引用させていただくと


Amazon Alexaとは、Amazonが提供する、クラウドベースの音声認識サービスです。Alexaに対応したデバイスが認識した音声はクラウドサービスに送信されます。クラウドサービスは音声をテキスト変換し、そのテキストを処理し、処理結果をデバイスに返して音声として再生されます。


Amazon Echo ≠ Alexa で、Echoは Amazon が販売している Alexa 対応ガジェットという位置づけみたいですね。


Alexa Skill

こちらも先程のクラスメソッドさんの記事から引用させていただくと、


Alexa Skillとは、Alexa対応デバイスから音声として送られてきて、Alexaが音声から変換したテキストに対する処理を実行するプログラムです。

Amazon自体、またはサードパーティが開発し公開しています。SkillはLambda Functionで実装されます。


Alexa Skill のエンドポイントとしては Lambda 以外にも、任意のHTTPSエンドポイントを設定できるみたいですが、今回はお手軽な Lambda を使って書くことにします。


なぜ Clojure じゃなくて ClojureScript を選んだの?

Lambda上でのClojure/cljsのパフォーマンスを比較したこちらサイトでの検証結果をみると

Lambda
RAM
First call
Once warm

Clojure
256MB
8900ms
1500ms

ClojureScript
128MB
2300ms
1600ms

(※ 上記サイトからの引用)

Clojure(バックエンドはJVM)だと初動がものすごく遅いため、そんなに頻繁に使われないような Skill には向いてなさそうだと判断し ClojureScript で書くことを決めました。1


Alexa Skill を書き始める前にハマったこと

Alexa Skill Kit というSDKが公式に Node.js/Java向けに公開されているのでこれを使いたかったのですが、npmパッケージを cljs から呼び出すためにちょっとハマっちゃいました...

詳しくはこちらの私の記事を参照ください。

ClojureScript内からnpmモジュールを呼び出す(npm-deps)


自作Alexa SkillをEcho実機で喋ってもらうまで

自作Alexa SkillをEcho実機で喋ってもらうまでの大まかな流れは以下のとおりです。

前準備


  1. Amazon Developerアカウント作成

  2. AWSアカウント作成

Alexa Skillの公開、並びに実機でのテストにはAmazon Developerアカウントが必要となります。

※ AWSアカウントとは違うので注意!

Amazon Developerアカウント作成は こちらの記事のステップ1を、AWSアカウント作成は こちらの記事で詳しく説明されていますので、ここでは割愛します。

スキル開発〜登録


  1. Skillの作成

  2. SkillのバックエンドとなるLambda関数を準備

  3. Skill に Lambda関数を連携させる

  4. サービスシュミレーターで動作確認

  5. その他設定を入力

  6. テスター設定


1. Skillの作成

Amazon Developerコンソールにサインインし、「Alexa」メニュー(①)をクリック。

画面遷移後、Alexa Skill Kitの「始める」ボタン(②)をクリック。

右上の「新しいスキルを追加する」ボタン(③)をクリック。


スキル情報

以下のように設定する。

項目
設定値
備考

スキルの種類
カスタム対話モデル
-

言語
Japanese
-

スキル名
cljsバージョン確認
任意の値でOK

呼び出し名
バージョン確認
Echoに呼びかける際にキーとなるので注意!

※ グローバルフィールドはそのままでOK

設定後、「保存」ボタンをクリック。

保存が終わると「次へ」ボタンが現れるのでクリック。


対話モデル

こちらは以下のように設定する。


インテントスキーマ

{

"intents": [
{
"intent": "CljsVersionIntent"
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}

※ カスタムスロットタイプ関係は空欄のままでOK


サンプル発話

CljsVersionIntent シーエルジェーエス の 最新のバージョン を 教えて

CljsVersionIntent クロージャースクリプト の 最新のバージョン を 教えて
CljsVersionIntent cljs の 最新のバージョン を 教えて
CljsVersionIntent ClojureScript の 最新のバージョン を 教えて
CljsVersionIntent 最新バージョン を 教えて
CljsVersionIntent 最新バージョン を 調べて
CljsVersionIntent 最新バージョン は どう
CljsVersionIntent 最新バージョン は
CljsVersionIntent バージョンチェック して


インテントスキーマ、カスタムスロットタイプについてはAlexaスキル開発トレーニングがわかりやすいので、こちらの記事を参照ください...

設定後、「保存」→「次へ」ボタンをクリック。

次のページで Lambda関数のエンドポイントが必要になるので、一旦ここまででストップ。


2. SkillのバックエンドとなるLambda関数を準備

AWSコンソールにサインイン後、Lambdaのダッシュボードを開き「関数の作成」ボタンをクリック。


関数の作成

「設計図」を選択し(①)、検索欄に「Alexa」を入力しEnterを押す(②)。

検索結果から「alexa-skill-kit-sdk-factskill」を選択(③)。


関数の設定

項目
設定値
備考

名前
cljsVersionNotify
任意の値でOK

ロール


←を選択するとロール作成用の別ページが開くので「許可」をクリック

まだこの段階では Lambda 関数のコードは変更できないので、右下の「関数の作成」ボタンをクリック。


トリガーの追加

Alexa からの呼び出しイベントに対応するために、トリガーを設定します。

左のトリガー 一覧から「Alexa Skills Kit」を選択。

アクセス許可を設定する必要があるので、トリガー設定にある「追加」ボタンをクリック。

さあ、これで Lambda関数に関する設定ができました。

次はサンプルコードを自作コードに置き換えましょう!


関数を書く

ここからが楽しい実装の時間ですね

今回、ターゲットはブラウザではなく Node.js なので、figwheel-node テンプレートを使います。

$ lein new figwheel-node cljs-version-notify

project.clj の :cljsbuild -> :builds の "dev""prod" に :npm-deps と :install-deps オプションを追加。


project.clj

diff --git a/project.clj b/project.clj

index 6ad593a..fda3880 100644
--- a/project.clj
+++ b/project.clj
@@ -25,6 +25,8 @@
:output-to "target/js/compiled/cljs_version_notify.js"
:output-dir "target/js/compiled/dev"
:target :nodejs
+ :npm-deps {:alexa-sdk "^1.0.23"}
+ :install-deps true
:optimizations :none
:source-map-timestamp true}}
{:id "prod"
@@ -33,6 +35,7 @@
:output-to "server.js"
:output-dir "target/js/compiled/prod"
:target :nodejs
+ :npm-deps {:alexa-sdk "^1.0.23"}
:optimizations :simple}}]}

:profiles {:dev {:dependencies [[figwheel-sidecar "0.5.13"]


src/cljs_version_notify/core.cljs に以下のように記述。


src/cljs_version_notify/core.cljs

(ns cljs-version-notify.core

(:require [cljs.nodejs :as nodejs]))

(nodejs/enable-util-print!)

(defonce Alexa (nodejs/require "alexa-sdk"))

(def ^:const cljs-latest-version "1.9.946") ; TODO: 最新バージョンを動的に取得したい...
(def ^:const stop-message "クロージャースクリプトの最新バージョン番号が気になったら、また声をかけてくださいね。")

(def skill-handlers #js {"LaunchRequest" (fn [] (this-as this (.emit this "AMAZON.HelpIntent")))
"SessionEndedRequest" (fn [] (this-as this (.emit this "AMAZON.StopIntent")))
"AMAZON.HelpIntent" (fn [] (this-as this
(.emit this
":ask"
(str "クロージャースクリプトの最新バージョン番号をお知らせする非公式のスキルです。"
"クロージャースクリプトの最新バージョン番号をお知らせしましょうか?"))))
"CljsVersionIntent" (fn [] (this-as this
(let [message (str "最新のcljsのバージョンは" cljs-latest-version "です。")]
(.emit this ":tell" message)
(.log js/console (str "message: " message)))))
"AMAZON.CancelIntent" (fn [] (this-as this (.emit this "AMAZON.StopIntent")))
"AMAZON.StopIntent" (fn [] (this-as this (.emit this ":tell" stop-message)))})

(defn- handler [event context callback]
(let [alexa (.handler Alexa event context)]
(.registerHandlers alexa skill-handlers)
(.execute alexa)))

(set! (.-exports js/module) #js {:handler handler})


cljs → js

$ lein cljsbuild once prod

Compiling ClojureScript...
Compiling ["server.js"] from ["src"]...
Successfully compiled ["server.js"] in 25.799 seconds.

プロジェクトのルートディレクトリに server.js という名で出力されます。


出力したJSコードをLambda関数として設定

zipにしてアップロードしても良いのですが、今回は手動で設定しましょう。

注意!

関数エディタが表示されていない場合は、関数名をクリックしてください。

トランスパイルしたJSコードを貼り付け(①)、エディタの変更を保存(②)

コード保存後、ページ右上の「保存」ボタンをクリック。

保存ボタンの上の ARN は次で使うので控えておいてください。


3. Skill に Lambda関数を連携させる

再び Amazon Developerコンソールを開きます。


設定

項目
設定値
備考

サービスエンドポイントのタイプ
AWS Lambda の ARN
-

デフォルト
(環境によって異なる)
Lambda関数設定時に確認したARN値

他の設定は「いいえ」、または未選択でOK。

設定後、「保存」→「次へ」ボタンをクリック。


4. サービスシュミレーターで動作確認

「発話を入力してください」欄に Skillを使う際に話しけたい文章を入力し、「cljsバージョン確認を呼び出す」ボタンをクリック。

右側の サービスレスポンス最新のcljsのバージョンは... と言った文言が含まれていれば動作OK!

実機がある場合は「次へ」をクリック。


5. その他設定を入力

公開情報プライバシーとコンプライアンス をいい感じに入力します。

入力例

公開情報
プライバシーとコンプライアンス



6. テスター設定

5 の設定が終わると 「☆スキルのベータテスト」ボタンが現れるので、お使いのEchoに紐付けたメールアドレス宛にテストリンクを送付できるようになります。





これで実機でテストできるようになりました!


まとめ

長々とお付き合いありがとうございました。

ご覧の通りClojureScript でも簡単に Alexa Skill 書くことができました。

これを機にもっと ClojureScript で書かれた Skill が増えるといいなと思っています。

今回作った Skill は Alexa Skillストアに並んでいるのでぜひ使ってみてください!

コード全体をご覧になりたい方ははこちらをどうぞ

https://github.com/toku345/cljs-version-notify/tree/0.1.0



今回できなかったことリスト


ClojureScript のバージョンを動的に取得する

→ こちらは現在、実装中です...


AWS SAM を使ってデプロイする

→ こちらも実装中です...


Clojureでも実装してみる

Clojure / cljs で Lambda を本格的に使いたいときは k2nakamura さんのこちらスライド CLJ/CLJS向けSERVERLESSソリューションがめっちゃ役に立ちます!





  1. もちろんClojureでもRAMを増やせば立ち上がりも早くなるみたいですが、クラウド破産が怖かったので...