DEV Community

Cover image for Develop a LINE Bot in C# for Beginner Part 2 : Adding AI to your Bot
Kenichiro Nakamura
Kenichiro Nakamura

Posted on

Develop a LINE Bot in C# for Beginner Part 2 : Adding AI to your Bot

In this article, I will add Intelligence to your bot. If you missed part 1 of the series, you can read it here

Agenda for this article

  • Setup your Language Understanding Intelligent Service (LUIS) and create an application.
  • Integrate your bot with LUIS application.
  • Enhance your bot to reply depending on intent.

Okay, let's start!

LUIS

LUIS is one of Microsoft Cognitive Services and it can parse natural language into
Intents and Entities.

1. Login to LUIS. You can login by using same account as your Azure Subscription,
LUIS https://www.luis.ai

2. Click "Create new app" and set the value as follows.
- Name: Contoso Burger LUIS
- Culture: English

3. First, add an Entity. Entity is a keyword which you want to extract from the sentence. Click Entity on the left side and click Create new entity.

image

4. Set menu as name, and type as Simple.

image

5. Next, add an Intent. Intent is a meaning of entire sentence. Click Intents on the left menu and click "Create new intent". Name it as "order".

6. Now, you need to at least 5 example utterance. Enter "I want a cheese burger", and hit Enter. Then select "cheese burger" and set it as menu entity. To do so, left click "cheese" first, then we you see context menu popup, move mouse to the right to "buger" and left click again, then click "menu".

image

7. "Cheese burger" converted to "menu".

image

8. Enter, at least, four more similar utterances.

9. Create another intent as "greeting".

10. Enter at least five greetings, like hello, hi, good evening, etc.

11. Click "Train" button on the top right to let LUIS learn your intents.

That's all for now.

Publish LUIS and get access key

1. Once train completed, select "PUBLISH" menu on top.

image

2. Click "Publish to production slot" to publish it.

3. Copy the "Key String" on the bottom.

4. Select SETTINGS tab and copy the "Application ID" in the settings page.

Add LUIS to your bot

1. Open Integrated Terminal in Visual Studio Code.

2. Run the following command to add NuGet package.

dotnet add package microsoft.cognitive.luis
dotnet restore
Enter fullscreen mode Exit fullscreen mode

3. Add using statement in LineBotApp.cs

using Microsoft.Cognitive.LUIS;
Enter fullscreen mode Exit fullscreen mode

4. Add following property before the constructor. Update the AppId and Key appKey to your LUIS.

private LuisClient luisClient = new LuisClient(
            appId: "fa1fd370-7e9a-4dc1-a555-286dc180353f", 
            appKey: "4501df87a8944328b3bd07ed8adb6508");
Enter fullscreen mode Exit fullscreen mode

5. Update HandleTextAsync method with following code.

private async Task HandleTextAsync(string replyToken, string userMessage, string userId)
{  
    var replyMessage = new TextMessage($"You said: {userMessage}");
    // Analyze the input via LUIS
    var luisResult = await luisClient.Predict(userMessage);
    if(luisResult.TopScoringIntent.Name == "greeting")
    {
        replyMessage.Text = "Welcome to Contoso Burger!"; 
    }
    else if(luisResult.TopScoringIntent.Name == "order")
    {
        // If menu is specified.
        if(luisResult.Entities.ContainsKey("menu"))
        {
            replyMessage.Text = $"You order is {luisResult.Entities["menu"].First().Value}.";
        }
        else
        {
            replyMessage.Text = "Welcome to Contoso Burger! What do you want to order?"; 
        }
    }

    await messagingClient.ReplyMessageAsync(replyToken, new List<ISendMessage> { replyMessage });
}
Enter fullscreen mode Exit fullscreen mode

6. Put break point after obtaining luisResult and hit F5 to start debugging.

image

7. Send greeting from simulator. Once breakpoint hit, then confirm the data of luisResult.

image

