Bot Framework の Activity を(改めて)整理

Bot Framework を使う上で重要なのが Activity。
・・・なのですが、知ってる人には当たり前すぎるためか、意外と情報が見つからない気がします。

先日、勉強会で Activity の観点で Bot Framework を紹介したのですが、思った以上に高評価でした(リップサービス込みだとしても)。
ちょっと所用で間が開いてしまいましたが、改めて Bot Framework の Activity を整理してみます。


■ Activity とは

Bot Framework で Activity とは、直感的には「吹き出し」の一つ一つのこと、相手に送信するメッセージの単位のことです。人間が発信するものもBot が発信するものも区別なく Activity です。

複数行

以下では、テキストだけ、画像を含むなど、見た目ごとに Activity を順に紹介します。
なおソースコードをもっとシンプルにできるのですが、今回は Activity ごとに記述するべき内容を見るために、全体としてはだいぶ冗長なコードになっています。

 


■ テキストのみを送信

Bot Framework の Activity は、「メール」と比べるとわかりやすいと思います。
メールは基本的にはテキストを送ります。HTML メールはリッチな見た目ですが、送信しているデータはテキストです。

以下では、

  • context ・・・Bot Framework の対話の文脈(コンテキストのほうが伝わりやすいですね)
  • activity ・・・以下のコードでは、Bot にとってユーザー(人間)から送信されてきた Activity のこと。この activity に返信することで、ユーザーと Bot とが対話を続けます。
  • text ・・・Bot から送信するテキストメッセージ

です。

Bot からテキストメッセージを送信するには、ユーザーからの activity に対して CreateReply することで、送信用の Activity を生成します。
string の引数で送信するテキストメッセージを指定することができます。次の例で手で来るように、Activity.Text プロパティに値を設定することでもテキストメッセージを指定できるので、その時のケースで使いやすいほうを使ってください。
あとは context の PostAsync メソッドに CreateReply した Activity を渡します。

private static async Task ReplyTextAsync(IDialogContext context, Activity activity, string text)
{
    var reply = activity.CreateReply(text);
    await context.PostAsync(reply);
}

テキスト

テキストメッセージは1行に限定されるものではありません。
“\n\n” で改行することができます。

private static async Task ReplyMultiLineText(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "Activity.Textプロパティにメッセージを入れても同じ" +
                 "\n\n複数行を返すこともできます\n\n" +
                 "URLを含むこともできます\n\nhttp://dev.botframework.com/";
    await context.PostAsync(reply);
}

複数行


■ 画像を送信

テキストだけではない内容をユーザーに送信したい場合、あとで紹介するようにカード(HeroCard, ThumbnailCard、今後は AdaptiveCard も)を使います。
ただしテキスト+画像1枚の場合は、Attachment クラスのインスタンスで直接画像の URL を指定することができます。

private static async Task ReplyImageAttachment(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "画像だけならば直接置くこともできます";
    reply.Attachments.Add(new Attachment
    {
        ContentUrl = "http://~/botneko_1.jpg",
        ContentType = "image/jpeg",
        Name = "正面"
    });
    await context.PostAsync(reply);
}

画像


■ カード

画像1枚と簡単な説明程度であれば、上のように Attachment クラスで直接画像の URL を指定する方法があります。
が、もっとリッチな情報を送信したいことも多いと思います。
そのような時はカードを使います。現時点では、HeroCard (大きいサイズの画像を貼る)と ThumbnailCard (小さいサイズの画像を貼る)の2種類があります。

どちらのクラスも、プロパティやカードの作り方は同じです。

HeroCard だとこんな風になります。
Activity のテキストの他に、HeroCard は Title および Subtitle の二つのテキストを持つことができます。画像との位置関係は下の図のようになります。

private static async Task ReplyImageInCard(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "画像はカードに置くこともできます";

    var heroCard = CreateHeroCard("正面", "カメラ目線",
        "http://~/botneko_1.jpg");
    var heroCardAttach = heroCard.ToAttachment();
    reply.Attachments.Add(heroCardAttach);

    await context.PostAsync(reply);
}

private static HeroCard CreateHeroCard(string title, string subtitle, string imagePath)
{
    var image = new CardImage
    {
        Url = imagePath,
        Alt = title
    };

    var card = new HeroCard
    {
        Title = title,
        Subtitle = string.IsNullOrEmpty(subtitle) ? null : subtitle,
        Images = new List<CardImage> { image }
    };

    return card;
}

HeroCard

ThumbnailCard だと、下のようになります。
こちらも、画像と Title, Subtitle との位置関係を見てみてください。(説明上はちょっとフライングですが、下の図の通り、カードにはボタンを置くこともできます)

private static async Task ReplyThumbnailCard(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "小さい画像のカードもあります";

    var thumbnailCard = CreateThumbnailCardWithButton("正面", "カメラ目線",
        "http://~/botneko_1.jpg");
    var thumbnailCardAttach = thumbnailCard.ToAttachment();

    reply.Attachments.Add(thumbnailCardAttach);

    await context.PostAsync(reply);
}

