2

I do have 3 QnA Service. I want them to be used in a single BOT at the same time. How can this be implemented using C#. My initial idea is to put the KB ID and Sub Key into an array (how to implement that or would an array works?).. I saw some code in Node.JS but I cannot figure it out how to convert the code in C#.

public class QnaDialog : QnAMakerDialog
{
    public QnaDialog() : base(
        new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey1"],
        ConfigurationManager.AppSettings["QnaKnowledgebaseId1"], "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5)),

        new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey2"],
        ConfigurationManager.AppSettings["QnaKnowledgebaseId2"], "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5)),

        new QnAMakerService(new QnAMakerAttribute(ConfigurationManager.AppSettings["QnaSubscriptionKey3"],
        ConfigurationManager.AppSettings["QnaKnowledgebaseId4"], "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5))
        )
    {
    }
}
Nicolas R
  • 13,812
  • 2
  • 28
  • 57
  • What have you tried so far? Can you show some code? – ThomasVdBerge May 02 '18 at 10:37
  • Hi @ThomasVdBerge, updated the question with the code in the QnA Dialog: thanks a lot for the reply.. – Fermin Maniti May 02 '18 at 10:52
  • How should be used your 3 QnA Service: 1 should be selected for the query (for example if you have 1 service for each language) or all together (for example if each service contains different things)? – Nicolas R May 02 '18 at 11:14
  • Hi @NicolasR.. I want to consume all together those 3 QnA Service at the same time. I have 3 apps that I am supporting each has its own QnA service. So a user can ask questions pertaining those 3 apps without switching the QnA Service from 1 to another. – Fermin Maniti May 02 '18 at 12:02
  • Ok so they are targeting different questions. See my answer below, it's possible using this implementation but you have to fix something in the QnAMakerDialog (so you can't use the nuget package but have to get the sources, but it will be working) – Nicolas R May 02 '18 at 12:03
  • Cool, Awesome! let me try this and get back to you soon,. thanks a million! – Fermin Maniti May 02 '18 at 13:12
  • Hi @NicolasR. I cannot make it work. I created a new QnA maker Web App from my Azure Subscription download the code into my local machine and followed the code you mentioned in: "QnAMaker knowledge bases in a single bot by providing several services in the attribute" but it is giving me this error : "Exception: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index [File of type 'text/plain']" .. If I limit the QnA service into 1 it works fine. .thanks again.. – Fermin Maniti May 03 '18 at 15:48
  • Did you look at all my "but" paragraph? Looks like you are in this case.. – Nicolas R May 03 '18 at 15:51
  • Oh my goodness, It is my bad, apologies. I got it now!!!. Can I please let me know how to integrate the code that you have pulled in my project? I saw that it was already checked in. Is the nugget package not yet updated now? – Fermin Maniti May 03 '18 at 16:16

1 Answers1

1

You can use several QnAMaker knowledge bases in a single bot by providing several services in the attributes.

A basic implementation using QnAMakerDialog from Nuget package BotBuilder.CognitiveServices would be:

[Serializable]
[QnAMaker("QnaSubscriptionKey1", "QnaKnowledgebaseId1", "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.50, 3)]
[QnAMaker("QnaSubscriptionKey2", "QnaKnowledgebaseId2", "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5, 3)]
[QnAMaker("QnaSubscriptionKey3", "QnaKnowledgebaseId3", "Hmm, I wasn't able to find an article about that. Can you try asking in a different way?", 0.5, 3)]
public class RootDialog : QnAMakerDialog
{
}

BUT (yes, there is a "but") you may encounter an exception during the treatment of your messages in some cases. As QnAMakerDialog is open-sourced (sources are here) you can easily discover that the problem is in the implementation of the return of the services calls, in MessageReceivedAsync:

var sendDefaultMessageAndWait = true;
qnaMakerResults = tasks.FirstOrDefault(x => x.Result.ServiceCfg != null)?.Result;
if (tasks.Count(x => x.Result.Answers?.Count > 0) > 0)
{
    var maxValue = tasks.Max(x => x.Result.Answers[0].Score);
    qnaMakerResults = tasks.First(x => x.Result.Answers[0].Score == maxValue).Result;

    if (qnaMakerResults != null && qnaMakerResults.Answers != null && qnaMakerResults.Answers.Count > 0)
    {
        if (this.IsConfidentAnswer(qnaMakerResults))
        {
            await this.RespondFromQnAMakerResultAsync(context, message, qnaMakerResults);
            await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
        }
        else
        {
            feedbackRecord = new FeedbackRecord { UserId = message.From.Id, UserQuestion = message.Text };
            await this.QnAFeedbackStepAsync(context, qnaMakerResults);
        }

        sendDefaultMessageAndWait = false;
    }
}

if (sendDefaultMessageAndWait)
{
    await context.PostAsync(qnaMakerResults.ServiceCfg.DefaultMessage);
    await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
}

In this code, this line of code will break if not all of your services have an answer for your question (ie: if at least one of your QnAMaker KB does not have an answer for your question)

tasks.Max(x => x.Result.Answers[0].Score);

Workaround: you can implement you own QnAMakerDialog by getting the sources and fixing the method like this for example:

public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
    var message = await argument;

    if (message != null && !string.IsNullOrEmpty(message.Text))
    {
        var tasks = this.services.Select(s => s.QueryServiceAsync(message.Text)).ToArray();
        await Task.WhenAll(tasks);

        if (tasks.Any())
        {
            var sendDefaultMessageAndWait = true;
            qnaMakerResults = tasks.FirstOrDefault(x => x.Result.ServiceCfg != null)?.Result;

            var qnaMakerFoundResults = tasks.Where(x => x.Result.Answers.Any()).ToList();
            if (qnaMakerFoundResults.Any())
            {
                var maxValue = qnaMakerFoundResults.Max(x => x.Result.Answers[0].Score);
                qnaMakerResults = qnaMakerFoundResults.First(x => x.Result.Answers[0].Score == maxValue).Result;

                if (qnaMakerResults?.Answers != null && qnaMakerResults.Answers.Count > 0)
                {
                    if (this.IsConfidentAnswer(qnaMakerResults))
                    {
                        await this.RespondFromQnAMakerResultAsync(context, message, qnaMakerResults);
                        await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
                    }
                    else
                    {
                        feedbackRecord = new FeedbackRecord { UserId = message.From.Id, UserQuestion = message.Text };
                        await this.QnAFeedbackStepAsync(context, qnaMakerResults);
                    }

                    sendDefaultMessageAndWait = false;
                }
            }

            if (sendDefaultMessageAndWait)
            {
                await context.PostAsync(qnaMakerResults.ServiceCfg.DefaultMessage);
                await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
            }
        }
    }
}
Nicolas R
  • 13,812
  • 2
  • 28
  • 57
  • I created an issue ticket on Github about this problem and will provide a pull request in order to put this fix on the main repository – Nicolas R May 02 '18 at 12:05
  • Pull request created: https://github.com/Microsoft/BotBuilder-CognitiveServices/pull/100 – Nicolas R May 02 '18 at 13:19