Back to Blog
5 min read

Building FAQ Bots with Azure Bot Service and QnA Maker

FAQ bots can significantly reduce support workload by answering common questions automatically. Azure QnA Maker combined with Bot Service makes it easy to create intelligent FAQ bots. Here is how to build one.

Creating a QnA Maker Knowledge Base

Using the QnA Maker Portal

  1. Go to qnamaker.ai
  2. Create a new knowledge base
  3. Link to Azure resources

Or use the REST API:

# Create QnA Maker resource
az cognitiveservices account create \
    --name qna-maker-2020 \
    --resource-group rg-bots \
    --kind QnAMaker \
    --sku S0 \
    --location westus \
    --yes

Adding Content to Knowledge Base

From URLs

POST https://westus.api.cognitive.microsoft.com/qnamaker/v4.0/knowledgebases/{kbId}
Content-Type: application/json
Ocp-Apim-Subscription-Key: {subscription-key}

{
    "add": {
        "urls": [
            "https://mycompany.com/faq",
            "https://mycompany.com/support/common-questions"
        ]
    }
}

Adding Q&A Pairs Directly

{
    "add": {
        "qnaList": [
            {
                "id": 1,
                "answer": "You can reset your password by clicking 'Forgot Password' on the login page, entering your email, and following the instructions sent to your inbox.",
                "source": "Manual",
                "questions": [
                    "How do I reset my password?",
                    "I forgot my password",
                    "Can't login to my account",
                    "Password reset",
                    "Change password"
                ],
                "metadata": [
                    {
                        "name": "category",
                        "value": "account"
                    }
                ]
            },
            {
                "id": 2,
                "answer": "Our business hours are Monday to Friday, 9 AM to 5 PM AEST. You can reach us at support@company.com or call 1800-XXX-XXX.",
                "source": "Manual",
                "questions": [
                    "What are your business hours?",
                    "When are you open?",
                    "Contact hours",
                    "How can I contact support?"
                ],
                "metadata": [
                    {
                        "name": "category",
                        "value": "general"
                    }
                ]
            }
        ]
    }
}

Training and Publishing

# Train the knowledge base
POST https://westus.api.cognitive.microsoft.com/qnamaker/v4.0/knowledgebases/{kbId}/train
Ocp-Apim-Subscription-Key: {subscription-key}

# Publish
POST https://westus.api.cognitive.microsoft.com/qnamaker/v4.0/knowledgebases/{kbId}
Ocp-Apim-Subscription-Key: {subscription-key}

Creating the Bot

Bot Framework Integration

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.AI.QnA;

public class QnABot : ActivityHandler
{
    private readonly QnAMaker _qnaMaker;
    private readonly ILogger<QnABot> _logger;

    public QnABot(QnAMaker qnaMaker, ILogger<QnABot> logger)
    {
        _qnaMaker = qnaMaker;
        _logger = logger;
    }

    protected override async Task OnMessageActivityAsync(
        ITurnContext<IMessageActivity> turnContext,
        CancellationToken cancellationToken)
    {
        var options = new QnAMakerOptions
        {
            Top = 3,
            ScoreThreshold = 0.5f
        };

        var response = await _qnaMaker.GetAnswersAsync(turnContext, options);

        if (response != null && response.Length > 0)
        {
            var topAnswer = response[0];

            // Check confidence
            if (topAnswer.Score > 0.7)
            {
                await turnContext.SendActivityAsync(
                    MessageFactory.Text(topAnswer.Answer),
                    cancellationToken);
            }
            else
            {
                // Offer multiple options
                await SendMultipleAnswersAsync(turnContext, response, cancellationToken);
            }
        }
        else
        {
            await turnContext.SendActivityAsync(
                MessageFactory.Text("I'm sorry, I don't have an answer for that. Would you like to speak to a human agent?"),
                cancellationToken);
        }
    }