private static ThumbnailCard CreateThumbnailCardWithButton(string title, string subtitle, string imagePath)
{
    var image = new CardImage
    {
        Url = imagePath,
        Alt = title
    };
    var action = new CardAction
    {
        Type = "imBack",
        Title = title,
        Value = $"{title} ({subtitle})",
        Image = imagePath
    };

    var card = new ThumbnailCard
    {
        Title = title,
        Subtitle = string.IsNullOrEmpty(subtitle) ? null : subtitle,
        Images = new List<CardImage> { image },
        Buttons = new List<CardAction> { action },
        Tap = action
    };

    return card;
}

ThumbnailCard


■ ボタン

Activity にはボタンを配置することもできます。ボタンは1個だけではなく、複数個貼れます。

下の例では CardAction.Type として ImBack をしています。この場合は、ユーザーがボタンを慰した場合、Value の値を Bot に対して送信します。

private static async Task ReplyCardWithButtons(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "ボタンの利用";

    var buttonsCard = new HeroCard { Title = "カードにボタンを配置することもできます" };
    buttonsCard.Buttons.Add(new CardAction { Type = ActionTypes.ImBack, Title = "猫", Value = "猫" });
    buttonsCard.Buttons.Add(new CardAction { Type = ActionTypes.ImBack, Title = "犬", Value = "犬" });
    buttonsCard.Buttons.Add(new CardAction { Type = ActionTypes.ImBack, Title = "ウサギ", Value = "ウサギ" });
    var buttonsCardAttach = buttonsCard.ToAttachment();
    reply.Attachments.Add(buttonsCardAttach);

    await context.PostAsync(reply);
}

Button


■ 選択肢

Windows のメッセージボックスのように、ユーザーに Yes/No やいくつかの選択肢のいずれかを選択させたいこともあります。

二択の場合には PromptDialog.Confirm を呼び出します。それ以上の選択肢がある場合には PromptDialog.Choice を呼び出します。(Choice を二択で使うことも可能ですが)

PromptDialog.Confirm(context, ConfirmSelectAsync, "二択です");
private async Task ConfirmSelectAsync(IDialogContext context, IAwaitable<bool> result)
{
    var answer = await result;
    await context.PostAsync($"「{answer}」の選択ですね");

    context.Wait(MessageReceivedAsync);
}

二択

PromptDialog.Choice(context, ChoiceSelectAsync, new List<string> { "猫", "犬", "ウサギ" }, "三択です");
private async Task ChoiceSelectAsync(IDialogContext context, IAwaitable<object> result)
{
    var answer = await result;
    await context.PostAsync($"「{answer}」ですね");

    context.Wait(MessageReceivedAsync);
}

三択


■ リスト

Activity にはカードを複数添付することができます。
カード自体は HeroCard でも ThumbnailCard でも(それ以外、例えば AdaptiveCard でも)かまいません。

private static async Task ReplyHeroCardsInList(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "Attachmentを複数持つことができます";

    var heroCards = CreateHeroCardArray();
    foreach (var card in heroCards)
    {
        reply.Attachments.Add(card.ToAttachment());
    }

    await context.PostAsync(reply);
}

public static HeroCard[] CreateHeroCardArray()
{
    var cards = new HeroCard[3];
    cards[0] = CreateHeroCardWithButton("正面", "カメラ目線",
        "http://~/botneko_1.jpg");
    cards[1] = CreateHeroCardWithButton("袋", "狭い場所が好き",
        "http://~/botneko_2.jpg");
    cards[2] = CreateHeroCardWithButton("後ろ", "かすかにウサギの模様",
        "http://~/botneko_3.jpg");

    return cards;
}

リスト

コードは省略しますが、ThumbnailCard のリストはこんな実行結果になります。

ThumbnailCard リスト

添付可能な数は Activity.Attachments が IList<Attachment> であることから制限はありません。ただし縦方向のスクロールが発生してしまうので、Chat アプリケーションとしてはユーザビリティの注意が必要です。カードの個数や使いどころは十分に注意してください。


■ 横方向のリスト

複数の Attachment は横方向に並べることもできます。縦方向が “List” であるのに対して、縦方向は “Carousel” です。

縦方向のリストとの違いは、Activitiy.AttachmentLayout  プロパティに AttachmentLayoutTypes.Carousel を設定する点だけです。

private static async Task ReplyHeroCardsInCarousel(IDialogContext context, Activity activity)
{
    var reply = activity.CreateReply();
    reply.Text = "リストは横に並べることができます";

    var heroCarouselCards = CreateHeroCardArray();
    foreach (var card in heroCarouselCards)
    {
        reply.Attachments.Add(card.ToAttachment());
    }
    reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;

    await context.PostAsync(reply);
}

public static HeroCard[] CreateHeroCardArray()
{
    var cards = new HeroCard[3];
    cards[0] = CreateHeroCardWithButton("正面", "カメラ目線",
        "http://~/botneko_1.jpg");
    cards[1] = CreateHeroCardWithButton("袋", "狭い場所が好き",
        "http://~/botneko_2.jpg");
    cards[2] = CreateHeroCardWithButton("後ろ", "かすかにウサギの模様",
        "http://~/botneko_3.jpg");

    return cards;
}

