BEACHSIDE BLOG

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

グローバルメッセージハンドラー (2/2) - Bot Frameworkの基本機能 (global message handlers using Scorables)

前回から引き続き、本題の Scorables の実装です。

Overview

Environment

Scorables の実装のイメージとしては、

  • スコアリングをするクラスの作成
  • (必要であれば)トップスコアだった場合に処理をするDialogの作成

です。

2. 簡易なScorableの実装

会話をリセットする Scorables の実装

よくある機能?、会話をリセットする Scorables を実装します。動作イメージは、「リセット」と入力されたら、会話がリセットされるシンプルな例です。

Dialog フォルダーの下に Global というフォルダーを作り、ResetScorable というクラスを作りました(フォルダー構成イマイチですね)。

f:id:beachside:20170720214815p:plain


まずはコードを。

"はじめてのScorables"だと説明どころはたくさんあるんですが...ざっくり行きましょう。

Socrableなクラスを実装するには、ScorableBase クラスを継承させます。13行目のGenericの型パラメーターについては、後述します。詳しくはBotBuilderのソースのここら辺とかですが、更新頻度も多いと思いますので、確認したときに都度ここから掘っていくと良いかも。

github.com

ざっくり解説:コンストラクター

コンストラクターの引数で IDialogTask を受け取っています(17行目)が、これはDIしてるの、実装については後述します。

処理としては、SetField.NotNull メソッドを呼んでいるだけです(19行目)。これは BotBuilder 内のメソッドで、3つ目の引数(コンストラクターで受け取った値)がNULLだったら2つめの引数(ここだと nameof(_task) )のExceptionをはく、NULLでなければ1つめの引数に値をセットするというよくある処理です。Guardメソッドですね。

ざっくり解説:Scorables 内の流れ

ResetScorable クラスで実装しているメソッドは、ScorableBase クラスの abstract なメソッドのみです。コンストラクターが実行されて以降の基本的な流れをざっくり表にしました。

処理順 メソッド名 概要
1 PrepareAsync スコアを判別するための前処理をします。戻り値の型は、ScorableBaseのGenericの2つめの型パラメーターとなります。
2 HasScore PrepareAsyncの戻り値が、このメソッドの2つ目の引数に入ってきます。このメソッドの戻り値は、スコアを持っているか否かです。
3 GetScore HasScoreメソッドの処理が true の場合のみ、スコアを出力します。スコアの型は、ScorableBaseのGenericの3つめの型パラメーターとなります。
4 PostAsync 複数の Scorables がある場合、トップスコアのクラスのPostAsyncメソッドが実行されます。
5 DoneAsync PostAsync後の後処理を実装します。

あくまでざっくりです。正確に把握したい場合は、ソースコードを読むのが確実です。コードだけだと読むの辛いので、BotBuilder自体をデバッグすると追いやすかったです。


今回の実装である ResetScorable クラスで具体的に説明すると、

処理順 メソッド名 概要
1 PrepareAsync 22行目。ユーザーが入力したメッセージが「リセット」という文字列であれば、その文字列を返す
2 HasScore 33行目。2つめの引数(=PrepareAsyncメソッドの戻り値)がNULLでなければtrue(スコアがある)、NULLだったらFalse(スコアがない)を出力
3 GetScore 38行目。スコア1.0を返す。
4 PostAsync 43行目。処理として会話をリセットする。
5 DoneAsync 45行目。後処理は特になし。

具体的なアクションをするのであれば、Dialog を実装して PostAsync メソッドで呼び出すようにするとよいです(後述)。

とりあえずここまでを動かしてたいので、Autofac の実装をしてしまいましょう。

3. DIの実装

Application_Start() メソッド(Global.asax.cs)で実装しました。

Autofac 自体の説明をすると本質的なところから外れるので省略しますが、21行目あたりの書き方で実装していく感じです。IDialogTask 自体のDIは BotBuilder 側でやっているのでここでは呼ぶだけです。ガチで開発するとコードも増えるので、責務に応じてModule化してDIの登録をするのが現実的ですね。

Instance Scope も大事ですが...リンクのみにします。

Controlling Scope and Lifetime — Autofac 4.0 documentation

では、さっくりデバッグしてみましょう。

f:id:beachside:20170720214845p:plain


「リセット」と入力すると、Dialogがリセットされ、会話が最初からになります(動作がちょっとバカっぽいですが...サンプルなので...)。

4. Scorableの実装からDialogへ

ユーザーが「ヘルプ」と入力したら、HelpDialog を呼び出す Scorables を実装してみます。

先に HelpDialog を作ります。Globalフォルダの下に HelpDialog クラスを作成してコードを実装します。

Dialogが呼ばれたらとりあえず挨拶して、ユーザーからの入力を確認したら処理終了という、ヘルプ機能とは完全に無縁な実装です。なんてことでしょう。

そして本題の Scorables。

先ほど実装したのと大差ありません。28行目で、「ヘルプ」と入力されたかを判断しています。 ポイントは、PostAsync メソッド(46行目)です。DialogStackに割り込んでダイアログの呼び出すには、この方法でできます。message がnullでなければって処理を書いていますが、特に意味はなく、色々試していたゴミが残っただけです。

後は、HelpScorable の DIを実装です。先ほど書いた(Global.asax.cs の)ResisterDependency メソッドで今回の部分を追加します(9~11行目)。

( HelpScorable クラス内にある、52行目の TODO// DI は今回は放置...)

説明が雑ですが、実行してみましょう。

f:id:beachside:20170720214857p:plain

5. Scorableの実装の注意点

実装して気づいた点を2つ上げておきます。

  • DialogStackに差し込まれることは理解しておく
    今回のサンプルだと、「ヘルプ」を連発すれば、その分 HelpDialogがネストされてグダグダになります。ガチで実装する際は、状態の管理が必要かなーと思います。

  • スコアリングに注意
    今回はスコアをすべて1.0を返していますが、これだと複数の Scorables を実装した時に同一の点数になることもあります。
    同一のスコアが複数存在する場合を試したところ、DIで先に登録した方が動作しました(今後もそうなるかはわかりませんが)。意味不明な動作をしないようスコアの管理はちゃんとしましょう♪

ちなみに同一スコアの事故は、以前に「ユーザーとボット」の会話から「ユーザーとオペレーター(人)」の会話に切り替える実装をしたときにバグらせた経験が...Scorables を作れば作るほどスコア管理大事です♪

参考

docs.microsoft.com

github.com

blog.botframework.com

http://autofac.readthedocs.io/en/latest/