8. Send "One cheese burger, please" and confirm it is categorized as "order" and menu contains "cheese burger".

image

Improve the conversation flow

It seems work well so far. But customer can order anything at the moment, such as "I want to order a Wapper.". LUIS provides other types of Entity and "list" types works better if you already know the menus.

1. Go back to LUIS, and go to BUILD tab -> Entities.

2. Select existing menu entity, and click "Delete Entity".

3. Then "Create new entity". This time, select "List" type.

image

4. Enter "Cheese Burger", "Plain Burger", "Vegi Burger", "Amazing Burger" in the list. You can also add synonyms .

image

5. Hit Train button and re-learn. Unlike Simple type, you don't have to specify which words are menu in utterance. Once training completed, publish it.

6. Update the HandleTextAsync to handle when no menu entities found.

private async Task HandleTextAsync(string replyToken, string userMessage, string userId)
{  
    ISendMessage replyMessage = new TextMessage("");
    // Analyze the input via LUIS
    var luisResult = await luisClient.Predict(userMessage);
    if(luisResult.TopScoringIntent.Name == "greeting")
    {
        replyMessage = new TextMessage("Welcome to Contoso Burger!"); 
    }
    else if(luisResult.TopScoringIntent.Name == "order")
    {
         // If menu is specified.
        if(luisResult.Entities.ContainsKey("menu"))
        {
             replyMessage = new TextMessage($"You order is {luisResult.Entities["menu"].First().Value}.");
        }
        else
        {
            // If no menu is specified, then shows them using buttons.
            replyMessage = new TemplateMessage("menu", new ButtonsTemplate(
                title: "menu",
                text: "Which burger you want to eat?",
                actions: new List<ITemplateAction>(){
                    new MessageTemplateAction("Cheese Burger", "cheese"),
                    new MessageTemplateAction("Plain Burger","plain"),
                    new MessageTemplateAction("Vegi Burger","vegi"),
                    new MessageTemplateAction("Awesome Burger","awesome"),
                }));
        }
    }

    await messagingClient.ReplyMessageAsync(replyToken, new List<ISendMessage> { replyMessage });
}
Enter fullscreen mode Exit fullscreen mode

7. Restart the debugger and send "I want to order a burger". You can see button template is returned.

image

8. Take an order part looks okay. Let's implement asking delivery address next. But before doing so, we need to write a code to store the order for this user, otherwise the bot will forget the order. There are many ways to achieve this, but the template include Azure Storage integration by default, so let's use it. Add Order.cs in Models folder.

Order.cs
using Microsoft.WindowsAzure.Storage.Table;

namespace contosoburgerbot.Models
{
    public class Order : EventSourceState
    {
        public string Menu { get; set; }
        public string Location_Address { get; set; }
        public string Location_Title { get; set; }

