アルパカのメモ

ボットのしくみ

前提条件

  • Visual Studio 2019 Community 版で開発
  • 言語はC#を選択

ボットのしくみ

参考:ボットのしくみ - Bot Service | Microsoft Docs

ユーザーとボットとの間で行われるやり取りの一つ一つを「アクティビティ」と呼ぶ。 Bot Framework Serviceは、ユーザーがボットとの接続に使っているアプリ(Teamsとかの各チャネル)の情報をボットへ送る。 それぞれのチャネルは、固有の追加情報を含むことがある。 下図は、シンプルなEcho Botがどのようにアクティビティをやり取りするかを示している。


引用元:上記参考サイト

図には「conversation update」と「message」のアクティビティが書かれている。

「conversation update」アクティビティは、ユーザーがボットとの会話を始めたときなどに送られてくる。 ユーザーとボットの会話が始まった時は、2つの「conversation update」アクティビティが送られてくる。 1つはユーザーの追加、もう1つはボットの追加である。

「message」アクティビティは、ユーザーとボット間のメッセージを運ぶ。メッセージはシンプルなテキストだったり、画像やカードだったりする。

HTTPの詳細

アクティビティがBot Framework Serviceからボットに送られる際は、HTTP POSTリクエストの形になっている。 ボットはそのリクエストに対して、HTTP status 200を返す。ボットから返すアクティビティは、別のHTTP POSTリクエストとしてBot Framework Serviceへ送る。 それに対して、Bot Framework Serviceから HTTP status 200が返ってくる。

ボットは、リクエストを受け取ってから15秒以内に HTTP status 200 を返さなくてはいけない。もし返せなかった場合は、HTTP GatewayTimeout(504)がおこる。

ターン

基本的に、ユーザーとボットのやり取りは、ユーザーのメッセージにボットが応答する形になる。 この行きかえりのやりとりを「ターン」という。 「ターンコンテキスト(turn context)」オブジェクトは、アクティビティに関する情報を提供してくれる(送受信者、チャネルなど)。

アクティビティを処理する流れ

アクティビティをWebアプリケーション側で処理する流れを下図に示した。

Webアプリケーションは、C#のASP.NETのプロジェクトだったり、Node.jsのExpressやRestifyなどのフレームワークを使用したアプリだったりする。 これはAzureポータルでBot Serviceを作るときに選択したSDK言語によって変わる。

Bot Service がユーザーからのメッセージを受け取ると、WebアプリケーションへHTTP POSTリクエストを送る。 リクエストにはJSON形式のデータが含まれている。

WebアプリケーションのControllerクラスがそのリクエストを受け取ると、Adapterクラスの処理を呼び出す。 AdapterクラスがSDKのコアとなるコンポーネントで、このクラスがリクエストをデシリアライズし、TurnContextを生成したり、ActivityHandlerクラスのメソッドを呼び出したりする。

TurnContextは、ボットがアウトバウンド(ボット→ユーザー)アクティビティを送るための機能を提供する。 他にもアクティビティを更新・削除するためのメソッドも提供していて、それぞれのメソッドは非同期で実行される。

ただ、ボットの処理の終わりにはcontextオブジェクトが破棄されるため、 アクティビティ関連のメソッドを実行する際は、awaitを付けてメソッドの処理が終わるのを確実に待たないといけない。

Activity Handler

いわゆるボットクラス。ここでインバウンドのメッセージを処理して、どんなアウトバウンドのメッセージを返すかを実装する。

アクティビティの処理は、AdapterからActivityHandlerに渡される。ボットを作るときは、このActivityHandlerを拡張してロジックを実装していく。 ActivityHandlerには各イベントごとにメソッドがあるので、それぞれをオーバーライドして拡張していく感じ。 それぞれのメソッドに既存のロジックはないので、シンプルにメソッドをオーバーライドすれば良い(一部例外あり)。

C#でテンプレートを使って新しいプロジェクトを作った場合、ActivityHandlerを継承したクラスが既に作られている。EmptyBot とか、EchoBotがそれにあたる。

基本形

Echo Bot のActivityHandlerが参考になる。

namespace EchoBot1.Bots
{
    public class EchoBot : ActivityHandler
    {
        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var replyText = $"Echo: {turnContext.Activity.Text}";
            await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
        }

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeText = "Hello and welcome!";
            // ユーザーとボットが会話に参加した場合、ボットについても当メソッドが呼び出されるので、
            // メンバーidがボットと同一でない場合だけ、挨拶を送信するようになっている。
            foreach (var member in membersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
                }
            }
        }
    }
}