BEACHSIDE BLOG

MicrosoftとかC#を好むレンジャーの個人的メモ

BotFramework (V3) - Dialog 入門

レッツ 第4次産業革命

f:id:beachside:20160718162831j:plain

Bot Frameworkでの開発で基本となる「Dialog」についてレッツモリモリ行きましょう。 公式のドキュメントのDialogの部分 http://docs.botframework.com/en-us/csharp/builder/sdkreference/forms.htmlDialogs | Bot Builder SDK C# Reference Library | Bot Framework あたりを参考にしてます。

Bot開発を初めてする場合は、VSの開発テンプレートとEmulatorに関する事前準備をします。 beachside.hatenablog.com

> Environment

検証した環境は以下です。

> Overview

項番2で説明する機能に対して、項番4~項番6の異なる方法でBotを作ってみます。

1. 単純なEchoBot
2. このあと作るボットの機能概要
3. 共通部品をつくっておきます
4. Stateを持ったBot その1
5. Stateを持ったBot その2「CommandDialog」
6. Stateを持ったBot その3「Chain」

1. 単純なEchoBot

最初は、何か入力したらそれを返すBotを作ります。

VSで、新しいプロジェクト作成→右上の検索でbotを入力してBotAppricationを選択します。プロジェクト名は、「DialogDemo」にしました。 f:id:beachside:20160715200041p:plain ASP.NET WebAPIのプロジェクトができました。

(個人的な趣味で)お決まりのDialogsというフォルダを追加しました。その中にSimpleEchoDialogクラスを追加しました。 f:id:beachside:20160715201033p:plain

そしてコード。

SimpleEchoDIalogクラスは、おきまりのSerializableAttributeがついており(これ大事)、IDialogインターフェースを実装し、メソッドをさらりと書きます。

次は、Botの呼び出し元となるWebAPIのコントローラーのコードを書いていきます。プロジェクトを作成すると、MessagesControllerができています。 ここのコードを以下のようにざっくり変更しましょう。

Botのメッセージのやりとりは、Activityクラスが中心となります。Activityクラスについては、本家ドキュメントに概要が書いてあります。 メッセージを受けとると、28行目でDoSomethingAsyncメソッドを呼んで、Botの処理をするようにしています。
Dialogを使うときの基本的な方法として、Conversation.SendAsyncメソッドで処理をします。詳しくは本家ドキュメントにて。

先ほどSimpleEchoDIalogクラスはIDialogが実装されているので、インスタンス化したところでStartAsyncメソッドが実行されます。 デバッグしてEmulatorから何か入力してみましょう。

f:id:beachside:20160715203017p:plain

ほい、想像通りな動作です。

2. このあと作るボットの機能概要

同じ動きをするボットをいくつかのパターンで実装します。その機能は以下です。

  • [機能1] 文字を入力したら、エコーして文字数も教えてくれる
  • [機能2] 会話した回数を数えてくれる
  • [機能3] クライアントの入力された文字列が「reset」だったら、以下のリセット処理をする
    • 「(クラス名)Are you sure you want to reset the count?」と確認メッセージを返す
    • 「はい」と返答がきたらカウンターを0に戻す
    • 「いいえ」だったらカウンターはリセットしない

3. 共通部品をつくっておきます

このあと、何かと同じコードを書くので、Utility的なクラスを書いておきます。

カウンターをインクリメント/リセットするメソッド、Botが返答するメッセージのメソッドを書いています。

4. Stateを持ったBot その1

機能は、前述したとおりです。ここでは、ソリューションエクスプローラーのDialogsフォルダの下にStateEchoDialogクラスを作り、以下コードを実装しました。

StateEchoDialogクラスにはお決まりのSerializableAttributeがついています。カウンターは、StateEchoDialogクラスのprivateメンバー変数_countで保持しています。 StartAsyncメソッドでカウンターを初期化(0にセット)し、MessageReceivedAsyncメソッドを実行します。MessageReceivedAsyncメソッドの中では、第2引数のargumentからクライアントのデータをとることができます。

25行目のifで、クライアントが入力した文字列が「reset」かどうかを判定します。そして、[機能1]、[機能2]は、35行目で実装しています。 [機能3]のリセット処理は、27行目のPromptDialog.Confirmからです。「reset」入力後にリセットを本当にするかの確認は、PromptDialog.Confirmの機能で行います。はい/いいえの応答後、44行目のAfterResetAsyncメソッドでリセットするしないの処理を行います。

MessagesControllerクラスのDoSomethingAsyncメソッドは、以下のように変更します。

これで実行すると、無事に動きます。

f:id:beachside:20160715212301p:plain

会話をしたカウントもちゃんと取れています。

ところで余談ですが、resetと入力したときに今回書いたクラスに変わらないことありますか? f:id:beachside:20160718141937p:plain

コードを変更してデバッグしても、私の環境では古いコードを認識したままってのが頻繁におきます....。そんなときはEmulatorを再起動するか、古いコード(今回だとSimpleEchoDIalogクラス)を全部コメントアウトすると、ちゃんと動きます。そう、このために、reset時にクラス名を表示してちゃんとどうさしているか確認していたわけでして....これなんなんでしょうね...

5. Stateを持ったBot その2「CommandDialog」

同様の機能をCommandDialogを使って実装してみます。 ソリューションエクスプローラーのDialogsフォルダの下にEchoCommandDialogクラスを追加し、以下のコードを書きます。

ここからは、会話回数のカウンターをWebService側で保持するのではなく、会話のコンテキスト側で保持するようにします。 本家ドキュメントのBot State Service | Bot Builder SDK C# Reference Library | Bot Frameworkの内容を参考にしています。

ここでは、UserDatacountというKeyのValueに会話の回数を保存しました。DialogSampleUtilクラスのIncrementCountメソッド(DialogSampleUtil.csの7行目)とResetCountメソッド(DialogSampleUtil.csの15行目)がその処理になります。 UserData以外にも Bot State Service | Bot Builder SDK C# Reference Library | Bot Frameworkに記載があるようにStateを保持するプロパティとそのSet/Getメソッドが用意されています。用途に応じて使い方を検討するところですね。

[機能1][機能2]は、33行目からのOnDefaultで処理しています。35行目のDialogSampleUtil.IncrementCountメソッドでUserDataに現在のカウントを取得して1つプラスし、37行目でメッセージを返答しています。 [機能3]は、15~32行目の処理です。

MessagesControllerクラスのDoSomethingAsyncメソッドは、以下のように変更します。

これでデバッグ実行しても同じように動きます。resetした時にクラス名が更新されていることは注意してください。

6. Stateを持ったBot その3「Chain」

次は、本命(?)のChainを使ってみます。本家のドキュメントでは以下を参考にしました。
Dialog Chains

ソリューションエクスプローラーのDialogsフォルダの下にEchoChainDialogクラスを追加し、以下のコードを書きます。

Chainは、Switchで入力されたメッセージに応じて処理を分岐させています。 [機能1][機能2]のエコーを返す処理は、36行目のDefaultCaseの辺りです。[機能3]のリセット処理は、12行目。主要な処理は上記の例同様DialogSampleUtilクラスの処理を使っています。 34行目は、「help」と入力されたときの処理を冗談半分に作りました。 MessagesControllerクラスのDoSomethingAsyncメソッドは、以下のように変更します。

デバッグで実装するとちゃんとうごきますね。 f:id:beachside:20160718152124p:plain

今日はここまでですね。