Amazon Web Services ブログ

Amazon Lex Streaming API を使用して自然な会話体験を提供する方法

自然な会話には、一時停止や中断が含まれることがよくあります。カスタマーサービスの通話中、発信者(訳注:カスタマーサービスに電話をかけた人)は、質問に答える前に、必要な情報を検索している間、会話を一時停止するか、回線を保留するように要求する場合があります。例えば、発信者は、請求書の支払いを行うときにクレジットカードの詳細を取得するために時間を必要とすることがよくあります。発信者は、エージェント(訳注:カスタマーサービスに応答する担当者)が質問を終える前に、人間のエージェントの回答を遮ることがあります。(例えば、クレジットカードの CVVコードとは何ですか?右上にある3桁のコードです。)人間のエージェントとの会話と同じように、ボットと対話している電話の発信者は、ボットを中断したり、電話を止めるように指示したりすることができます。これまでは、クライアントの属性を管理したり、AWS Lambda 関数を介してコードを記述したりすることにより、Amazon Lex 上でこのようなダイアログをオーケストレーションする必要がありました。ホールドパターンを実装するには、ボットが会話を継続できるように、以前のインテントを追跡するためのコードが必要でした。このような会話のオーケストレーションは、構築や維持が複雑で、会話型インターフェースの市場投入までの時間に影響を与えていました。さらに、中断できるかどうかなどのプロンプトの特性は、クライアントのセッション属性で定義されていたため、ユーザー体験はバラバラでした。

Amazon Lex の新しいストリーミング会話API により、洗練された自然な会話をさまざまなコミュニケーションチャネルで提供できるようになりました。また、Wait and continue や Interrupt機能を使ってボットを構築する際に、一時停止、中断、ダイアログ構造を簡単に設定できるようになりました。これにより、会話の全体的な設計と実装が簡素化され、管理が容易になります。これらの機能を使用することで、ボットビルダーは、仮想エージェントやIVRシステムの会話能力を迅速に向上させることができます。

新機能 Wait and continue では、会話を待機状態にする機能がスロットの引き出し中に表示されます。発信者が情報取得のための時間を要求した場合、「はい、準備ができたら知らせてください」 などの “Wait” メッセージで応答するようにスロットを設定することができます。また、「ポリシーIDの準備ができました、どうぞ」のような定義されたキューに基づいて、”Continue” というレスポンスで会話を続けるようにボットを設定することもできます。オプションで、「まだ待機中」プロンプトを設定して、「まだここにいます」や 「もっと時間が必要な場合はお知らせください」などのメッセージを再生することもできます。これらのメッセージの再生頻度を設定し、ユーザー入力に対する最大待機時間を設定することができます。最大待機時間内に発信者が何も入力しない場合、Amazon Lex はスロットの入力を促してダイアログを再開します。次のスクリーンショットは、Amazon Lex コンソールのWait and continue の設定オプションを示しています。

 

 

Interrupt機能を使用すると、ボットがプロンプトを再生しているときに、発信者が割り込むことができます。発信者は、プロンプトが完了する前にボットに割り込んで質問に答えることができます。この機能はプロンプトレベルで表示され、デフォルト設定として提供されます。Amazon Lex コンソールで Prompt for slot の Advanced options を選択し、Slot prompts の下で、ユーザーがプロンプトを中断できるようにする設定を有効にします。(訳注1)

 

 

これらの機能を設定した後、StartConversation API を使用して Lexボットとのストリーミング対話を開始することができます。ストリーミング機能により、会話の一部として必要なユーザー入力の取得、状態遷移の管理、イベントの処理、レスポンスの配信が可能になります。入力には音声、テキスト、DTMFの3種類があり、応答には音声またはテキストがあります。ダイアログは、インテントを引き出し、スロットを入力し、インテントを確認し、最後にインテントを閉じることで進行します。ストリーミングを使用すると、InProgressWaitingConfirmedDeniedFulfilledReadyForFulfillmentFailed などのさまざまな会話状態に基づいてインテントを定義できます。様々なダイアログとインテントの状態の詳細なリストについては、Amazon Lex インテントのドキュメントを参照してください。

StartConversation APIは、クライアントと Amazon Lex 間の双方向通信に HTTP/2 を使用します。クライアントは、会話全体を通じて単一の長時間実行ストリームを維持し、さまざまなイベントを介してサーバーと必要な情報を交換する(またはその逆)ことが期待されます。 Amazon Lex ボットのストリーミングの詳細については、Lexストリーミングのドキュメントを参照してください。

