Bot Framework / Formflowの使い方【入門】

あらすじ

Microsoft bot frameworkのFormflow(フォームフロー)の基本機能と高度な機能について探っていきます。

基礎編ではFormflowとは何か、そして基本的なFormflowの実行方法を掘り下げていきます。

応用編では、フォームフィールドに値を動的に割り当てる方法、そしてカルーセル・フォーマット内のヒーローカードとしてのフィールドオプションの表示方法を見ていきます。

前提条件

  • チャットボットとMicrosoft Bot frameworkの基本的な知識がある。
  • Visual Studio community2017とBot Application Templateがインストール済み。
  • Bot framework Emulatorがインストール済み。

1. 基礎編

1-1. Bot Frameworkとは?

チャットボットは最近、最も広まりつつある技術の一つですが、Microsoftはボット作成、そしてそれをSkype、Facebook Messenger、Slack等のチャネルで使える大変協力なフレームワークを提供しています。

この記事では、Formflowについて集中したいのでBot frameworkの基本についての詳細には深く触れません。

Formflowの基礎と、重要性に関して説明していきます。

1-2. Formflowとは?

Formflowは必要な情報をあなたに指定させ、会話を管理し、質問から質問へと自動的にユーザーを導くBot Builder SKDライブラリです。

多くの機能が含まれ、定義されたガイド付きの対話アプリを創れます。

Formflowは私たちのために手間がかかる作業の殆どをしてくれています。

フィールドのプロンプトを用意する、あいまいさを明確にする、一つ一つの手順の進行状況を表示する、戻る、その他多くの機能があります。

これらの機能の殆どが既にインストール済みで、フォームを修正したりカスタマイズすることもできます。

以下はFormflowに含まれている機能のリストです。

  • ヘルプとガイダンス
  • 数字、文字の両方の入力を理解
  • 何を理解したか、していないかのフィードバックを与える
  • 必要に応じて内容を明確にするための質問をする
  • 手順間のナビゲーションをする
  • フィールドごとのカスタムプロンプト
  • 自動的にプロンプトを生成するときと、フィールドのヘルプに使うテンプレート
  • 一致する用語
  • オプションと条件付きフィールドのサポート
  • 入力値の検証

Formflowの基本機能と使用例について更に知るために下記のMicrosoftのオフィシャル記事をご覧ください。

カスタマイズするためには3つの主な方法があります。

  • Attributes(属性):ダイアログを適切に管理するために、クラスに追加できるアイテム
  • カスタムビジネスロジック:カスタム値を設定し取得するためのロジック
  • フォームビルダークラス:細かいコントロールができる

動的なFormflowを理解するために、基本的な機能を簡単な事例を使って、始めてみましょう。

1-3. Formflowの基本的機能

シンプルなレストランボットの例を使います。

1-3-1. Formflowフォームを作成する

Bot frameworkのフォームは必要な情報を適用するためのフィールドまたはプロパティのあるクラスです。

このデモでは、既に定義されたメニューからユーザーが選択し注文できる基本的な注文システムを使用します。

この簡単なレストランデモでは、下記のコードで示された、選択された商品、デリバリー方法、ユーザーネーム、電話番号、住所を知る必要があります。

[Serializable]
    public class OrderDetails
    {
        public List Items { get; set; }

        public DeliveryOptions DeliveryMode { get; set; }

        public string UserName { get; set; }
        public string Phone { get; set; }
        public string address { get; set; }
    }
    // This starts at 1 because 0 is the "no value" value
    public enum DeliveryOptions
    {
        TakeAway = 1,
        Delivery
    }
    public enum MenuOptions
    {
        CrispyChicken = 1,
        ChickenWings,
        ChickenDrumStick,
        ChickenPopcorn  
  
    }

これは、レストランの商品を注文するための、ボットとユーザー間の対話を作る、とても基本的なFormflowフォームです。

ご覧の様に、主なクラスは〔Serializable〕属性にマークされ状態が保持されています。

プロパティはString型とenum型です。

String型のプロパティとはユーザーがプロパティにプロンプトしたとき、Stringインプットを受け入れるということです。

一方でEnum型のプロパティが二つあります。

これは、それらのプロパティが選択か、Enum内のオプションによりプロンプトが表示するということです。

前述したとおりFormflowでは多くのカスタマイズが可能ですが、はじめは基本的な機能に集中してこのフォームがどのように作動するかを見ていきましょう。

1-3-2. フォームの構成

OrderDetailsクラスフォームを作るために、タイプとFormBuilderをインスタンス化します。この例では、OrderDetailsというタイプからイニシャライズします。

以下が基本的な例です。