    private async Task SendMultipleAnswersAsync(
        ITurnContext turnContext,
        QueryResult[] results,
        CancellationToken cancellationToken)
    {
        var card = new HeroCard
        {
            Title = "Did you mean one of these?",
            Buttons = results.Select(r => new CardAction
            {
                Type = ActionTypes.ImBack,
                Title = TruncateQuestion(r.Questions.FirstOrDefault()),
                Value = r.Questions.FirstOrDefault()
            }).ToList()
        };

        var attachment = card.ToAttachment();
        await turnContext.SendActivityAsync(
            MessageFactory.Attachment(attachment),
            cancellationToken);
    }

    private string TruncateQuestion(string question)
    {
        return question?.Length > 50 ? question.Substring(0, 47) + "..." : question;
    }
}

Configuration

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<QnAMaker>(sp =>
    {
        var qnaEndpoint = new QnAMakerEndpoint
        {
            KnowledgeBaseId = Configuration["QnAMaker:KnowledgeBaseId"],
            EndpointKey = Configuration["QnAMaker:EndpointKey"],
            Host = Configuration["QnAMaker:Host"]
        };

        var options = new QnAMakerOptions
        {
            Top = 3,
            ScoreThreshold = 0.5f
        };

        return new QnAMaker(qnaEndpoint, options);
    });

    services.AddTransient<IBot, QnABot>();
}

Multi-Turn Conversations

Enable follow-up prompts for complex Q&A:

{
    "add": {
        "qnaList": [
            {
                "id": 10,
                "answer": "We offer several subscription plans. Which type are you interested in?",
                "questions": ["What subscription plans do you have?"],
                "context": {
                    "isContextOnly": false,
                    "prompts": [
                        {
                            "displayOrder": 1,
                            "displayText": "Personal Plans",
                            "qnaId": 11
                        },
                        {
                            "displayOrder": 2,
                            "displayText": "Business Plans",
                            "qnaId": 12
                        },
                        {
                            "displayOrder": 3,
                            "displayText": "Enterprise Plans",
                            "qnaId": 13
                        }
                    ]
                }
            }
        ]
    }
}

Handle prompts in the bot:

protected override async Task OnMessageActivityAsync(
    ITurnContext<IMessageActivity> turnContext,
    CancellationToken cancellationToken)
{
    var options = new QnAMakerOptions
    {
        Top = 1,
        Context = GetQnAContext(turnContext)
    };

    var response = await _qnaMaker.GetAnswersAsync(turnContext, options);

    if (response?.Length > 0)
    {
        var answer = response[0];

        // Check for follow-up prompts
        if (answer.Context?.Prompts?.Length > 0)
        {
            await SendPromptCardAsync(turnContext, answer, cancellationToken);
        }
        else
        {
            await turnContext.SendActivityAsync(
                MessageFactory.Text(answer.Answer),
                cancellationToken);
        }

        // Store context for next turn
        SaveQnAContext(turnContext, answer);
    }
}

Active Learning

Improve answers based on user interactions:

public async Task TrainFromFeedbackAsync(string question, string selectedAnswer)
{
    var feedbackRecords = new FeedbackRecords
    {
        Records = new[]
        {
            new FeedbackRecord
            {
                UserId = "user1",
                UserQuestion = question,
                QnaId = GetQnaIdForAnswer(selectedAnswer)
            }
        }
    };

    await _qnaMaker.CallTrainAsync(feedbackRecords);
}

Deploying to Azure Bot Service

# Create bot service
az bot create \
    --resource-group rg-bots \
    --name faq-bot-2020 \
    --kind webapp \
    --sku S1 \
    --location australiaeast \
    --appid $APP_ID \
    --password $APP_PASSWORD

# Connect to channels
az bot msteams create --name faq-bot-2020 --resource-group rg-bots
az bot webchat create --name faq-bot-2020 --resource-group rg-bots

FAQ bots built with QnA Maker can handle a significant portion of support queries, freeing up human agents for complex issues.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.