横方向リスト

ThumbnailCard 横リスト


 

長い記事になりましたが、以上が Activity の使い方のバリエーションです。

Build 2017 で AdaptiveCard が紹介され、これを使えばさらに表現力のあるカードを作れます。その場合でも、Activity の使い方は変わりません。

まずは本稿で Bot のユーザビリティを理解してください。

さらに長くなりますが、最後に RootDialog.cs の全ソースを載せます。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.ModelBinding;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace BotActivityDemo.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog&lt;object&gt;
    {
        public Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);

            return Task.CompletedTask;
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var activity = await result as Activity;
            if (activity != null)
            {
                var text = activity.Text;

                switch (text)
                {
                    case "テキスト":
                        await ReplyTextAsync(context, activity, "テキストだけならば、Activityだけで対話できます");
                        return;
                    case "複数行":
                        await ReplyMultiLineText(context, activity);
                        break;
                    case "画像":
                        await ReplyImageAttachment(context, activity);
                        break;
                    case "カード":
                        await ReplyImageInCard(context, activity);
                        break;
                    case "ボタン付きカード":
                        await ReplyCardWithSingleButton(context, activity);
                        break;
                    case "ボタン":
                        await ReplyCardWithButtons(context, activity);
                        break;
                    case "サムネイル":
                        await ReplyThumbnailCard(context, activity);
                        break;
                    case "二択":
                        PromptDialog.Confirm(context, ConfirmSelectAsync, "二択です");
                        return;
                    case "三択":
                        PromptDialog.Choice(context, ChoiceSelectAsync, new List&lt;string&gt; { "猫", "犬", "ウサギ" }, "三択です");
                        return;
                    case "リスト":
                        await ReplyHeroCardsInList(context, activity);
                        break;
                    case "サムネイルリスト":
                        await ReplyThumbnailCardsInList(context, activity);
                        break;
                    case "横リスト":
                        await ReplyHeroCardsInCarousel(context, activity);
                        break;
                    case "サムネイル横リスト":
                        await ReplyThumbnailCardsInCarousel(context, activity);
                        break;
                    default:
                        await ReplyTextAsync(context, activity, $"「{text}」と言いましたね");
                        break;
                }
            }

            context.Wait(MessageReceivedAsync);
        }

        private static async Task ReplyTextAsync(IDialogContext context, Activity activity, string text)
        {
            var reply = activity.CreateReply(text);
            await context.PostAsync(reply);
        }

        private static async Task ReplyMultiLineText(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "Activity.Textプロパティにメッセージを入れても同じ" +
                         "\n\n複数行を返すこともできます\n\n" +
                         "URLを含むこともできます\n\nhttp://dev.botframework.com/";
            await context.PostAsync(reply);
        }

        private static async Task ReplyImageAttachment(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "画像だけならば直接置くこともできます";
            reply.Attachments.Add(new Attachment
            {
                ContentUrl = "botneko_1.jpg",
                ContentType = "image/jpeg",
                Name = "正面"
            });
            await context.PostAsync(reply);
        }

        private static async Task ReplyImageInCard(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "画像はカードに置くこともできます";

            var heroCard = CreateHeroCard("正面", "カメラ目線",
                "botneko_1.jpg");
            var heroCardAttach = heroCard.ToAttachment();
            reply.Attachments.Add(heroCardAttach);

            await context.PostAsync(reply);
        }

        private static async Task ReplyCardWithSingleButton(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "カードにボタンを入れることもできます";

            var heroButtonCard = CreateHeroCardWithButton("正面", "カメラ目線",
                "botneko_1.jpg");
            var heroButtonCardAttach = heroButtonCard.ToAttachment();

            reply.Attachments.Add(heroButtonCardAttach);

            await context.PostAsync(reply);
        }

        private static async Task ReplyCardWithButtons(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "ボタンの利用";

            var buttonsCard = new HeroCard { Title = "カードにボタンを配置することもできます" };
            buttonsCard.Buttons.Add(new CardAction { Type = ActionTypes.ImBack, Title = "猫", Value = "猫" });
            buttonsCard.Buttons.Add(new CardAction { Type = ActionTypes.ImBack, Title = "犬", Value = "犬" });
            buttonsCard.Buttons.Add(new CardAction { Type = ActionTypes.ImBack, Title = "ウサギ", Value = "ウサギ" });

            var buttonsCardAttach = buttonsCard.ToAttachment();
            reply.Attachments.Add(buttonsCardAttach);

            await context.PostAsync(reply);
        }

        private static async Task ReplyThumbnailCard(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "小さい画像のカードもあります";

            var thumbnailCard = CreateThumbnailCardWithButton("正面", "カメラ目線",
                "botneko_1.jpg");
            var thumbnailCardAttach = thumbnailCard.ToAttachment();

            reply.Attachments.Add(thumbnailCardAttach);

            await context.PostAsync(reply);
        }

        private async Task ConfirmSelectAsync(IDialogContext context, IAwaitable<bool> result)
        {
            var answer = await result;
            await context.PostAsync($"「{answer}」の選択ですね");

            context.Wait(MessageReceivedAsync);
        }

        private async Task ChoiceSelectAsync(IDialogContext context, IAwaitable<object> result)
        {
            var answer = await result;
            await context.PostAsync($"「{answer}」ですね");

            context.Wait(MessageReceivedAsync);
        }

        private static async Task ReplyHeroCardsInList(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "Attachmentを複数持つことができます";

            var heroCards = CreateHeroCardArray();
            foreach (var card in heroCards)
            {
                reply.Attachments.Add(card.ToAttachment());
            }

            await context.PostAsync(reply);
        }

        private static async Task ReplyThumbnailCardsInList(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "ThumbnailCardももちろんリストにできます";

            var thumbnailCards = CreateThumbnailCardArray();
            foreach (var card in thumbnailCards)
            {
                reply.Attachments.Add(card.ToAttachment());
            }

            await context.PostAsync(reply);
        }

        private static async Task ReplyHeroCardsInCarousel(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "リストは横に並べることができます";

            var heroCarouselCards = CreateHeroCardArray();
            foreach (var card in heroCarouselCards)
            {
                reply.Attachments.Add(card.ToAttachment());
            }
            reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;

            await context.PostAsync(reply);
        }

        private static async Task ReplyThumbnailCardsInCarousel(IDialogContext context, Activity activity)
        {
            var reply = activity.CreateReply();
            reply.Text = "ThumbnailCardも横に並べられます";

            var thumbnailCarouselCards = CreateThumbnailCardArray();
            foreach (var card in thumbnailCarouselCards)
            {
                reply.Attachments.Add(card.ToAttachment());
            }
            reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;

            await context.PostAsync(reply);
        }

        private static HeroCard CreateHeroCard(string title, string subtitle, string imagePath)
        {
            var image = new CardImage
            {
                Url = imagePath,
                Alt = title
            };

            var card = new HeroCard
            {
                Title = title,
                Subtitle = string.IsNullOrEmpty(subtitle) ? null : subtitle,
                Images = new List<CardImage> { image }
            };

            return card;
        }

        private static HeroCard CreateHeroCardWithButton(string title, string subtitle, string imagePath)
        {
            var image = new CardImage
            {
                Url = imagePath,
                Alt = title
            };
            var action = new CardAction
            {
                Type = "imBack",
                Title = title,
                Value = $"{title} ({subtitle})",
                Image = imagePath
            };

            var card = new HeroCard
            {
                Title = title,
                Subtitle = string.IsNullOrEmpty(subtitle) ? null : subtitle,
                Images = new List<CardImage> { image },
                Buttons = new List<CardAction> { action },
                Tap = action
            };

            return card;
        }

        private static ThumbnailCard CreateThumbnailCardWithButton(string title, string subtitle, string imagePath)
        {
            var image = new CardImage
            {
                Url = imagePath,
                Alt = title
            };
            var action = new CardAction
            {
                Type = "imBack",
                Title = title,
                Value = $"{title} ({subtitle})",
                Image = imagePath
            };

            var card = new ThumbnailCard
            {
                Title = title,
                Subtitle = string.IsNullOrEmpty(subtitle) ? null : subtitle,
                Images = new List<CardImage> { image },
                Buttons = new List<CardAction> { action },
                Tap = action
            };

            return card;
        }

        public static HeroCard[] CreateHeroCardArray()
        {
            var cards = new HeroCard[3];
            cards[0] = CreateHeroCardWithButton("正面", "カメラ目線",
                "botneko_1.jpg");
            cards[1] = CreateHeroCardWithButton("袋", "狭い場所が好き",
                "botneko_2.jpg");
            cards[2] = CreateHeroCardWithButton("後ろ", "かすかにウサギの模様",
                "botneko_3.jpg");

            return cards;
        }

        public static ThumbnailCard[] CreateThumbnailCardArray()
        {
            var cards = new ThumbnailCard[3];
            cards[0] = CreateThumbnailCardWithButton("正面", "カメラ目線",
                "botneko_1.jpg");
            cards[1] = CreateThumbnailCardWithButton("袋", "狭い場所が好き",
                "botneko_2.jpg");
            cards[2] = CreateThumbnailCardWithButton("後ろ", "かすかにウサギの模様",
                "botneko_3.jpg");

            return cards;
        }
    }
}

 

