Search code examples
botframeworkchatbotazure-language-understandingazure-qna-maker

How to construct a QnA Maker Instance Class in C# - CoreBot?


I got a System.AggregateException after running my Core Bot C# sample.

In the Startup.cs I added the class as followes:

     services.AddSingleton<ChitChatRecognizer>();

The Recognizer Class looks like:

    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 CoreBot
    {
        public class ChitChatRecognizer : ActivityHandler
        {

            private readonly IConfiguration _configuration;
            private readonly ILogger<ChitChatRecognizer> _logger;
            private readonly IHttpClientFactory _httpClientFactory;

            public ChitChatRecognizer(IConfiguration configuration, ILogger<ChitChatRecognizer> 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["QnAEndpointKey"],
                    Host = _configuration["QnAEndpointHostName"]
                },
                null,
                httpClient);

                _logger.LogInformation("Calling QnA Maker");

                var options = new QnAMakerOptions { Top = 1 };

                // The actual call to the QnA Maker service.
                var response = await qnaMaker.GetAnswersAsync(turnContext, options);
                if (response != null && response.Length > 0)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(response[0].Answer), cancellationToken);
                }
                else
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text("No QnA Maker answers were found."), cancellationToken);
                }
            }
        }
    }

I'm not even using the Class yet but can't even start my program without the error. What should I do?

ErrorCode:

"Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: CoreBot.ChitChatRecognizer Lifetime: Singleton ImplementationType: CoreBot.ChitChatRecognizer': Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate 'CoreBot.ChitChatRecognizer'.) (Error while validating the service descriptor 'ServiceType: Microsoft.BotBuilderSamples.Dialogs.MainDialog Lifetime: Singleton ImplementationType: Microsoft.BotBuilderSamples.Dialogs.MainDialog': Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate 'CoreBot.ChitChatRecognizer'.) (Error while validating the service descriptor 'ServiceType: Microsoft.Bot.Builder.IBot Lifetime: Transient ImplementationType: Microsoft.BotBuilderSamples.Bots.DialogAndWelcomeBot`1[Microsoft.BotBuilderSamples.Dialogs.MainDialog]': Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate 'CoreBot.ChitChatRecognizer'.)"


Solution

  • Your setup is odd...you're making your ChitChatRecognizer, which is a bot (it derives from ActivityHandler), and then later DI into either a dialog or another bot, I imagine? Also it seems you want to treat QnAMaker (the recognizer for QnA) as a singleton, but you initialize it in OnMessageActivityAsync--meaning it won't initialize unless your bot receives a message activity, so if you're trying to initialize something else the requires a QnAMaker, it won't exist on startup.


    Anyways, to answer the question of how do you insert QnAMakerinto corebot, if you wanted to add QnAMaker as a singleton, you can add it as singleton in startup, then call it from your bot code when you need to make a call to QnAMaker.

    Add QnAMaker to Corebot Sample

    In Startup.ConfigureServices:

    // Register QnAMaker recognizer
    services.AddSingleton<MyQnaMaker>();
    

    MyQnAMaker.cs

    namespace Microsoft.BotBuilderSamples
    {
        public class MyQnaMaker
        {
            public MyQnaMaker(IConfiguration configuration)
            {
                ChitChatRecognizer = new QnAMaker(new QnAMakerEndpoint
                {
                    KnowledgeBaseId = configuration["QnAKnowledgebaseId"],
                    EndpointKey = configuration["QnAEndpointKey"],
                    Host = configuration["QnAEndpointHostName"]
                });
            }
    
            public QnAMaker ChitChatRecognizer { get; set; }
        }
    }
    

    DialogAndWelcomeBot.cs constructor

    DI QnAMaker into the Bot

            public DialogAndWelcomeBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger, MyQnaMaker myQnAMaker)
                : base(conversationState, userState, dialog, logger, myQnAMaker)
    

    DialogBot.cs

            public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger, MyQnaMaker qnaMaker)
            {
                ConversationState = conversationState;
                UserState = userState;
                Dialog = dialog;
                Logger = logger;
                MyQnaMaker = qnaMaker;
            }
    
    ...
    
            protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
            {
                Logger.LogInformation("Running dialog with Message Activity.");
    
                if (turnContext.Activity.Text == "yo")
                {
                    await CallQnAMaker(turnContext, cancellationToken);
                } else {
                    // Run the Dialog with the new message Activity.
                    await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
                }
    
            }
    
            private async Task CallQnAMaker(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
            {
                var options = new QnAMakerOptions { Top = 1 };
    
                // The actual call to the QnA Maker service.
                var response = await MyQnaMaker.ChitChatRecognizer.GetAnswersAsync(turnContext, options);
                if (response != null && response.Length > 0)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(response[0].Answer), cancellationToken);
                }
                else
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text("No QnA Maker answers were found."), cancellationToken);
                }
            }
    

    Add more to your classes as needed, this is just the bare minimum. Above in DialogBot.cs, you can see that I have the trigger to call QnAMaker be if the user types in "yo" as a message, for testing purposes.


    Running Corebot

    enter image description here


    Alternatives

    Alternatively you could just new up a new QnAMaker within the bot's message handler (like DialogBot.OnMessageActivityAsync()), similar to how is done in sample 11.qnamaker, and do away with the singleton.