kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

Alexa Conversationsのチュートリアルをやってみた③ コンテキストを維持しながら別のAPIを追加する

f:id:kun432:20200809103200p:plain

Alexa Conversationsのチュートリアルやってみたの第3回です。

今回は、APIからの結果を別のAPIに渡すというのを同じコンテキストの中でやってみたいと思います。

目次

サンプルのダイアログ

"context carry-over"と"User correction"とはそもそも何か?会話のサンプルを見てもらえばわかりやすいかと思います。

Alexa, open pet match.
(アレクサ、ペットマッチをひらいて)

Welcome to pet match. I can find the best dog for you. What are two things you’re looking for in a dog?
(ペットマッチにようこそ。あなたにピッタリの犬を教えます。あなたが求める犬の条件を2つ教えてください。)

I want a large dog with high energy.
(大きくて、活発なのがいいな)

Do you want a dog that is good with family or better at guarding?
(人懐っこいのと、番犬向きとどちらがいいですか?)

Family.
(人懐っこいやつ)

Okay, in that case I recommend a Chihuahua.
(それならチワワがおすすめです。)

What is that?
(それってどんなやつ?)

The Chihuahua is one of the smallest dog breeds, bred in the Chihuahua region of Mexico...
(チワワは、メキシコのチワワ地方で生まれた小型犬種の1つです。...)

ちょっと文章と回答があってないのは気にしないでください。ポイントは、4行目でAPIにおすすめ犬種をリクエストしたあと、その犬種を元にして、6行目でさらに犬種の説明をAPIから取得する、ということですね。

前回の"context carry-over"と同じく、ダイアログモデルで同じことをする場合にはセッションアトリビュートを使う必要があり、かつ、こういった機能を増やしていくとその機能ごとに実装が必要になります。ということでAlexa Conversationsならこれがシンプルにできるということみたいですね。やってみましょう。

ダイアログの編集

今回もまずダイアログを修正していきます。Alexa ConversationsのDialogsをひらいて、dialog1の「編集」をクリックします。

f:id:kun432:20200817221423p:plain

以下の会話を追加しましょう。

U: What is that?
A: A chihuahua is ...

f:id:kun432:20200817221959p:plain

とりあえず保存しておきましょう。

f:id:kun432:20200817221933p:plain

スロットタイプの作成

今回、ユーザの発話についてはスロットのアノテートは行う必要はありません。ユーザの発話にはスロットが含まれないからですね。 逆にAlexaの発話にはスロットのアノテートが必要です。こちらはAPIから犬種の説明という新しい情報が入っているからですね。Alexa ConversationsではAPIからの戻りの型もスロットタイプとして定義します。左のメニューから「スロットタイプ」メニューを開いて「+スロットタイプ」をクリックします。

f:id:kun432:20200817222412p:plain

まず、普通に犬種の説明が入るスロットタイプを作成します。"Create a custom slot type with values"を選択して、スロットタイプ名は"stringLiteral"にします。"Next"をクリックします。

f:id:kun432:20200817223548p:plain

スロット値は"dummy"と入れておきます。ユーザの発話ではなく、APIから文字列を取るだけなのでこういう使い方なんでしょう。スロット値を登録したら"Save"します。

f:id:kun432:20200817223735p:plain

もう一つ、今度はAPIのレスポンスを入れておくためのスロットタイプを作成します。"Create a custom slot type with properties"を選択、スロットタイプ名は"getDescriptionResult"とします。"Next"をクリックします。

f:id:kun432:20200817222655p:plain

"Add a new property"をクリックして、プロパティ名は"description"、スロットタイプはさきほど追加した"stringLiteral"を選択します。最後に"Save"してください。

f:id:kun432:20200817224212p:plain

発話セット

5行目のユーザの発話に対して、発話セットを設定していきましょう。これはダイアログ画面からインラインでも編集ができます。ダイアログメニューからdialog1をひらいてください。

f:id:kun432:20200817221423p:plain