public static IForm BuildForm()
        {
            return new FormBuilder()
                    .Message("Welcome to demo Restaurant bot!")
                    .OnCompletion(async (context, order) => 
                     {
                        await context.PostAsync("Thanks for your order!");
                     })
                    .Build();
        }

BuilderFormメソッドはFormBuilder<OrderDetails>インスタンスに返ってきます。

FormBuilderは数個のメソッドを繋げられる流れるようなインターフェースを提供します。

Messageはボットがコミュニケートを始める時、ユーザーへのメッセージを示します。

FormBuilderはOrderDetailsクラスを使い会話を管理します。

ボットが終了したら、OnCompletionにパスされたLambdaがユーザーと再び交信します。

ここでレポートをデータベースに保存したり、Web上にあげるなどのアクションができます。

コンテクストはDialogContextで、Lambdaはcontextを使い最終的なメッセージをユーザーに送ります。

lambdaのOrderパラメータはOrderDetailsクラスインスタンスを保持し、ユーザーの回答を抽出するのに使えるFlormflowが集めた状態を保持します。

私はOrderDetailsクラス内にBuildFormメソッドを入れました。

必要なパッケージの入った完全なクラスを見てみましょう

using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using Microsoft.Bot.Builder.Dialogs;
namespace RestaurantDemo.Model
{
[Serializable]
public class OrderDetails
{
public List Items { get; set; }
public DeliveryOptions DeliveryMode { get; set; }
public string UserName { get; set; }
public string Phone { get; set; }
public string address { get; set; }
public static IForm BuildForm()
{
return new FormBuilder()
.Message("Welcome to demo Restaurant bot!")
.OnCompletion(async (context, order) =>
{
await context.PostAsync("Thanks for your order!");
})
.Build();
}
}
// This starts at 1 because 0 is the "no value" value
public enum DeliveryOptions
{
TakeAway = 1,
Delivery
}
public enum MenuOptions
{
CrispyChicken = 1,
ChickenWings,
ChickenDrumStick,
ChickenPopcorn
}
}

1-3-3. フォームを呼び出す

フォームクラスが準備できたので、ユーザーが会話を始めたときにフォームを呼び出します。

それをするためにいくつかの方法があります。

Dialog内からフォームを呼びだすか、開始するか、Post Method内のMessageControllerから直接提供できます。

このデモではシンプルにMessageControllerから直接呼び出します。

public async Task Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => Chain.From(() => FormDialog.FromForm(OrderDetails.BuildForm)));
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}

上記のコードスニペットで、PostメソッドはVisual studio内で新しいBot Applicationプロジェクトを組み立てる時に見る事ができる標準メソッドです。

OrderDetailsフォームを会話を開始した部分を見てみましょう。

await Conversation.SendAsync(activity, () => Chain.From(() => FormDialog.FromForm(OrderDetails.BuildForm)));

ここでFormDialogを使いました。

FromFormメソッドによりOrderDetailを使いOrderDetailsフォームをパスします。

1-3-4. 動作確認する

Formflowが作動している事を確かめるために、Visual studioの中のアプリケーションを起動する必要があります。

Bot frameworkエミュレーターでWebアプリケーションが動作しているアドレスを入力します。

これでメッセージを送り、フォームをトリガーすることができます。

Microsoft Bot framework

上記の画像では、シンプルなクラスがどの様にガイド付きの対話アプリケーションを作るかを見ることができます。

速く使いやすく、少数のコードを書くだけで良く、機能のパッケージが全て含まれています。

上記の画像の様にOKと言う代わりに、確認の前にNoとも言えました。

その場合、フォームは、現在の選択を変更するオプションを提示してくれます。

または、会話の途中でStatusと入力すると、フォームの状況を表示し、Helpとタイプするとオプションに関してのヘルプが提示されます。

これでFormflowの基本機能について終わります。

次は、動的Formflowやカルーセル・フォーマットなどのForm optionをどの様に表示するか等、もっと高度な機能について掘り下げていきます。

2. 発展編

Formflowは素晴らしい機能とカスタマイズ能力が詰まっていますが、実際のビジネスの場面で常に要求される機能があまりないとも言えます。

例えばdatabaseの様な特別なデータソースやRest api等からメニューを読み込む必要のある料理デリバリーの例です。

基礎編の記事で見てきた通り、基本的なFormflowには動的なオプションを処理する能力はありませんが、幸いMicrosoftは動的なフォームオプションをサポートするために、Formbuilderクラスによりフォームをカスタマイズできる拡張ポイントを提供しています。

現実のビジネスにおいて必要かもしれないもう一つの例は、メニューの名前だけでなく写真と説明を表示する事です。