広告
カテゴリー: Bot Framework, Bot Service, Cogbot, 未分類 | タグ: , , | 4件のコメント

QnA Maker + Bot Builder SDK for C# で複数回答を返す Q&A Bot を作る

2回シリーズで、QnA Maker + Bot Builder SDK for C# で Q&A Bot を作成する手順を紹介しました。

今回はその続編として、複数回答を返す Bot を作成してみます。

Q&A に限らず、Bot からの応答は絶対一つに限られるわけではなく、スコア付きで複数の回答の候補が見つかることが多いはずです。

前回説明したシンプルな Q&A Bot では、一番スコアの高いものを自動的に返答していました。これを実現しているのが QnAMakerDialog のデフォルトの実装です。

それに対して今回は、

  • 該当しそうな回答が見つからない場合の「ごめんなさい」メッセージ
  • 2番目以降のスコアの回答
  • スコアは低いけど、こんな回答が関連しているかもというもの

を返すようにしたいと思います。


■ QnA Maker と QnAMakerDialog の復習

QnA Maker でナレッジベースを作る方法、Bot Builder SDK の QnAMakerDialog の基本的な使い方は、前の投稿を参考にしてください。

参考までに、今回のナレッジベースはこんな内容です。

2017-06-28 16-54-37


■ QnAMakerDialog の QnAMaker 属性

