Microsoft Azureのチャットボットで会話ログを取得してみる

技術系
スポンサーリンク
スポンサーリンク

チャットボットの会話ログ取得の意義

チャットボットは育つ

以下の記事にてチャットボットを作成しました。

そのままでもそれなりに回答を返してくれるのですが、チャットボットは育てられる(QnA MakerにてQA集のメンテナンスができる)ので、会話ログを取ってそこから育成のヒントにできればと思いました。

ユーザーさんがどのように質問をしてくるかを集積できれば、メンテナンスが捗るかなと。

 

会話ログを取得するには

中身はC#

以下チャットボットのカスタマイズ記事で触れたとおり、Azureのチャットボットの中身はC#で書かれています。

標準の機能でもログを拾うぐらいできるのかもしれませんが、せっかくなのでコーディングをしてログが出るようにしてみたいと思います。

ソースコード編集の仕方やビルドの仕方はカスタマイズの記事をご参照ください。当記事では端折ります。

QnABot.csの修正箇所

やることは単純です。

  • ログ出力用の関数作成
  • ログ出力用関数の呼び出し部を実装
  • 適切にusingステートメント追加

深夜にざっくりで書いていますので、細かいところはあれですが…コピペしていただいてもログは確実に出ると思います。

尚、AzureでDateTime.Nowをすると標準時で取ってきます。日本時間にしたい場合は9時間プラスしてあげればOKです。

また、Shift JISなど日本語系の文字コードは確か使えません。

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Text;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.AI.QnA;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Microsoft.BotBuilderSamples
{
    public class QnABot : ActivityHandler
    {
        private readonly IConfiguration _configuration;
        private readonly ILogger<QnABot> _logger;
        private readonly IHttpClientFactory _httpClientFactory;

        public QnABot(IConfiguration configuration, ILogger<QnABot> logger, 
            IHttpClientFactory httpClientFactory)
        {
            _configuration = configuration;
            _logger = logger;
            _httpClientFactory = httpClientFactory;
        }

        protected override async Task OnMessageActivityAsync(
                                          ITurnContext<IMessageActivity> turnContext,
                        CancellationToken cancellationToken)
        {
            var httpClient = _httpClientFactory.CreateClient();

            var qnaMaker = new QnAMaker(new QnAMakerEndpoint
            {
                KnowledgeBaseId = _configuration["QnAKnowledgebaseId"],
                EndpointKey = _configuration["QnAAuthKey"],
                Host = GetHostname()
            },
            null,
            httpClient);

            _logger.LogInformation("Calling QnA Maker");

            // The actual call to the QnA Maker service.
            var response = await qnaMaker.GetAnswersAsync(turnContext);
            if (response != null && response.Length > 0)
            {
                await turnContext.SendActivityAsync(
                          MessageFactory.Text(response[0].Answer), 
                     cancellationToken);
                            
                //ログ出力へ(回答見つかった場合)
                LogOutput(turnContext.Activity.Text, response[0].Answer);
            }
            else
            {
                await turnContext.SendActivityAsync(
                          MessageFactory.Text("回答が見つかりませんでした。"),
                          cancellationToken);
                
                //ログ出力へ(回答見つからなかった場合)
                LogOutput(turnContext.Activity.Text, "回答が見つかりませんでした。");
            }
        }
        
        //ログ出力
        private void LogOutput(string question, string answer)
        {
            //日本時間に合わせる
            DateTime date = DateTime.Now.AddHours(9);
            
            //文字コードの設定(utf-16)
            Encoding sjisEnc = Encoding.GetEncoding(1200);
            
            //ログ出力
            StreamWriter writer =  new StreamWriter(@"Log.txt", true, sjisEnc);
            writer.WriteLine(date.ToString("yyyy/MM/dd HH:mm:ss.fff") + 
                             "\n質問:" + question + "\n回答:" + answer + "\n");
            writer.Close();
        }

        private string GetHostname()
        {
            var hostname = _configuration["QnAEndpointHostName"];
            if (!hostname.StartsWith("https://"))
            {
                hostname = string.Concat("https://", hostname);
            }

            if (!hostname.EndsWith("/qnamaker"))
            {
                hostname = string.Concat(hostname, "/qnamaker");
            }

            return hostname;
        }
    }
}