Amazon Lex Streaming API の動作

これらをすべて、ボットにまとめてみましょう。まず Amazon Lex コンソールを使ってボットを設定し、次に以下のサンプルテレフォニーアプリケーションを使ってボットと対話します。この記事では、クレジットカードでの請求書支払いを完了するためのシンプルなボットを使用します。発信者は、クレジットカードの情報を取得するのに時間がかかることが多いので、クレジットカードの番号を入力するときは、Wait and continue 構文を有効にしています。また、割り込みを管理するために、サービス側の割り込みを設定しています。

以下は、ボットとの会話の例です。

発信者:アカウントで支払いをしたいのですが。
ボット:はい、まずはお客様の アカウントID を教えてください。
発信者:はい、アカウントID は1234です。
ボット:素晴らしい。残高は 100ドルです。今日はいくらお支払いになりますか?
発信者:100ドル。
ボット:わかりました。クレジットカードの 16桁の番号を教えてください。
発信者:はい、ちょっと待ってもらえますか?
ボット:もちろんです。

—————-数秒後—————-

ボット:準備ができたら教えてください。急ぐ必要はありません。

—————-数秒後—————-

発信者:ただいま戻りました。
ボット:わかりました、どうぞ。
発信者:クレジットカード番号は 1234 5678 1234 5678 です。
ボット:了解しました。繰り返しますが 1234 5678 1234 5678 で正しいですか?
発信者:はい。
ボット:わかりました。CVV コードは何ですか?カード裏面の署名欄の右上に記載されています。
発信者:123。
ボット:素晴らしい。 それでは、末尾が 5678のカードを使って、お客様のアカウントで 100ドルのお支払いを処理します。
発信者:わかりました。
ボット:支払いは完了しました。確認コードは 1234です。

最初のステップは、支払いの処理とアカウント残高を取得するインテントを持つ Amazon Lexボットを構築することです。ProcessPayment インテントでは、支払い金額、クレジットカード番号、CVVコード、有効期限など、支払い処理に必要な情報を引き出します。GetBalanceAmount インテントは、アカウントの残高を提供します。FallbackIntent は、構成された2つのインテントのいずれかでユーザーの入力を処理できなかった場合に起動されます。

サンプルボットの導入

サンプルボットを作成するには、以下の手順を実行します。これにより、PaymentsBot という Amazon Lex のボットが作成されます。

  1. Amazon Lex コンソールで、Create botを選択します。
  2. Bot Configuration セクションで、ボットに PaymentsBot という名前を付けます。
  3. AWS Identity and Access Management(IAM)のパーミッションと COPPAフラグを指定します。
  4. Nextを選択します。
  5. LanguagesEnglish(US) を選択します。
  6. Done を選択します。
  7. ProcessPayment インテントと GetBalanceAmount インテントをボットに追加します。
  8. ProcessPayment インテントには、以下のスロットを追加します。
    1. PaymentAmount スロットには、組み込みの AMAZON.Number スロットタイプを使用します。
    2. CreditCardNumber スロットには、組み込みの AMAZON.AlphaNumeric スロットタイプを使用します。
    3. CVV スロットには、組み込みの AMAZON.Number スロットタイプを使用します。
    4. ExpirationDate スロットには、組み込みの AMAZON.Date スロットタイプを使用します。
  9. 各スロットにスロットエリシテーションのプロンプトを設定します。
  10. ProcessPayment インテントに対して、クロージング・レスポンスを構成する。
  11. 同様に、GetBalanceAmount インテントにスロットとプロンプトを追加して構成します。
  12. Build を選択して、ボットをテストします。(訳注2)

ボット作成の詳細については、Lex V2 のドキュメントを参照してください。

Wait and continue を設定する

  1. ProcessPayment インテントを選択し、CreditCardNumber スロットに移動します。
  2. Advanced options を選択して、スロットエディタを開きます。(訳注3)
  3. そのスロットの Wait and continue を有効にします。
  4. WaitStill WaitingContinue の各レスポンスを用意します。
  5. インテントを保存し、Build を選択します。

