目次
はじめに
Alexa-Hostedスキルのセッションで扱うデータを、S3 (Simple Storage Service)を使って永続化し、ローカル環境でテストする方法を紹介します。ローカルのS3環境は、AWS S3と互換性のあるMinIO Object Storageを使用します。
対象の読者
Alexaのカスタムスキルについて基本的な知識のある方を対象とします。カスタムスキル自体やAlexa開発者コンソールの使い方についての説明は本稿には含みません。
前提条件
ローカル環境の構築については、前記事である「Alexa-HostedスキルをNGROKを使ってローカル環境でテストする」をお読みください。前記事で構築したローカル環境にMinIO環境を追加するものとします。またスキルのテスト方法なども前記事に従うものとします。
永続アトリビュートについて
Alexaのカスタムスキルでは、スキル内で使用するデータをアトリビュートとして管理できます。このアトリビュートには3種類あります。
- リクエストアトリビュート
- セッションアトリビュート
- 永続アトリビュート
参考: アトリビュートの管理
本稿が扱うのは、この中の永続アトリビュートです。永続アトリビュートは、セッションを超えて記憶しておきたい情報です。例えば、ユーザーの好みであるとか、以前の注文の履歴などが相当します。この永続アトリビュートは、PersistenceAdapter
を利用して、ストレージの中に保存できます。
カスタムスキルでは、S3とDynamoDBをストレージとして使った永続化がサポートされています。この記事ではS3を使って永続化します。(初稿の執筆時には、Alexa-Hosted環境ではS3のみがサポートされていたので一択でした。しかし現在はDynamoDBも選択できます。)
ローカル環境からアクセスするS3環境として、AWS S3と互換性のあるMinIO Object Storageを使用します。
カスタムスキルをローカル環境で実行しながら、ネットワーク越しにAWS S3にアクセスする選択肢もあります。ただAlexa-Hostedスキルの場合は、ホストされているAWS S3サービスのエンドポイントやバケットの情報を、スキルの実行時に環境変数から取り出す必要があります。ローカル環境でスキルを実行している間は、これらの環境変数にはアクセスできないため、S3環境もローカルに用意する必要があります。
全体構成
全体構成を下図に示します。

