今回はボットアプリとユーザー間のやり取りを、インターセプトする方法を紹介します。
概要
ミドルウェアを会話の前後に差し込めると、会話のログ取得や、会話のフォワードなど、様々なことを既存のコードに影響を与えることなくできます。
インターセプトの実装
1. ボットアプリの Services フォルダに ActivityLogger.cs を追加し、コードを以下と差し替えます。
using Microsoft.Bot.Builder.History; using Microsoft.Bot.Connector; using System.Diagnostics; using System.Threading.Tasks; namespace O365Bot.Services { public class ActivityLogger : IActivityLogger { public async Task LogAsync(IActivity activity) { Debug.WriteLine($"From:{activity.From.Id} - To:{activity.Recipient.Id} - Message:{activity.AsMessageActivity()?.Text}"); } } }
2. Global.asax.cs に処理差し込み用のコードを追加します。以下のコードと差し替えます。自分用の IoC Container と登録先を間違えないようにしてください。
using Autofac; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Internals.Fibers; using O365Bot.Handlers; using O365Bot.Services; using System.Configuration; using System.Web.Http; namespace O365Bot { public class WebApiApplication : System.Web.HttpApplication { public static IContainer Container; protected void Application_Start() { this.RegisterBotModules(); GlobalConfiguration.Configure(WebApiConfig.Register); AuthBot.Models.AuthSettings.Mode = ConfigurationManager.AppSettings["ActiveDirectory.Mode"]; AuthBot.Models.AuthSettings.EndpointUrl = ConfigurationManager.AppSettings["ActiveDirectory.EndpointUrl"]; AuthBot.Models.AuthSettings.Tenant = ConfigurationManager.AppSettings["ActiveDirectory.Tenant"]; AuthBot.Models.AuthSettings.RedirectUrl = ConfigurationManager.AppSettings["ActiveDirectory.RedirectUrl"]; AuthBot.Models.AuthSettings.ClientId = ConfigurationManager.AppSettings["ActiveDirectory.ClientId"]; AuthBot.Models.AuthSettings.ClientSecret = ConfigurationManager.AppSettings["ActiveDirectory.ClientSecret"]; var builder = new ContainerBuilder(); builder.RegisterType().As (); builder.RegisterType ().As (); Container = builder.Build(); } private void RegisterBotModules() { var builder = new ContainerBuilder(); // グローバルメッセージの処理登録 builder.RegisterModule(new ReflectionSurrogateModule()); builder.RegisterModule (); // インターセプトの登録 builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); builder.Update(Conversation.Container); } } }
エミュレーターでの検証
1. 前回実装したプロアクティブ通知のコードがあると、エミュレーターからの検証が少し面倒なため、RootDialog.cs の DoWork メソッドを以下のコードに差し替えて、エミュレーターからの場合通知の登録をしないようにします。
private async Task DoWork(IDialogContext context, IMessageActivity message) { if (message.ChannelId != "emulator") { using (var scope = WebApiApplication.Container.BeginLifetimeScope()) { var service = scope.Resolve(new TypedParameter(typeof(IDialogContext), context)); // 変更を購読 var subscriptionId = context.UserData.GetValueOrDefault ("SubscriptionId", ""); if (string.IsNullOrEmpty(subscriptionId)) { subscriptionId = await service.SubscribeEventChange(); context.UserData.SetValue("SubscriptionId", subscriptionId); } else await service.RenewSubscribeEventChange(subscriptionId); // 会話情報を購読 id をキーに格納 var conversationReference = message.ToConversationReference(); if (CacheService.caches.ContainsKey(subscriptionId)) CacheService.caches[subscriptionId] = conversationReference; else CacheService.caches.Add(subscriptionId, conversationReference); // 会話情報はロケールを保存しないようなので、こちらも保存 if (!CacheService.caches.ContainsKey(message.From.Id)) CacheService.caches.Add(message.From.Id, Thread.CurrentThread.CurrentCulture.Name); } } if (message.Text.Contains("get")) // GetEventDialog を呼び出し、完了時に ResumeAfterDialog を実行 await context.Forward(new GetEventsDialog(), ResumeAfterDialog, message, CancellationToken.None); else if (message.Text.Contains("add")) // ユーザー入力を引き渡す必要はないので、CreateEventDialog を Call で呼び出す。 context.Call(new CreateEventDialog(), ResumeAfterDialog); }
2. エミュレーターを起動して接続。
3. メッセージを送信。
4. Visual Studio の Output ビューに会話が記録されていることを確認。
テストの追加
今回は、ログを取得しかしていないため、ユーザー操作には影響がありません。よってファンクションテストは書きませんが、ユニットテストには、インターセプターを登録して、既存テストに影響ないか確認しておきます。
1. UnitTest1.cs の RegisterBotModules メソッドを以下のコードに差し替えます。
////// グローバルメッセージおよびインターセプター登録 /// private void RegisterBotModules(IContainer container) { var builder = new ContainerBuilder(); // グローバルメッセージの処理登録 builder.RegisterModule(new ReflectionSurrogateModule()); builder.RegisterModule(); // インターセプトの登録 builder.RegisterType().AsImplementedInterfaces().InstancePerDependency(); builder.Update(container); }
2. すべてのユニットテストを実行します。
具体的な用途
今回はただのロギングでしたが、ログも実際は DocumentDB などに格納すると後で解析が楽にできます。その他の便利なシナリオは、人間へのハンドオフです。これはユーザーがボットと話している過程で、人間の介入が必要になった場合に切り替える処理のことです。サンプルが以下にあるので、是非ご覧ください。
https://github.com/tompaana/intermediator-bot-sample
皆さんならどんなシナリオで使うかも、是非教えてください。
まとめ
差し込みの入り口はとても簡単でしたが、できることが無限にあるので楽しいですね。影響があるものはユニットテスト、ファンクションテストともに書いてください。次回は多言語を理解する仕組みとして、LUIS (Language Understanding Intelligent Service) との統合を紹介します。
This post first appeared on MSDN Blogs | Get The Latest Information, Insights, Announcements, And News From Microsoft Experts And Developers In The MSDN Blogs., please read the originial post: here