この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
Googleアシスタントでオリジナルの動作を実装するには、Dialogflow を使用します。
Dialogflowでは、通常、バックエンドの処理に、Cloud Functions for Firebase が使われますが、今回は、ここをAWS Lambda(以下、Lambda)及び、Amazon API Wategay(以下、API Wategay)で実装してみました。
最初に動作している様子です。
2 Alexa SDK風の実装
Dialogflow用のSDKであるActions on Google Client Library(actions-on-google) は、そのままではLambdaで使用することはできません。
actions-on-googleをLambdaで実行させるために actions-on-lambda というブリッジ実装もありましたが、 「すっかり慣れてきた、Alexa SDKと同じように書きたい!」 という身勝手で、ここはラッパーを自作することにしました。
次のコードは、自作したラッパーを使用して作成したコードです。既に Alexa SDK(V2) を使ったことがある方なら、ほとんど同じであることが感じて頂けるのでは無いでしょうか・・・
index.ts
import { GoogleCloudDialogflowV2WebhookRequest } from 'actions-on-google';
import { DialogFlow, HandlerInput, RequestHandler } from './DialogFlow'; // 自作ラッパー
const CalcIntentHandler: RequestHandler = {
canHandle(handlerInput: HandlerInput) {
return handlerInput.requestEnvelope.intentName == 'CalcIntent';// CalcIntentを処理する
},
handle(handlerInput: HandlerInput) {
let params = handlerInput.requestEnvelope.parameters;
const birthday = new Date(params.year, params.month - 1, params.day); // 誕生日
const today = new Date(); //今日
const ms = today.getTime() - birthday.getTime(); // 日付差(ミリ秒)
const days = Math.floor(ms / (1000 * 60 * 60 *24)); // ミリ秒から日付へ変換
const speechText = '今日は、あなたが生まれてから、' + (days + 1) + '日目です';
return handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(false)
.getResponse();
}
};
export async function handle(event: GoogleCloudDialogflowV2WebhookRequest, context: any) {
const dialogFlow = new DialogFlow()
.addRequestHandlers(
CalcIntentHandler)
.create();
return dialogFlow.invoke(event, context);
}
3 DialogFlow.ts
DialogFlow.tsは、ちょうどAlexa SDKの部分を代替えするようなラッパーとなっています。
(1) RequestとResponse
actions-on-googleは、TypeScriptで作成されており、Dialogflowからのリクエストとレスポンスが型定義されいます。今回は、その型情報をそのまま利用させて頂きました。
Dialogflowは、最近V2となりましたが、V1とV2では、JSON形式が結構変わっているようです。しかし、この辺はSDKの型情報をそのまま使うことで安全に実装できそうです。
import { GoogleCloudDialogflowV2WebhookRequest } from 'actions-on-google';
import { GoogleCloudDialogflowV2WebhookResponse } from 'actions-on-google';
(2) ハンドラ
処理のメインとなるハンドラのインターフェースは、Alexa SDK(V2)と同じとしました。canHandler()が処理対象となるかどうかの判定で、handle()が実装の本体です。
ただし、レスポンスについては、actions-on-googleで定義されているGoogleCloudDialogflowV2WebhookResponseとしています。
export interface RequestHandler {
canHandle(handlerInput: HandlerInput): Promise<boolean> | boolean;
handle(handlerInput: HandlerInput): Promise<GoogleCloudDialogflowV2WebhookResponse> | GoogleCloudDialogflowV2WebhookResponse;
}
(3) レスポンスビルダー
レスポンスビルダーは、現状、メソッドが下記の3つだけですが、使い方は、Alexa SDK(V2)と同じです。
speak(speechOutput: string): this
withShouldEndSession(val: boolean): this
getResponse(): GoogleCloudDialogflowV2WebhookResponse
export class ResponseBuilder {
private response: GoogleCloudDialogflowV2WebhookResponse;
constructor(){
this.response = {
payload: {
google: {
expectUserResponse: true,
richResponse: {
items: [
{
simpleResponse: {
textToSpeech: ''
}
}
]
}
}
}
}
}
speak(speechOutput: string): this {
if (this.response.payload && this.response.payload.google) {
let google = this.response.payload.google
if (google.richResponse && google.richResponse.items ) {
let items = google.richResponse.items;
if (items.length > 0) {
if (items[0].simpleResponse) {
items[0].simpleResponse.textToSpeech = speechOutput;
}
}
}
}
return this;
}
withShouldEndSession(val: boolean): this {
if( this.response.payload ) {
if(this.response.payload.google ) {
if(this.response.payload.google.expectUserResponse ) {
this.response.payload.google.expectUserResponse = val;
}
}
}
return this;
}
getResponse(): GoogleCloudDialogflowV2WebhookResponse{
return this.response;
}
}
(4) リクエストの処理
Dialogflowからのリクエストは、RequestEnvelopeで処理されます。レクエストについては、ちょっとAlexaと差異が大きいので、インテント名やパラメータをコンストラクタでパースしてしまいました。
export class RequestEnvelope {
intentName: string;
query: string;
parameters: ApiClientObjectMap<any>;
userId: string;
constructor(request: GoogleCloudDialogflowV2WebhookRequest) {
if (request.queryResult){
if( request.queryResult.intent && request.queryResult.intent.displayName ) {
this.intentName = request.queryResult.intent.displayName;
}
if( request.queryResult.queryText ) {
this.query = request.queryResult.queryText;
}
if( request.queryResult.parameters ) {
this.parameters = request.queryResult.parameters;
}
}
if (request.responseId) {
this.userId = request.responseId;
}
// console.log('IntentName: ' + this.intentName);
// console.log('Query: ' + this.query);
// console.log('Parameters: ' + JSON.stringify(this.parameters));
// console.log('UserId: ' + this.userId);
}
}
実装時は、下記のように、RequestEnvelopeから簡単に取り出せます。
let intentName = handlerInput.requestEnvelope.intentName;
let params = handlerInput.requestEnvelope.parameters;
4 Dialogflowの定義
最後になりましたが、Dialogflowでの定義を簡単に紹介させて頂きます。
- Default Welcome Intentは、最初のメッセージを変更しただけです。
- インテントの定義は、CalcIntentだけで、生年月日を取得するようになっています。
- Fullfilmentでは、WebhookにAPI Gateway のEndpointを指定しています。
- Action on Googleでは、呼び出し名を「生まれて何日」とし、Actionsで先Dialogflowに繋いでいます。
5 最後に
今回は、DialogflowのWebhookをLambdaで実装し、Alexaのスキル風に書けるようにしてみました。
音声認識アプリの実装は、各社結構似たところがあるように思います。各社の技術を似た実装で乗り入れる仕組みを用意しておくと、もしかすると工数削減に有効かもしれません。
今回実装したコードは、下記に置きました。参考になれば幸いです。
※簡単に真似ただけですので、Alexa SDKを置き換えるようなものではないことを予めご了承ください。
[GitHub] https://github.com/furuya02/DialogflowOnLambda/