Chatbotのダイアログ

2017年10月19日(木)
樋口 勝一
連載の3回目となる今回は、Bot Builder SDKが提供するダイアログ機能を用いて、シンプルなChatbotを作成してみる。

複数のダイアログを使い分ける

ダイアログは、一つの主(メイン)となる話題を進める途中で、枝分かれした副(サブ)の話題を展開することが可能です。主となるメインダイアログから副となるサブダイアログを呼び出し、サブダイアログを終了するときにはメインダイアログに戻り値を引き渡して、対話を続行することが可能です。

例えばレストランでランチを注文する場合を考えてみましょう。まず初めに料理のコースを決めます。その次にサービスのドリンクを選びます。お店によっては、デザートも選べる場合があるかもしれません。ひとつひとつの枝分かれした内容の中にそれぞれ選択肢があり、決定事項が存在します。ダイアログは、それぞれ一つの話題を取り扱います。つまり、メインのダイアログから分岐して、細かい話題はサブダイアログで取り扱い、最終的にすべての決定事項を組み合わせてランチの注文が完了するということです。

ダイアログの分岐

ダイアログの分岐

複数のダイアログを組み合わせたランチの注文用のChatbotを作成してみましょう。

リスト4:RootDialog.cs

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System.Collections.Generic;

namespace Chatbot201707.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog<object>
    {
        private List<string> menuList = new List<string>() { "ランチコース", "ドリンク", "デザート", "終了" };
        private string lunch;
        private string drink;
        private string dessert;

        public Task StartAsync(IDialogContext context)
        {
            context.Wait(HelloMessage);
            return Task.CompletedTask;
        }

        private async Task HelloMessage(IDialogContext context, IAwaitable<object> result)
        {
            await context.PostAsync("いらっしゃいませ!ご注文を伺います。");
            MenuMessage(context);
        }

        private void MenuMessage(IDialogContext context)
        {
            PromptDialog.Choice(context, SelectDialog, menuList, "ランチメニューをお選びください。");
        }

        private async Task SelectDialog(IDialogContext context, IAwaitable<object> result)
        {
            var selectedMenu = await result;
            switch (selectedMenu)
            {
                case "ランチコース":
                    context.Call(new LunchDialog(), LunchResumeAfterDialog);
                    break;
                case "ドリンク":
                    context.Call(new DrinkDialog(), DrinkResumeAfterDialog);
                    break;
                case "デザート":
                    context.Call(new DessertDialog(), DessertResumeAfterDialog);
                    break;
                case "終了":
                    await context.PostAsync("ご注文を承りました。");
                    context.Wait(HelloMessage);
                    break;
            }
        }

        private async Task LunchResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
        {
            lunch = await result;
            await context.PostAsync($"ランチコースは {lunch} ですね。");
            MenuMessage(context);
        }

        private async Task DrinkResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
        {
            drink = await result;
            await context.PostAsync($"お飲み物は {drink} ですね。");
            MenuMessage(context);
        }

        private async Task DessertResumeAfterDialog(IDialogContext context, IAwaitable<string> result)
        {
            dessert = await result;
            await context.PostAsync($"デザートは {dessert} ですね。");
            MenuMessage(context);
        }
    }
}

RootDialog.csを修正します。

private List<string> menuList = new List<string>() { "ランチコース", "ドリンク", "デザート", "終了" };

複数のダイアログを使い分ける場合は、初めに選択肢としてメニューを表示すると使いやすいChatbotになります。メインの対話の選択肢として、「ランチコース、ドリンク、デザート、終了」をList型の文字列として定義しておきます。

private string lunch; private string drink; private string dessert;

この三つの変数は、分岐したダイアログからの戻り値(結果)を格納するために定義しておきます。

HelloMessage

挨拶のメッセージの後にMenuMessage(context)を呼び出して、選択型のプロンプトを表示します。

MenuMessage

Bot FrameworkのBot Builder SDKでは選択肢メニュー用としてPromptDialog.Choiceという便利な機能が用意されています。

PromptDialog.Choice(context, SelectDialog, menuList, "ランチメニューをお選びください。");

最初の引数contextは、スタート時から引き継がれている対話の流れです。二番目の引数には、ユーザーが選択した時の処理を記述したメソッドを指定します。例えば「ランチコースを選んだ場合」、「ドリンクを選んだ場合」のように、switch文で場合分けの処理が可能です。三番目の引数にList形式の文字列を指定すると、メニューの選択肢として表示してくれます。四番目の引数にユーザーに表示するメッセージを記述します。

PromptDialog.Choiceでメニューの表示

PromptDialog.Choiceでメニューの表示

SelectDialog

ユーザーが選択した内容に応じた処理を記述します。「終了」以外はサブダイアログをcontext.Callメソッドで呼び出し、対話を分岐します。context.Callメソッドの二番目の引数でサブダイアログが終了してメインダイアログに戻った際に、サブダイアログからの戻り値を受け取るなど処理をするメソッドを定義しておきます。「終了」を選択した場合は、context.PostAsyncメソッドで注文確定のメッセージを送信し、context.Wait メソッドでHelloMessageを呼び出して最初に戻ります。

LunchResumeAfterDialog

ランチコースを選択して、サブダイアログのLunchDialogを表示した後、サブダイアログが閉じられてメインダイアログに対話が戻った時の処理を記述しています。

「lunch = await result;」で変数lunchにサブダイアログからの戻り値(選択したランチコース)を格納します。

「await context.PostAsync($"ランチコースは {lunch} ですね。");」で選択内容の確認メッセージを送信します。

「MenuMessage(context);」でMenuMessageメソッドに戻って、他の項目を選択できるよう再度メニューを表示します。