        public Order() 
        {
            SourceType = "order";
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

9 Update Controllers¥LineBotControllers.cs to let storage handle Order model.

public async Task<IActionResult> Post([FromBody]JToken req)
{ 
    var events = WebhookEventParser.Parse(req.ToString());
    var connectionString = appsettings.LineSettings.StorageConnectionString;
    var blobStorage = await BlobStorage.CreateAsync(connectionString, "linebotcontainer");
    var orderState = await TableStorage<Order>.CreateAsync(connectionString, "eventsourcestate");
    var app = new LineBotApp(lineMessagingClient, orderState, blobStorage);
    await app.RunAsync(events);
    return new OkResult();
}
Enter fullscreen mode Exit fullscreen mode

10 Update LineBotApp.cs so that the bot can store order to storage.

  • Add property and constructor to handle order object.
  • Add method to handle Location message.
  • Add logic to save order and handle location message inside HandleTextAsync method. By the way, I use URL Scheme to automatically launch a map to select location. This is nice feature of LINE app.
using Line.Messaging;
using Line.Messaging.Webhooks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using contosoburgerbot.CloudStorage;
using contosoburgerbot.Models;
using Microsoft.Cognitive.LUIS;

namespace contosoburgerbot
{
    internal class LineBotApp : WebhookApplication
    {
        private LineMessagingClient messagingClient { get; }
        private TableStorage<Order> orderState { get; }
        private BlobStorage blobStorage { get; }
        private LuisClient luisClient = new LuisClient(
            appId: "fa1fd370-7e9a-4dc1-a555-286dc180353f",
            appKey: "4501df87a8944328b3bd07ed8adb6508");

        public LineBotApp(LineMessagingClient lineMessagingClient, TableStorage<Order> tableStorage, BlobStorage blobStorage)
        {
            this.messagingClient = lineMessagingClient;
            this.orderState = tableStorage;
            this.blobStorage = blobStorage;
        }

        protected override async Task OnMessageAsync(MessageEvent ev)
        {
            switch (ev.Message.Type)
            {
                case EventMessageType.Text:
                    await HandleTextAsync(ev.ReplyToken, ((TextEventMessage)ev.Message).Text, ev.Source.UserId);
                    break;
                case EventMessageType.Location:
                    var location = ((LocationEventMessage)ev.Message);
                    await HandleLocationAsync(ev.ReplyToken, location, ev.Source.Id);
                    break;
            }
        }

        private async Task HandleLocationAsync(string replyToken, LocationEventMessage location, string userId)
        {
            // Get an order
            var order = await orderState.FindAsync("order", userId);
            // Save the address first
            order.Location_Address = location.Address;
            order.Location_Title = location.Title;
            await orderState.UpdateAsync(order);
            await messagingClient.ReplyMessageAsync(replyToken, new[] {
                        new TextMessage($"Thanks! We will deliver {order.Menu}to {order.Location_Title}!!")
                    });
        }

        private async Task HandleTextAsync(string replyToken, string userMessage, string userId)
        {
            ISendMessage replyMessage = new TextMessage("");

            // Analyze the input via LUIS
            var luisResult = await luisClient.Predict(userMessage);
            if (luisResult.TopScoringIntent.Name == "greeting")
            {
                replyMessage = new TextMessage("Welcome to Contoso Burger!");
            }
            else if (luisResult.TopScoringIntent.Name == "order")
            {
                // If menu is specified.
                if (luisResult.Entities.ContainsKey("menu"))
                {
                    // Save the order in Azure storage
                    var menu = luisResult.Entities["menu"].First().Value;
                    var order = new Order(){ Menu = menu, SourceId = userId };
                    await orderState.UpdateAsync(order);
                    // Send URL scheme to check delivery address.
                    replyMessage = new TemplateMessage("location", new ButtonsTemplate(
                        title: "Send address",
                        text: $"Thanks for order. Please give me the delivery address.",
                        actions: new List<ITemplateAction>(){
                            new UriTemplateAction("Send address","line://nv/location")
                            }));
                }
                else
                {
                     // If no menu is specified, then shows them using buttons.
                    replyMessage = new TemplateMessage("menu", new ButtonsTemplate(
                        title: "menu",
                        text: "Which burger you want to eat?",
                        actions: new List<ITemplateAction>(){
                            new MessageTemplateAction("Cheese Burger", "cheese"),
                            new MessageTemplateAction("Plain Burger","plain"),
                            new MessageTemplateAction("Vegi Burger","vegi"),
                            new MessageTemplateAction("Awesome Burger","awesome"),
                        }));
                }
            }

            await messagingClient.ReplyMessageAsync(replyToken, new List<ISendMessage> { replyMessage });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Deploy to Azure and final test

1. Run the following command to commit and push to Azure.

git add .
git commit -m update
git push
Enter fullscreen mode Exit fullscreen mode

2. Once push complete, use your mobile device to test the application.

Hope all went well :)

Summary

In this article, we added intelligence to the bot by using LUIS and Azure Storage. Of course you need to keep improving the bot to handle various your input!

References

Discussion (0)