MinIOについて
MinIOは、Amazon S3と互換性のあるストレージサーバーです。クラウド上のサービスの他、幾つかのプラットフォーム向けにランタイムが配布されています。今回はDocker向けのコンテナとして配布されているものを利用します。
事前準備
ツールのインストール
前記事で構築した環境に加えて、下記のツールをインストールしているものとします。
- Docker Compose (こちら)
Docker本体もこちらからインストールします
この他にMinIO Object Serviceのサーバーが必要です。こちらはDockerのコンテナとして提供されているものを使うため、実行時にインストールされます。
追加モジュールのインストール
~/alexa-skill-hoge/lambda
のディレクトリで、下記のコマンドを実行します。
npm install --save ask-sdk-s3-persistence-adapter
npm install --save dotenv
環境変数の設定
カスタムスキルの中で、後述する永続性の組み込みのために、環境変数を参照しています。この環境変数を~/alexa-skill-hoge/lambda/.env
の中に定義します。Webサービスとして呼び出された場合は、この環境変数を参照します。
S3_PERSISTENCE_BUCKET='mys3bucket'
MINIO_ACCESS_KEY_ID = 'myminioadmin'
MINIO_SECRET_ACCESS_KEY = 'myminioadmin'
MINIO_HOST = '127.0.0.1'
MINIO_PORT = 9000
SKILL_LOCAL = true
.envがデプロイされるのを避けるために、.gitignoreに登録しておきます。
lambda/node_modules
.ask/
ask-resources.json
lambda/.env
プロジェクトの作成
前記事で作成したプロジェクトを使用するものとします。
永続性の組み込み
MinIOコンテナの起動設定 (Docker Compose)
MinIOサーバーのコンテナを起動するために、Docker Composeを使います。Docker Composeを使うと、起動時の各種設定を~/alexa-skill-hoge/docker-compose.yaml
ファイルに定義できるので、コンテナの管理が楽になります。
version: "3"
services:
s3-local:
container_name: s3local
image: minio/minio
ports:
- 9000:9000
command: server /data
volumes:
- /data:/data
environment:
- MINIO_ACCESS_KEY=myminioadmin
- MINIO_SECRET_KEY=myminioadmin
環境変数(environment
)の、MINIO_ACCESS_KEY
, MINIO_SECRET_KEY
は、MinIOサーバーによって読み込まれます。ここで設定されているmyminioadmin
は、ブラウザへのログインや、カスタムスキルからのAPIコールでも使用されます。
書き込まれたデータは、ローカルマシンの/data
に書き込まれます。この設定はvolumes
に定義されているので、必要ならば変更してください。Windows環境の場合は、C:/data:/data
のような形式になります。コロンの前がホスト環境、後ろがコンテナ環境のパスになります。
カスタムスキルの変更
カスタムスキルのコード(~/alexa-skill-hoge/lambda/index.js
)を変更して、セッションの結果を保存します。
「ほげを開いて」と発話すると実行されるLaunchRequestHandler
を書き換えて、「○回目の呼び出しです」と応答するようにします。呼び出された回数をS3に記録します。
モジュールの読み込み
必要なモジュールを読み込みます。
const AWS = require('aws-sdk');
const Adapter = require('ask-sdk-s3-persistence-adapter');
require('dotenv').config()
永続化アダプタの構成情報の生成
永続化するデータの読み書きには、ASK SDKの提供するS3PersistenceAdapter
を使用します。S3PersistenceAdapterの作成時に、構成情報を渡す必要があります。この構成情報の中に、S3のエンドポイントやバケット名を指定します。
この構成情報を生成する関数を下記に示します。
function createS3Config() {
const bucketName = process.env.S3_PERSISTENCE_BUCKET;
const isLocal = process.env.SKILL_LOCAL ? true : false;
let s3Config;
if (isLocal) {
console.log('Local Environment');
console.log(`S3 Bucket Name = ${bucketName}`);
const accessKeyId = process.env.MINIO_ACCESS_KEY_ID;
const secretAccessKey = process.env.MINIO_SECRET_ACCESS_KEY;
const minioHost = process.env.MINIO_HOST;
const minioPort = process.env.MINIO_PORT;
console.log(`Access Key = ${accessKeyId}`);
console.log(`Secret Access Key = ${secretAccessKey}`);
console.log(`MINIO Host = ${minioHost}`);
console.log(`MINIO Port = ${minioPort}`);
const s3Client = new AWS.S3({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
endpoint: `http://${minioHost}:${minioPort}/`,
s3ForcePathStyle: true,
signatureVersion: 'v4'
});
s3Config = {
bucketName: bucketName,
objectKeyGenerator: (param) => {return 'myObjectKey';},
// pathPrefix: 'chandora',
s3Client: s3Client
};
}
else {
const bucketName = process.env.S3_PERSISTENCE_BUCKET;
console.log('Remote Environment');
console.log(`S3 Bucket Name = ${bucketName}`);
s3Config = {
bucketName: bucketName
};
}
return s3Config;
}
バケット名の取得
S3のバケット名を取得します。Alexa-HostedスキルがホストするLambda環境では、環境変数S3_PERSISTENCE_BUCKET
にバケット名が設定されています。ローカル環境の場合は、.env
の中に同じ環境変数を定義し、mys3bucket
をバケット名として設定しています。
ローカル環境の判別
ローカル環境で実行されている場合は、環境変数SKILL_LOCAL
がtrue
に設定されています。これを使ってローカル環境かどうかを判別しています。
S3クライアントの作成
ローカル環境で実行されている場合は、S3にアクセスするためのクライアントを作成します。このクライアントは、aws-sdk
の提供しているS3クラス
から作成します。作成時にMinIOサーバーにアクセスするための情報を指定します。それらの情報を~/alexa-skill-hoge/lambda/.env
の中に環境変数として定義しています。
リモート環境で実行されている場合は、PersistenceAdapter
の中でクライアントが作成されるため、何もする必要はありません。
キージェネレータの指定
キージェネレータは、S3にデータを保存する時のキーを提供するものです。何も指定しないと、デフォルトではuser id
をキーに使うようになっています。このuser id
は、Alexaサーバーから送られてきたメッセージに含まれているものです。
ローカル環境で実行されている場合は、キージェネレータを指定して、user id
ではなくmyObjectKey
という固定の文字列を使うように変更しています。
当初ローカル環境でも、キージェネレータを指定していませんでした。しかしS3への書き込みでエラーが発生してしまいました。
エラーについて調べたところ、キーを元にファイルシステムにディレクトリを作成しており、そこでエラーが発生していました。キーの長さが非常に長かったことから、ファイルシステムのパスの文字数の制約に引っかかっている可能性が考えられました。キージェネレータを指定し、短いキーに変更したところエラーが解消しました。厳密な問題判別は行っていませんが、現象的には上記の推測で合っていそうです。
この問題が発生したのは、Windows環境です。Windows環境ではパスの長さは合計で260文字という制約があります。筆者は普段Windows環境を使っているため、この制約に引っかかってしまったようです。なお本稿の内容を検証するために、Ubuntuの環境を構築してテストしています。そちらの環境ではこの問題は再現していません。
スキルを公開すると、複数のユーザーからのリクエストを受け付けるようになります。その場合は、S3に保管されるデータは、ユーザーごとに別れている必要があります。このために、user id
がキーとして使われているもと思われます。これを固定の文字列にしてしまうと、ユーザーごとのデータを保持できません。
ローカル環境のテストでは、単一ユーザーからの発話のみを受け付ける前提であれば、これで問題ありません。もし複数ユーザーからの発話を受け付けながらテストする場合は、Linux環境に構築してキージェネレータを使わないようにしてください。
永続化アダプタの生成とスキルへの組み込み
永続化アダプタを生成します。
const persistenceAdapter = new Adapter.S3PersistenceAdapter(createS3Config());
永続化アダプタをスキルに組み込みます。
const handlers = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
HelloWorldIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
FallbackIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler)
.addErrorHandlers(
ErrorHandler)
.withCustomUserAgent('sample/hello-world/v1.2')
.withPersistenceAdapter(persistenceAdapter);
ハンドラの変更
ハンドラを変更して、呼び出されるたびにカウントアップし、「○回目の呼び出しです」と返事するようにします。
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
async handle(handlerInput) {
const attributesManager = handlerInput.attributesManager;
let s3Attributes = await attributesManager.getPersistentAttributes() || {counter: 0};
let counter = s3Attributes.counter || 1;
const speakOutput = `${counter}回目の呼び出しです`;
++ counter;
s3Attributes.counter = counter;
attributesManager.setPersistentAttributes(s3Attributes);
await attributesManager.savePersistentAttributes();
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
プロジェクトのファイル構成
プロジェクトのディレクトリには、この時点で下記のようなファイルが作成されています。
alexa-skill-hoge ├── docker-compose.yaml ├── lambda │ ├── index.js │ ├── package.json │ ├── server.js │ └── util.js └── skill-package/ ├── interactionModels/ │ └── custom/ │ └── ja-JP.json └── skill.json
ローカル環境の起動
MinIOサーバーの起動
~/alexa-skill-hoge
ディレクトリで、MinIOサーバーを、Docker Composeを使って起動します。
sudo docker-compose up
下記のような画面が表示されたらMinIOサーバーが起動されています。

初回の起動後はバケットを作成する必要があります。ブラウザでhttp://127.0.0.1:9000
にアクセスします。ログイン画面が表示されたら、Access Key
, Secret Key
に、myminioadmin
と入力します。

ログインできたら、右下のプラス(+)ボタンを押し、次にその上に表示されるCreate bucket
ボタンを押します。バケットの名前を求められるので、mys3bucket
と入力します。

バケットが作成されました。

バケットは一度作成すると永続化されるので、MinIOサーバーを再起動しても、再度作成する必要はありません。
カスタムスキルの起動
前記事に従ってカスタムスキルを起動します。
npm start

NGROKの起動
前記事に従ってNGROKを起動します。
ngrok http 3000

エンドポイントの設定
前記事に従ってエンドポイントを設定します。

カスタムスキルのテスト
Alexa開発者コンソールでテスト
タブを選びます。Alexaシミュレータから、「ほげを開いて」と入力します。「1回目の呼び出しです」と返事が返ってきます。何度か「ほげを開いて」と入力してみてください。「○回目の呼び出しです」と回数が1つずつ増えて行けば期待通りに動いています。

おわりに
Alexa-Hostedスキルを、S3によるセッションの永続化も含めて、ローカル環境でテストする方法を紹介しました。
スキルの使い勝手を向上するには、過去のやり取りからユーザーの好みに対応したり、会話のコンテキストを維持しながらマルチターンのやり取りを行うなど、ステートフルな処理が必要になってきます。そのためには、S3を利用したセッションの永続化は、重要な役割を果たします。このS3の永続化も含めてローカル環境でテストできることは、開発者にとって大いに役立つものと思います。
付録. ソースコード
更新記録
日付 | 内容 |
2021/07/17 | docker-compose.yamlファイのVolumesの定義例で、Windows環境の場合”C:\data:/data”としていたものを”C:/data:/data”に変更。VS CodeのDocker Extensionから起動した場合、Volumeの構成が正しく行われないため (本稿のコマンドラインからの起動例では問題ない)。 |
2021/04/11 | ソースコードをGithubに移動 .gitignoreの.envのパスを修正 |
2021/03/14 | タイトル変更 ASK CLI V2に対応した記述に変更 |
2020/02/02 | 初版公開 |