今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

Microsoft Bot Frameworkで簡単なBotを作ってみる (4) ~FormFlowを使った対話~

今までにやったことと今回のゴール

前回の記事やそれ以前の記事では、次のようなことを行いました。 。

  • 電力使用状況を聞くとPromptDialogで確認したうえで電力使用状況を教えてくれるDialogを作成
  • 電力使用状況は、Yahoo!の提供するAPIを使用して取得
  • BotをAzureに公開したあと、Bot ConnectorでSkypeに接続

前回までは、「電力使用状況は?」と聞くと、東京電力の電力使用状況を教えてくれるbotを作っていました。今回は対話部分に更に手を加えて、「電力使用状況は?」と聞いたら電力会社の確認をしてくれるような対話を実装してみようと思います。そのような処理を自力で書くこともできるのですが、今回はせっかくなのでそのような用途のために提供されているFormFlowを使用して対話を定義していきます。



FormFlowとは?

どのように動くのか?

FormFlowは、自分で定義したクラスのメンバ変数を埋めてくれるような対話を自動的に生成してくれる機能です。
例えば、このような対話を簡単に生成することができます。
f:id:kanohk:20160708014916p:plain

対話で埋めるクラスを定義する

前回までは東京電力の電力使用状況を返していましたが、今回はどの電力会社の電力使用状況を知りたいかを対話を通して聞き出すことが目的です。まずは、その電力会社一覧をenumで定義します。

public enum AreaOption
{
    北海道電力, 東北電力, 東京電力, 中部電力, 関西電力, 九州電力
}

次に、1回の電力使用状況の問い合わせに対応するクラス、ElectricityUsageQuery クラスを次のように定義します。

[Serializable]
public class ElectricityUsageQuery
{
    [Describe("電力会社")]
    [Prompt("どの電力会社について知りたいですか?{||}")]
    public AreaOption? Area;

    public static IForm<ElectricityUsageQuery> BuildForm()
    {
        return new FormBuilder<ElectricityUsageQuery>()
            .Field(nameof(Area))
            .Confirm("{Area}の電力使用状況を取得します。よろしいですか?")
            .OnCompletion(ReportUsage)
            .Build();
    }

    private static async Task ReportUsage(IDialogContext context, ElectricityUsageQuery query)
    {
        var usage = ElectricityUsageAPI.GetElectricityUsage(query.Area.GetValueOrDefault().ToEnglishString());
        await context.PostAsync(usage != null ? string.Format("{0}kWです", usage) : "取得できませんでした。。");
        context.PerUserInConversationData.SetValue("state", BotState.Default);
    }
}

ポイントは Area というメンバ変数です。この関数内で定義されたメンバ変数を埋めるような対話をFormFlowでは生成することができます。
例えば電力使用会社だけではなくて、日付も対話を通して知りたい場合は、ここに Date というようなメンバ変数も追加することになります。

この Area の上についている Describe と Prompt という属性は、それぞれ Area という変数をbotがどのように表現するか、Area についてbotがどのように質問するかをそれぞれ指定しています。
この他にも幾つか属性を指定できますが、詳細は公式ドキュメントを参照してください。
http://docs.botframework.com/sdkreference/csharp/forms.html#attributes

また、Promptないで使われている {||} といった表記はパターンと呼ばれるBot Framework特有の表記で、詳細はこちらを参照してください。
http://docs.botframework.com/sdkreference/csharp/forms.html#patterns


もうひとつのポイントは BuildForm という関数です。この関数が呼び出されることによって、実際に対話が生成されます。
この関数の中を見ると何となく分かるかと思いますが、どのように対話を進めていくかを定義することができます。
この例の場合は、Field(nameof(Area)) というメソッドで Area を埋めるような質問をする、そして Confirm() でユーザーの入力が正しいか確認したあと、OnCompletion()で実際の処理(つまり、電力使用状況を取得して表示する)を行う、という流れです。
他にも Message や AddRemainingFields といったメソッドがあるので、こちらも公式ドキュメントを参照していただければと思います。
http://docs.botframework.com/sdkreference/csharp/de/d9d/class_microsoft_1_1_bot_1_1_builder_1_1_form_flow_1_1_form_builder.html

定義した FormFlow を呼び出す

先ほど定義した FormFlow は、次のように呼び出します。

    [BotAuthentication]
    public class MessagesController : ApiController
    {
        internal static IDialog<ElectricityUsageQuery> MakeUsageDialog()
        {
            return Chain.From(() => FormDialog.FromForm(ElectricityUsageQuery.BuildForm));
        }

        public async Task<Message> Post([FromBody]Message message)
        {
            if (message.Type == "Message")
            {

                Message reply = null;
                if (message.Text == "電力使用状況は?" || message.GetBotPerUserInConversationData<BotState>("state") == BotState.Usage)
                {
                    message.SetBotPerUserInConversationData("state", BotState.Usage);

                    // FormFlow を呼び出して、電力会社について情報を集める
                    return await Conversation.SendAsync(message, MakeUsageDialog);
                }
                else
                {
                    reply = message.CreateReplyMessage("(。・ ω<)ゞてへぺろ♡");
                }
                return reply;
            }
            else
            {
                return HandleSystemMessage(message);
            }
        }

        // 省略
    }

ここまで実装して実行すると、最初の画像のような対話が実現できます。

ソースコード

いつもどおり、今回のソースコードもGitHubにアップロードしました。
github.com

次回記事:
www.madopro.net