Dialogだとデフォルトでサポートされている機能で、カルーセル・フォーマットのヒーローカードを簡単に表示する事ができます。

Formflowではどうでしょう?

私たちは両方の長所を得ることができるでしょうか?

答はYesです。Microsoft bot Frameworkはそれができる柔軟性もあります。

では例を見ながら進めていきましょう。

2-1. 動的Formflow

動的なFormflowが作動するのを見るために、前回使ったシンプルな料理オーダーの例を使います。

ここでは、MenuItemsの既定義の値があるenumの代わりに、MenuItemsを読み込むためにサンプルデータソースを使います。

より良く理解するために、下記のOrderDetailsフォームを見て下さい。

2-1-1. OrderDetailsクラスの編集

[Serializable]
public class OrderDetails
{
public List MenuItems { get; set; }
public DeliveryOptions DeliveryMode { get; set; }
public string UserName { get; set; }
public string Phone { get; set; }
public string address { get; set; }
}
// This starts at 1 because 0 is the "no value" value
public enum DeliveryOptions
{
TakeAway = 1,
Delivery
}
[Serializable]
public class MenuOption
{
public string ItemName { get; set; }
public string Description { get; set; }
public string ItemImage { get; set; }
}
}

上記の通り、MenuItems以外は全て前回の例と同じです。

今回MenuItemsは、ListMenuOptionとなり、MenuOptionは既定義の値のenumではなくクラスとなります。

外部のデータソースからOrderDetailsの中のMenuItemsの値を読み込みます。

シンプルに、List<MenuItems>に返るクラスとメソッドを定義しましょう。

public class MenuDB  
  
{       
 
public static List GetAllMenuOptions()  
      
{       
     
return new List() 
{
new MenuOption(){ ItemName = "CrispyChicken" },
new MenuOption(){ ItemName = "ChickenWings" },
new MenuOption(){ ItemName = "ChickenDrumStick" },
new MenuOption(){ ItemName = "ChickenPopcorn" },
};        
}    
}

実際に使用するときは、このメソッドをあなた自身の実装にいつでも置き換えることができます。

そして最も大切な部分です。

FormbuilderのBuildForm()メソッドを修正します。

動的なフォームの値をサポートするためにBuildForm()を修正しましょう

public static IForm BuildForm()
{
 var menuItems = MenuDB.GetAllMenuOptions();
 var builder = new FormBuilder();  
           
 builder            
.Message("Welcome to demo Restaurant bot!")
            
.Field(new FieldReflector(nameof(MenuItems))          
.SetType(null)       
     
.SetDefine((state, field) =>            
{                
foreach (var item in menuItems)                
{                    
field                    
.AddDescription(item, item.ItemName)                    
.AddTerms(item, item.ItemName);                
}                 
return Task.FromResult(true);            
}))            
.AddRemainingFields()            
.OnCompletion(async (context, order) =>            {                
await context.PostAsync("Thanks for your order!");            
});             
return builder.Build();        
}     
}

上記のコードサンプルを詳しく見ていく前に、ここまでで前回の基本的Formflowから変更した事項を見ていきましょう。

Form builderにメソッドが2つ追加されました。

Fieldメソッドと、AddRemainingFieldメソッドです。

FieldメソッドとFieldReflectorクラスを使用するために、Microsoft.Bot.Builder.FormFlow.Advancedのパッケージをインポートする必要があります。

これは基本的に、フォームフィールドを自由に定義し既定動作を完全にオーバーライドできる拡張性を与えます。

Advanced.SetType(null) フィールドタイプnullはenumフィールドを表しています。

Advanced.SetDefineはフィールドを定義するasync delegateを指定しています。

この例では、値を読み込むためにデータソースを処理しました。

Delegateは現在の状況のオブジェクトに渡され、Advanced.Fieldは動的に定義されました。

DelegateはフィールドのFluentメソッドを使い動的に値を定義しています。

この例では、値は文字列でAddDescriptionとAddTermsメソッドはそれぞれ値について説明と用語を指定しています。

これで動的Formflowをサポートするための変更が終了しました。

しかしこれはFormbuilderクラスがフォームの動作をカスタマイズする、数々の可能性の始まりでしかありません。

2-2. Formflow内のヒーローカードとカルーセル

更にエキサイティングな、カルーセル・フォーマット内のヒーローカードのフォームのフィールドオプション表示の能力について見ていきましょう。

以下はカルーセルのカードがどのようにみえるかのサンプルです

カルーセル・フォーマットでのメニュー表示を説明するために、注文のユースケースを少し変えます。

カルーセル・フォーマットのヒーローカードの表示は複数ではなく1つのアイテムを表示するので一度に選択できるメニューは1つです。

