Chatbotのダイアログ
Chatbotのダイアログとは
Bot FrameworkのBot Builder SDKは、「ダイアログ(対話)」という機能を提供しています。ダイアログの説明に入る前に、ネットでなにか調べものをする際の手順を考えてみましょう。例えば、週末にキャンプを予定しているので、天気予報のサイトで週末の奥多摩町の天気を知りたいとします。一般的にWebサイトから調べる手順としては、以下のようになるでしょう。
- 天気予報のサイトを検索
- 検索結果から天気予報サイトを選択
- 日本全国の地図から関東地方を選択
- 関東地方から東京都を選択
- 東京都から奥多摩町を選択
- 土曜日、日曜日の天気を選択
少々回りくどいですが、このような流れで最終的な目的の情報にたどり着くことができます。各ステップでの画面の表示は、直前にユーザーが選択した結果と関連したものになるため、ユーザーの選択結果ごとにWebサイトの画面内容も切り替わります。これは、様々なユーザーインターフェースを持つ一般的なアプリケーションの仕様です。
一方Chatbotは、ユーザーとボットの対話が基本的なインターフェースとなります。対話によるユーザーインターフェースは、1枚のチャット画面だけです。Chatbotを開発する場合は、1つの話題ごとにそれまでの対話内容をきちんと保持して、必要に応じて適切な回答をする必要があります。一般的なアプリケーションのように、質問のたびに画面を切り替えるわけにはいきません。Bot FrameworkのBot Builder SDKでは、ユーザーごとの内容や話題=対話の流れをきちんと保持し続けることを、基本機能の一部として提供しています。これがダイアログ(対話)と呼ばれる機能です。
ダイアログの作成
では実際にダイアログを作成してみましょう。名前、年齢、性別を入力する簡単なものですが、ダイアログの基本的な使い方を確認します。前回作成した「ChatBot201707」を利用します。今回の記事で使用するファイル一式は、以下のリンクからダウンロード可能です。
まずDialogsフォルダーに、空白のクラスファイル「HelloDialog.cs」を追加します。
HelloDialog.csの中身は「 RootDialog.cs」の一部をコピーして、最低限のダイアログを作成するためのテンプレートとして用意します。
using System; using System.Threading.Tasks; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; namespace Chatbot201707.Dialogs { [Serializable] public class HelloDialog : IDialog<object> { public Task StartAsync(IDialogContext context) { context.Wait(MessageReceivedAsync); return Task.CompletedTask; } private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result) { var activity = await result as Activity; await context.PostAsync("こんにちは"); context.Done<object>(null); } } }
上記リストの内容を、一つずつ細かく見ていきましょう。
参照設定
usingで参照しているクラスは、ダイアログを作成するために最低限必要なクラスです。
Serializable属性
ダイアログはシリアル化可能でなければならないという制約があるのでSerializable属性を付けます。Chatbotでは対話の状態を保存するために必要となります。
IDialog<object>
IDialog<T>インターフェースを実装したクラスがダイアログになります。<T>はダイアログが終了した時に戻り値として返す値の型を表し、上記の例では「object」となります。
StartAsync
IDialogインターフェースはStartAsyncメソッドが開始時に呼び出されます。
context.Wait
ダイアログは基本的にcontext.Waitメソッドにメッセージを処理するメソッドを渡す形で定義します。
await context.PostAsync
ユーザーにメッセージを返します。
context.Done
ダイアログによる対話の流れが終了したらcontext.Doneメソッドでダイアログを終了します。戻り値はないので、nullを返しています。
Taskクラスとasync/await
C# 5.0から導入された、Taskクラスの構文の一つです。async Task は非同期処理のメソッドとなり、他のメソッドの終了を待つことなく独立して実行~終了されます。awaitは非同期処理の終了を待って値を取り出します。最後にTask.CompletedTaskで完了済みのTaskを取得しています。ダイアログでは非同期処理が基本となります。
ダイアログのコードを詳細に理解するために、多少難しい話が続きましたが、上記の基本となるコードをテンプレートとして利用すれば、特に難しいことは気にすることなくダイアログを作成することができます。早速このダイアログを実行してみましょう。MessagesControllerクラス(MessagesController.cs)のPostメソッドでHelloDialogを呼び出すように変更して実行します。
await Conversation.SendAsync(activity, () => new Dialogs.HelloDialog());
このままでは何を入力しても「こんにちは」しか返ってきませんが、ダイアログとしてはきちんと動いていることがわかります。これを元に、少しずつカスタマイズして対話らしくしていきましょう。
ダイアログで対話のやり取り
ダイアログで対話の流れを維持しつつユーザーとやり取りをするためには、「一つの対話処理が終わったら、context.Waitメソッドで次の処理に対話を引き渡す」という作業が必要です。HelloDialogを修正して対話らしいやり取りができるようにしてみましょう。
using System; using System.Threading.Tasks; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Connector; namespace Chatbot201707.Dialogs { [Serializable] public class HelloDialog : IDialog<object> { public string userName; public string userPlace; public Task StartAsync(IDialogContext context) { context.Wait(HelloMessage); return Task.CompletedTask; } private async Task HelloMessage(IDialogContext context, IAwaitable<object> result) { var activity = await result as Activity; await context.PostAsync("こんにちは!\r\n\r\nお名前は?"); context.Wait(NameMessage); } private async Task NameMessage(IDialogContext context, IAwaitable<object> result) { var activity = await result as Activity; userName = activity.Text; await context.PostAsync($"{userName}さん、お住まいはどちらですか?"); context.Wait(PlaceMessage); } private async Task PlaceMessage(IDialogContext context, IAwaitable<object> result) { var activity = await result as Activity; userPlace = activity.Text; await context.PostAsync($"ようこそチャットボットへ! {userPlace}にお住いの{userName}さん"); context.Done<object>(null); } } }
修正を加えたHelloDialog.csの詳細を見ていきましょう。
StartAsync
最初の処理としてユーザーが何か入力したら、HelloMessageメソッドを呼び出します。
HelloMessage
「await context.PostAsync("こんにちは!\r\n\r\nお名前は?")」であいさつと名前を尋ねるメッセージを送信します。改行は「\r\n」を2回入力します。「context.Wait(NameMessage)」で、次の処理NameMessageに対話を引き渡します。
NameMessage
「userName = activity.Text」でユーザーが入力した名前を受け取ります。次の質問である住まいを尋ねるメッセージを送信し、PlaceMessageに対話を引き渡します。
PlaceMessage
「userPlace = activity.Text」でユーザーが入力した地名を受け取ります。「await context.PostAsync($"ようこそチャットボットへ! {userPlace}にお住いの{userName}さん") 」で、これまで入力された名前と地名を含めてようこそメッセージを送信します。
「context.Done<object>(null)」でダイアログを終了します。この後、ユーザーが何か入力すると初めに戻ってMessagesControllerクラス(MessagesController.cs)のPostメソッドが実行されることになります。つまり再びHelloDialogが呼び出され、対話が開始されます。
少しずつですがChatbotと対話ができるようになってきました。