CRM Chat Bot: Part 3 – Development Deep Dive + Integration To Dynamics CRM

In the part 2 of the series, we have discussed on the concept of how the Bot Framework library is used to assist us building conversational bot. As promised in the previous post, I’m going to share the source code on how the bot framework can be integrated with Dynamics CRM.

The Story

Now, to build a conversational bot, it will begin with the use case of the bot. In my example, I’m going to use an imaginary  car dealer with this simple “User Story” from Scrum principle: “As a customer, I want to be able chat and let the company know that I want to test drive a car, so that I can make an informed decision when I’m buying the car”.

The Implementation

In the previous post, we have discussed the concept of Dialog, Form Flow and Luis Dialog. Now in this post, I will show on how these concept can be applied.

In general I’m creating the TestDriveDetail class to contain the test drive request detail:


namespace CrmChatBot.Model
{
[Serializable]
public class TestDriveDetail
{
public string CarMake { get; set; }
public string CarModel { get; set; }
public string RequestedTime { get; set; }
public string CustomerName { get; set; }
public string PhoneNumber { get; set; }
}
}

Notice that all classes that will be used in Bot Framework will need to be decorated with [Serializable].

And a simple helper class to create the record to CRM.


public static void CreateTestDrive(TestDriveDetail testDrive, IOrganizationService crmService)
{
var lead = new Microsoft.Xrm.Sdk.Entity(EntityName);
//lead.Attributes
lead.Attributes.Add(Field_Subject, $"Test Drive Request by {testDrive.CustomerName}");
lead.Attributes.Add(Field_FirstName, testDrive.CustomerName);
lead.Attributes.Add(Field_Description, $@"Test drive request summary:
{Environment.NewLine}Car Make: {testDrive.CarMake},
{Environment.NewLine}Car Model: {testDrive.CarModel},
{Environment.NewLine}Requested Time: {testDrive.RequestedTime},
{Environment.NewLine}Customer Name: {testDrive.CustomerName},
{Environment.NewLine}Phone Number: {testDrive.PhoneNumber}");
crmService.Create(lead);
}

Now, I’ll give the example of the simple implementation with the 3 different techniques (Dialog, Form Flow and Luis Dialog).

Sample #1: Simple Dialog

DialogBot.gif

The sample dialog is a series of prompt and at the end of the process it the store the information in CRM Online. Below is sample code of the Dialog, how the chain of prompts are created and at the end it is storing the record in CRM.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System.Text.RegularExpressions;
using CrmChatBot.Model;
using CrmChatBot.CRM;
namespace CrmChatBot.Dialogs
{
[Serializable]
public class CarInquiryDialog :IDialog<object>
{
protected TestDriveDetail testDriveDetail;
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
//CrmDataConnection.GetAPI();
if (message.Text.Contains("test drive"))
{
testDriveDetail = new TestDriveDetail();
PromptDialog.Text(
context: context,
resume: CarMakeHandler,
prompt: "What car make do you want to test?",
retry: "Sorry, I don't understand that."
);
}
else if (message.Text == "No")
{
}
else
{
await context.PostAsync("Hi there, anything that I can help for you today?");
context.Wait(MessageReceivedAsync);
}
}
public virtual async Task CarMakeHandler(IDialogContext context, IAwaitable<string> argument)
{
var carMake = await argument;
testDriveDetail.CarMake = carMake;
PromptDialog.Text(
context: context,
resume: CarModelHandler,
prompt: "What car model do you want to test?",
retry: "Sorry, I don't understand that."
);
}
public async Task CarModelHandler(IDialogContext context, IAwaitable<string> argument)
{
var carModel = await argument;
testDriveDetail.CarModel = carModel;
PromptDialog.Text(
context: context,
resume: PreferredTimeHandler,
prompt: "When would you like to come for test drive?",
retry: "Sorry, I don't understand that."
);
}
public async Task PreferredTimeHandler(IDialogContext context, IAwaitable<string> argument)
{
var prefTime = await argument;
testDriveDetail.RequestedTime = prefTime;
PromptDialog.Text(
context: context,
resume: CustomerNameHandler,
prompt: "Your name please?",
retry: "Sorry, I don't understand that."
);
}
public async Task CustomerNameHandler(IDialogContext context, IAwaitable<string> argument)
{
var customerName = await argument;
testDriveDetail.CustomerName = customerName;
PromptDialog.Text(
context: context,
resume: ContactNumberHandler,
prompt: "What is the best number to contact you?",
retry: "Sorry, I don't understand that."
);
}
public async Task ContactNumberHandler(IDialogContext context, IAwaitable<string> argument)
{
var contactNumber = await argument;
testDriveDetail.PhoneNumber = contactNumber;
await context.PostAsync($@"Thank you for your interest, your request has been logged. Our sales team will get back to you shortly.
{Environment.NewLine}Your test drive request summary:
{Environment.NewLine}Car Make: {testDriveDetail.CarMake},
{Environment.NewLine}Car Model: {testDriveDetail.CarModel},
{Environment.NewLine}Requested Time: {testDriveDetail.RequestedTime},
{Environment.NewLine}Customer Name: {testDriveDetail.CustomerName},
{Environment.NewLine}Phone Number: {testDriveDetail.PhoneNumber}");
//CrmLead.CreateTestDrive(testDriveDetail, CrmDataConnection.GetAPI());
CrmLead.CreateTestDrive(testDriveDetail, CrmDataConnection.GetOrgService());
context.Done<string>("Test drive has been logged");
}
}
}

Sample #2: Form Flow

FormFlowBot.gif

As you can see at the above screen, the form flow is automatically generate the questions with the pre-defined options. Below is the source code of Form Flow implementation:


using CrmChatBot.CRM;
using CrmChatBot.Model;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace CrmChatBot.FormFlow
{
public enum CarMakeOptions { Unknown, Honda, Toyota };
public enum CarModelOptions { Unknown, Jazz, City, CRV, Accord, HRV, Yaris, Corolla, Camry };
[Serializable]
public class CarInquiryFormFlow
{
public CarMakeOptions CarMake;
public CarModelOptions CarModel;
public string PreferredTime;
public string Name;
public string ContactNumber;
public static IForm<CarInquiryFormFlow> BuildForm()
{
OnCompletionAsyncDelegate<CarInquiryFormFlow> processRequest = async (context, state) =>
{
await context.PostAsync($@"Your test drive request summary:
{Environment.NewLine}Car Make: {state.CarMake.ToString()},
{Environment.NewLine}Car Model: {state.CarModel.ToString()},
{Environment.NewLine}Requested Time: {state.PreferredTime},
{Environment.NewLine}Customer Name: {state.Name},
{Environment.NewLine}Phone Number: {state.ContactNumber}");
var testDriveDetail = new TestDriveDetail
{
CarMake = state.CarMake.ToString(),
CarModel = state.CarModel.ToString(),
RequestedTime = state.PreferredTime,
CustomerName = state.Name,
PhoneNumber = state.ContactNumber
};
// save the data to CRM
CrmLead.CreateTestDrive(testDriveDetail, CrmDataConnection.GetOrgService());
};
return new FormBuilder<CarInquiryFormFlow>()
.Message("Welcome to the car test drive bot!")
.Field(nameof(CarMake))
.Field(nameof(CarModel))
.Field(nameof(PreferredTime))
.Field(nameof(Name))
.Field(nameof(ContactNumber))
.AddRemainingFields()
.Message("Thank you for your interest, your request has been logged. Our sales team will get back to you shortly.")
.OnCompletion(processRequest)
.Build();
}
}
}

To initiate the Form Flow from message controller:


public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
// Initiates the form flow
await Conversation.SendAsync(activity, MakeRootDialog);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
internal static IDialog<CarInquiryFormFlow> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(CarInquiryFormFlow.BuildForm));
}

