この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
AIソリューション部の平内(SIN)です。
Amazon Lex(以下、Lex)では、 validation及び、FulfillmentでLambdaファンクションを呼び出すことが可能であり、きめ細かいスロットのバリデーションや、インテント完了時の処理を定義することができます。
Lexの仕組みは、インテント、スロット、ダイアログモードなど、その骨格はAmazon Alexa(以下、Alexa)に非常に似ていますが、Alexa SDKのような環境がないため、既に、Alexaの開発に慣れている方にも、Lex開発は、ちょっと戸惑うというのが現状かも知れません。
本記事で紹介する、Lex SDKは、この問題を解決するため、Alexa SDKと同じようなスタイルでLex開発することを目標としたSDKであり、JavaScript及び、TypeScriptで利用可能です。
https://github.com/furuya02/lex-sdk
今回は、昨日、このLex SDKに追加された、セッション情報や、永続化情報を簡単に扱うことができるAttributesManagerクラスの利用方法について紹介させて頂きます。
2 AttributesManagerクラス
AttributesManagerは、アトリビュートの管理のためのクラスであり、下記の3種類のデータを扱うことが出来ます。
- リクエストアトリビュート(1リクエストのライフサイクルでのみ存続)
- セッションアトリビュート(スキルセッションが継続している間に存続)
- 永続アトリビュート(セッションのライフサイクルを終了しても存続)
利用可能なメソッドなメソッドは以下の7つです。
// リクエストアトリビュート用
getRequestAttributes() : {[key : string] : any};
setRequestAttributes(requestAttributes : {[key : string] : any}) : void;
// セッションアトリビュート用
getSessionAttributes() : {[key : string] : any};
setSessionAttributes(sessionAttributes : {[key : string] : any}) : void;
// 永続アトリビュート用
getPersistentAttributes() : Promise<{[key : string] : any};
setPersistentAttributes(persistentAttributes : {[key : string] : any}) : void;
savePersistentAttributes() : Promise<void>;
AttributesManagerは、Lex.HandlerInputのプロパティとして定義されており、必要な情報(アトリビュー)を取得(若しくは、保存)に利用することが出来ます。
下記は、SampleIntentのハンドラで、Lex.HandlerInput経由で取得したAttributesManager使用して、セッション情報を取得している例です。
const SampleIntentHandler: Lex.RequestHandler = {
canHandle(h: Lex.HandlerInput) {
return (h.intentName == 'SampleIntent')
},
async handle(h: Lex.HandlerInput) {
// セッション情報の取得
let attributes = await h.attributesManager.getPersistentAttributes();
3 動作確認用ボット
特に何もないテスト用のボットで動作確認していきます。テスト用のボットは、以下のようになっています。
- インテントは、SmpleIntentのみ
- サンプル発話は「Hello」のみ
- スロットなし
- validation及び、FulfillmentにLambda(LexSmaple)を設定
4 リクエストアトリビュート
最初に、1回のリクエストのライフサイクルでのみ存続するデータであるリクエストデータを使うサンプルです。
リクエストデータは、getRequestAttributes()で取得し、setRequestAttributes()で保存できます。
ハンドラ間でデータのやり取りができていることを確認するために、すべてのハンドラの前に処理を挿入できるaddRequestInterceptors()で追加したハンドラ(requestInspectorHandler)でデータを設定し、それを別のハンドラ(SampleIntentHandler)で使用しています。
import * as Lex from 'lex-sdk';
let bot: Lex.Bot;
exports.handler = async function (event: Lex.IntentRequest, context: any) {
console.log(JSON.stringify(event));
if (!bot) {
bot = Lex.BotBuilder()
.addRequestHandlers(SampleIntentHandler)
.addRequestInterceptors(requestInspectorHandler)
.create();
}
return bot.invoke(event, context);
}
const requestInspectorHandler: Lex.RequestInterceptor = {
process(h: Lex.HandlerInput) {
// リクエスト情報の設定
let attributes = h.attributesManager.getRequestAttributes();
attributes.speak = 'test'; // リクエストデータとして、speakを格納する
h.attributesManager.setRequestAttributes(attributes);
}
}
const SampleIntentHandler: Lex.RequestHandler = {
canHandle(h: Lex.HandlerInput) {
return (h.intentName == 'SampleIntent')
},
async handle(h: Lex.HandlerInput) {
// リクエスト情報の取得
const attributes = h.attributesManager.getRequestAttributes();
const speak = attributes.speak; // リクエストデータのspeakを取り出す
if (h.source === Lex.InvocationSource.DialogCodeHook) {
return h.responseBuilder
.getDelegateResponse(h.slots)
} else { // FulfillmentCodeHook
const message = { contentType: Lex.ContentType.PlainText, content: speak};
return h.responseBuilder
.getCloseResponse(
Lex.FulfillmentState.Fulfilled,
message)
}
}
}
動作は、以下のようになっています。
5 セッションアトリビュート
リクエストアトリビュートは、あまり使う場面が無いかも知れませんが、セッションデータは、比較的利用頻度が高いのではないでしょうか。
リクエストデータは、getSessionAttributes()で取得し、setSessionAttributes()で保存できます。
setSessionAttributes()で設定されたアトリビュートは、ResponseBuilderがレスポンスを生成する際に、自動的にSttributeに追加されます。
import * as Lex from 'lex-sdk';
let bot: Lex.Bot;
exports.handler = async function (event: Lex.IntentRequest, context: any) {
console.log(JSON.stringify(event));
if (!bot) {
bot = Lex.BotBuilder()
.addRequestHandlers(
SampleIntentHandler)
.create();
}
return bot.invoke(event, context);
}
const SampleIntentHandler: Lex.RequestHandler = {
canHandle(h: Lex.HandlerInput) {
return (h.intentName == 'SampleIntent')
},
async handle(h: Lex.HandlerInput) {
if (h.source === Lex.InvocationSource.DialogCodeHook) {
return h.responseBuilder
.getDelegateResponse(h.slots)
} else { // FulfillmentCodeHook
// リクエスト情報の取得
const attributes = h.attributesManager.getSessionAttributes();
if(attributes.counter == undefined) {
attributes.counter = 0;
}
attributes.counter = Number(attributes.counter) + 1;
h.attributesManager.setSessionAttributes(attributes);
let speak = `カウンターは、${attributes.counter}です。`
const message = { contentType: Lex.ContentType.PlainText, content: speak};
return h.responseBuilder
.getCloseResponse(
Lex.FulfillmentState.Fulfilled,
message)
}
}
}
上記のコードを実行した結果です。コンソール上でのテストでは、Clear chat historyを押すまでは、同一セッションとして動作するため、カウンターがインクリメントされている様子が確認できます。
6 永続アトリビュート
最後に永続アトリビュートです。セッションに関係なく、ボットとして永続的にデータを保持したい場合に利用できます。
永続データは、getPersistentAttributes()で取得し、setPersistentAttributes()で保存でき、savePersistentAttributes()で永続化(DB上書き)されます。setPersistentAttributes()では、まだ、DBへ書き込まれていないことにご注意下さい。
(1) テーブル定義
なお、永続アトリビュートを使用する場合、.withTableName()による、DynamoDBのテーブルの定義が必要です。.withTableName()の第1パラメータは、テーブル名であり、第2引数は、主キーに格納するデータです。
Alexa SDKの場合、主キーに格納する値は、デフォルトでUserIdが利用され、各ユーザーごとのデータが保持される仕組みとなっていますが、Lexでは、ユーザーを識別するという概念自体が存在しないため、ここでは、値を明示的に指定して、ボットに固有のデータとして保存する仕様としています。
※ 実は、LexにのUserIdがあるのですが、この値は、クライアント側アプリが自由に設定できるため、ユーザーの識別に利用できる保証はありあせん。
exports.handler = async function (event: Lex.IntentRequest, context: any) {
console.log(JSON.stringify(event));
if (!bot) {
bot = Lex.BotBuilder()
.addRequestHandlers(SampleIntentHandler)
.withTableName("tableName", "TestBot") // テーブル名指定
.withAutoCreateTable(true) //テーブル作成もスキルから行う
.create();
}
return bot.invoke(event, context);
}
(2) 利用例
永続データを保存・取得しているサンプルです。
import * as Lex from 'lex-sdk';
let bot: Lex.Bot;
exports.handler = async function (event: Lex.IntentRequest, context: any) {
console.log(JSON.stringify(event));
if (!bot) {
bot = Lex.BotBuilder()
.addRequestHandlers(
SampleIntentHandler)
.withTableName("SampleBotTable", "SampleBot") // テーブル名指定
.withAutoCreateTable(true) //テーブル作成もスキルから行う
.create();
}
return bot.invoke(event, context);
}
const SampleIntentHandler: Lex.RequestHandler = {
canHandle(h: Lex.HandlerInput) {
return (h.intentName == 'SampleIntent')
},
async handle(h: Lex.HandlerInput) {
if (h.source === Lex.InvocationSource.DialogCodeHook) {
return h.responseBuilder
.getDelegateResponse(h.slots)
} else { // FulfillmentCodeHook
// 永続情報の取得
const attributes = await h.attributesManager.getPersistentAttributes();
if(attributes.counter == undefined) {
attributes.counter = 0;
}
attributes.counter = Number(attributes.counter) + 1;
// 永続情報の保存
h.attributesManager.setPersistentAttributes(attributes);
await h.attributesManager.savePersistentAttributes();
let speak = `カウンターは、${attributes.counter}です。`
const message = { contentType: Lex.ContentType.PlainText, content: speak};
return h.responseBuilder
.getCloseResponse(
Lex.FulfillmentState.Fulfilled,
message)
}
}
}
実行すると、下記のようになります。 Clear chat historyを押して、セッションを新たに開始しても、カウンターは初期化されません。
DynamoDBでは、永続データが保存されていることを確認できます。
(3) DynamoDBの定義
Lsx SDKでDynamoDBを使用すると、デフォルトで、Lambdaの配置されたリージョンのDynamoDBが利用されます。 これを、変更したい場合や、ローカルの試験中に別のパーミッションで動作させたい場合などは、DynamoDBオブジェクトを自前で定義することも可能です。
import * as Lex from './lex-sdk';
const AWS = require('aws-sdk');
AWS.config.credentials = new AWS.SharedIniFileCredentials({profile: 'my-profile'});
let bot: Lex.Bot;
exports.handler = async function (event: Lex.IntentRequest, context: any) {
console.log(JSON.stringify(event));
if (!bot) {
bot = Lex.BotBuilder()
.addRequestHandlers(SampleIntentHandler)
.withTableName("SampleBotTable", "SampleBot") // テーブル名指定
.withAutoCreateTable(true) //テーブル作成もスキルから行う
.withDynamoDbClient(
new AWS.DynamoDB({ apiVersion: "latest", region: "ap-northeast-1" })
)
.addErrorHandlers(ErrorHandler)
.create();
}
return bot.invoke(event, context);
}
7 最後に
今回は、セッション情報や、永続化情報を簡単に扱うことができるAttributesManagerクラスの利用方法について紹介させて頂きました。
Lex SDKは、まだまだ、雑な実装ですが、逐次進化できるように頑張りたいと思います。
弊社ではAmazon Connectのキャンペーンを行なっております。
4月に引き続き、5月も「無料Amazon Connectハンズオンセミナー」を開催予定です。導入を検討されておられる方は、是非、お申し込み下さい。
また音声を中心とした各種ソリューションの開発支援も行なっております。