I Didnt Understand Please Try Again Bot Framework
- An OS that supports .Net (Windows/macOS/Linux)
- .NET (Core) SDK version three.1 or later
- PowerShell Core or Windows PowerShell (y'all tin can utilise any trounce, but you'll demand to make minor modifications)
- Azure subscription (become a gratuitous subscription)
- A costless Twilio account
- Azure CLI 2.ii.0 or afterward
- A .NET IDE (Recommended: Visual Studio 2019 or after, Visual Studio Code with the C# plugin, or JetBrains Rider).
- Intent: The purpose or goal expressed in a user's utterance.
- Entity: An detail or an element that is relevant to the user'south intent
- Utterance: Unit of measurement of voice communication. Every judgement from a user is an utterance.
- [projectRoot]\CognitiveModels\FlightBooking.cs
- [projectRoot]\CognitiveModels\FlightBooking.json
- [projectRoot]\CognitiveModels\FlightBookingEx.cs
- [projectRoot]\Dialogs\BookingDialog.cs
- [projectRoot]\Dialogs\MainDialog.cs
- [projectRoot]\BookingDetails.cs
- [projectRoot]\FlightBookingRecognizer.cs
- DoctorBooking.cs: This file will incorporate the
DoctorBooking
form, which represents the data returned by LUIS. - DoctorBookingEx.cs: This file will extend
DoctorBooking
using a partial class to simplify accessing the entities of the LUIS results - Navigate to GitHub releases page of the Bot Framework Emulator projection
- Click on the setup file for your OS to download it.
- Later the download is completed, click the file to start the installation. Follow the installation wizard, and use the default options to complete the installation.
- A resource group: Y'all have already created a
rg-bot
resource group earlier. - A managed identity: Although this resources won't be used for the Twilio bot, information technology is required by the Bot Framework deployment templates
- An App Service plan and an App Service
- A Bot Service
- -k or --resource-grouping: The resource group the resources should be placed in, in this case into the "rg-bot" resource grouping you lot created earlier.
- -due north or --proper name: The proper noun of the App Service programme, which is asp-bot. "asp" is curt for App Service programme.
- -l or --location: The Azure location the resource should reside in. Replace
[AZURE_LOCATION]
with the location closest to you or your users, like you did when creating the resource group before. - --sku: The size (CPU/RAM/etc.) of the App Service plan by SKU, which in this case is F1 (free).
- appId: The value of
clientId
in the response of thecreate identity
control. You tin can also query theclientId
like this:az identity show -g rg-bot -n identity-appointment-bot --query clientId
- tenantId: The value of
tenantId
in the response of thecreate identity
command. You tin also query thetenantId
like this:az account show --query tenantId
- appServicePlanLocation: The Azure region y'all used when creating your App Service plan.
- botId: The proper noun for you Bot Service. This proper name has to exist globally unique. Supercede
[UNIQUE_SUFFIX]
with anything that would make the proper noun unique, like "firstname-lastname1234". If it doesn't accept the name, alter it upwardly and try once again. - newWebAppName: The name for your App Service. This name has to be globally unique because it will be used equally a subdomain to azurewebsites.net. Replace
[UNIQUE_SUFFIX]
with anything that would brand the name unique, like "firstname-lastname1234". If it doesn't have the name, change it up and try once more. - Go and buy a new phone number from Twilio. The cost of the phone number will exist practical to your free promotional credit if you're using a trial business relationship.
Make certain to take note of your new Twilio phone number. Yous'll need information technology later on! - If you are using a trial Twilio account, y'all can but send text messages to Verified Caller IDs. Verify your phone number or the phone number you want to SMS if information technology isn't on the list of Verified Caller IDs.
- Lastly, you'll demand to observe your Twilio Account SID and Auth Token. Navigate to your Twilio account page and take note of your Twilio Account SID and Auth Token located at the bottom left of the page.
- Replace
[YOUR_LUIS_API_KEY]
with the LUIS Principal Fundamental y'all took note of earlier. - Replace
[YOUR_TWILIO_PHONE_NUMBER]
with your Twilio Phone Number you bought earlier. Enter the phone number using the E.164 which looks like+11234567890
. - Replace
[YOUR_TWILIO_ACCOUNT_SID]
with your Twilio Business relationship SID which you took notation of earlier. - Replace
[YOUR_BOT_TWILIO_ENDPOINT]
with the webhook URL you took note of earlier. It should look like https://your-hostname.azurewebsites.net/api/twilio. - Authentication: When a bot needs to access resources on behalf of a user, you must authenticate the user identity. The user authentication tin can exist handled by Identity providers such equally Azure Advert with OAuth 2.0. Your bot will use the token generated by Azure to access those resource. The details on how to add together Azure AD authentication to a bot can be found at Microsoft's documentation.
- Bot Framework Composer: It is an open-source visual designer and authoring tool to create a bot with Azure Bot Service. You tin can use information technology to build dialogs in the UI and visualize the flow to business concern users. It besides allows yous to train LUIS models within the tool, thus saving the demand to switch between unlike environments. If your project requires involving non-technical people to bot development, it is definitely a practiced tool to consider.
Build a Doctor Appointment Bot with Azure Bot Service, Linguistic communication Understanding, and Twilio SMS
Telehealth services tin improve access to healthcare for people living in remote areas. One common issue in remote areas is unreliable cyberspace service. It's a claiming for patients in remote areas to make appointments via spider web applications when internet service is poor or not available at all.
SMS is a reliable and low-cost alternative to reach people living in remote areas. In this article, you volition learn how to build a md appointment booking system which allows users to volume via SMS.
System Overview
The architecture of the SMS booking system is illustrated beneath.
Twilio is the communication aqueduct between Microsoft Azure Bot Service and the finish user. The core of the organization is an Azure Bot congenital on the Microsoft Bot Framework. When the bot receives a message, information technology asks Language Agreement (LUIS) to analyze the message. LUIS responds with the intent of the message, which the bot uses to respond with a helpful answer.
Prerequisites
You'll need these technologies to follow along:
Yous can find the completed source code in this GitHub repo.
Create the Language Understanding Service app
Azure Configuration
Y'all need to create a resource group to store the Azure resources y'all will create later.
Open a PowerShell and sign in to Azure using the Azure CLI:
Run the following command to create a new resource group:
az group create --name rg-bot --location [AZURE_LOCATION]
Supervene upon [AZURE_LOCATION]
with the proper name of your preferred Azure location.
Every resource in Azure is stored in a resource group, and every resource and resources group in Azure is stored in a specific Azure region or location. In that location are different reasons for picking dissimilar locations, only most commonly y'all want to pick the location that's closest to you and your end users. You tin can find all regions by running az account list-locations -o table
. Find your preferred location and use the value from the Name
column to specify the location when creating Azure resource. Keep in mind that non all resources are available in all Azure locations.
To collaborate with specific types of Azure resources, the resource provider for those resources types needs to be enabled. The nearly common resources providers are enabled by default, but you'll be using Language Understanding (LUIS) which is role of the Cognitive Services resources provider that is not enabled by default. You can register the Cognitive Services resource provider in Azure Portal, or using the Azure CLI as shown beneath.
Start, check the status of the Cognitive Services resource provider using this control:
az provider show --namespace Microsoft.CognitiveServices -o table
If the RegistrationState is UnRegistered, then run the following command to annals it:
az provider annals --namespace Microsoft.CognitiveServices --expect
This command may take a infinitesimal to complete.
Create the LUIS app and railroad train the model
Language Understanding (LUIS) is a cloud-based conversational AI service, part of Azure'sCognitive Services. Information technology can process natural linguistic communication text to predict overall meaning, and pull out relevant, detailed information.
To learn how to utilise LUIS, you demand to know a few cadre concepts:
LUIS will act as the brain of the bot by helping the bot understand the incoming messages.
Create the LUIS App
Log in to the LUIS portal with your Azure business relationship.
If you are a new user to LUIS, yous volition be prompted to Choose an authoring resource.
Click Create a new authoring resource if you lot haven't created one before.
Some other modal will announced to Create a new authoring resource.
Choose "rg-bot" in the Azure resource group dropdown, and enter "appointment-booking" in the Azure resource proper noun field. Then pick your preferred region in the Location dropdown, choose "F0" in Pricing Tier, and click Done.
Yous volition be redirected dorsum to previous Cull an authoring resource modal. Now, yous can click on the Done button, and then you will be redirected to the dashboard page.
On the dashboard page, click Create new app. The Create new app modal will appear.
Enter "Appointment-booking" in the Proper noun field and click Done.
After the app is created, a tutorial modal will be shown. Click exterior the modal to go to the newly created app details folio.
You'll need to collect some primal information for afterwards use. Click on the MANAGE tab and then on the Settings link. Take note of the App ID. Now, click on the Azure Resource link on the left menu so click on the Authoring Resources tab.
Take note of the Primary key (LUIS API central) and the Endpoint URL.
Now, the appointment-booking LUIS app is created successfully. The side by side footstep is to create the heart of the app: the LUIS model.
Import the Model
There are two ways to create the model. Yous can navigate to the Build tab of the app, and and then create entities and intents manually. Or yous tin import a predefined model file into the app. To save time, you can download this LUIS model JSON file and import it.
After downloading the JSON model file, navigate to the Versions page via MANAGE > Versions. Click on the Import button, and choose "Import as JSON" to open the import popup as shown beneath.
Click on the Cull file push button, select your JSON file, and click on the Done button. The new model will exist imported into the app.
Navigate to the Build > App Avails page and selection "vAppointmentBookingBot.LUISModel.json" from the versions dropdown which you can find in the breadcrumb navigation at the meridian-left of the page.
Now, you lot will run into the newly created intents and entities.
Train, Exam, and Publish the LUIS Model
After the intents and entities are imported, the Railroad train button in the summit navigation bar is enabled.
Click the Railroad train button to get-go the training process. The Train button will be disabled and the Test button will exist enabled afterwards preparation is completed.
To test the new model, click on the Examination push. A Test flyout panel will appear on the correct. You lot can blazon an utterance into the test panel to endeavour it out.
In the screenshot below, "i desire to meet doctor kathy" is given a score 0.973 out of 1 for the BookAppointment intent. In the Inspect window, it also identifies the Dr. entity every bit "kathy" correctly.
Since the exam result looks pretty practiced, you can publish the LUIS app now. Click on the Publish button on the navigation bar, select the Production Slot, and click Done.
After publishing is completed, a successful message notification is shown every bit shown above. That means the LUIS app is prepare to exist used!
Build the bot
Create the bot using a Bot Framework template
In this tutorial, you're going to use Bot Framework v4 SDK Templates to create the bot projection. Open up a beat and install the Bot Framework templates using the .NET CLI with these commands:
dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot dotnet new -i Microsoft.Bot.Framework.CSharp.CoreBot dotnet new -i Microsoft.Bot.Framework.CSharp.EmptyBot
You'll only be using the CoreBot template in this tutorial, simply experience free to explore the EchoBot and EmptyBot template.
Now, y'all can utilise the newly installed template to generate a new bot projection. Run the following control to create the bot project:
dotnet new corebot -n AppointmentBot
After creating the projection with the previous control, the project is created into the AppointmentBot/CoreBot folder and the root namespace is set to "CoreBot". This is inconsistent with how .NET templates normally work, but it can easily be rectified. The following PowerShell script will move the contents into the AppointmentBot folder, rename the project, and alter all the namespaces to "AppointmentBot". Run the following script using PowerShell:
$CorrectProjectName = "AppointmentBot" Push-Location "./$CorrectProjectName" Move-Detail ./CoreBot/* ./ Remove-Item ./CoreBot Motion-Particular ./CoreBot.csproj "./$CorrectProjectName.csproj" Become-ChildItem * -Recurse -File | ForEach-Object { (Go-Content $_) -supersede 'CoreBot', $CorrectProjectName | Fix-Content $_ } Pop-Location
Open the projection using your preferred .Cyberspace editor. The project structure will look like below.
The generated project comes with a flight booking bot sample. Remove those related model and dialog files as listed below.
To salvage time, you can run the script beneath to remove the above files. Run the script from the project root binder:
rm CognitiveModels/FlightBooking.cs rm CognitiveModels/FlightBooking.json rm CognitiveModels/FlightBookingEx.cs rm Dialogs/BookingDialog.cs rm Dialogs/MainDialog.cs rm BookingDetails.cs rm FlightBookingRecognizer.cs
You volition also need to remove lines 41 to 51 in the Startup.cs file. Those are the references to the deleted files.
// Register LUIS recognizer services.AddSingleton<FlightBookingRecognizer>(); // Annals the BookingDialog. services.AddSingleton<BookingDialog>(); // The MainDialog that will be run by the bot. services.AddSingleton<MainDialog>(); // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. services.AddTransient<IBot, DialogAndWelcomeBot<MainDialog>>();
After the cleanup, the Startup
form will look like below:
public class Startup { // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddHttpClient().AddControllers().AddNewtonsoftJson(); // Create the Bot Framework Authentication to be used with the Bot Adapter. services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>(); // Create the Bot Adapter with error handling enabled. services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>(); // Create the storage we'll be using for User and Chat country. (Memory is neat for testing purposes.) services.AddSingleton<IStorage, MemoryStorage>(); // Create the User country. (Used in this bot's Dialog implementation.) services.AddSingleton<UserState>(); // Create the Conversation state. (Used by the Dialog system itself.) services.AddSingleton<ConversationState>(); } // This method gets called by the runtime. Use this method to configure the HTTP asking pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseDefaultFiles() .UseStaticFiles() .UseWebSockets() .UseRouting() .UseAuthorization() .UseEndpoints(endpoints => { endpoints.MapControllers(); }); // app.UseHttpsRedirection(); } }
Appointment Booking Cerebral Model
Now that the project has been cleaned up, you tin can start implementing your ain logic. Next, you'll be creating the model, which LUIS will render to us with assay data.
Next, you'll create these files under the CognitiveModels folder:
Create the CognitiveModels/Doctorbooking.cs and add the post-obit lawmaking:
using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.AI.Luis; using Newtonsoft.Json; using System.Collections.Generic; namespace AppointmentBot.CognitiveModels { public partial class DoctorBooking : IRecognizerConvert { public cord Text; public string AlteredText; public enum Intent { BookAppointment, Cancel, GetAvailableDoctors, None }; public Dictionary<Intent, IntentScore> Intents; public class _Entities { // Built-in entities public DateTimeSpec[] datetime; // Lists public string[][] Doctor; // Example public class _Instance { public InstanceData[] datetime; public InstanceData[] Physician; public InstanceData[] AvailableDoctors; } [JsonProperty("$instance")] public _Instance _instance; } public _Entities Entities; [JsonExtensionData(ReadData = true, WriteData = true)] public IDictionary<cord, object> Backdrop { get; set; } public void Convert(dynamic result) { var app = JsonConvert.DeserializeObject<DoctorBooking>(JsonConvert.SerializeObject(outcome, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); Text = app.Text; AlteredText = app.AlteredText; Intents = app.Intents; Entities = app.Entities; Properties = app.Backdrop; } public (Intent intent, double score) TopIntent() { Intent maxIntent = Intent.None; var max = 0.0; foreach (var entry in Intents) { if (entry.Value.Score > max) { maxIntent = entry.Key; max = entry.Value.Score.Value; } } return (maxIntent, max); } } }
I generated this class using the Bot Framework CLI and provided information technology for your convenience, but you can also generate this yourself.
You'll need to install node.js and the BF CLI first, if yous want to generate the Doctorbooking.cs yourself.
Yous can install the BF CLI using the following control: npm i -g @microsoft/botframework-cli
Download the LUIS model JSON file to your project directory, then run the following command from the project root directory:
bf luis:generate:cs --in=AppointmentBookingBot.LUISModel.json --out=CognitiveModels/DoctorBooking.cs --className=AppointmentBot.CognitiveModels.DoctorBookin
Create the CognitiveModels/ DoctorBookingEx.cs file and add the following code:
using System.Linq; namespace AppointmentBot.CognitiveModels { // Extends the partial DoctorBooking class with methods and properties that simplify accessing entities in the luis results public partial class DoctorBooking { public string Medico { get { var doctorChosen = Entities?._instance?.Doctor?.FirstOrDefault()?.Text; return doctorChosen; } } // This value volition be a TIMEX. And we are only interested in a Date so grab the kickoff result and driblet the Time part. // TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Twelvemonth. public string AppointmentDate => Entities.datetime?.FirstOrDefault()?.Expressions.FirstOrDefault()?.Split('T')[0]; } }
Connect the bot to the LUIS App
To integrate the bot service with the LUIS app, you lot need to add the LUIS App ID, API central, and API Endpoint URL into the project configuration.
Supplant the contents of appsettings.json with the JSON below:
{ "MicrosoftAppType": "", "MicrosoftAppId": "", "MicrosoftAppPassword": "", "MicrosoftAppTenantId": "", "LuisAppId": "[YOUR_LUIS_APP_ID]", "LuisApiKey": "<SET_USING_USER_SECRETS>", "LuisApiEndpointUrl": "[LUIS_ENDPOINT_URL]" }
Replace [YOUR_LUIS_APP_ID]
with your LUIS App ID, and [LUIS_ENDPOINT_URL]
with the LUIS Endpoint URL y'all took annotation of earlier.
Please note that you lot should not store sensitive information including API keys or tokens in your source-code. That's why you'll configure the LuisApiKey
using the Secret Manager tool.
Enable the Secret Manager tool for your project past running the following command at the project root directory:
Run the post-obit command to configure the LuisApiKey
using the Secret Manager:
dotnet user-secrets fix "LuisApiKey" "[YOUR_LUIS_API_KEY]"
Replace [YOUR_LUIS_API_KEY]
with the LUIS App Primary Fundamental you lot took note off earlier.
The bot application will retrieve the settings yous just configured to establish the connection to your LUIS app in the AppointmentBookingRecognizer
form below. Create a new file AppointmentBookingRecognizer.cs and add the following contents:
using Microsoft.Bot.Builder; using Microsoft.Bot.Architect.AI.Luis; using Microsoft.Extensions.Configuration; using Arrangement.Threading; using Organisation.Threading.Tasks; namespace AppointmentBot { public class AppointmentBookingRecognizer : IRecognizer { private readonly LuisRecognizer _recognizer; public AppointmentBookingRecognizer(IConfiguration configuration) { var luisIsConfigured = !string.IsNullOrEmpty(configuration["LuisAppId"]) && !string.IsNullOrEmpty(configuration["LuisApiKey"]) && !string.IsNullOrEmpty(configuration["LuisApiEndpointUrl"]); if (luisIsConfigured) { var luisApplication = new LuisApplication( configuration["LuisAppId"], configuration["LuisApiKey"], configuration["LuisApiEndpointUrl"]); // Gear up the recognizer options depending on which endpoint version yous want to use. // More details can be constitute in https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 var recognizerOptions = new LuisRecognizerOptionsV3(luisApplication) { PredictionOptions = new Microsoft.Bot.Builder.AI.LuisV3.LuisPredictionOptions { IncludeInstanceData = true, } }; _recognizer = new LuisRecognizer(recognizerOptions); } } // Returns true if luis is configured in the appsettings.json and initialized. public virtual bool IsConfigured => _recognizer != null; public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken) => await _recognizer.RecognizeAsync(turnContext, cancellationToken); public virtual async Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken) where T : IRecognizerConvert, new() => await _recognizer.RecognizeAsync<T>(turnContext, cancellationToken); } }
A recognizer is used to recognize user input and render intents and entities within a DialogContext
. In the AppointmentBookingRecognizer
form, a connection is established to the LUIS API endpoint. It as well implements the RecognizeAsync
method, which is called by dialogs to excerpt intents and entities from a user'southward utterance.
Control the Conversation Flow using Dialogs
You lot demand to use Dialogs to manage conversation between the user and the bot.
Dialogs are a central concept in the SDK, providing ways to manage a long-running conversation with the user. A Dialog tin be composed with other dialogs.
Bot framework provides a rich set of dialogs to make it easier to create a chat flow. In this example, you will create an AppointmentBookingDialog
class to manage the main conversation. It is composed of a few dialogs, including a waterfall dialog and prompt dialogs.
The waterfall dialog is used to define the sequence of steps. As illustrated in the diagram below, the bot interacts with the user via a linear process.
Create a new file AppointmentDetails.cs into project root and add the following code:
namespace AppointmentBot { public grade AppointmentDetails { public string Doctor { get; gear up; } public cord AppointmentDate { get; set; } } }
The AppointmentDetails
grade is the model course for the dialog. Side by side, create the AppointmentBookingDialog.cs file into the Dialogs folder. AppointmentBookingDialog
grade will implement the process above. Add the following code to the file:
using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; using Microsoft.Recognizers.Text.DataTypes.TimexExpression; using Arrangement.Threading; using System.Threading.Tasks; namespace AppointmentBot.Dialogs { public class AppointmentBookingDialog : CancelAndHelpDialog { private const string DoctorStepMsgText = "Who would you like to run into?"; public AppointmentBookingDialog() : base(nameof(AppointmentBookingDialog)) { AddDialog(new TextPrompt(nameof(TextPrompt))); AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); AddDialog(new DateResolverDialog()); AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { DoctorStepAsync, AppointmentDateStepAsync, ConfirmStepAsync, FinalStepAsync, })); // The initial child Dialog to run. InitialDialogId = nameof(WaterfallDialog); } private async Task<DialogTurnResult>DoctorStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var bookingDetails = (AppointmentDetails)stepContext.Options; if (bookingDetails.Doc == null) { var promptMessage = MessageFactory.Text(DoctorStepMsgText, DoctorStepMsgText, InputHints.ExpectingInput); return expect stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); } return await stepContext.NextAsync(bookingDetails.Doctor, cancellationToken); } private async Task<DialogTurnResult> AppointmentDateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var bookingDetails = (AppointmentDetails)stepContext.Options; bookingDetails.AppointmentDate = (string)stepContext.Result; if (bookingDetails.AppointmentDate == null || IsAmbiguous(bookingDetails.AppointmentDate)) { return await stepContext.BeginDialogAsync(nameof(DateResolverDialog), bookingDetails.AppointmentDate, cancellationToken); } render await stepContext.NextAsync(bookingDetails.AppointmentDate, cancellationToken); } private async Task<DialogTurnResult> ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var bookingDetails = (AppointmentDetails)stepContext.Options; bookingDetails.AppointmentDate = (cord)stepContext.Result; var messageText = $"Delight confirm, I have you volume with Doctor: {bookingDetails.Doc} on: {bookingDetails.AppointmentDate}. Is this right?"; var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput); render look stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); } individual async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { if ((bool)stepContext.Outcome) { var bookingDetails = (AppointmentDetails)stepContext.Options; return expect stepContext.EndDialogAsync(bookingDetails, cancellationToken); } return await stepContext.EndDialogAsync(goose egg, cancellationToken); } individual static bool IsAmbiguous(string timex) { var timexProperty = new TimexProperty(timex); return !timexProperty.Types.Contains(Constants.TimexTypes.Definite); } } }
Main Dialog
The MainDialog
form manages the master process period. Create the MainDialog.cs file in the Dialogs folder and add the following code:
using AppointmentBot.CognitiveModels; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; using Microsoft.Extensions.Logging; using Microsoft.Recognizers.Text.DataTypes.TimexExpression; using System; using System.Collections.Generic; using Arrangement.Linq; using System.Threading; using System.Threading.Tasks; namespace AppointmentBot.Dialogs { public class MainDialog : ComponentDialog { private readonly AppointmentBookingRecognizer _luisRecognizer; protected readonly ILogger Logger; // Dependency injection uses this constructor to instantiate MainDialog public MainDialog(AppointmentBookingRecognizer luisRecognizer, AppointmentBookingDialog appointmentDialog, ILogger<MainDialog> logger) : base of operations(nameof(MainDialog)) { _luisRecognizer = luisRecognizer; Logger = logger; AddDialog(new TextPrompt(nameof(TextPrompt))); AddDialog(appointmentDialog); AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { IntroStepAsync, ActStepAsync, FinalStepAsync, })); // The initial child Dialog to run. InitialDialogId = nameof(WaterfallDialog); } private async Chore<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { if (!_luisRecognizer.IsConfigured) { expect stepContext.Context.SendActivityAsync( MessageFactory.Text("NOTE: LUIS is non configured. To enable all capabilities, add 'LuisAppId', 'LuisApiKey' and 'LuisApiEndpointUrl' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken); return wait stepContext.NextAsync(nix, cancellationToken); } // Use the text provided in FinalStepAsync or the default if it is the first time. var messageText = stepContext.Options?.ToString() ?? "How can I assistance y'all with today?"; var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput); return wait stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken); } private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { if (!_luisRecognizer.IsConfigured) { // LUIS is not configured, we only run the BookingDialog path with an empty BookingDetailsInstance. render await stepContext.BeginDialogAsync(nameof(AppointmentBookingDialog), new AppointmentDetails(), cancellationToken); } // Telephone call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) var luisResult = expect _luisRecognizer.RecognizeAsync<DoctorBooking>(stepContext.Context, cancellationToken); switch (luisResult.TopIntent().intent) { case DoctorBooking.Intent.BookAppointment: var validDoctor = await ValidateDoctors(stepContext.Context, luisResult, cancellationToken); if (!validDoctor) { return await stepContext.ReplaceDialogAsync(InitialDialogId, "Doctor Peter, Susan and Kathy are available?", cancellationToken); } // Initialize BookingDetails with any entities we may have found in the response. var bookingDetails = new AppointmentDetails() { // Go destination and origin from the blended entities arrays. Dr. = luisResult.Doc, AppointmentDate = luisResult.AppointmentDate, }; // Run the AppointmentBookingDialog giving it whatsoever details nosotros have from the LUIS phone call, it will make full out the remainder. return await stepContext.BeginDialogAsync(nameof(AppointmentBookingDialog), bookingDetails, cancellationToken); case DoctorBooking.Intent.GetAvailableDoctors: // We haven't implemented the GetAvailableDoctorsDialog so nosotros simply display a mock message. var getAvailableDoctorsMessageText = "Doctor Kathy, Medico Peter are bachelor today"; var getAvailableDoctorsMessage = MessageFactory.Text(getAvailableDoctorsMessageText, getAvailableDoctorsMessageText, InputHints.IgnoringInput); await stepContext.Context.SendActivityAsync(getAvailableDoctorsMessage, cancellationToken); break; default: // Catch all for unhandled intents var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a dissimilar way (intent was {luisResult.TopIntent().intent})"; var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput); await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken); suspension; } return await stepContext.NextAsync(zip, cancellationToken); } // Shows a warning if the medico is not specified or doctor entity values can't exist mapped to a canonical item in the Airport. private static async Task<Boolean> ValidateDoctors(ITurnContext context, DoctorBooking luisResult, CancellationToken cancellationToken) { var doctorChoosen = luisResult.Doctor; var noDoctor = string.IsNullOrEmpty(doctorChoosen); if (noDoctor) { var messageText = "Delight choose a doctor"; var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput); await context.SendActivityAsync(message, cancellationToken); } return !noDoctor; } individual async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { // If the child dialog ("AppointmentBookingDialog") was cancelled, the user failed to ostend or if the intent wasn't date Booking // the Result here will be null. if (stepContext.Result is AppointmentDetails result) { // Now we have all the booking details telephone call the booking service. // If the call to the booking service was successful tell the user. var timeProperty = new TimexProperty(effect.AppointmentDate); var appointmentDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now); var messageText = $"I have you booked to Physician {consequence.Doctor} on {appointmentDateMsg}"; var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput); expect stepContext.Context.SendActivityAsync(message, cancellationToken); } // Restart the principal dialog with a unlike bulletin the 2d time around var promptMessage = "What else can I exercise for you?"; return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken); } } }
This diagram gives you an overview of what the MainDialog
course does.
It'due south quite a lot of code, just the of import part of the MainDialog
class is below:
private async Job<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { if (!_luisRecognizer.IsConfigured) { // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. return wait stepContext.BeginDialogAsync(nameof(AppointmentBookingDialog), new AppointmentDetails(), cancellationToken); } // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) var luisResult = await _luisRecognizer.RecognizeAsync<DoctorBooking>(stepContext.Context, cancellationToken); switch (luisResult.TopIntent().intent) { case DoctorBooking.Intent.BookAppointment: var validDoctor = await ValidateDoctors(stepContext.Context, luisResult, cancellationToken); if (!validDoctor) { return await stepContext.ReplaceDialogAsync(InitialDialogId, "Doctor Peter, Susan and Kathy are bachelor?", cancellationToken); } // Initialize BookingDetails with any entities we may have establish in the response. var bookingDetails = new AppointmentDetails() { // Become destination and origin from the composite entities arrays. Doctor = luisResult.Doctor, AppointmenDate = luisResult.AppointmentDate, }; // Run the AppointmentBookingDialog giving it whatsoever details we have from the LUIS call, information technology will fill out the remainder. render await stepContext.BeginDialogAsync(nameof(AppointmentBookingDialog), bookingDetails, cancellationToken); case DoctorBooking.Intent.GetAvailableDoctors: // Nosotros haven't implemented the GetAvailableDoctorsDialog and then we just brandish a mock message. var getAvailableDoctorsMessageText = "Dr. Kathy, Md Peter are available today"; var getAvailableDoctorsMessage = MessageFactory.Text(getAvailableDoctorsMessageText, getAvailableDoctorsMessageText, InputHints.IgnoringInput); expect stepContext.Context.SendActivityAsync(getAvailableDoctorsMessage, cancellationToken); intermission; default: // Grab all for unhandled intents var didntUnderstandMessageText = $"Lamentable, I didn't get that. Delight endeavour asking in a different fashion (intent was {luisResult.TopIntent().intent})"; var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput); await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken); break; } render await stepContext.NextAsync(aught, cancellationToken); }
In a nutshell, when a message activity is received, the bot runs the MainDialog
.
The MainDialog
prompts the user using the IntroStepAsync
method, then calls the ActStepAsync
method.
In the ActStepAsync
method, the bot calls the LUIS app to get the luisResult
object which volition include the user'due south intent and entities. The user's intent and entities are used to make up one's mind the adjacent step, either performing validation or invoking other dialogs.
At the finish, the bot calls the FinalStepAsync
method to complete or cancel the procedure.
Twilio adapter and controller
By default, the Azure Bot service will connect to the web chat channel which is handled by the default AdapterWithErrorHandler
adapter. The default adapter is injected into the default BotController
class, and the controller exposes an endpoint /api/messages.
To connect the bot to Twilio, yous will create a new TwilioAdapterWithErrorHandler
class extended from the TwilioAdapter grade. Run the following control to install the Microsoft.Bot.Builder.Adapters.Twilio NuGet bundle:
dotnet add parcel Microsoft.Bot.Builder.Adapters.Twilio --version 4.15.0
Subsequently the NuGet installation is completed, create the TwilioAdapterWithErrorHandler.cs file in the project root directory, and add the following code:
using Microsoft.Bot.Builder.Adapters.Twilio; using Microsoft.Bot.Builder.TraceExtensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace AppointmentBot { public form TwilioAdapterWithErrorHandler : TwilioAdapter { public TwilioAdapterWithErrorHandler(IConfiguration configuration, ILogger<TwilioAdapter> logger) : base(configuration, null, logger) { OnTurnError = async (turnContext, exception) => { // Log any leaked exception from the application. logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); // Send a message to the user await turnContext.SendActivityAsync("The bot encountered an error or bug."); look turnContext.SendActivityAsync("To continue to run this bot, please ready the bot source code."); // Send a trace activity, which volition exist displayed in the Bot Framework Emulator await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/fault", "TurnError"); }; } } }
To handle the HTTP webhook requests from Twilio, yous'll need to add a TwilloController
. Create a new file TwilioController.cs in the Controllers folder, and add the following code:
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Adapters.Twilio; using Organisation.Threading; using Arrangement.Threading.Tasks; namespace AppointmentBot.Controllers { [Route("api/twilio")] [ApiController] public grade TwilioController : ControllerBase { private readonly TwilioAdapter _adapter; private readonly IBot _bot; /// <summary> /// Initializes a new instance of the <encounter cref="BotController"/> class. /// </summary> /// <param proper noun="adapter">adapter for the BotController.</param> /// <param name="bot">bot for the BotController.</param> public TwilioController(TwilioAdapter adapter, IBot bot) { _adapter = adapter; _bot = bot; } /// <summary> /// PostAsync method that returns an async Task. /// </summary> /// <returns>A <see cref="Task{TResult}"/> representing the effect of the asynchronous performance.</returns> [HttpPost] [HttpGet] public async Task PostAsync() { // Consul the processing of the HTTP POST to the adapter. // The adapter will invoke the bot. await _adapter.ProcessAsync(Request, Response, _bot, default(CancellationToken)); } } }
The endpoint for TwilioController
is /api/twilio. After adding the new endpoint, the bot tin handle messages via both web channel and Twilio SMS channel.
Finally, you need to register the dialogs and LUIS recognizer in the Startup
grade. Insert the following lines at the end of the ConfigureServices
method in the Startup.cs file:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Bot.Connector.Authentication; using Microsoft.Bot.Builder.Adapters.Twilio; using AppointmentBot.Bots; using AppointmentBot.Dialogs; namespace AppointmentBot { public class Startup { // This method gets called past the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddHttpClient().AddControllers().AddNewtonsoftJson(); // Create the Bot Framework Hallmark to be used with the Bot Adapter. services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>(); // Create the Bot Adapter with fault handling enabled. services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>(); // Create the storage nosotros'll be using for User and Conversation land. (Memory is great for testing purposes.) services.AddSingleton<IStorage, MemoryStorage>(); // Create the User state. (Used in this bot's Dialog implementation.) services.AddSingleton<UserState>(); // Create the Conversation state. (Used by the Dialog organization itself.) services.AddSingleton<ConversationState>(); // Register LUIS recognizer services.AddSingleton<AppointmentBookingRecognizer>(); // Register the AppointmentBookingDialog services.AddSingleton<AppointmentBookingDialog>(); // The MainDialog that will be run by the bot. services.AddSingleton<MainDialog>(); // Create the bot every bit a transient. In this case the ASP Controller is expecting an IBot. services.AddTransient<IBot, DialogAndWelcomeBot<MainDialog>>(); // Create the Twilio Adapter services.AddSingleton<TwilioAdapter, TwilioAdapterWithErrorHandler>(); } // This method gets called past the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseDefaultFiles() .UseStaticFiles() .UseWebSockets() .UseRouting() .UseAuthorization() .UseEndpoints(endpoints => { endpoints.MapControllers(); }); // app.UseHttpsRedirection(); } } }
Test Locally
Y'all'll need to install the Bot Framework Emulator to examination the bot locally. To Install the Bot Framework Emulator:
Adjacent, get-go the bot project using the .NET CLI:
Now, start the Bot Emulator, click on the Open Bot push button, and enter the bot's URL in your local environs, which past default, is http://localhost:3978/api/messages. And then click on the Connect button.
A chat window will exist opened. You can blazon the bulletin and beginning testing.
Afterward you are happy with test results, the bot can be deployed to Azure.
Deploy the bot to Azure
To deploy the .Cyberspace bot to Azure, you need to employ Azure CLI to create the following resources:
To create a new App Service plan, run the post-obit control:
az appservice plan create -g rg-bot -northward asp-bot --location [AZURE_LOCATION] --sku F1
Hither's what the parameters practise:
To brand sure the .NET projection will exist deployed correctly, y'all'll demand to generate a deployment file. The deployment file can be generated with the command beneath:
az bot fix-deploy --lang Csharp --code-dir "." --proj-file-path "AppointmentBot.csproj"
Delight notation that --code-dir
and --proj-file-path
need to friction match together to resolve the path to the project file.
Create the managed identity using the following command:
az identity create --resources-group "rg-bot" --proper name "identity-appointment-bot"
After the command finished, a new "identity-date-bot" managed identity has been added in Azure, which will be used to create the new App Service and Bot Service in the next stride.
The App Service and Bot Service tin be generated using the existing App Service plan and the Azure Resources Manager (ARM) template which is function of the "CoreBot" .Internet template.
You be using the DeploymentTemplates/template-with-preexisting-rg.json ARM template, merely information technology requires a lot of parameters, which is why you should use a parameter file. Create a new ParameterFiles folder in the project root and create a new file RegisterAppParams.json with the post-obit contents:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "appId": { "value": "[CLIENT_ID_FROM_MANAGED_IDENTITY]" }, "appType": { "value": "UserAssignedMSI" }, "tenantId": { "value": "[TENANT_ID]" }, "existingUserAssignedMSIName": { "value": "identity-appointment-bot" }, "existingUserAssignedMSIResourceGroupName": { "value": "rg-bot" }, "botId": { "value": "bs-bot-[UNIQUE_SUFFIX]" }, "newWebAppName": { "value": "appointment-bot-[UNIQUE_SUFFIX]" }, "existingAppServicePlan": { "value": "asp-bot" }, "appServicePlanLocation": { "value": "[AZURE_LOCATION]" } } }
Some parameters have already been configured with the names of the previously created resources, but you lot still demand to update a few with your own specific settings:
After the parameter file is updated, run the post-obit command to generate the App Service and Bot Service:
az deployment group create ` --resource-group "rg-bot" ` --template-file "./DeploymentTemplates/template-with-preexisting-rg.json" ` --parameters "@ParameterFiles/RegisterAppParams.json"
Azure will take a minute to deploy this infrastructure. Subsequently the App Service is generated, run the post-obit control beneath to get the App Service hostname:
az webapp bear witness -m rg-bot -n engagement-bot-[UNIQUE_SUFFIX] --query 'hostNames[0]'
Supercede [UNIQUE_SUFFIX]
with the suffix you used in the parameters file. Accept notation of this hostname as you will need it later on.
At present that the App Service infrastructure has been provisioned, you tin deploy your local .NET bot project. Run the following command which will create a ZIP file and deploy the Nothing file to App Service:
az webapp up -m rg-bot -n appointment-bot-[UNIQUE_SUFFIX]
Information technology can take about 30 seconds for the deployment to consummate. When it is done, y'all will see the success response like beneath:
{ "active": truthful, "author": "Northward/A", "author_email": "N/A", "consummate": true, "deployer": "ZipDeploy", "end_time": "2022-02-27T21:58:45.0863404Z", "id": "b3c4cbb7a470479ebd7a2c6dd17bd70f", "is_readonly": true, "is_temp": false, "last_success_end_time": "2022-02-07T21:58:45.0863404Z", "log_url": "https://twilio-date-bot-app-service.scm.azurewebsites.internet/api/deployments/latest/log", "message": "Created via a push deployment", "progress": "", "provisioningState": "Succeeded", "received_time": "2022-02-27T21:57:25.4159Z", "site_name": "twilio-appointment-bot-app-service", "start_time": "2022-02-07T21:57:25.5565272Z", "status": four, "status_text": "", "url": "https://twilio-date-bot-app-service.scm.azurewebsites.net/api/deployments/latest" }
Setting up Twilio for SMS communication
You've tested the bot locally using the web chat, but the goal of this tutorial is to use SMS to communicate. To receive and send SMS messages, you'll need a Twilio Phone Number.
When your Twilio Phone Number receives a text bulletin, Twilio should forrad it to your .NET bot hosted on Azure App Service. To configure that, navigate to Phone numbers > Manage > Active numbers, and click on your Twilio Phone Number to admission the Configure page.
Under the Messaging section, prepare the dropdown under CONFIGURE WITH OTHER HANDLERS to "Webhook", and in the side by side text field, enter "https://", so paste in the App Service hostname you took notation of earlier, and and so enter "/api/twilio". The URL should expect like https://your-hostname.azurewebsites.net/api/twilio.
Click the Salve button on the bottom left. Have note of this webhook URL, you volition need it again soon.
Lastly, you lot need to add some configuration to your App Service. Run the post-obit command to configure the app settings:
az webapp config appsettings ready -g rg-bot -n appointment-bot-[UNIQUE_SUFFIX] --settings ` LuisApiKey=[YOUR_LUIS_API_KEY] ` TwilioNumber=[YOUR_TWILIO_PHONE_NUMBER] ` TwilioAccountSid=[YOUR_TWILIO_ACCOUNT_SID] ` TwilioAuthToken=[YOUR_TWILIO_AUTH_TOKEN] ` TwilioValidationUrl=[YOUR_BOT_TWILIO_ENDPOINT]
Before running the control, replace the placeholders.
You tin can restart the App Service to make sure the app settings are loaded by the bot. Run the following command to restart the App Service:
az webapp restart -one thousand rg-bot -northward date-bot-[UNIQUE_SUFFIX]
End-to-End Test
Finally, you lot have built and assembled all the moving parts. Let'south examination it!
Send a text message to your Twilio Phone Number and yous should run into a response from your bot. Here you can see I sent the below SMS letters to my Twilio Phone Number, and it works!
Next Steps
There are some important bot features that aren't covered in this article. You may like to explore further when developing a production class bot.
Decision
In this article, yous walked through the steps to build an SMS booking system using Twilio, Azure Bot Framework, and LUIS. You could extend this by adding more channels, expanding the LUIS model to support real-life scenarios, and incorporating other features like prototype recognition or multiple linguistic communication support.
Yan Sun is a full stack developer. He loves coding, e'er learning, writing and sharing. Yan can be reached at sunny [at] gmail.com.
Related Posts
How to send Emails in C# .NET with FluentEmail, Razor, and SendGrid
May 11, 2022
Learn how to generate emails in C# .NET using Razor templates and transport them using FluentEmail and SendGrid.
How to better configure C# and .Cyberspace applications for Twilio
April 11, 2022
Learn how to use multiple configuration sources, strongly-typed objects, and implement the options blueprint in your .Internet applications
What'south new in the Twilio helper library for ASP.Cyberspace (v5.73.0 - Apr 2022)
Apr 06, 2022
Learn most what'south new and old with the Twilio helper library for ASP.NET in version 5.73.0
How to Send SMS without a Phone Number using C# .Cyberspace and an Alphanumeric Sender ID
Apr 05, 2022
You don't always demand a phone number to send SMS! Learn how to send text messages using Alphanumeric Sender IDs.
SendGrid APIを使用し、C#と.NET 6でメールを送信する方法
Apr 01, 2022
SendGrid .Internet SDKと.NET 6コンソールアプリケーションを使用してメールを送信する方法をご紹介します。
Organize Incoming Email Attachments with C# and ASP.NET Cadre using Twilio SendGrid Inbound Parse
Mar 22, 2022
Learn how to receive emails in ASP.Cyberspace Core and organize attachments using Twilio SendGrid Entering Parse
Source: https://www.twilio.com/blog/doctor-appointment-bot-with-azure-bot-service-language-understanding-and-twilio-sms
0 Response to "I Didnt Understand Please Try Again Bot Framework"
Post a Comment