Bot Framework SDK (Bot Builder SDK?) v4 で v3 から大きく実装方法が変わったわけですが、結構 v4.1, 4.2, 4.3, 4.4... と進んでいくうちに、意外と v4 当初の作り方が非推奨になったりしてしょんぼりすることがあったので v4.4.3 時点での Bot Framework SDK の推奨っぽい土台作りを見てみようと思います。ついでに個人的な興味で ASP.NET Core 3.0 Preview 6 で使ってみようと思います。
ASP.NET Core Web アプリケーションから始めてみる
Bot Framework のプロジェクトテンプレートは最初から設定されてるので便利なのですが、何が設定されてるかは何処かで一度確認しないといけないので ASP.NET Core Web アプリケーションテンプレートから始めていこうと思います。ということで Empty から始めます。 あと HTTPS 今回はいらないのでオフっと。
Microsoft.Bot.Builder.Integration.AspNet.Core
パッケージを NuGet から追加します。
v4 初期の頃はエンドポイントは自動で Bot Framework SDK が追加してくれてたのですが、それがなくなったので自前で ASP.NET Core のコントローラーを定義するようになっています。それに対応するために MVC まわりの設定を Startup.cs
の Configure
メソッドを変更します。
publicvoid Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); }
この後 ConfigureServices
メソッドでサービスを登録してまわるのですがボットがないと登録するボットが無いのでボット用のクラスを作成します。v4 当初は IBot
インターフェースを実装するという形でしたが最近 ActivityHandler
クラスを継承する形が推奨っぽいです。
using Microsoft.Bot.Builder; namespace BotStepByStep { publicclass MyBot : ActivityHandler { } }
ActivityHandler
クラスは IBot
インターフェースを実装してるので同じっちゃぁ同じですけどね。
自分のボットクラスが出来たので Startup
クラスの ConfigureServices
メソッドに必要なものを追加していきます。
publicvoid ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>(); services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>(); services.AddTransient<IBot, MyBot>(); }
あとは api/messages
で POST のリクエストを受け取るコントローラーを定義して、そこで IBotFrameworkAdapter
と IBot
を使って Bot Framework に処理を流して完了です。
using Microsoft.AspNetCore.Mvc; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; using System.Threading.Tasks; namespace BotStepByStep.Controllers { [Route("api/messages")] [ApiController] publicclass BotController : ControllerBase { privatereadonly IBotFrameworkHttpAdapter _adapter; privatereadonly IBot _bot; public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) { _adapter = adapter; _bot = bot; } [HttpPost] public async Task PostAsync() { await _adapter.ProcessAsync(Request, Response, _bot); } } }
v4 であった .bot
ファイルが無くなった(非推奨になった)ので appsettings.json
や Azure App Service の構成や Azure App Configuration で設定情報を管理できるようになるのでいいですね。
Echo ボットの実装
動かしたいところですが、このままだとボットに何も実装してないので以下のようにオウム返しする実装を追加します。
using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; namespace BotStepByStep { publicclass MyBot : ActivityHandler { protectedoverride async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { await turnContext.SendActivityAsync(turnContext.Activity.Text); } } }
実行したら Bot Framework Emulatorで繋いでみます。
繋いで適当に話しかけてみると…
動いた!!やったね!!
ユーザーとの対話状態を覚えてほしい
そんなときはダイアログですね!!というわけで以下のダイアログのライブラリをインストールします。
- Microsoft.Bot.Builder.Dialogs
Bot Framework には色々なダイアログの種類があるのですが…今回は WaterfallDialog
を使ってみようと思います。
名前の通り順番に流れるようにステップをこなしていくダイアログです。
ダイアログ自体の詳細はこちらのドキュメントで。
まずはダイアログが裏で状態を覚えるために使うステート管理機能を ConfigureServices
メソッドで登録します。
publicvoid ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>(); services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>(); services.AddTransient<IBot, MyBot>(); // for state services.AddSingleton<IStorage, MemoryStorage>(); services.AddSingleton<UserState>(); services.AddSingleton<ConversationState>(); }
Bot にダイアログを追加します。ComponentDialog
を継承した MainDialog
クラスを作ります。
using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; namespace BotStepByStep { publicclass MainDialog : ComponentDialog { public MainDialog() : base("MainDialog") { AddDialog(new TextPrompt("TextPrompt")); AddDialog(new WaterfallDialog("Steps", new WaterfallStep[] { async (context, cancellationToken) => { await context.Context.SendActivityAsync("最初のステップです。"); return await context.PromptAsync("TextPrompt", new PromptOptions { Prompt = MessageFactory.Text("名前を入力してください"), }); }, async (context, cancellationToken) => { var name = context.Result asstring; context.Values["name"] = name; return await context.PromptAsync("TextPrompt", new PromptOptions { Prompt = MessageFactory.Text("何か気の利いたコメントを入力してください"), }); }, async (context, cancellationToken) => { var comment = context.Result asstring; var name = context.Values["name"] asstring; await context.Context.SendActivityAsync($"{name}さんの好きな言葉は「{comment}」ですね。"); return await context.EndDialogAsync(); }, })); InitialDialogId = "Steps"; } } }
プロンプトが入力をいい感じにしてくれるやつです。複雑なダイアログのサンプルは以下のリポジトリーにあります。
そして ConfigureServices
でダイアログを登録して
publicvoid ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>(); services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>(); services.AddTransient<IBot, MyBot>(); // for state services.AddSingleton<IStorage, MemoryStorage>(); services.AddSingleton<ConversationState>(); // dialog services.AddSingleton<Dialog, MainDialog>(); }
Bot ではダイアログを起動して、ダイアログの処理が終わったらステートを保存するようにします。
using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; using System.Threading; using System.Threading.Tasks; namespace BotStepByStep { publicclass MyBot : ActivityHandler { public MyBot(ConversationState conversationState, Dialog dialog) { ConversationState = conversationState; Dialog = dialog; } public ConversationState ConversationState { get; } public Dialog Dialog { get; } protectedoverride async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken); await ConversationState.SaveChangesAsync(turnContext, cancellationToken: cancellationToken); } } }
動かしてみると…
ちゃんと定義したダイアログに従って会話が今どこにいるのかという情報を保持しつつ処理が進んでいくのがわかると思います。
まとめ
そろそろ、Bot Framework 落ち着いたかな??