これは単にシンプルにするためなので ヒーローカード表示をデモアプリケーションの中でそれほど修正しなくても良い事が分かります。

ではコードを見てみましょう。

MenuDBクラスで、MenuItemプロパティを返しました。

public class MenuDB
{
public static List GetAllMenuOptions()
{
return new List() {
new MenuOption(){ItemName = "CrispyChicken" , Description ="4 pcs of crispy chicken.", ItemImage="http://divascancook.com/wp-content/uploads/2015/01/IMG_0231.jpg" },
new MenuOption(){ ItemName = "ChickenWings" , Description= "6 pcs of crispy chicken wings." , ItemImage="https://www.munatycooking.com/wp-content/uploads/2016/05/crispy-spicy-chicken-wings-5.jpg"},
new MenuOption(){ ItemName = "ChickenDrumStick" , Description="4 pcs of chicken drumsticks." , ItemImage="https://www.budgetbytes.com/wp-content/uploads/2016/03/Crispy-Baked-Honey-Sriracha-Chicken-Drumsticks-above-straight.jpg" },
new MenuOption(){ ItemName = "ChickenPopcorn", Description = "1 medium chicken popcorn.", ItemImage = "http://mybodymykitchen.com/wp-content/uploads/2016/02/jalapeno-popcorn-chicken-1024x1024.jpg" },
};
}
}

これは簡単です。

URLの画像はグーグル検索で出てきた無作為なサイトから取りました。

それでは次のOrderDetailsクラスに移りましょう

[Serializable]    
public class OrderDetails    
{        
public MenuOption MenuItems { get; set; }         
public DeliveryOptions DeliveryMode { get; set; }         
public string UserName { get; set; }        
public string Phone { get; set; }        
public string address { get; set; }
          
public static IForm BuildForm()        
{             
var menuItems = MenuDB.GetAllMenuOptions();   
          
var builder = new FormBuilder();
             
builder            
.Message("Welcome to demo Restaurant bot!")            
.Field(new FieldReflector(nameof(MenuItems))
.SetType(null)            
.SetDefine((state, field) =>            
{                
foreach (var item in menuItems)                
{                    
field                    
.AddDescription(item, new DescribeAttribute() { Title = item.ItemName, Description = item.ItemName, SubTitle = item.Description, Image = item.ItemImage})                    
.AddTerms(item, item.ItemName);                
}                 
return Task.FromResult(true);            
})            
.SetPrompt(new PromptAttribute(" What would you like to order? \n {||} \n")
{                
ChoiceStyle = ChoiceStyleOptions.Carousel             
})        
    
.SetAllowsMultiple(false)            
)            
.AddRemainingFields()            
.OnCompletion(async (context, order) =>            
{                
await context.PostAsync("Thanks for your order!");            
});       
      
return builder.Build();        
}  
   
}

ここでは幾つかの変更が見られます。

はじめに、OrderDetailsクラスのMenuItemsプロパティがMenuOptionタイプではありません。

BuildForm()メソッドの中のフィールドをAddDescriptionメソッドに変更しました。

もう一つのオーバーロードをそのメソッドに使いました。

field.AddDescription(item, new DescribeAttribute() { Title = item.ItemName, Description = item.ItemName, SubTitle = item.Description, Image = item.ItemImage})

可能なチャネルでフィールドオプションがカルーセル・フォーマットのヒーローカードとして表示されるために必要な全てのプロパティを、DescriptionAttributeが取り仕切ります。

最後に、ChoiceStyleがカルーセルになるように指定したSetPromptメソッドの設定をします。

.SetPrompt(new PromptAttribute(" What would you like to order? \n {||} \n")
{
ChoiceStyle = ChoiceStyleOptions.Carousel
})

それをChoiceStyleOptions.Autoに設定することさえできます。

可能な時はいつでもFormflowがカルーセル・フォーマットの表示を確認し、そうでなければ既定オプションに戻るので、これは良い方法です。

まとめ

これでFormflowのいくつかの高度な機能について終わります。

これがお役に立つことを願います。

前述したようにBot frameworkのFormflowでは大変多くのカスタマイズができるので、可能なオプションを公式ドキュメント等にてご参照してください。

デモで使用した全てのコードをGithubにアップロードし、ここからアクセスできます。

原文

https://chatbotslife.com/formflow-in-microsoft-bot-framework-part-1-f45122595290 https://chatbotslife.com/formflow-in-microsoft-bot-framework-part-2-e920f53e0f6c

チャットボットライフとの提携により、翻訳し掲載しています。
チャットボットライフとは、最新のボット、AI、NLP、ツール等を扱うメディアです。