Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

Bot Builder SDK v4 を使って QA ボットを作ってみよう

$
0
0

とりあえず、スタート地点は Bot Builder SDK v4 のテンプレートの中の Echo ボットを使います。

テンプレートを Visual Studio に追加するにはこの拡張機能を入れます。

marketplace.visualstudio.com

Echo のテンプレートを使って作成したら、とりあえず EchoWithCounterBot.csMyQABot.csとかにリネームして、その時に出てくるダイアログでクラス名も変更するようにします。

そして https://qnamaker.aiにアクセスして適当なナレッジベースを作ります。 出来たらトレーニングとかさせて公開しておきましょう。

ここらへんのドキュメントが参考になります。

docs.microsoft.com

BotConfiguration.bot に対して QnA Maker 向けの設定を追加します。 msbot コマンドでこんな感じで出来ます。

msbot コマンドは node.js を入れて以下のコマンドで入ります。

npm i -g msbot
msbot connect qna --name "QnA" --kbId <your kbid> --subscriptionKey <your subscription key> --endpointKey <your endpoint key> --hostname <hostname>

--subscriptionKeyが Azure ポータルに作成された QnA Maker のリソースの Keys からとれる値で、--kbidは PUBLISH したあとに出てくる URL の中の /knowledgebases/<ここ>/generateAnswerの値で、--endpointKeyが PUBLISH した後の Authorization: EndpointKey <ここ>になります。--hostnameは Azure ポータルに作成されている QnA Maker のデプロイ先の Web App の概要ページにある URL の値です。

コマンドを BotConfiguration.bot のあるフォルダで打つと QnA Maker の定義が追加されます。

じゃぁ QnA Maker を呼びます。NuGet で Microsoft.Bot.Builder.AI.QnAを追加します。追加の際に Bot Builder SDK のバージョンが古いと言われたら NuGet パッケージマネージャーからさくっと Bot Builder SDK 関連のものをアップデートしましょう。

次に、QnAMaker の設定を読み込んでアプリから使えるようにします。Bot Builder SDK のサンプルでは、BotServices クラスを定義してそこに各種サービスを突っ込むのがセオリーっぽいです。ここでは QnAMaker だけしか使わないのと他の煩わしい部分を省くために BotServices クラスを省いて QnAMaker クラスを直接使います。

まず、Startup.cs に以下のメソッドを追加します。

private QnAMaker InitQnAMaker(BotConfiguration config)
{
    var qna = (QnAMakerService)config.Services.Single(x => x.Type == ServiceTypes.QnA);
    returnnew QnAMaker(new QnAMakerEndpoint
    {
        KnowledgeBaseId = qna.KbId,
        EndpointKey = qna.EndpointKey,
        Host = qna.Hostname,
    });
}

そして、Startup.cs の ConfigureServices メソッドの AddBot メソッドの呼び出しの後ろらへんに以下のコードを追加して QnAMaker クラスも services に登録しておきます。QnAMaker を作るには BotConfiguration が必要なので、それの生成を AddBot から ConfigureServices の先頭に移動します。

var secretKey = Configuration.GetSection("botFileSecret")?.Value;
var botFilePath = Configuration.GetSection("botFilePath")?.Value;
var botConfig = BotConfiguration.Load(botFilePath ?? @".\BotConfiguration.bot", secretKey);

そして AddBot の後当たりに以下の行を追加します。

services.AddSingleton(sp =>
{
    var qna = (QnAMakerService)botConfig.Services.Single(x => x.Type == ServiceTypes.QnA);
    returnnew QnAMaker(new QnAMakerEndpoint
    {
        KnowledgeBaseId = qna.KbId,
        EndpointKey = qna.EndpointKey,
        Host = qna.Hostname,
    });
});

MyQnABot のコンストラクタで、この QnAMaker クラスも受け取るようにします。

publicclass MyQnABot : IBot
{
    privatereadonly EchoBotAccessors _accessors;
    privatereadonly QnAMaker _qnaMaker; // 追加privatereadonly ILogger _logger;

    /// <summary>/// Initializes a new instance of the <seecref="MyQnABot"/> class./// </summary>/// <paramname="accessors">A class containing <seecref="IStatePropertyAccessor{T}"/> used to manage state.</param>/// <paramname="loggerFactory">A <seecref="ILoggerFactory"/> that is hooked to the Azure App Service provider.</param>/// <seealsocref="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1#windows-eventlog-provider"/>public MyQnABot(EchoBotAccessors accessors, QnAMaker qnaMaker, ILoggerFactory loggerFactory) // 引数追加
    {
        if (loggerFactory == null)
        {
            thrownew System.ArgumentNullException(nameof(loggerFactory));
        }

        _logger = loggerFactory.CreateLogger<MyQnABot>();
        _logger.LogTrace("EchoBot turn start.");
        _accessors = accessors ?? thrownew System.ArgumentNullException(nameof(accessors));
        _qnaMaker = qnaMaker; // 追加
    }

あとは OnTurnAsync で呼ぶだけ。

public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    if (turnContext.Activity.Type == ActivityTypes.Message)
    {
        var answers = await _qnaMaker.GetAnswersAsync(turnContext);
        if (answers?.Any() ?? false)
        {
            // 答えがあった
            await turnContext.SendActivityAsync(answers[0].Answer, cancellationToken: cancellationToken);
        }
        else
        {
            // 答えがなかった
            await turnContext.SendActivityAsync("ごめんね…もっと勉強するわ…", cancellationToken: cancellationToken);
        }
    }
    else
    {
        await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
    }
}

動かしてみましょう。

f:id:okazuki:20181029154110p:plain

元にした QA が 一般法人向け Office 365 とは | よく寄せられる質問なので、それっぽくなってます。

おまけ

わからない QA に対して何かしてみましょう。例えばわからない質問がきたら Microsoft Teams に飛ばすとか。MS Teams の投稿先にしたいチャネルでコネクタを追加します。Incoming Webhook があるので追加します。作成すると URL が貰えるのでコピーします。

本番では appsettings.json とか Azure にデプロイする場合はアプリケーション設定とかに URL を追加してそこから読むのがいいのですが、今回はさぼってハードコードします。マネしないでね。

public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
    if (turnContext.Activity.Type == ActivityTypes.Message)
    {
        var answers = await _qnaMaker.GetAnswersAsync(turnContext);
        if (answers?.Any() ?? false)
        {
            // 答えがあった
            await turnContext.SendActivityAsync(answers[0].Answer, cancellationToken: cancellationToken);
        }
        else
        {
            // 答えがなかった
            await turnContext.SendActivityAsync("ごめんね…もっと勉強するわ…", cancellationToken: cancellationToken);
            using (var c = new HttpClient())
            {
                await c.PostAsync("さっきゲットした URL をここに",
                    new StringContent(JsonConvert.SerializeObject(MessageFactory.Text($"{turnContext.Activity.From.Name} さんからわからない質問がありました。質問内容「{turnContext.Activity.Text}」")), Encoding.UTF8, "application/json"));
            }
        }
    }
    else
    {
        await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
    }
}

ちゃんとわからない質問がきたら Teams にメッセージが出るようになりました。ばっちり。

f:id:okazuki:20181029155049p:plain


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>