"What is that?"をクリックすると、右にメニューが出てきます。まずダイアログアクトです。これはAPIを再度実行するので"Invoke APIs"を選択します。

f:id:kun432:20200817224648p:plain

発話セットを選択するドロップダウンから"Create New Utterance Set"を選択します。

f:id:kun432:20200817224904p:plain

発話セットの作成画面がインラインで開きますので設定していきます。発話セット名は"invoke_getDescription_recommendationResult"にします。APIはちょっと置いといてください。

f:id:kun432:20200817225157p:plain

下にスクロールして、以下のサンプル発話を登録していきます。一つづつ登録しても良いですし、バルクインポートしてもいいですね。登録したら"Save"します。

what's that
what is that
what is it
what
tell me about that

f:id:kun432:20200817230208p:plain

ダイアログのページでも"Save"しておいてください。

f:id:kun432:20200817231740p:plain

API定義

では、犬種の説明を行うAPIを定義していきましょう。ダイアログの6行目をクリックして、右のメニューで、ダイアログアクトは"Notify Success"、APIは"Create a new API"を選択します。

f:id:kun432:20200817232037p:plain

インラインでAPI新規作成画面が開きました。新しいAPI名は"getDescription"にします。

f:id:kun432:20200817232340p:plain

次に引数です。犬種の説明のためには犬種名が引数として必要になりますが、これは最初のAPIで取得した"getRecommendationResult"に入っていますので、それをそのまま渡すことができます。つまり、"context carry-over"がここでも有効になるわけですね。 "Add a new argument"をクリックして、引数名に"getRecommendationResult"、スロットタイプも"getRecommendationResult"を選択します。

f:id:kun432:20200817233426p:plain

次にレスポンステンプレートを作成しますが、ここはちょっとスキップして、APIからの戻り値の型を設定しましょう。ここが先ほど作成した"getDescriptionResult"になります。選択したら「保存」します。

f:id:kun432:20200817233121p:plain

では変数と引数のマッピングです。最初のAPIの結果は変数"getRecommendationResult0"に入っているのでこれをAPI引数として渡して、結果を"getDescriptionResult0"で受け取ります。このあたり、もうスロットなのか変数なのか引数なのか、よくわからなくなってきますね・・・

f:id:kun432:20200817233623p:plain

API定義ができたので、発話セットでスキップしていたAPIを設定しましょう。5行目のユーザの発話をクリックして、右のメニューに設定されている発話セット"invoke_getDescription_recommendationResult"の横の鉛筆アイコンをクリックして、発話セットを編集します。

f:id:kun432:20200818013545p:plain

発話セットの設定画面で、ダイアログアクト"Invoke APIS"の横のAPIに"getDescription"を選択して"Save"します。

f:id:kun432:20200818013725p:plain

これで残りはレスポンステンプレートだけです。

レスポンステンプレート

最後にAPIからの結果を受けて、Alexaが発話するためのレスポンステンプレートを設定します。ダイアログの最後の行をクリックして、右のメニューの一番下、レスポンスのドロップダウンから "Create a new response" を選択します。

f:id:kun432:20200818014033p:plain

レスポンステンプレート設定画面がインラインで開きますので、まずテンプレート名を"notifySuccess_getDescription"にしましょう。

f:id:kun432:20200818014215p:plain

サンプル発話を登録する前に、下にスクロールしてArgumentsで引数を設定します。"Add a new argument"をクリックして、スロットタイプ"getDescriptionResult"で引数名も同じに設定します。

f:id:kun432:20200818022919p:plain

ではサンプル発話の登録です。上にスクロールして、Audio Responseのドロップダウンから"Create a new prompt"を選択します。

f:id:kun432:20200818014619p:plain

プロンプト名は"notifySuccess_getDescription"とします。で、ダイアログに入力していた"A Chihuahua is ..." というのがサンプル発話として登録されていると思いますが、これを全選択して、スロットタイプ"getDescriptionResult.description"として設定します。

f:id:kun432:20200818023127p:plain

「保存」して「Save」します。

