Bot, 開発

Dialog を使った”会話”の実装

こんにちは SecretaryBot チームのマサです。

A’の一問一答Botの開発という投稿で、Bot の基本的な開発方法は分かったかと思います。ただ、「一問一答」と明記されている通り、MessageControllerだけでは”会話”の実装がやりづらいです。実際に作っていただくとわかるのですが、Q1にはA1、Q2にはA2と回答できますが、以下のような連続した会話の実装が難しいんですね。

  1. (Bot) あなたの名前は?
  2. マサです
  3. (Bot) 教えてくれてありがとうマサ。何がしたいですか?
  4. 明日のミーティングの設定がしたいです
  5. (Bot) 明日のミーティングですね。誰とですか?
  6. … 以下続く…

上記に加えてBotが複数の話題 (ミーティング設定、天気を教えてくれるなど)を扱えるように実装しようとすると、複雑怪奇な if文、switch文を書いて、会話状態をキャッシュするような機構を作る必要があって辛いです。。

せっかくフレームワークを使っているので、その良さを活かしちゃいましょう!

Dialogを使って”会話”を実装する

Bot Framework の公式ドキュメントにはDialogの解説がありますが、僕は最初良くわからなかったです。理由は簡単で「どんな時にどう役に立つのか?」「どう実装するか」という情報が読み取りづらかったのです。文章読みつつ開発・デバッグしてたら分かってきたので、そういった学びを皆さんに共有できればと思います。

どんな時にどう役立つのか?

Dialog は会話を実装するのに役立ちます。例えば、「会議調整をする」「レストランを予約する」などの話題を扱うには連続性が必要ですが、そういった一連のやり取りをDialogを使うと実装できます。

実装前に概念を理解する

なるほど、連続性のある会話を実装できるのか。じゃあ早速サンプルコードを読んでいこう!と思いそうですが、先に概念を理解したほうが良いです。

まずは言葉を定義して進めましょう。「会議調整をする」という一連のやり取りのことを「話題」と呼び、その中の「誰とですか?」などの個別のやり取りを「文脈」ということにします。上のやり取りでいうと、1-6までが話題、1-2, 3-4のような個別のやり取りを文脈ということになります。普段人と話をするときも、「今の」話題・文脈をふまえて返答するはずで、それ以外の話題の条件分岐などは意識の外にあると思います。

Bot Framework でも話題・文脈を表すための概念があります。話題はDialog、文脈はDialogのメソッドとして表します。また、今何の話をしているか?を管理するために、会話のスタックが用意されています。そして、スタックの一番上にあるものが「今の」話題・文脈を表しています。例えば、会議調整の話題の中で「名前を尋ねる」「何をしたいか聞く」「参加者を訪ねる」という順に文脈が推移していく場合、下図のようになっています(スタックと言っているのに全然積みあがらないじゃんと思うかもしれませんが、話題が複数になるともっとスタックっぽくなります)。

そして、スタックの一番上のメソッドが処理するようにフレームワークが調整してくれるので、「名前がくる前提」でメソッドをシンプルに書けます。

実装する

それでは、実装しましょう。今回は最初の会議調整の話題を例に実装します。簡単なサンプルを作ったので動作を先に見たい方は使ってみてください。

今回の例では、1話題・複数文脈なので、Dialogは1つ、Dialog内にはメソッドが複数あります。名前はMeetingDialogにしました(プロジェクトをVisual Studioで開いた後にDialogsフォルダを確認してみてください)。

using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace SimpleDialogBot.Dialogs
{
    [Serializable]
    public class MeetingDialog : IDialog<object>
    {
        public async Task StartAsync(IDialogContext context)
        {
            context.Wait(AskName);
        }

        public virtual async Task AskName(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            await context.PostAsync("What's your name?");
            context.Wait(AskDemand);
        }

        public virtual async Task AskDemand(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            var message = await result;
            await context.PostAsync($"Thanks {message.Text}. what do you want to do?");
            context.Wait(AskAttendee);
        }

        public virtual async Task AskAttendee(IDialogContext context, IAwaitable<IMessageActivity> result)
        {
            await context.PostAsync("You want to set meeting tomorrow. With whom?");
            context.Done<object>(null);
        }
    }
}

MeetingDialogクラスは[Serializable]Attributeがついています。BotFrameworkはC#のクラスをjsonにシリアライズして使っているので「このクラスはシリアライズしていいよ」と明示的に書いてあげている感じです(間違っていたら教えてください。)なお、これを書かないと 500 Internal Server Errorが発生します。

MeetingDialogクラスはIDialogインターフェースを実装しており、最低限StartAsyncメソッドの実装が必要になります。StartAsyncはDialogが呼び出されたとき(話題が始まったとき)に必ず呼び出されるメソッドです。

話題を続けていくためには、ここで次の文脈をpushしてあげる必要があります。スタックに文脈をpushするためには、context.wait()というメソッドを使います。こうすると、現在の文脈はMeetingDialog.AskNameだとフレームワーク側で認識してくれるので、次に話かけられた時には必ずAskNameに処理が渡されます。

AskNameが呼び出されたタイミングでスタックからはPopされるので、AskNameの処理が終わるまでに次の文脈をpushする必要があります。同じように続けていますが、AskAttendeeではcontext.done()メソッドを使っています。これは明示的に話題が終わったことを示すのですが、複数のDialogを利用する際に重要になってきます。なお、スタックを管理するためのcontext.wait()context.done()もしないとエラーが発生するので気を付けてください。

DialogはControllerから呼び出す必要があるのですが、下記のように書くことで実現できます。

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity.Type == ActivityTypes.Message)
            {
                await Conversation.SendAsync(activity, () => new MeetingDialog());
            }
            else
            {
                HandleSystemMessage(activity);
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }

実行すると下記のようになります。会話になりましたね!

今回はDialogの概念や、Dialogを使った会話の実装について共有しました。次回は複数の話題・文脈を使うための方法や、その間でどのようにデータを保持するか?について書いていきたいと思います。

2 thoughts on “Dialog を使った”会話”の実装”

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中