これでボットは、Wait and continue のダイアログ構成をサポートするように設定されました。次に、クライアントコードを設定します。Lex ボットとの対話には、テレフォニーアプリケーションを使うことができます。GitHub プロジェクトでは、Twilio を使ったテレフォニー IVR インターフェースを設定するためのコードをダウンロードできます。このリンクには、テレフォニーインターフェースを設定するための情報と、テレフォニーインターフェースと Amazon Lex の間で通信するためのクライアントアプリケーションのコードが含まれています。

それでは、先ほど Amazon Lex コンソールで有効にしたボット設定を使用するための、クライアント側の設定を確認してみましょう。クライアントアプリケーションでは、Java SDK を使用して支払い情報を取得します。最初は、ConfigurationEvent を使用して会話のパラメータを設定します。その後、入力イベント(AudioInputEventTextInputEventDTMFInputEvent)の送信を開始し、入力タイプに応じてユーザーの入力をボットに送信します。オーディオデータを送信する場合は、複数のAudioInputEvent イベントを送信する必要があり、各イベントにはデータのスライスが含まれます。

サービスはまず TranscriptEvent で応答して文字起こしを行い、次に IntentResultEvent を送信してインテント分類の結果を表示します。その後、Amazon Lex は、発信者に再生する応答を含む応答イベント(TextResponseEvent または AudioResponseEvent)を送信します。発信者がボットに回線の保留を要求した場合、インテントは Waiting 状態に移行し、Amazon Lex は、TranscriptEventIntentResultEvent、および応答イベントの別のセットを送信します。発信者が会話の継続を要求すると、インテントは InProgress 状態に設定され、サービスは TranscriptEventIntentResultEvent、および応答イベントの別のセットを送信します。ダイアログが Waiting 状態にある間、Amazon Lex は IntentResultEvent と応答イベントのセットで、”Still waiting ” メッセージごとに応答します(サーバーが開始した応答には Transcript イベントはありません)。発信者がボットプロンプトをいつでも中断した場合、Amazon Lex は  PlaybackInterruptionEvent を返します。