QnAMakerDialog を継承するクラスでは、QnAMaker 属性を付与します。

この属性は 5個の引数を指定できます。

QnAMaker 属性の最も大事な点は、QnA Service の KnowlegdebaseId (属性の 1個目の引数) と SubscriptionKey (属性の 2個目の引数) を渡すことですが、他の3個の引数は以下のような意味を持ちます。

  • 第3引数・・・回答が見つからない場合のメッセージ
  • 第4引数・・・回答の候補とするスコアの最低値、デフォルトは 0.3
  • 第5引数・・・回答の候補として何個取得するか、デフォルトは 1

例えば、以下の場合には、スコアが 0.3未満のものは回答の候補に含めません。また回答の個数は 3個までという意味です。

2017-06-28 17-26-54


■ 回答が見つからない場合

回答が見つからない場合は、QnAMaker 属性の第3引数の文字列を返します。

例えば、今回のナレッジベースではライオンについての Q&A は持っていないため、以下の回答を返します。

2017-06-28 17-14-28

■ 複数の回答を返したい場合

複数の回答を返したい場合は、QnAMakerDialog の RespondFromQnAMakerResultAsync メッセージをオーバーライドします。

2017-06-28 17-39-08

QnAMaker 属性の第4引数のスコア以上のものが、第5引数の個数までナレッジベースから取得されます。

取得した回答は resultAnswers プロパティに入っているので、各アイテムの Answer プロパティで回答文が、Score プロパティでその回答のスコアが得られます。

2017-06-28 17-13-01

■ スコアが低い回答しか得られない場合

ナレッジベースから一応回答は得られたもののスコアが低い場合には、QnAFeedbackStepAsync メソッドが呼ばれます。このメソッドをオーバーライドすることで明示的に回答を返すことができます。

念のため、IsConfidentAnswer メソッドでスコアが低いことを確認した上で(文字通り「自信のある回答かどうか」)、Answers プロパティの値を整形します。Answers の要素は QnAMakerResult であり、スコアが低いことを除いては RespondFromQnAMakerResultAsync メソッドの時と同じように処理できます。

2017-06-28 17-48-42

2017-06-28 17-13-532017-06-28 17-14-59


複数個の回答候補やスコアの低いものをどのようにユーザーに見せるかはアプリ次第ですが、今回のコードでよりユーザーにやさしい回答を返すことができます。

非常に簡単なコードですが、Q&A Bot としては一通りの応答をしてくれるようになりました。

あとは運用しながら、ナレッジベースをどうやって育てていくかがポイントになりそうです。

念のため、以下に QnAMakerDialog を継承したクラス(今回は AnimalQnADialog)のコードを載せておきます。

using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace AnimalQnaBot.Dialogs
{
    [Serializable]
    [QnAMaker("63174189522d4406910a50b3fd501515", "4d723a69-e337-420f-8e30-4d3f97ac35b0",
        "別の言い方で試してください", 0.3, 3)]
    public class AnimalQnaDialog : QnAMakerDialog
    {
        protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
        {
            var bestAnswer = result.Answers.First();
            await context.PostAsync($"{bestAnswer.Answer} ({bestAnswer.Score:0.00})");

            if (result.Answers.Count > 1)
            {
                var sb = new StringBuilder();
                sb.Append("以下が答えかもしれません");

                foreach (var answer in result.Answers.Skip(1))
                    sb.Append($"\n\n{answer.Answer} ({answer.Score:0.00})");

                await context.PostAsync(sb.ToString());
            }
        }

        protected override async Task QnAFeedbackStepAsync(IDialogContext context, QnAMakerResults result)
        {
            if (!IsConfidentAnswer(result))
            {
                var sb = new StringBuilder();
                sb.Append("自信がありませんが、以下が関連しているかもしれません");

                foreach (var answer in result.Answers)
                    sb.Append($"\n\n{answer.Answer} ({answer.Score:0.00})");

                await context.PostAsync(sb.ToString());
            }
        }
    }
}
カテゴリー: Bot Framework, Cogbot, Cognitive Services | タグ: , , | 1件のコメント

QnA Maker + Bot Builder SDK for C# で簡単に Q&A Bot を作る (2) ~ C# で Bot Aplication 編

QnA Maker と Bot Builder SDK for C# でQ&A Bot アプリケーションを作成する手順を紹介します。

2 回に分けた 2回目の今回は Bot Builder SDK for C# で Bot アプリケーション開発 の手順を紹介します。

※1回目の QnA Maker 操作手順は こちら

QnA Maker と Bot Builder SDK、それに Cognitive Services の NuGet パッケージを使うと、非常に簡単に、コードらしいコードを書かずに Q&A Bot が作れます。