※11/13 追記

先ほど別のボットを新規作成してみたら少し構成が変わっていました。

QnABotのソースを上記のまま上書きしても動かないかもしれませんが、やることはそんなに変わらないと思います。

気が向いたら詳しい修正箇所をまたここに追記するか、新しい記事で取り上げます。

 

※11/20 追記

QnAMakerBaseDialog.cs内にて、同じようにSendActivityAsyncを呼んでいるところがあります。ここにログ出力用の関数呼び出し部を書けばOKです。

以下のようになりますね。

private async Task<DialogTurnResult> DisplayQnAResult(WaterfallStepContext stepContext,
                                                      CancellationToken cancellationToken)
{
    var dialogOptions = GetDialogOptionsValue(stepContext);
    var qnaDialogResponseOptions =
        dialogOptions[QnADialogResponseOptions] as QnADialogResponseOptions;
    var reply = stepContext.Context.Activity.Text;

    if (reply.Equals(qnaDialogResponseOptions.CardNoMatchText,
                     StringComparison.OrdinalIgnoreCase))
    {
        await stepContext.Context.SendActivityAsync(
            qnaDialogResponseOptions.CardNoMatchResponse,
            cancellationToken: cancellationToken).ConfigureAwait(false);
        return await stepContext.EndDialogAsync().ConfigureAwait(false);
    }

    // If previous QnAId is present, replace the dialog
    var previousQnAId = Convert.ToInt32(dialogOptions[PreviousQnAId]);
    if (previousQnAId > 0)
    {
        return await stepContext.ReplaceDialogAsync(QnAMakerDialogName,
                                                    dialogOptions, 
                                                    cancellationToken).ConfigureAwait(false);
    }

    // If response is present then show that response, else default answer.
    if (stepContext.Result is List<QueryResult> response && response.Count > 0)
    {
        await stepContext.Context.SendActivityAsync(response.First().Answer,
              cancellationToken: cancellationToken).ConfigureAwait(false);
        LogOutput(stepContext.Context.Activity.Text, response.First().Answer);
    }
    else
    {
        await stepContext.Context.SendActivityAsync(qnaDialogResponseOptions.NoAnswer,
              cancellationToken: cancellationToken).ConfigureAwait(false);
        LogOutput(stepContext.Context.Activity.Text, qnaDialogResponseOptions.NoAnswer);
    }

    return await stepContext.EndDialogAsync().ConfigureAwait(false);
}

さて、コーディングが終わったので、ビルド後、Webチャットで試してみます。

このような会話を展開した後、オンラインコードエディターに戻ってみると…

[wwwroot]配下にログのテキストファイル(Log.txt)ができていますね!

ただし、鬼のように文字化けしております。

良い感じの回避策はあると思いますが、今回のところは、暫定回避としてソースコードをダウンロードして中身を確認することにします。

「ボットのソースコードをダウンロードする」をクリック。

ダウンロードされたファイル群から、Log.txtを探します。

良い感じで質問と回答の会話ログが取れています!

 

まとめ

今回はAzureチャットボットの会話ログ出力をやってみました。

やはりオンラインコードエディターでC#でサクサクいじれるのは良いですね。

腰を据えて取り組む必要のある大きな改修などでは、ソースコードをダウンロードしてVisual Studio上でいじるほうがエラーの原因等特定しやすくて良いんでしょうけど。

今回はここまで!

 

スポンサーリンク
スポンサーリンク
技術系
\この記事をシェアする!/
\Follow Me!/
ぽんこつSEの無事是名馬

コメント

タイトルとURLをコピーしました