【C#/Bot Framework】ボットチャットにダイアログ機能をつける

「C#」「Visual Studio」「Microsoft Bot Framework」でボットチャットにダイアログをつける方法について入門者向けにまとめました。

ダイアログ機能①

ダイアログとは、「対話」「会話」という意味です。
チャットボットにおけるダイアログ機能は、次のようなインターフェースで「ユーザー」と「ボット」が対話できます。

今回は、どのダイアログ機能の使い方を紹介します。

① Visual Studioで[Bot Application]のプロジェクトを開きます。

補足「Microsoft Bot Frameworkのインストール・動作確認」はこちら
1 【VS2017】Microsoft Bot Framework 一式をインストール
2 テンプレートのパッケージ更新
3 エミュレータの動作確認

② [Dialogs]フォルダ → 空白のクラスファイル「名前は任意だが今回の例ではDialogTest.cs」を追加します。

DialogTest.cs

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

namespace Bot_Application1.Dialogs
{
    [Serializable]
    public class DialogTest : IDialog<object>
    {
        public string userName;
        public string userPlace;
        
        // 最初の処理
        public Task StartAsync(IDialogContext context)
        {
            // 最ユーザーが何か入力したら、MessageReceivedAsyncメソッドを呼び出し
            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.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(BusinessMessage);
        }
        private async Task BusinessMessage(IDialogContext context, IAwaitable<object> result)
        {
            var activity = await result as Activity;

            // ユーザーが入力した用件を取得
            userBusiness = activity.Text;

            // ボットがユーザーにメッセージを返信
            await context.PostAsync($"{userBusiness}についてですね、{userName}さん少々お待ちください");

            // 次の処理(null:なし)
            context.Done<object>(null);
        }
    }
}
補足
Serializable属性 チャットボットでは対話の状態を保存するために[Serializable]が必要となります。(シリアル化可能という制約が必要)
IDialog<object> IDialog<T>インターフェースを実装したクラスがダイアログになります。(<object>はダイアログが終了した時に戻り値として返す値の型)

③ 「MessagesController.cs」のPostメソッドでDialogTestクラスを呼び出すように次のように1行を変更します。

■変更後

await Conversation.SendAsync(activity, () => new Dialogs.DialogTest());

ダイアログ機能②サブダイアログを使った分岐

ダイアログは、次のようにメインの対話を分岐させてサブの話題を展開できます。

RootDialog.cs

RootDialog.csを次のように書き換えます。

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

namespace Bot_Application1.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog<object>
    {   // 回答メニュー
        private List<string> answer1List = new List<string>() { "マスター・・・?", "ちゃうで" };
        private string order;

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

        private async Task FirstMessage(IDialogContext context, IAwaitable<object> result)
        {
            await context.PostAsync("問おう、貴方が私のマスターか。");

            // 回答メニューの表示
            Question1Message(context);
        }

        private void Question1Message(IDialogContext context)
        {
            // 選択肢メニュー
            PromptDialog.Choice(context, SelectDialog, answer1List, "※回答を選択");
        }

        private async Task SelectDialog(IDialogContext context, IAwaitable<object> result)
        {   // 選択した回答を取得
            var selectedAnswer = await result;

            if (selectedAnswer.ToString() == answer1List[0])
            {   // ボットのメッセージ
                await context.PostAsync($"サーヴァントセイバー、召喚に従い参上した。マスター、指示を。");
                // サブダイアログの呼び出し
                context.Call(new Question1YesDialog(), Question1YesAfterDialog);
            }
            else if (selectedAnswer.ToString() == answer1List[1])
            {    // ボットのメッセージ
                await context.PostAsync($"えっ・・・。");
            }
        }

        private async Task Question1YesAfterDialog(IDialogContext context, IAwaitable<string> result)
        {
            // ダイアログの回答結果を取得
            order = await result;

            // ボットのメッセージ
            await context.PostAsync($"了解した、{order}する");
        }
    }
}

Question1YesDialog.cs

Question1YesDialog.csを追加します。
※サブダイアログのクラス

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

namespace Bot_Application1.Dialogs
{
    [Serializable]
    public class Question1YesDialog : IDialog<string>
    {
        private List<string> answerList = new List<string>() { "敵を迎撃", "守りを優先" };
        public Task StartAsync(IDialogContext context)
        {
            PromptDialog.Choice(context, this.SelectDialog, this.answerList, "※回答を選択");
            return Task.CompletedTask;
        }

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

MessagesController.cs

MessagesController.csを次のように編集します。

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace Bot_Application1
{
    [BotAuthentication]
    public class MessagesController : ApiController
    {
        /// <summary>
        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        /// </summary>
        public async Task 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;
        }

        private Activity HandleSystemMessage(Activity message)
        {
            if (message.Type == ActivityTypes.DeleteUserData)
            {
                // Implement user deletion here
                // If we handle user deletion, return a real message
            }
            else if (message.Type == ActivityTypes.ConversationUpdate)
            {
                // Handle conversation state changes, like members being added and removed
                // Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
                // Not available in all channels
            }
            else if (message.Type == ActivityTypes.ContactRelationUpdate)
            {
                // Handle add/remove from contact lists
                // Activity.From + Activity.Action represent what happened
            }
            else if (message.Type == ActivityTypes.Typing)
            {
                // Handle knowing tha the user is typing
            }
            else if (message.Type == ActivityTypes.Ping)
            {
            }

            return null;
        }
    }
}
関連ページ
1 【Microsoft Bot Framework入門】ボット作成・使い方
2 【C#入門】基礎文法とサンプル集
関連記事