より柔軟な応答をするような Q&A Bot を作るにはコードも必要ですが、まずはアプリとしてきちんと動作するものを簡単に作ってみます。


■ Bot アプリケーション開発の基本(復習)

Bot アプリケーション開発の基本、プロジェクトテンプレートから新規アプリを作り、Emulator で実行するまでについては、こちら を参照。

念のため、本当に概要のみ紹介すると、

  1. Bot Application のプロジェクトテンプレートをダウンロード、VS のテンプレートフォルダーにコピー
  2. 新規プロジェクトを作成
  3. F5 で実行
  4. Bot Framework Emulator で動作確認

今回の Q&A Bot はここをスタートとします。

■ Q&A Bot アプリケーションの開発手順

上記で準備した Bot アプリを Q&A Bot アプリにするには以下の手順を踏みます。

  1. Cognitive Services の NuGet パッケージをインストールします。2017-06-21 22-33-59
  2. QnAMakerDialog を継承するクラスを追加します。今回は特にメソッドの追加は必要なく、SubscriptionKey と KnowledgebaseId を指定するためにクラスを作成します。(ここでは AnimalQnaDialog.cs とします)作成したクラス QnAMaker に属性として “QnAMaker” を追加して SubscriptionKey と KnowledgebaseId を指定します。もう一つのオプションは、回答を取得できなかった場合の応答です。
    QnAMaker 属性はあと二つパラメーターの指定が可能ですが、今回はここまでにしておきます。
    2017-06-22-0-00-56

  3. MessageControler.cs で、ユーザーの入力を処理する IDialog として、先ほど作成した AnimalQnaDialog を指定します。
    2017-06-26 13-52-12

以上です。簡単です。

Bot Framework Emulator を実行すると、質問に対して応答してくれます。

2017-06-22 0-08-58

少し触ってみて適切な応答を返してくれない場合は、QnA Maker に戻ってナレッジの追加・編集と再学習をしてください。


今回は Bot アプリケーションらしい開発をしていませんが、基本的な手順と Bot がどのように動作するかの感触は掴んでもらえると思います。

また、Q&A Bot のイメージもつかめるかと思います。

要求としてこれで足りる時は(ナレッジの登録や再学習は適宜必要ですが)、QnA Maker を利用、これだと足りないなという時には LUIS を使ってもっと高度な Bot アプリを作るのがよいと思います。

その切り分けをするためにも、まずは QnA Maker を試してみるといいですね。

カテゴリー: Bot Framework, Cogbot, Cognitive Services | タグ: , , | 3件のコメント

QnA Maker + Bot Builder SDK for C# で簡単に Q&A Bot を作る (1) ~ QnA Maker 編

QnA Maker と Bot Builder SDK for C# でQ&A Bot アプリケーションを作成する手順を紹介します。

2 回に分けた 1 回目の今回は QnA Maker の操作手順を紹介します。
※2回目の Bot Builder SDK for C# で Bot 開発は こちら

QnA Maker 自体は他のサイトでも紹介されていますが、既存の Q&A ページからインポートする手順の紹介が多いようなので、今回は TSV ファイルからインポートしてみます。


■ QnA Maker とは

QnA Maker は、文字通り Q&A のナレッジベースを開発・公開するサービスです。

ユーザーの質問に対して適切な回答(適切だと思われる回答)を返してくれます。

Cognitive Service のサービスの一つである LUIS は、あらかじめ Intent  や Entity を定義して、ユーザーの意図をどのように解析するべきか学習させます。(その分、手間がかかることもある)
それに対して QnA Maker はもっとシンプルで、質問と回答とのセットを登録して、QnA Maker に対して学習を指示するだけです。精度が出づらいこともありますが、ナレッジを早く開発できます。

■ QnA Maker の操作手順

