こんにちは、サイオステクノロジー技術部武井です。今回は、Azure上でチャットボットを構築するサービス「Azure Bot Service」について、一筆したためたいと思います。
5回シリーズでお届けする予定で、第1回目はチャットボットサービスの概要がテーマとなります。
- 概要編
- 今回はこちら → Azure Bot Service編
- QnA Maker編
- LUIS編
- Azureのことに何でも答えてくれるLINEボット作る編
本シリーズの成果物は以下のGitHubにあがっております。
https://github.com/noriyukitakei/AzureFAQBot
Azure Bot Serviceとは?
ノンコーディングでチャットボットが作成できるサーバーレスなサービスです。
※でもちょっと凝ったことしようとすると、Bot Frameworkを使ったコーディングが必須です
通常、チャットボットを作る際には、RestAPIの基盤となるWebサーバー及びアプリケーションサーバー、会話のステートを格納するためのストレージ、認証基盤、チャットボットのプログラムそのものなどなど、たくさんのものが必要になります。Azure Bot Serviceは、これらのものを1つに詰め込んだ、いわゆるオールインワンパッケージなのです。
下図は、Azure Bot Serviceの構成図になります。
Azure Bot Serviceはいくつかの層で構成されております。
ユーザークライアント層
FacebookやLINEなどの、ユーザーがチャットボットを利用するためのツールがある層です。Webアプリケーションであればブラウザ、スマホアプリであればダウンロードしたアプリそのものになります。
Web層
ユーザーが発話したとき、その内容がユーザークライアント層にあるLINEやFacebookからHTTPリクエストとして送信されてきます。Web層は、そのリクエストを受け取る層であり、チャネルとWeb Appから構成されます。
チャネルは、LINEやFacebookからのHTTPリクエストを受け取り、後述するBot Frameworkが理解出来る形式に変換します。LINE用のチャネル、Facebook用のチャネルといった形で、様々なチャネルが用意されてます。こうすることで、Bot Frameworkをベースとした、たった1つのチャットボットアプリケーションを作るだけで、LINEやTeams、Facebookなど色々なチャットボットクライアントに対応出来るのです。実際の構成は、各チャネルごとに固有のコネクタサーバーというものがあり、LINE用のコネクタサーバーが、LINEからのリクエストを受け付けます。
そしてWeb Appの中には、Bot Frameworkという、Azure Bot Serviceを稼働させるためのコアとなるアプリケーションフレームワークがあります。通常であれば、ユーザーからの発話に対する返答、会話フローの実装、ユーザーのセッション処理などを自前で作成しなければなりませんが、この辺りの開発を楽にするライブライやクラス群を提供するのが、Bot Frameworkです。Bot Frameworkの利用言語は、C#もしくはnode.jsになります。
Cognitiveサービス層
ユーザーとの対話をより人間らしいものにするためのAIサービスが配置される層です。後述するQnA MakerやLUISなどがこの層に含まれます。
外部サービス層
多種多様な認証方式を持つIDプロバイダーであるAzure AD、Office365を始めとした様々なマイクロソフトクラウドサービスへのAPIを提供しているGraph APIなど、チャットボットが応答を返すために参照する外部サービスがこの層に含まれます。もちろんマイクロソフト以外のサービスも例外ではありません。Salesforce、サイボウズなどのサードパーティー製のサービスや、自社独自開発のWebサービスが含まれることもあります。
処理の流れ
先程の構成図に沿って、ユーザーが発話してから、チャットボットが応答を返すまでの流れを解説します。
まず、クライアント層にあるLINEやFacebookからユーザーが発話します。その発話内容は、HTTPリクエストに乗って、チャネルに届けられます。
チャネルは、クライアント層のFacebookやLINE固有のHTTPリクエストを、その先のWeb App上にあるBot Frameworkが理解出来る形式に変換して、Bot Frameworkに渡します。
Web App上のBot Framework(の上で動作するアプリ)は、チャネルからのHTTPリクエストを受け取り、必要に応じて外部サービスにアクセスします。Azure ADで認証したり、後述するQnA MakerやLUISなどのAIサービスを利用して、人間らしいレスポンスを作成します。
Bot Frameworkは、今までのプロセスを経て作成されたHTTPレスポンスをチャネル経由でユーザークライアント層のLINEやFacebookに返します。
今までの一連のフローを見て、お気付きの方もいらっしゃるとは思いますが、チャットボットの開発は、今までのWebアプリケーション開発となんら変わりはありません。
すみません。うんちくが長くなりました。次章からは実際に構築する手順をご説明しますので、まだブラウザバックしないでください。
Azure Bot Serviceを用いた構築・開発手順
では、実際にAzure Bot Serviceで簡単なチャットボット(ユーザーが発話した内容をオウム返しするだけのもの)を構築してみましょう。
構築・開発の流れは以下のようになります。
1.Azure Bot Serviceのリソース作成
Azureでのチャットボットアプリケーション開発に必要である、チャネルやWeb AppなどのリソースをAzureポータルから作成します。
2.ボットのソースコードのダウンロード
1で作成したAzure Bot Service上にデプロイされたチャットボットアプリケーションのソースコードをダウンロードします。
3.Visual Studioで開発
2でダウンロードしたソースコードをVisual Studioに取り込み、開発を行います。
4.Bot Framework Emulatorでテスト
3で開発したチャットボットアプリケーションを、Bot FrameworkのエミュレーターであるBot Framework Emulatorでテストをします。
5.Visual StudioからWeb Appへデプロイ
開発したチャットボットアプリケーションをVisual StudioからWeb Appへデプロイします。これでチャットボットアプリケーションは本番稼働状態となります。
では、次章より、このフローに基づいて、実際に構築・開発をしていきます。
Azure Bot Serviceのリソース作成
Azureポータルの左メニューから「リソースの作成」をクリックして表示されるテキストボックスに「bot」と入力してエンターを押します。すると以下のように「Web App Bot」と表示されますので、これをクリックします。これがAzure Bot Serviceのリソースになります。
「作成」をクリックします。
以下の画面が表示されます。
上図の各項目を以下の要領で入力してください。
■ ボット名
先程ご説明したWeb Appのホスト名として利用されるため、Azure全体で一意である必要があります。
■ サブスクリプション
ご利用のAzureのサブスクリプションを選択します。
■ リソースグループ
Azure Bot Serviceに利用する各種リソース(Web Appやチャネル)が配置されるリソースグループを選択します。
■ 場所
Azure Bot Serviceに利用する各種リソースが配置されるデータセンターの場所を選択します。一般的には、ユーザーが利用する場所に近い場所を選択します。
■ 価格レベル
F0とS1を選択することができます。F0はLINEやFacebook、Skypeと言った代表的なチャネル(Standardチャネル)の利用であれば、メッセージ数が無制限に利用できるプランです。ここで、メッセージ数という言葉が出てきましたが、これは、ユーザーがbot に送ったメッセージの数と、bot からユーザーに送られたメッセージ数の合算になります。つまり、以下のようなやりとりが実施された場合、メッセージ数は3 になります。
Web ChatやDirect Lineなどの一部の特別なチャネル(Premiumチャネル)は、F0のプランで10,000メッセージを超えた段階でエラーになります。このような場合は、S1プランのご検討が必要になります。ただし、S1プランは、1,000メッセージあたり課金が発生します。また、S1はF1と異なり、SLA(Service Level Agreement)が適用され、これを下回ると返金の対象になります。
■ アプリ名
ボット名と同じ値が入力されます。実際には、こちらがWeb Appのホスト名となります。
■ ボットテンプレート
ここで選択したテンプレートの内容のサンプルアプリが、自動的にWeb App上にデプロイされた状態で、Azure Bot Serviceのリソースが作成されます。ここでは、Echo Bot(ユーザーが発話した内容をおうむ返しのように返すだけのボット)を選択します。
■ App Service Plan / 場所
Web Appのプランを選択します。ここでは、あくまで検証用途なので、F0でいいと思います。
■ Azure Storage
概要編でお話しした「会話型チャットボット」では、いくつかの質問を繰り返して、初めてユーザーの意図がわかります。よって、最後の質問が終わるまで、途中の回答をどこかに保存しなければなりません。この辺り、Webアプリケーションのセッション管理と同じですよね。この「最後の質問が終わるまでの、途中の回答の保存先」をAzure Storageにすることが出来ます。ここでは、そのためのAzure Storageを選択します。新規で作成するか、既存のものを選択するかを決めることができます。
■ Application Insights
Bot Framework上で構築したチャットボットアプリケーションのアクセスログ取得やリソース監視をしたい場合は、オンにしてください。
■ Application Insightsの場所
Application Insightsが配置される場所を選択してください。利用するユーザーに近い場所が一般的にはよいかと思います。
■ MicrosoftアプリIDとパスワード
MicrosoftアプリIDとパスワードとは、平たく言ってしまえば、Azure Active DirectoryによってOpenID Connectで認証するためのクライアントIDとパスワードになります。ここで「アプリIDとパスワードの自動作成」としますと、Azure Active DirectoryにクライアントID(Azure Active Directory的にはアプリケーションIDとかMicrosoftアプリIDとか言いますが同じ意味です)とパスワードが自動的に作成されます。
そのクライアントIDとパスワードを利用すれば、OpenID Connectのフローにより、チャットボットの認証が可能になります。
具体的には、Bot Framework上のチャットボットアプリケーションの中で、ユーザーが未認証と判断した場合、MicrosoftアプリIDとパスワードによってOpenID Connectの認証エンドポイントを作成して、そこにリダイレクトして、Azure Active Directoryの認証画面を表示して、、、といった流れで認証を行います。以降はOpenID Connectのフローに従って認証します。
よって、LINEやFacebookなどの、既に認証が提供されているメッセージングプラットフォーム、もしくはWeb Chatで認証が不要な場合は、このMicrosoftアプリIDとパスワードは必要としません。今回は、認証処理は実装しませんので、「アプリIDとパスワードの自動作成」としてください(何か選ばないと先に進めないので)。
最後に「作成」をクリックして、暫く待つとリソースが作成されます。
ボットのソースコードのダウンロード
リソースグループに移動します。「種類」が「Web アプリ ボット」のものをクリックします。
左メニューの「ビルド」をクリックします。「ボットのソースコードをダウンロードする」をクリックして、「はい」をクリックします。すると、チャットボットアプリケーションのソースコードをZIPしたファイルがダウンロードできます。
Visual Studioを起動します(今回のバージョンは2019です)。以下の画面が表示されますので、「プロジェクトやソリューションを開く」をクリックします。
先程ダウンロードしたソースコードを解凍して、「EchoBot.sln」を開きます。
こんな感じになります。
主要なソースコードをご説明致します。Bot Frameworkは.NET Coreがベースになっております。.Net Coreの詳細は、ブログ「多分わかりやすいASP.NET Core Webアプリケーション」をご参照下さい。
まずはC#のプログラムのエントリポイントProgram.csからです。
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; namespace Microsoft.BotBuilderSamples { public class Program { // プログラムのエントリポイントです。つまり、このメソッドから // チャットボットアプリケーションは起動します。 public static void Main(string[] args) { // Webアプリケーションを起動するために使用するWebサーバーの設定を定義します。 // 一般的なASP.NET Core Webアプリケーションの起動シーケンスです。 // どのWebサーバーを使うか、どのような設定ファイルを読み込むかといったような、 // Webサーバーの起動に必要な情報を定義します。下記の「CreateDefaultBuilder」 // というメソッドは、一般的なWebサーバーの定義をやってくれています。 // このあたりの詳細は以下のブログを参考にしてください。 // https://tech-lab.sios.jp/archives/15758 CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } }
次にStartup.csです。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.BotBuilderSamples.Bots; using Microsoft.Bot.Builder.EchoBot; namespace Microsoft.BotBuilderSamples { public class Startup { private const string BotOpenIdMetadataKey = "BotOpenIdMetadata"; // .NET Coreのいろんな設定を取得できるIConfigurationの実装を // DIコンテナから取得してます。IConfigurationの実装は.NET Coreが自動でDIコンテナに入れてくれています。 public Startup(IConfiguration configuration) { Configuration = configuration; } // 上のコンストラクタにて、IConfigurationインターフェースの実装を代入する // COnfigurationのプロパティを定義しています。プロパティの詳細は以下のブログにて。 // https://tech-lab.sios.jp/archives/15979 public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); // 後述するBotControllerクラス(チャットボットアプリケーションのHTTPエンドポイント)で // 利用するIBotFrameworkHttpAdapterインターフェースの実装をDIしています。 // IBotFrameworkHttpAdapterは送受信されたメッセージをHTTPプロトコルで処理する // 実装のインターフェースで、AdapterWithErrorHandlerは、特にエラー処理の部分を // 詳細に記述したクラスです。このあたりは、本ブログの「アダプターについて」で // 説明します。ここで行われているDIについては、以下のブログを参考にしてください。 // https://tech-lab.sios.jp/archives/16092 // https://tech-lab.sios.jp/archives/16598 services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>(); // IBotインターフェースを実装したEchoBotをDIしています。このクラスに // メッセージを受信したときの実際の処理を記述します。今回は、受け取ったメッセージを // そのまま変える処理をEchoBotに実装しています。EchoBotについては後述します。 // EchoBotは、後述するBotControllerクラス(チャットボットアプリケーションのHTTPエンドポイント)にて、 // DIコンテナから取得されます。 // ※どうしてTransientなのかというのがちょっと疑問です。Singletonではだめなのでしょうか? services.AddTransient<IBot, EchoBot>(); } // ASP.NET Coreのランタイムから呼ばれるメソッドで、HTTPのリクエストパイプラインを設定します。 // 詳細は以下のブログを見てください。 // https://tech-lab.sios.jp/archives/15758 public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // 環境変数ASPNETCORE_ENVIRONMENTに設定された値がDevelopmentつまり開発環境だったら、 // より詳細なエラーメッセージを出力するという設定です。Developmentの場合は // env.IsDevelopmentメソッドで判定が出来ます。その他、APS.NET Coreでは // Developmentの他にもStaging、Productionという設定を標準でサポートしています。 // Development:開発(env.IsDevelopmentで判定可能) // Staging:ステージング(env.IsStagingで判定可能) // Production:本番(env.IsProductionで判定可能) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { // HTTPではないリクエストをHTTPSにリダイレクトする設定です。 app.UseHsts(); } // 特定のディレクトリ(デフォルトはwwwroot)配下のファイルを静的コンテンツとして扱えるようにします。 app.UseDefaultFiles(); // 以下のファイル名に一致するファイルを静的コンテンツのリストから探して // 一番最初に見つかったものを表示させる設定です。 app.UseStaticFiles(); // .NET CoreのMVCを使う設定です。 app.UseMvc(); } } }
チャットボットアプリケーションのAPIのコントローラーであるBotController.csです。
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; namespace Microsoft.BotBuilderSamples.Controllers { // チャットボットアプリケーションのHTTPエンドポイントです。 // https://[Web Appのホスト名]:ポート番号/api/messagesにアクセスしたときに // 呼び出されるクラスであり、チャットボットアプリケーション唯一のHTTPエンドポイントです。 [Route("api/messages")] [ApiController] public class BotController : ControllerBase { private readonly IBotFrameworkHttpAdapter Adapter; private readonly IBot Bot; // 先程IDコンテナにDIしたEchoBot(IBotインターフェースを実装)、 // AdapterWithErrorHandler(IBotFrameworkHttpAdapterを実装)のインスタンスを // DIコンテナから取得しています。 public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) { Adapter = adapter; Bot = bot; } // PostメソッドでHTTPリクエストが送信されたときに呼び出されるメソッドです。 [HttpPost] public async Task PostAsync() { // ProcessAsyncはHTTPのレスポンスを生成する処理で、 // IBotFrameworkHttpAdapterに定義されているメソッドです。 await Adapter.ProcessAsync(Request, Response, Bot); } } }
Bot FrameworkをベースとしたチャットボットアプリケーションのコアなクラスであるEchoBot.csです。
using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; namespace Microsoft.BotBuilderSamples.Bots { // ユーザーが発話したメッセージを処理するクラスです。ActivityHandlerを継承します。 // ActivityHandlerは実際にはIBotインターフェースを実装しています。 public class EchoBot : ActivityHandler { // ユーザー(相手や自分も含む)が発話を行なったときに呼ばれるメソッドです。引数turnContextにはユーザーが発話したメッセージに関する // 様々な情報が入っています。cancellationTokenについては、処理をキャンセルするためのものであり、詳細は // 以下のブログを参考にしてください。 // https://tech-lab.sios.jp/archives/15986 protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { // SendActivityAsyncはメッセージを送信するメソッドです。 // turnContext.Activity.Textにはユーザーが発話したメッセージそのものが入っています。 await turnContext.SendActivityAsync(MessageFactory.Text($"Echo: {turnContext.Activity.Text}"), cancellationToken); } // こちらはチャットボットにメンバーが追加されたときに実行されるメソッドですが、今回の本筋とは // 関係がないので、説明を割愛致します。 protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken) { foreach (var member in membersAdded) { if (member.Id != turnContext.Activity.Recipient.Id) { await turnContext.SendActivityAsync(MessageFactory.Text($"Hello and welcome!"), cancellationToken); } } } } }
今回Azure Active Directoryによる認証は使わないので、appsettings.jsonのMicrosoftAppIdとMicrosoftAppPasswordは空にします。空にしないと後述するBot Emulator起動時に、MicrosoftAppIdとMicrosoftAppPasswordを入力しなければいけないのですが、それはめんどくさいので。
{ "MicrosoftAppId": "", "MicrosoftAppPassword": "", "ScmType": "None" }
Visual Studioでの開発
先程Visual Studioに取り込んだソースコードを、要件に応じて、適宜機能追加・修正します。
Bot Framework Emulatorでテスト
Bot Framework Emulatorというものを使うと、ローカルでチャットボットアプリケーションのテストが可能です。以下のURLからBot Framework Emulatorをダウンロードして下さい。
https://github.com/Microsoft/BotFramework-Emulator/releases/
MacやWindowsなど各環境に合わせたものをダウンロードしてインストールして下さい。ウィザードに従えば猿でもインストールできます。
Visual Studioで先程のEchoBotを起動します。
するとブラウザが起動します。Bot URL(以下ではhttps://localhost:3978/api/messagesですが環境によって変わるかもしれません)をメモします。
そして次にBot Framework Emulatorを起動します。「Open Bot」をクリックします。
「Bot URL」に先程メモしたBot URLを入力して、「Connect」をクリックします。
先程Visual Studioに取り込んだEchoBotがBot Framework Emulatorの中で起動します。「Type your message」と書いてあるテキストボックスの中にメッセージを入れてエンターすると、オウム返ししてくれます。素晴らC。
Visual StudioからWeb Appへデプロイ
Visual StudioからWeb Appにデプロイします。ソースコードの改修が終わって本番に反映するフェーズですね。
Visual Studioからプロジェクトを右クリックして「発行」をクリックします。
「開始」をクリックします。
「既存のものを選択」にチェックして、「発行(U)」をクリックします。
適切なサブスクリプション、リソースグループを選択し、先程作成したWeb Appを選択して、「OK(O)」をクリックします。
「出力」のウィンドウに以下のように表示されれば成功です。
デプロイしたチャットボットアプリケーションをAzureポータルからテストできます。
これでデプロイできました。簡単ですね。
補足:BotAdapterについて
こちらは補足的なものですが、Bot Frameworkでも一番重要と思われるBotAdapterのソースを見てみました。きっかけは、Startup.csでDIしているAdapterWithErrorHandlerってなんだろうと疑問に思ったことです。
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
どうやら以下のようなクラス構成になっているようです。
ではAdapterWithErrorHandlerクラスから見てみたいと思います。
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Microsoft.BotBuilderSamples { public class AdapterWithErrorHandler : BotFrameworkHttpAdapter { public AdapterWithErrorHandler(IConfiguration configuration, ILogger<BotFrameworkHttpAdapter> logger, ConversationState conversationState = null) : base(configuration, logger) { OnTurnError = async (turnContext, exception) => { // Log any leaked exception from the application. logger.LogError($"Exception caught : {exception.Message}"); // Send a catch-all apology to the user. await turnContext.SendActivityAsync("Sorry, it looks like something went wrong."); if (conversationState != null) { try { // Delete the conversationState for the current conversation to prevent the // bot from getting stuck in a error-loop caused by being in a bad state. // ConversationState should be thought of as similar to "cookie-state" in a Web pages. await conversationState.DeleteAsync(turnContext); } catch (Exception e) { logger.LogError($"Exception caught on attempting to Delete ConversationState : {e.Message}"); } } }; } } }
OnTurnErrorってなんでしょうか?名前とソースコードから想像するに、ユーザーがメッセージを発話したときのエラー処理を代入するDelegateのようですが、、、。
ということで継承元の一番上のBotAdapterまで辿ってみます。
public Func<ITurnContext, Exception, Task> OnTurnError { get; set; }
OnTurnErrorという変数がありました。やっぱDelegate型の変数のようです。同クラスの中に以下のようなメソッドがありました。
protected async Task RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) { BotAssert.ContextNotNull(turnContext); // Call any registered Middleware Components looking for ReceiveActivityAsync() if (turnContext.Activity != null) { try { await MiddlewareSet.ReceiveActivityWithStatusAsync(turnContext, callback, cancellationToken).ConfigureAwait(false); } catch (Exception e) { if (OnTurnError != null) { await OnTurnError.Invoke(turnContext, e).ConfigureAwait(false); } else { throw; } } } else { // call back to caller on proactive case if (callback != null) { await callback(turnContext, cancellationToken).ConfigureAwait(false); } } }
なんとなくExceptionをcatchしてOnTurnErrorをInvokeしているところから、メッセージが発話したときの例外処理をしているように見えます。じゃぁ、RunPipelineAsyncって何しているんだろう、、、ていうのは次の機会に分析してみたいと思います(´・ω・`)中途半端ですみません。
まとめ
Bot Frameworkを使うと、とても簡単にチャットボットアプリケーションの開発ができることが、おわかりいただけかと思います。ステキですね。もうBot Frameworkなしでは生きていけません。No Bot Framework, No Life!!
次回は、QnA Maker編になります!!