クライアントコードの主な要素を見ていきましょう。

  1. Amazon Lexクライアントを作成します:
    AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider
            .create(AwsBasicCredentials.create(accessKey, secretKey));
    
    LexRuntimeV2AsyncClient lexRuntimeServiceClient = LexRuntimeV2AsyncClient.builder()
            .region(region)
            .credentialsProvider(awsCredentialsProvider)
            .build();
  2. データをサーバーに公開するハンドラを作成します:
    EventsPublisher eventsPublisher = new EventsPublisher();
  3. ボットの応答を処理するハンドラを作成します。
    public class BotResponseHandler implements StartConversationResponseHandler {
    
        private static final Logger LOG = Logger.getLogger(BotResponseHandler.class);
    
    
        @Override
        public void responseReceived(StartConversationResponse startConversationResponse) {
            LOG.info("successfully established the connection with server. request id:" + startConversationResponse.responseMetadata().requestId()); // would have 2XX, request id.
        }
    
        @Override
        public void onEventStream(SdkPublisher<StartConversationResponseEventStream> sdkPublisher) {
    
            sdkPublisher.subscribe(event -> {
                if (event instanceof PlaybackInterruptionEvent) {
                    handle((PlaybackInterruptionEvent) event);
                } else if (event instanceof TranscriptEvent) {
                    handle((TranscriptEvent) event);
                } else if (event instanceof IntentResultEvent) {
                    handle((IntentResultEvent) event);
                } else if (event instanceof TextResponseEvent) {
                    handle((TextResponseEvent) event);
                } else if (event instanceof AudioResponseEvent) {
                    handle((AudioResponseEvent) event);
                }
            });
        }
    
        @Override
        public void exceptionOccurred(Throwable throwable) {
            LOG.error(throwable);
            System.err.println("got an exception:" + throwable);
        }
    
        @Override
        public void complete() {
            LOG.info("on complete");
        }
    
        private void handle(PlaybackInterruptionEvent event) {
            LOG.info("Got a PlaybackInterruptionEvent: " + event);
    
            LOG.info("Done with a  PlaybackInterruptionEvent: " + event);
        }
    
        private void handle(TranscriptEvent event) {
            LOG.info("Got a TranscriptEvent: " + event);
        }
    
    
        private void handle(IntentResultEvent event) {
            LOG.info("Got an IntentResultEvent: " + event);
    
        }
    
        private void handle(TextResponseEvent event) {
            LOG.info("Got an TextResponseEvent: " + event);
    
        }
    
        private void handle(AudioResponseEvent event) {//synthesize speech
            LOG.info("Got a AudioResponseEvent: " + event);
        }
    
    }
  4. ボットとの接続を開始します。
    StartConversationRequest.Builder startConversationRequestBuilder = StartConversationRequest.builder()
            .botId(botId)
            .botAliasId(botAliasId)
            .localeId(localeId);
    
    // configure the conversation mode with bot (defaults to audio)
    startConversationRequestBuilder = startConversationRequestBuilder.conversationMode(ConversationMode.AUDIO);
    
    // assign a unique identifier for the conversation
    startConversationRequestBuilder = startConversationRequestBuilder.sessionId(sessionId);
    
    // build the initial request
    StartConversationRequest startConversationRequest = startConversationRequestBuilder.build();
    
    CompletableFuture<Void> conversation = lexRuntimeServiceClient.startConversation(
            startConversationRequest,
            eventsPublisher,
            botResponseHandler);
  5. ConfigurationEvent を使用して会話のパラメータを設定します。
    public void configureConversation() { String eventId = "ConfigurationEvent-" + eventIdGenerator.incrementAndGet(); ConfigurationEvent configurationEvent = StartConversationRequestEventStream .configurationEventBuilder() .eventId(eventId) .clientTimestampMillis(System.currentTimeMillis()) .responseContentType(RESPONSE_TYPE) .build(); eventWriter.writeConfigurationEvent(configurationEvent); LOG.info("sending a ConfigurationEvent to server:" + configurationEvent); }
  6. 音声データをサーバーに送信します。
    public void writeAudioEvent(ByteBuffer byteBuffer) { String eventId = "AudioInputEvent-" + eventIdGenerator.incrementAndGet(); AudioInputEvent audioInputEvent = StartConversationRequestEventStream .audioInputEventBuilder() .eventId(eventId) .clientTimestampMillis(System.currentTimeMillis()) .audioChunk(SdkBytes.fromByteBuffer(byteBuffer)) .contentType(AUDIO_CONTENT_TYPE) .build(); eventWriter.writeAudioInputEvent(audioInputEvent); }
  7. クライアント側での中断の管理
    private void handle(PlaybackInterruptionEvent event) { LOG.info("Got a PlaybackInterruptionEvent: " + event); callOperator.pausePlayback(); LOG.info("Done with a PlaybackInterruptionEvent: " + event); }
  8. コードを入力すると、接続が解除されます。
    public void disconnect() {
    
        String eventId = "DisconnectionEvent-" + eventIdGenerator.incrementAndGet();
    
        DisconnectionEvent disconnectionEvent = StartConversationRequestEventStream
                .disconnectionEventBuilder()
                .eventId(eventId)
                .clientTimestampMillis(System.currentTimeMillis())
                .build();
    
        eventWriter.writeDisconnectEvent(disconnectionEvent);
    
        LOG.info("sending a DisconnectionEvent to server:" + disconnectionEvent);
    }

ボットをデスクトップに配置してテストすることができます。

知っておきたいこと

Amazon Lex V2 ConsoleとAPIを使用する際に注意すべき点を以下にまとめました。

  • 地域と言語 – Streaming API は、既存のすべての地域で利用でき、現在のすべての言語をサポートしています。
  • Lex V1コンソールとの相互運用性 – Streaming API はLex V2 コンソールと API でのみ利用可能です。
  • Amazon Connect との統合 – この記事を書いている時点では、Lex V2 API は Amazon Connect ではサポートされていません。近い将来のロードマップの一部として、この統合を提供する予定です。
  • 価格 – 詳細は、Lex の価格設定ページをご覧ください。

お試しください

Amazon Lex Streaming API が利用可能になりましたので、本日よりご利用いただけます。ボットを設計して起動し、ご意見をお聞かせください。詳細については、LexストリーミングAPI のドキュメントをご覧ください。

訳注

  • 訳注1:説明とスクリーンショットを最新のコンソール状態に合わせて変更しました。
  • 訳注2:手順に記載されていませんが Sample utterances の設定がない場合はBuildでエラーします。
  • 訳注3:説明を最新のコンソール状態に合わせて変更しました。

 

翻訳はソリューションアーキテクト赤澤が担当しました。原文はこちらです。