QnA Maker (https://qnamaker.ai/) で Q&A のナレッジベースを作る手順は以下の通り。

  1. [Create new Service] で新しいサービスを作成します。2017-06-21 0-02-32
  2. (多くの  QnA Maker 紹介ページでは、ここで既存のFAQ ページを指定するが)[FAQ File] を指定する。ナレッジを管理するには TSV (タブ区切り CSV、なお文字コードは UTF-8 にしないと内容を読み取ってくれないので注意)
    2017-06-21 0-03-14

    参考までに、今回は動物について答えてくれるこんな TSV ファイルを作ってみました。(今回のサンプルではこの程度で)
    2017-06-22 23-57-20
    TSV ファイルの Q&A を取り込むことができたら、ページ下部の [Create] をクリックします。今回のサンプルであれば 10秒もあればナレッジが作成されます。
  3. Test 画面でナレッジをテストします。この画面で質問を追加したり、質問に紐づける回答を変更したりすることができます。
    2017-06-21 0-12-35Q&A を追加・変更した場合は、必ず [Save and Retrain] をクリックしてください。(Save せずに別の画面に遷移すると、今回の追加・変更は失われます)[Knowledge Base] 画面で Q&A の追加・変更も可能です。
    2017-06-25 17-16-15
    学習できたら [Publish] でサービスを展開します。
    2017-06-21 0-16-502017-06-21 0-17-47

以上で Q&A のナレッジは完成。

KnowledgebaseId, SubscriptionKey は、Bot アプリで使います。これらの値はパスワードではないので、いつでもこの画面で確認できます。メモ帳にコピペしておくなどは不要です。


Bot Framework を理解するには Activity が必要ですが、意外とActivity 周りの記事がなかなか見つかりません。
Bot Framework を知っている人には当たり前すぎるんですかね?

今回は、その Activity に触れる前に、まず Bot アプリケーションを簡単に開発できる Q&A Bot(の前段として QnA Maker)を紹介しました。
今回と次回の記事とで Bot Framework の感触をつかんでから、もっと深いところに潜り込んでみてください。

カテゴリー: Bot Framework, Cogbot, Cognitive Services | タグ: , , | 3件のコメント

「Bot Framework 最新情報 2017」セッション資料を公開しました (第6回 Cogbot勉強会 2017/6/16) #cogbot

第6回 Cogbot 勉強会 (2017年6月16日開催) で、

Bot Framework 最新情報 2017

セッションを担当しました。

当日は、パブリックな場所ではお見せしないほうがいいかも・・・なデモをしてしまいました。(笑)
金曜夜でしたし「お堅いのもなぁ」と思いまして。


Bot Framework に関して、Build 2017 でのアップデートは 11項目ありました。

「それもカウントするの?」というのも含まれていたりしましたが、これから本格的に Bot Framework を使ってみようという方はまずここを抑えておくといいのではないかという観点で、当日は主に次のポイントを紹介しました。

  • Channel 追加(Bing Channel, Cortana Channel, Skype for Business)
  • Adaptive Card
  • Speech
  • Bot Analytics

■Channel 追加

Bing Channel, Cortana Channel は現時点では日本語未対応、Skype for Business は Preview ということもあり実用には遠いのですが、それでも Channel が増えることは Bot Framework にとっては大きなメリット、進歩です。

今後に向けて、これらのチャンネルでどんなソリューションが役に立ちそうか、今から考えを巡らせるのは面白そう。そういう意味で紹介しました。

特に、Skype for Business Channel は、先日の Office 365 勉強会でも紹介しましたが、企業システムで Bot を活用するためには絶対に欠かせない技術になりそうです。

■Adaptive Card

今回のアップデートで一番面白いのが、Adaptive Card だと思います。

これまで、HeroCard, ThumbnailCard が、見た目的にリッチな Card でしたが、表示項目やレイアウトに制約があったのも事実です。
その制約を超えて、さらにリッチなコンテンツを表示できる Adaptive Card は期待大です。Bot の可能性がさらに広がりそうです。

これを見せたいために、セッション前半で HeroCard, Thumbnail カードでアイドル画像をお見せしたり、List と Carousel の見た目を紹介しましたが、 意図が伝わりましたかね?(笑)

■Speech

これも大事な技術ですね。デモでの紹介はできませんでしたが、せっかくカジュアルな会話が可能な Bot です、音声が使えるほうが可能性が広がりそうですよね。(MR とか)

実はまだ私自身も評価できていない部分なので、触ってみて今後を考えてみたいと思います。

■Bot Analytics

どんなシステムでも、サービスを提供する側にとっては、利用率は大事です。

特に苦労しなくても(最初に一度だけ設定は必要ですけど)、継続して履歴が取れるのはありがたいです。
開発者も運用も、特別の苦労はいらないため、積極的に Bot Analytics を利用したいです。


当日の勉強会では、予想よりも多くの人が「Bot Framework を触ったことがある」とのことでうれしかったのですが、復習やもしかしたら初心者の方のための導入、Build 2017 でのアップデートがより理解しやすくなればと、セッション前半では Bot Framework の既存機能の概要を「Activity の観点」で紹介しました。

(リップサービス込みで)意外とこれが評判がよかったようなので、改めてブログで紹介しますね。

カテゴリー: Bot Framework, Cogbot | タグ: , | 1件のコメント

「Skype for Business + Bot (+ Graph API)」を公開しました (第19回 Office 365 勉強会 / 2017年6月3日(土)開催)

第19回 Office 365 勉強会 (2017年6月3日(土) 開催)に参加しました。

1コマ時間をいただき、

Skype for Business + Bot (+ Graph API)

というテーマでお話ししました。

https://docs.com/d/embed/D25190588-9544-9491-6390-001573447410%7eBfe5a7bff-fc39-9c7d-d35f-86e930988702

Build 2017 で発表された、Bot Framework の Skype for Business 対応ですが、日本語どころか英語でもなかなか情報がありません。(このページくらい でしょうか)
まあ Bot Channel があるので、Connector 登録さえできれば基本的には動作すると考えていいんでしょうけど。

そこで、ちょうど Office 365 勉強会が開催されるということで紹介したというわけです。

当日は、開発者さんの割合が少なく、あまり開発のディープな話は場違いなようなので、企業システムで Bot を活用するヒントとして、利用シーンの紹介などを交えながら Bot Framework を説明しました。
Visual Studio はデモのためにローカルで Bot を起動するために開いた程度 (笑)。

プラス、Connector 設定手順だけだけだとつまらないので、Bot で Graph API を呼び出す方法、Graph エクスプローラーの紹介をしました。

Skype for Business 対応は、現時点では Preview ということもあり、まだまだ実運用には向きません。

現時点の注意点としては、

  • Channel 登録が、他と比べるとちょっと面倒。Office 365 のテナントに登録する都合上、管理者権限で PowerShell を起動する必要がある
  • テナントの設定が反映されるまで時間がかかること。8時間は待ちましょう。私はこれを待ちきれず、何か設定ミスでもあるのかと思いせっかく登録した Channel を削除したりして無駄な時間を使ってしまいました。我慢して待ちましょう。朝になるころには反映されています。
  • シングルサインインができない。Skype for Business であっても、Graph API 呼び出しの前にはサインインが必要
  • Skype for Business ではプレゼンスが取れない。ちなみに Skype だと Bot は “アクティブ” になっています。

といったところです。

今日の話の内容は、今後ブログや他の場所で改めて紹介していきますね。

まだまだ実用的とは言えない、Bot の Skype for Business 対応ですが、新しい UX として間違いなく今後の企業システムで使われるはずです。(もちろん万能ではないだろうけど)
Bot に触れてみて、今後、どんなことに使えそうか、今から考えておくのがよいと思います。

カテゴリー: Bot Framework, Bot Service, Cogbot, Office 365 | タグ: , , , , | 1件のコメント

Yoga Book With Windows (LTE対応モデル) を使ってみた

Yoga Book を買いました。Windows 版です。せっかくなので LTE 対応モデルです。
サクッと持ち歩けて、安くて、「ネタ」になるようなデバイスがあるといいなと思ったので。

yogabook_site

 

少し使ってみた感想など。思いつくままの雑記です。(すいません)

■(噂の?)ハロキーボードの操作性

物理的にキーが並んでいない、LEDで表示されるだけのキーボードです。
毎日8時間使うとか、Visual Studioでばりばりコード書くとかは考えてはいけません。そこを割り切って、モバイル用のデバイスだと考えると意外と使いやすい。
指の位置を時々目で見て確認するとか右側の幅が狭いキーの時はチラッと手元を見ることを意識すれば、それほどミスタッチなしで入力できます。(いったんズレると数文字連続でミスすることになるので、時々黙視するのは大事)

■ ハロキーボードのフィードバック

これはいけません・・・。
バイブレーションは本体全体を盛大に震わせてくれます。机の上に置いているとシャレにならないくらい鳴り響きます。
タッチトーン(タイプ音)でのフィードバックもありますが、クリック音が鳴るのが遅いのでどのキーに対するフィードバックなのかが分からず、残念ながら結局無意味です。
ということで、フィードバックはオフにするのが正解。
“C:\Program Files\Lenovo\ControlApp.exe” がホロキーボードの設定アプリなので、これでフィードバックを止めてしまいましょう。

halo_keyboard_util

■ 外部キーボード(+マウス)

せっかくの軽くて薄いタブレットなのに間違った方向に向かっているかもしれませんが、外付けのキーボードとマウスがあると格段に使いやすくなります。
といっても荷物が増えてかさばるのもなんなので、私は Universal Foldable Keyboard と小型マウスを使っています。これならノートPC よりも軽くて持ち歩きの負担も少ないです。

DSC_0912

机がある時には外付けキーボードとマウスを使って、そうでない時はハロキーボードで入力というのが良いかと。

■ LTE 対応

個人としては初の SIMスロット付きのタブレットだったりします。これは便利でいいですね。しかも Windows 端末です。テザリングとかしなくてもいつでもネットにつながる安心感。
ちなみに私は IIJmio のファミリーシェアユーザー(一人ファミリー /w)なので、普段は、みおぽんで低速設定しています。これならメールチェックやちょっとした調べものでも(多少の忍耐と引き換えに・・・でも、個人的な感覚では許容範囲)通信料を気にしなくていいです。遅いなと思ったら高速にするようにすればお財布にも優しいデバイスになります。

miopon

■ microSD カードスロット

想定外によかったのが、microSD カードスロットが本体内に隠れてしまう点。SIMと一緒にトレーに載せて本体内に押し込みます。取り出すにはピンを穴に差し込んでロックを外さなければなりません。他のデバイスと違って、SD カードにうっかり触って飛び出させるような事故が起こりません。
ということで、安心して個人ファイルを SD カードに保存できます。ただし、ごみ箱には入らないので、誤って削除しないことだけは注意。私の場合は、OneDrive のローカル側フォルダーにしておいて、削除はしない(=削除は別のPCで行う)というルールで使おうと思っています。

 

■ 他の特徴

他にも、リアルペンでのメモ、動画ビューアー(Amazon Prime とか hulu ・・・もすぐドメイン変わっちゃうけど /w)、電子書籍ビューアー(Kindle)など、このデバイスらしい使い道があります。
長くなったので、そのあたりはまた改めて。

カテゴリー: デバイス | タグ: , , | コメントをどうぞ