view raw

gistfile1.txt

hosted with ❤ by GitHub

Sample #3: Luis Dialog

Now, we have seen how Dialog and Form Flow is getting the simple conversation started. However, if you might notice, the bot can only understand predefined keywords or options. (In Dialog, it is hard-coded to find “Test Drive” and in FormFlow it is directly asking the detail).

To overcome this, Microsoft has come up with a really cool Language Understanding Intelligent Service, a.k.a LUIS. In this post, I’m not going to describe in detail on how to setup LUIS model (will do next time), but I would like to introducing its capability that is able to predict/interpret the intent of the user. For this sample, I’ve prepared the following LUIS model:

luismodel

This model is configured to be able to interpret the intent of the user (Greeting, Test Drive, Ending Conversation, Brochure Request, None).

LuisBot.gif

Now, below is the source code on how the Luis Dialog is built.


using CrmChatBot.CRM;
using CrmChatBot.Model;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace CrmChatBot.LUIS
{
[LuisModel("LuisAppId", "LUIS Subscription Key")]
[Serializable]
public class CarInquiryLuisDialog : LuisDialog<object>
{
protected TestDriveDetail testDriveDetail;
public const string Entity_Car_Make = "CarMake";
public const string Entity_Car_Model = "CarModel";
public const string Entity_Date = "builtin.datetime.date";
private EntityRecommendation carMake;
private EntityRecommendation carModel;
private EntityRecommendation preferredDate;
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
string message = $"Sorry I did not understand: " + string.Join(", ", result.Intents.Select(i => i.Intent));
await context.PostAsync(message);
context.Wait(MessageReceived);
}
[LuisIntent("Greeting")]
public async Task Greeting(IDialogContext context, LuisResult result)
{
string message = $"Hi, is there anything that I could help you today?";
await context.PostAsync(message);
context.Wait(MessageReceived);
}
[LuisIntent("Ending Conversation")]
public async Task Ending(IDialogContext context, LuisResult result)
{
string message = $"Thanks for using Car Inquiry Bot. Hope you have a great day!";
await context.PostAsync(message);
context.Wait(MessageReceived);
}
[LuisIntent("Test Drive")]
public async Task TestDrive(IDialogContext context, LuisResult result)
{
testDriveDetail = new TestDriveDetail();
PromptDialog.Text(
context: context,
resume: CarMakeHandler,
prompt: "What car make do you want to test?",
retry: "Sorry, I don't understand that."
);
}
[LuisIntent("Brochure Request")]
public async Task BrocureRequest(IDialogContext context, LuisResult result)
{
testDriveDetail = new TestDriveDetail();
PromptDialog.Text(
context: context,
resume: BrochureRequestHandler,
prompt: "Which car do you want to get the brochure information?",
retry: "Sorry, I don't understand that."
);
}
#region Brochure Request Handler
public virtual async Task BrochureRequestHandler(IDialogContext context, IAwaitable<string> argument)
{
var car = await argument;
await context.PostAsync($"We have received your brochure request for {car}. Our sales team will send it out to you.");
PromptDialog.Confirm(
context: context,
resume: AnythingElseHandler,
prompt: "Is there anything else that I could help?",
retry: "Sorry, I don't understand that."
);
}
#endregion
#region Test Drive Prompt
public virtual async Task CarMakeHandler(IDialogContext context, IAwaitable<string> argument)
{
var carMake = await argument;
testDriveDetail.CarMake = carMake;
PromptDialog.Text(
context: context,
resume: CarModelHandler,
prompt: "What car model do you want to test?",
retry: "Sorry, I don't understand that."
);
}
public async Task CarModelHandler(IDialogContext context, IAwaitable<string> argument)
{
var carModel = await argument;
testDriveDetail.CarModel = carModel;
PromptDialog.Text(
context: context,
resume: PreferredTimeHandler,
prompt: "When would you like to come for test drive?",
retry: "Sorry, I don't understand that."
);
}
public async Task PreferredTimeHandler(IDialogContext context, IAwaitable<string> argument)
{
var prefTime = await argument;
testDriveDetail.RequestedTime = prefTime;
PromptDialog.Text(
context: context,
resume: CustomerNameHandler,
prompt: "Your name please?",
retry: "Sorry, I don't understand that."
);
}
public async Task CustomerNameHandler(IDialogContext context, IAwaitable<string> argument)
{
var customerName = await argument;
testDriveDetail.CustomerName = customerName;
PromptDialog.Text(
context: context,
resume: ContactNumberHandler,
prompt: "What is the best number to contact you?",
retry: "Sorry, I don't understand that."
);
}
public async Task ContactNumberHandler(IDialogContext context, IAwaitable<string> argument)
{
var contactNumber = await argument;
testDriveDetail.PhoneNumber = contactNumber;
await context.PostAsync($@"Thank you for your interest, your request has been logged. Our sales team will get back to you shortly.
{Environment.NewLine}Your test drive request summary:
{Environment.NewLine}Car Make: {testDriveDetail.CarMake},
{Environment.NewLine}Car Model: {testDriveDetail.CarModel},
{Environment.NewLine}Requested Time: {testDriveDetail.RequestedTime},
{Environment.NewLine}Customer Name: {testDriveDetail.CustomerName},
{Environment.NewLine}Phone Number: {testDriveDetail.PhoneNumber}");
//CrmLead.CreateTestDrive(testDriveDetail, CrmDataConnection.GetAPI());
CrmLead.CreateTestDrive(testDriveDetail, CrmDataConnection.GetOrgService());
PromptDialog.Confirm(
context: context,
resume: AnythingElseHandler,
prompt: "Is there anything else that I could help?",
retry: "Sorry, I don't understand that."
);
}
#endregion
public async Task AnythingElseHandler(IDialogContext context, IAwaitable<bool> argument)
{
var answer = await argument;
if (answer)
{
await GeneralGreeting(context, null);
}
else
{
string message = $"Thanks for using Car Inquiry Bot. Hope you have a great day!";
await context.PostAsync(message);
context.Done<string>("conversation ended.");
}
}
public virtual async Task GeneralGreeting(IDialogContext context, IAwaitable<string> argument)
{
string message = $"Great! What else that can I help you?";
await context.PostAsync(message);
context.Wait(MessageReceived);
}
}
}

That’s all for the sample codes! I hope this helps. For the code repository, feel free to have a look at github repo: https://github.com/andz88/CrmChatBot.

Stay tuned for the next part of this series: Deployment.

 

12 thoughts on “CRM Chat Bot: Part 3 – Development Deep Dive + Integration To Dynamics CRM

    • hi, thanks for the good feedback. Which ClientID that you are referring to? Bot Framework App ID? or The Azure AD Client ID? In my sample, I’m not using the Web API that requires Azure AD Client ID. It was there for experimental.

  1. Hi Margono,

    Nice job, congratulations.I take error which is “Exception: Object reference not set to an instance of an object.
    [File of type ‘text/plain’]”.Is there any suggestion about this error?

Leave a comment