f:id:kun432:20200818023325p:plain

ダイアログの画面に戻ったらレスポンスに渡す引数と変数のマッピングができるようになっているので、上のAPIから受け取った結果の変数である"getDescriptionResult0"を選択します。

f:id:kun432:20200818015857p:plain

あと、もう一つ、レスポンステンプレートはAPI定義と紐付けないといけないんでした。"APIs to call" で "getDescription" が選択されている右横の鉛筆アイコンをクリックします。

f:id:kun432:20200818020103p:plain

API定義の画面が開いたら、レスポンステンプレートで"notifySuccess_getDescription"を選択して"+"をクリックして紐付けます。

f:id:kun432:20200818020240p:plain

紐付けが終わったら「保存」します。

f:id:kun432:20200818020349p:plain

対話モデルはこれで終了です。"Save"して"モデルをビルド"してください。

f:id:kun432:20200818020525p:plain

バックエンド

最後にバックエンドコードに犬種の説明を返すAPIを追加します。コードエディタでindex.jsを開いて、以下のハンドラをGetRecommendationAPIHandlerの前あたりに入れてください。

const GetDescriptionAPIHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'Dialog.API.Invoked'
           && handlerInput.requestEnvelope.request.apiRequest.name === 'getDescription';
    },
    handle(handlerInput) {
        
        const recommendationResult = handlerInput.requestEnvelope.request.apiRequest.arguments.getRecommendationResult;
        
        // setting the default response.
        let databaseResponse = `I don't know much about ${recommendationResult.name}.`;
        
        const energy = recommendationResult.energy;
        const size = recommendationResult.size;
        const temperament = recommendationResult.temperament;
        
        // setting the actual response if we find a match for their preference
        if (energy !== null && size !== null && temperament !== null) {
            const key = `${energy}-${size}-${temperament}`;
            databaseResponse = data[key];
        } 
        
        const descriptionEntity = {
            description: databaseResponse.description
        };
        
        const response = buildSuccessApiResponse(descriptionEntity);
        console.log('GetDescriptionAPIHandler', JSON.stringify(response));
        
        return response;
    }
};

リクエストタイプとapiRequest.nameについては前々回お話したとおりなので割愛します。今回はAPI名が"getDescription"ということですね。

recommendationResultに1回目のAPIでおすすめ犬種を取得した時の情報が入ってます。そっか、PetMatch.jsonは、キーが"${energy}-${size}-${temperament}"でしたね、すっかり忘れてて犬種名で引くものだと思ってました、失礼しました。recommendationResultからそれぞれの情報を取り出して、キーを生成して、オブジェクトを検索して、マッチしたものからdescriptionを引っ張って返す、ということですね。getRecommendation API同様とてもシンプルです。

あと、skillBuilderハンドラにGetDescriptionAPIHandlerハンドラを登録するのをお忘れなく。

exports.handler = skillBuilder
    .addRequestInterceptors(RequestInterceptor)
    .addRequestHandlers(
        GetDescriptionAPIHandler,             // 追加
        GetRecommendationAPIHandler,
        IntentReflectorHandler,
        SessionEndedRequestHandler
    )
    .addResponseInterceptors(ResponseInterceptor)
    .addErrorHandlers(ErrorHandler)
    .lambda();

「保存」「デプロイ」します。

f:id:kun432:20200818022050p:plain

これで終了です。ではテストしてみましょう。

テスト

f:id:kun432:20200818032552p:plain

ちゃんと動いてますねー。マルチターンのコンテキストがかんたんに維持されて、新しい機能につなげることができるというのがわかった気がします。


Alexa Conversationsのチュートリアルを3回に渡ってご紹介しました。Alexa Liveのときも少し思ったのですが、実際に触ってみるのが一番理解が早いですね。Alexa Conversationsは複数のコンポーネントに分かれていて最初は非常に複雑な感があるので、とりあえず手を動かしてやってみることをおすすめします。

(とはいいつつ、まだ日本には来てないのですが・・・)