private async Task <OnTurnProperty> DetectIntentAndEntitiesAsync(ITurnContext turnContext) { // Handle card input (if any), update state and return. if (turnContext.Activity.Value != null) { var response = JsonConvert.DeserializeObject <Dictionary <string, string> >(turnContext.Activity.Value as string); return(OnTurnProperty.FromCardInput(response)); } // Acknowledge attachments from user. if (turnContext.Activity.Attachments != null && turnContext.Activity.Attachments.Count > 0) { await turnContext.SendActivityAsync("Thanks for sending me that attachment. I'm still learning to process attachments."); return(null); } // Nothing to do for this turn if there is no text specified. if (string.IsNullOrWhiteSpace(turnContext.Activity.Text) || string.IsNullOrWhiteSpace(turnContext.Activity.Text.Trim())) { return(null); } // Call to LUIS recognizer to get intent + entities. var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(turnContext, default(CancellationToken)); // Return new instance of on turn property from LUIS results. // Leverages static fromLUISResults method. return(OnTurnProperty.FromLuisResults(luisResults)); }
public static OnTurnProperty FromLuisResults(RecognizerResult luisResults) { var onTurnProperties = new OnTurnProperty(); onTurnProperties.Intent = luisResults.GetTopScoringIntent().intent; // Gather entity values if available. Uses a const list of LUIS entity names. foreach (var entity in luisEntities) { dynamic value = luisResults.Entities[entity]; string strVal = null; if (value is JArray) { // ConfirmList is nested arrays. value = (from val in (JArray)value select val).FirstOrDefault(); } strVal = (string)value; if (strVal == null) { // Don't add empty entities. continue; } onTurnProperties.Entities.Add(new EntityProperty(entity, strVal)); } return(onTurnProperties); }
/// <summary> /// Static method to create an on turn property object from card input. /// </summary> /// <param name="cardValue">context.activity.value from a card interaction</param> /// <returns>OnTurnProperty.</returns> public static OnTurnProperty FromCardInput(Dictionary <string, string> cardValues) { // All cards used by this bot are adaptive cards with the card's 'data' property set to useful information. var onTurnProperties = new OnTurnProperty(); foreach (KeyValuePair <string, string> entry in cardValues) { if (!string.IsNullOrWhiteSpace(entry.Key) && string.Compare(entry.Key.ToLower().Trim(), "intent") == 0) { onTurnProperties.Intent = cardValues[entry.Key]; } else { onTurnProperties.Entities.Add(new EntityProperty(entry.Key, cardValues[entry.Key])); } } return(onTurnProperties); }
public static OnTurnProperty FromLuisResults(RecognizerResult luisResults) { var onTurnProperties = new OnTurnProperty(); onTurnProperties.Intent = luisResults.GetTopScoringIntent().intent; // Gather entity values if available. Uses a const list of LUIS entity names. foreach (var entity in luisEntities) { var value = luisResults.Entities.SelectTokens(entity).FirstOrDefault(); if (value == null) { continue; } onTurnProperties.Entities.Add(new EntityProperty(entity, value)); } return(onTurnProperties); }
public static OnTurnProperty FromLuisResults(RecognizerResult luisResults) { var onTurnProperties = new OnTurnProperty(); onTurnProperties.Intent = luisResults.GetTopScoringIntent().intent; // Gather entity values if available. Uses a const list of LUIS entity names. foreach (var entity in luisEntities) { var value = luisResults.Entities.SelectTokens(entity).FirstOrDefault(); if (value == null) { continue; } object property = null; var val = value.First(); if (val.Type == JTokenType.Array) { var arr = (JArray)val; property = arr[0].ToString(); // Store first value } else if (val.Type == JTokenType.Object) { var obj = (JObject)val; if (obj["type"].ToString() == "datetime") { property = val; // Store the JToken from LUIS (includes Timex) } } else if (val.Type == JTokenType.Integer) { var num = (JValue)val; property = val.ToString(); // Store string for number of guests } onTurnProperties.Entities.Add(new EntityProperty(entity, property)); } return(onTurnProperties); }
/// <summary> /// Static method to create an on turn property object from card input. /// </summary> /// <param name="cardValues">context.activity.value from a card interaction</param> /// <returns>OnTurnProperty.</returns> public static OnTurnProperty FromCardInput(JObject cardValues) { // All cards used by this bot are adaptive cards with the card's 'data' property set to useful information. var onTurnProperties = new OnTurnProperty(); foreach (var val in cardValues) { string name = val.Key; JToken value = val.Value; if (!string.IsNullOrWhiteSpace(name) && string.Compare(name.ToLower().Trim(), "intent") == 0) { onTurnProperties.Intent = value.ToString(); } else { onTurnProperties.Entities.Add(new EntityProperty(name, value.ToString())); } } return(onTurnProperties); }
private async Task <DialogTurnResult> BeginWhatCanYouDoDialogAsync(DialogContext innerDc, OnTurnProperty onTurnProperty) { var context = innerDc.Context; // Handle case when user interacted with what can you do card. // What can you do card sends a custom data property with intent name, text value and possible entities. // See ../WhatCanYouDo/Resources/whatCanYouDoCard.json for card definition. var queryProperty = (onTurnProperty.Entities ?? new List <EntityProperty>()).Where(item => string.Compare(item.EntityName, QueryProperty) == 0); if (queryProperty.Count() > 0) { Dictionary <string, string> response; try { response = JsonConvert.DeserializeObject <Dictionary <string, string> >(queryProperty.ElementAtOrDefault(0).Value as string); } catch { await context.SendActivityAsync("Choose a query from the card drop down before you click `Let's talk!`"); return(new DialogTurnResult(DialogTurnStatus.Empty, null)); } if (response.TryGetValue("text", out var text)) { context.Activity.Text = text; await context.SendActivityAsync($"You said: '{context.Activity.Text}'."); } // Create a set a new onturn property await _onTurnAccessor.SetAsync(context, OnTurnProperty.FromCardInput(response)); return(await BeginDialogAsync(innerDc, response)); } return(await innerDc.BeginDialogAsync(WhatCanYouDo.Name)); }
public async override Task <DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) { var turnContext = dc.Context; var step = dc.ActiveDialog.State; // Get reservation property. var newReservation = await _reservationsAccessor.GetAsync(turnContext, () => new ReservationProperty()); // Get on turn property. This has any entities that mainDispatcher, // or Bot might have captured in its LUIS model. var onTurnProperties = await _onTurnAccessor.GetAsync(turnContext, () => new OnTurnProperty("None", new List <EntityProperty>())); // If on turn property has entities.. ReservationResult updateResult = null; if (onTurnProperties.Entities.Count > 0) { // ...update reservation property with on turn property results. updateResult = newReservation.UpdateProperties(onTurnProperties); } // See if updates to reservation resulted in errors, if so, report them to user. if (updateResult != null && updateResult.Status == ReservationStatus.Incomplete && updateResult.Outcome != null && updateResult.Outcome.Count > 0) { await _reservationsAccessor.SetAsync(turnContext, updateResult.NewReservation); // Return and do not continue if there is an error. await turnContext.SendActivityAsync(updateResult.Outcome[0].Message); return(await base.ContinueDialogAsync(dc)); } // Call LUIS and get results. var luisResults = await _botServices.LuisServices[LuisConfiguration].RecognizeAsync(turnContext, cancellationToken); var topLuisIntent = luisResults.GetTopScoringIntent(); var topIntent = topLuisIntent.intent; // If we don't have an intent match from LUIS, go with the intent available via // the on turn property (parent's LUIS model). if (luisResults.Intents.Count <= 0) { // Go with intent in onTurnProperty. topIntent = string.IsNullOrWhiteSpace(onTurnProperties.Intent) ? "None" : onTurnProperties.Intent; } // Update object with LUIS result. updateResult = newReservation.UpdateProperties(OnTurnProperty.FromLuisResults(luisResults)); // See if update reservation resulted in errors, if so, report them to user. if (updateResult != null && updateResult.Status == ReservationStatus.Incomplete && updateResult.Outcome != null && updateResult.Outcome.Count > 0) { // Set reservation property. await _reservationsAccessor.SetAsync(turnContext, updateResult.NewReservation); // Return and do not continue if there is an error. await turnContext.SendActivityAsync(updateResult.Outcome[0].Message); return(await base.ContinueDialogAsync(dc)); } // Did user ask for help or said cancel or continuing the conversation? switch (topIntent) { case ContinuePromptIntent: // User does not want to make any change. updateResult.NewReservation.NeedsChange = false; break; case NoChangeIntent: // User does not want to make any change. updateResult.NewReservation.NeedsChange = false; break; case HelpIntent: // Come back with contextual help. var helpReadOut = updateResult.NewReservation.HelpReadOut(); await turnContext.SendActivityAsync(helpReadOut); break; case CancelIntent: // Start confirmation prompt. var opts = new PromptOptions { Prompt = new Activity { Type = ActivityTypes.Message, Text = "Are you sure you want to cancel?", }, }; return(await dc.PromptAsync(ConfirmCancelPrompt, opts)); case InterruptionsIntent: default: // If we picked up new entity values, do not treat this as an interruption. if (onTurnProperties.Entities.Count != 0 || luisResults.Entities.Count > 1) { break; } // Handle interruption. var onTurnProperty = await _onTurnAccessor.GetAsync(dc.Context); return(await dc.BeginDialogAsync(InterruptionDispatcher, onTurnProperty)); } // Set reservation property based on OnTurn properties. await _reservationsAccessor.SetAsync(turnContext, updateResult.NewReservation); return(await base.ContinueDialogAsync(dc)); }
protected async Task <DialogTurnResult> BeginChildDialogAsync(DialogContext dc, OnTurnProperty onTurnProperty) { switch (onTurnProperty.Intent) { // Help, ChitChat and QnA share the same QnA Maker model. So just call the QnA Dialog. case QnADialog.Name: case ChitChatDialog.Name: case "Help": return(await dc.BeginDialogAsync(QnADialog.Name)); case BookTableDialog.Name: return(await dc.BeginDialogAsync(BookTableDialog.Name)); case WhoAreYouDialog.Name: return(await dc.BeginDialogAsync(WhoAreYouDialog.Name)); case WhatCanYouDo.Name: return(await dc.BeginDialogAsync(WhatCanYouDo.Name)); case "None": default: await dc.Context.SendActivityAsync("I'm still learning.. Sorry, I do not know how to help you with that."); await dc.Context.SendActivityAsync($"Follow[this link](https://www.bing.com/search?q={dc.Context.Activity.Text}) to search the web!"); return(new DialogTurnResult(DialogTurnStatus.Empty)); } }
public ReservationResult UpdateProperties(OnTurnProperty onTurnProperty) { var returnResult = new ReservationResult(this); return(Validate(onTurnProperty, returnResult)); }
public static ReservationResult Validate(OnTurnProperty onTurnProperty, ReservationResult returnResult) { if (onTurnProperty == null || onTurnProperty.Entities.Count == 0) { return(returnResult); } // We only will pull number -> party size, datetimeV2 -> date and time, cafeLocation -> location. var numberEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(PartySizeEntity)); var dateTimeEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(DateTimeEntity)); var locationEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(LocationEntity)); var confirmationEntity = onTurnProperty.Entities.Find(item => item.EntityName.Equals(ConfirmationEntity)); if (numberEntity != null) { // We only accept MaxPartySize in a reservation. var partySize = int.Parse(numberEntity.Value as string); if (partySize > MaxPartySize) { returnResult.Outcome.Add(new ReservationOutcome($"Sorry. {int.Parse(numberEntity.Value as string)} does not work. I can only accept up to 10 guests in a reservation.", PartySizeEntity)); returnResult.Status = ReservationStatus.Incomplete; } else { returnResult.NewReservation.PartySize = partySize; } } if (dateTimeEntity != null) { // Get parsed date time from TIMEX // LUIS returns a timex expression and so get and un-wrap that. // Take the first date time since book table scenario does not have to deal with multiple date times or date time ranges. var timexProp = ((JToken)dateTimeEntity.Value)?["timex"]?[0]?.ToString(); if (timexProp != null) { var today = DateTime.Now; var parsedTimex = new TimexProperty(timexProp); // Validate the date (and check constraints (later)) if (parsedTimex.DayOfMonth != null && parsedTimex.Year != null && parsedTimex.Month != null) { var date = DateTimeOffset.Parse($"{parsedTimex.Year}-{parsedTimex.Month}-{parsedTimex.DayOfMonth}"); returnResult.NewReservation.Date = date.UtcDateTime.ToString("o").Split("T")[0]; returnResult.NewReservation.DateLGString = new TimexProperty(returnResult.NewReservation.Date).ToNaturalLanguage(today); } // See if the time meets our constraints. if (parsedTimex.Hour != null && parsedTimex.Minute != null && parsedTimex.Second != null) { var timexOptions = ((JToken)dateTimeEntity.Value)?["timex"]?.ToObject <List <string> >(); var validtime = TimexRangeResolver.Evaluate(timexOptions, reservationTimeConstraints); returnResult.NewReservation.Time = ((int)parsedTimex.Hour).ToString("D2"); returnResult.NewReservation.Time += ":"; returnResult.NewReservation.Time += ((int)parsedTimex.Minute).ToString("D2"); returnResult.NewReservation.Time += ":"; returnResult.NewReservation.Time += ((int)parsedTimex.Second).ToString("D2"); if (validtime == null || (validtime.Count == 0)) { // Validation failed! returnResult.Outcome.Add(new ReservationOutcome("Sorry, that time does not work. I can only make reservations that are in the daytime (6AM - 6PM)", DateTimeEntity)); returnResult.NewReservation.Time = string.Empty; returnResult.Status = ReservationStatus.Incomplete; } } // Get date time LG string if we have both date and time. if (!string.IsNullOrWhiteSpace(returnResult.NewReservation.Date) && !string.IsNullOrWhiteSpace(returnResult.NewReservation.Time)) { returnResult.NewReservation.DateTimeLGString = new TimexProperty(returnResult.NewReservation.Date + "T" + returnResult.NewReservation.Time).ToNaturalLanguage(today); } } } // Take the first found value. if (locationEntity != null) { var cafeLocation = locationEntity.Value; // Capitalize cafe location. returnResult.NewReservation.Location = char.ToUpper(((string)cafeLocation)[0]) + ((string)cafeLocation).Substring(1); } // Accept confirmation entity if available only if we have a complete reservation if (confirmationEntity != null) { var value = confirmationEntity.Value as string; if (value != null && value == "yes") { returnResult.NewReservation.ReservationConfirmed = true; returnResult.NewReservation.NeedsChange = false; } else { returnResult.NewReservation.NeedsChange = true; returnResult.NewReservation.ReservationConfirmed = false; } } return(returnResult); }
public static ReservationResult FromOnTurnProperty(OnTurnProperty onTurnProperty) { var returnResult = new ReservationResult(new ReservationProperty()); return(Validate(onTurnProperty, returnResult)); }
protected async Task <DialogTurnResult> BeginChildDialogAsync(DialogContext dc, OnTurnProperty onTurnProperty) { switch (onTurnProperty.Intent) { // Help, ChitChat and QnA share the same QnA Maker model. So just call the QnA Dialog. case QnADialog.Name: case ChitChatDialog.Name: case "Help": return(await dc.BeginDialogAsync(QnADialog.Name)); case BookTableDialog.Name: return(await dc.BeginDialogAsync(BookTableDialog.Name)); case WhoAreYouDialog.Name: return(await dc.BeginDialogAsync(WhoAreYouDialog.Name)); case WhatCanYouDo.Name: return(await BeginWhatCanYouDoDialogAsync(dc, onTurnProperty)); case "None": default: await dc.Context.SendActivityAsync("I'm still learning.. Sorry, I do not know how to help you with that."); await dc.Context.SendActivityAsync($"https://giphy.com/gifs/cat-funny-cute-11IYKJ5sN73twk"); return(new DialogTurnResult(DialogTurnStatus.Empty)); } }