「DrinkResumeAfterDialog」「DessertResumeAfterDialog」も同様にサブダイアログ終了後の処理を記述します。

次に、メインダイアログから呼び出される各サブダイアログのコードを見てみましょう。

リスト5:LunchDialog.cs

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System.Collections.Generic;

namespace Chatbot201707.Dialogs
{
    [Serializable]
    public class LunchDialog : IDialog<string>
    {
        private List<string> menuList = new List<string>() { "A:チキン南蛮定食", "B:サバの塩焼定食", "C:本日のバスタセット(サラダ・スープ付き)" };
        public Task StartAsync(IDialogContext context)
        {
            PromptDialog.Choice(context, this.SelectDialog, this.menuList, "ランチコースをお選びください。");
            return Task.CompletedTask;
        }

        private async Task SelectDialog(IDialogContext context, IAwaitable<object> result)
        {
            var selectedMenu = await result;
            context.Done(selectedMenu);
        }
    }
}

LunchDialogはランチのコースを選択するサブダイアログです。サブダイアログとはいっても、特にクラスの定義などが異なるわけではなく、 IDialog<T>インターフェースを装備した普通のダイアログのクラスです。メインダイアログと同様にPromptDialog.Choiceを利用して選択メニューを表示しています。

SelectDialog

「var selectedMenu = await result;」でユーザーが選択した項目を取得しています。

メインダイアログと異なるのは、終了時に「context.Done(selectedMenu);」のようにしてメインダイアログに値を引き渡すという点です。context.Doneメソッドでサブダイアログを終了します。引数でselectedMenuを渡すことで、メインダイアログとなるRootDialog.cs のLunchResumeAfterDialogメソッドで「lunch = await result;」として選択内容を取得することができるようになっています。

リスト6:DrinkDialog.cs

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System.Collections.Generic;

namespace Chatbot201707.Dialogs
{
    [Serializable]
    public class DrinkDialog : IDialog<string>
    {
        private List<string> menuList = new List<string>() { "ホットコーヒー", "アイスコーヒー", "オレンジジュース" };
        public Task StartAsync(IDialogContext context)
        {
            PromptDialog.Choice(context, this.SelectDialog, this.menuList, "ドリンクをお選びください。");
            return Task.CompletedTask;
        }

        private async Task SelectDialog(IDialogContext context, IAwaitable<object> result)
        {
            var selectedMenu = await result;
            context.Done(selectedMenu);
        }
    }
}

リスト7:DessertDialog.cs

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System.Collections.Generic;

namespace Chatbot201707.Dialogs
{
    [Serializable]
    public class DessertDialog : IDialog<string>
    {
        private List<string> menuList = new List<string>() { "季節のフルーツ", "アイスクリーム", "プリン" };
        public Task StartAsync(IDialogContext context)
        {
            PromptDialog.Choice(context, this.SelectDialog, this.menuList, "デザートをお選びください。");
            return Task.CompletedTask;
        }

        private async Task SelectDialog(IDialogContext context, IAwaitable<object> result)
        {
            var selectedMenu = await result;
            context.Done(selectedMenu);
        }
    }
}

LunchDialog.csと同様にサブダイアログとして呼び出される、DrinkDialog.csとDessertDialog.csも、同仕様で作成しています。

メインダイアログと、各サブダイアログが出来上がったら実際に動かしてみましょう。Chatbotで最初に実行されるMessagesController.csのPostメソッドでRootDialogが呼び出されるように修正して、実行します。

リスト8:MessagesController.csのPostメソッドを修正

    public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
    {
        if (activity.Type == ActivityTypes.Message)
        {
            await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
        }
        else
        {
            HandleSystemMessage(activity);
        }
        var response = Request.CreateResponse(HttpStatusCode.OK);
        return response;
    }
サブダイアログを使ったChatbot

サブダイアログを使ったChatbot

このChatbotの主となる話題はランチを注文することですが、副次的な話題としてコース、飲み物、デザートへの分岐があります。対話の流れを維持しつつ、メインダイアログとサブダイアログを使い分けて話題を分岐して、最後にまとめています。さらに、PromptDialog.Choiceを使うことで、ユーザーからの入力を簡略化しています。

今回はChatbotで画面切り替えを行わずに、話題の分岐をダイアログで実現する基本的な方法を紹介しました。Bot FrameworkのBot Builder SDKではさらに細かいダイアログの仕様が紹介されていますので、より深くダイアログを理解したい場合にはこちらを参考にしてもらえればと思います。

GMOインターネット株式会社 Windowsソリューション チーフエグゼクティブ

GMOインターネットでWindowsのサービス開発運用に関わって16年、数年単位で進化し続けるMicrosoftのWindowsは新しもの好きにはたまらない製品です。自動販売機に見たことのないジュースがあれば、迷わすボタンを押します。そんなチャレンジが僕の人生を明るく、楽しくしてくれています。

お名前.com デスクトップクラウド
http://www.onamae-desktop.com/

お名前.com VPS Hyper-V
http://www.onamae-server.com/vps/hyperv/

連載バックナンバー

Web開発技術解説
第10回

Chatbotから話しかける(プロアクティブメッセージの送信)

2018/5/18
連載10回目となる今回は、Chatbotから先に話しかけるための手順を紹介します。
Web開発技術解説
第9回

Chatbotで画像コミュニケーションを実現する

2018/5/14
連載9回目となる今回は、Chatbotとの文字でのやり取りに画像を追加する方法を解説します。
Web開発技術解説
第8回

FacebookでChatbotを公開する

2018/4/17
連載8回目となる今回は、FacebookのMessengerを介してChatbotとやり取りする方法を解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています