[iOS][Amazon Lex] 音声でもタップでも操作できるBotアプリ(クライアント編)

aws-summit-tokyo-2017-logo

1 はじめに

Amazon Lex (以下、Lex) は、音声やテキストで利用するBotですが、バリデーション用のAmazon Lambda(以下、Lambda)を使用することで、一問一答ごとにサーバ側で処理を実装することが可能です。

そして、このサーバ側で動作するLambdaとクライアントとの間では、SessionAttributesと言うセッション情報がやり取りされており、ここに必要な情報を埋め込むことで、相互に通信することが可能です。

今回は、このSessionAttributesに必要な付加情報を埋め込み、専用クライアントでこれを表示することで、音声でもタップでも操作できるBotを作成しました。ちょっと、いい感じのUIとなりましたので紹介させて下さい。

なお、本記事は、クラアイント(iPhoneアプリ)側の紹介です。 バックエンド(Lambda)側については、せーののブログで紹介されていますので、そちらをご覧下さい。

#AWSSummit 用にAmazon Lexのデモアプリのバックグラウンドを作ってみた。

2 SessionAttributesに格納される情報

Botのストーリーは、概ね、次のようになっています。

User:   「コーヒーを下さい」
Bot:    「ようこそ!クラスメソッドコーヒーへ! どのコーヒーになさいますか?」
User:   「キリマンジャロを下さい」
Bot:    「砂糖はいくつ必要ですか?」
User:   「1つ下さい」
Bot:    「キリマンジャロに砂糖を1つで宜しいですか?」
User:   「はい」
Bot:    「オーダを承りました」

この一連の流れの中で、ユーザが返答するタイミングが何度かありますが、そのうちコーヒーの種類を問うタイミングに注目してみます。

ここでは、iPhoneの画面は、次にように表示されています。

001

本アプリでは、ユーザが選択できる項目の1つ1つを「カード」として管理しています。 上の場合は、4枚のカードがサーバから送られて来た例となります。

1枚のカードに表示されている項目は、次のようなものです。

  • 名前
  • 追加情報
  • 価格
  • 画像

ユーザーは、コーヒーの種類を音声で答えても、あるいは、カードをタップしても同じように処理が進みます。

音声での返答は、元々Botの機能で処理できるので問題ありませんが、もし、タップで返答した場合、コーヒー名を発声したようにサーバーに側に伝える必要があります。

そのため、カードには、次の情報が必要になります。

  • 注文するコーヒーの名前(Slotに格納する値)

このアプリでは、選択されたカードが分かりやすいようにグレーで縁取りされ、同時に他のカードが半透明になります。 そして、これは、タップした時だけではなく、音声で返答した場合も同様に動作します。

002

このUIを実現するため、カードには、次の情報も格納されています。

  • Slot名

元々、クライアントとサーバの間では、Slotsというデータがやりとりされており、Slotが埋まったら、その値が追加されて行きます。本アプリでは、その内容を常に確認し、表示したカードと一致した場合、選択状態に表示を更新しています。

従って、表示されている画面は、サーバ側で認識している内容と完全に一致していることが保証されています。

ここまで見て来た、カードに必要な情報を、SessionAttributesに格納すると、下のようになります。

"sessionAttributes": {
    "cards": [
        {
            "title":"Mocha",
            "subtitle":"Please enjoy the fragrant mocha",
            "body":"$4.15",
            "imageUrl":"https://exsample.com/mocha.png",
            "slotName":"CoffeeType",
            "slotValue":"Mocha"
        },
        {
            // 略
        }
    ]
}

なお、アプリでは、カードの他に、Lexからの発話や、ユーザーの発話も表示されています。

003

しかし、これらの情報は、元々、Lexの通信の中に含まれていますので、それをそのまま表示できます。

3 SessionAttributesの文字列化

Lexでは、iOS用のSDKが利用可能で、非常に簡単にLexクライアントが作成可能です。
参考:遂にGAとなった Amazon Lex をiOS SDKから使用してみました。

しかし、このSDKを使用する場合、SerssionAttributesのValueに文字列以外を入れると異常終了してしまいます。

本アプリで扱うカードは、複数であるのが前提で、可能であれば配列で表現したいところです。そこでcardsのvalueは、Lambda側で文字列に変換して送信し、iOS側でこれを復元しています。

Nodejs

var sessionAttributes = {
    "cards": JSON.stringify(cards);
}

Swift

if let cardsStr = sessionAttributes["cards"] {
    var cards = JSON.parse(cardsStr as! String)

    ・・・略・・・

4 AWSLexInteractionKit

SDKの使い方は、色々ありますが、今回は、AWSLexInteractionKitクラスを利用しました。
AWSLexInteractionKit Class Reference

AWSLexInteractionKitでは、Lexとの通信のために次のような主要メソッドが用意されています。

// 1.テキスト入力/テキストレスポンス
- (void)textInTextOut:(NSString *)inputText
// 2.テキスト入力/音声レスポンス
- (void)textInAudioOut:(NSString *)inputText
// 3.音声入力/音声レスポンス
- (void)audioInAudioOut
// 4.音声入力/テキストレスポンス
- (void)audioInTextOut

本アプリでは、基本的に「3.音声入力/音声レスポンス」で動作させ、カードをタップした場合だけ、一旦その処理を中断して「2.テキスト入力/音声レスポンス」を使用しています。

なお、最終的には、テキスト入力は、PostText、音声入力は、PostContext のAPIが、それぞれコールされています。

PostText

POST /bot/botName/alias/botAlias/user/userId/text HTTP/1.1
Content-type: application/json

{
   "inputText": "string",
   "sessionAttributes": {
      "string" : "string"
   }
}

PostContext

POST /bot/botName/alias/botAlias/user/userId/content HTTP/1.1
x-amz-lex-session-attributes: sessionAttributes
Content-Type: contentType
Accept: accept

inputStream

5 マイクのUI

アプリの最下部には、マイクのUIが有り、タップすると録音開始状態になります。 また、入力中や、レスポンス中に下記のように変化します。


待機中
005
マイク入力開始
004
入力音量に応じてアニメーション
007
Lexのレスポンス待機及び、Lexのレスポンス
006

AWSLexInteractionKitクラスでは、AWSLexMicrophoneDelegate及び AWSLexAudioPlayerDelegateが利用可能であり、それぞれ、録音と再生の開始・終了などが処理できます。

アプリでは、それぞれのタイミングで、マイクの描画を変更しています。

// MARK:  - AWSLexAudioPlayerDelegate
func interactionKit(onAudioPlaybackStarted interactionKit: AWSLexInteractionKit) {
    // Lexからのレスポンス再生
}

func interactionKit(onAudioPlaybackFinished interactionKit: AWSLexInteractionKit) {
    // マイク利用終了
}

// MARK:  - AWSLexMicrophoneDelegate
func interactionKit(onRecordingStart interactionKit: AWSLexInteractionKit) {
    // マイク利用中
}

func interactionKit(onRecordingEnd interactionKit: AWSLexInteractionKit, audioStream: Data, contentType: String) {
    // Lexからのレスポンス待ち
}

func interactionKit(_ interactionKit: AWSLexInteractionKit, onSoundLevelChanged soundLevel: Double) {
    // マイクの入力レベルの変化
}

6 最後に

今回は、Lexを利用したBotの専用クライアントを作成することで、音声とタップ操作がシームレスに利用可能なサンプルを作成してみました。

テキストだけで使用するBotも、音声のみで使用するBotも、今ひとつ、「ちょっと使いにくいな」と感じることがありましたが、画面表示による視覚情報の追加や、タップ操作も可能にすることで、少し気持ちの良いUIとなることを実感しました。

7 参考リンク


What is Amazon Lex
Amazon Lex API Reference
aws/aws-sdk-ios/AWS Lex