public IList <SettingMatch> MatchSettingValues( List <string> entityValues, double semantic_threshold, double antonym_disamb_percentage_of_max) { var matchable_entity_bag = this.MakeMatchableBagOfTokens(entityValues); if (matchable_entity_bag.IsEmpty()) { return(new List <SettingMatch>()); } var matching_bags = this.FindSemanticMatches(matchable_entity_bag, this.matchableValueNameBags, semantic_threshold); // If there are multiple semantic matches, they might be antonyms of each other (e.g., "right" vs. "left") because // the embeddings of antonyms are often very similar. if (matching_bags.Count() > 1) { matching_bags = this.DisambiguateAntonyms( matchable_entity_bag, matching_bags, 0.0, antonym_disamb_percentage_of_max, false); } IList <SettingMatch> matches = new List <SettingMatch>(); foreach (var bag in matching_bags) { SettingMatch match = new SettingMatch { SettingName = bag.CanonicalSettingName, Value = bag.CanonicalValueName }; matches.Add(match); } return(matches); }
/// <summary> /// Take the entities provided by LUIS (Setting and Value) to try and identify the vehicle setting we need to process. /// </summary> /// <param name="state">State object.</param> /// <param name="declarative">Indicates if special process for declarative utterances should be performed.</param> public void PostProcessSettingName(AutomotiveSkillState state, bool declarative = false) { if (state == null) { throw new ArgumentNullException(nameof(state)); } IList <SettingMatch> setting_matches = new List <SettingMatch>(); var has_matching_value_for_any_setting = false; ISet <string> setting_names_to_remove = new HashSet <string>(); // The Setting entity will contain any identified vehicle setting that was present in the utterance, e.g. front right airflow // The Value entity will contain any identified value relating to a vehicle setting that was present in the utterance, e.g. warm IList <AvailableSetting> selected_settings = new List <AvailableSetting>(); if (state.Entities.ContainsKey("SETTING")) { // If we have a Setting then try to find a match between the setting name provided and the available settings selected_settings = this.settingMatcher.MatchSettingNamesExactly(state.Entities["SETTING"].First()); // If we have not found an exact setting match but we have a value then combine Setting and Value together to identify a match if (!selected_settings.Any() && state.Entities.ContainsKey("VALUE")) { /* First try SETTING + VALUE entities combined to catch cases like "warm my seat", * where the value can help disambiguate which setting the user meant.*/ List <string> entityValuesToMatch = new List <string>(); entityValuesToMatch.AddRange(state.Entities["SETTING"]); entityValuesToMatch.AddRange(state.Entities["VALUE"]); selected_settings = this.settingMatcher.MatchSettingNames( entityValuesToMatch, SettingNameScoreThreshold, SettingNameAntonymDisambPercentageOfMax, false); } // If we still haven't found a match then try to match with just the setting but not exactly this time if (!selected_settings.Any()) { List <string> entityValuesToMatch = new List <string>(); entityValuesToMatch.AddRange(state.Entities["SETTING"]); selected_settings = this.settingMatcher.MatchSettingNames( entityValuesToMatch, SettingNameScoreThreshold, SettingNameAntonymDisambPercentageOfMax, false); } } // Do we have a selected setting name? if (selected_settings.Any()) { List <string> entityValuesToMatch = new List <string>(); if (state.Entities.ContainsKey("VALUE")) { entityValuesToMatch.AddRange(state.Entities["VALUE"]); } else if (state.Entities.ContainsKey("SETTING")) { // Sometimes the setting name itself is also a value, e.g., "defog" entityValuesToMatch.AddRange(state.Entities["SETTING"]); } foreach (var setting_info in selected_settings) { IList <SelectableSettingValue> selected_values = new List <SelectableSettingValue>(); if (entityValuesToMatch.Any()) { IList <SelectableSettingValue> selectable_values = new List <SelectableSettingValue>(); foreach (var value in setting_info.Values) { SelectableSettingValue selectable = new SelectableSettingValue { CanonicalSettingName = setting_info.CanonicalName, Value = value }; selectable_values.Add(selectable); } /* From the available setting values for the given setting name identify which one applies for this setting name * e.g. Set (when users says set temperature to 21 degrees * e.g. Increase (when user says increase temperature) * e.g. Decrease (when user says decrease temperature) * e.g. Off, Alert, Alert and Brake when user wants to control Park Assist */ selected_values = this.settingMatcher.DisambiguateSettingValues( entityValuesToMatch, selectable_values, SettingValueAntonymDisambThreshold, SettingValueAntonymDisambPercentageOfMax); // If we don't even have a VALUE entity, we can't match multiple values. // If the SETTING entity is really also a value, then it must match only one value. if (!state.Entities.ContainsKey("VALUE") && selected_values.Count() > 1) { selected_values.Clear(); } // For all selected values we return the canonical name for both the name and value foreach (var selected_value in selected_values) { SettingMatch match = new SettingMatch { SettingName = setting_info.CanonicalName, Value = selected_value.Value.CanonicalName }; setting_matches.Add(match); has_matching_value_for_any_setting = true; } } if (!selected_values.Any()) { SettingMatch match = new SettingMatch { SettingName = setting_info.CanonicalName }; setting_matches.Add(match); } AddAll(setting_names_to_remove, setting_info.IncludedSettings); } } else if (state.Entities.ContainsKey("VALUE") && !state.Entities.ContainsKey("SETTING")) { /* If we have no SETTING entity, match the VALUE entities against all the values of all the settings. * This handles queries like "make it warmer" or "defog", where the value implies the setting.*/ List <string> entityValuesToMatch = new List <string>(); entityValuesToMatch.AddRange(state.Entities["VALUE"]); setting_matches = this.settingMatcher.MatchSettingValues( entityValuesToMatch, SettingValueScoreThreshold, SettingValueAntonymDisambPercentageOfMax); has_matching_value_for_any_setting = true; foreach (var match in setting_matches) { var setting_info = this.settingList.FindSetting(match.SettingName); if (setting_info != null) { AddAll(setting_names_to_remove, setting_info.IncludedSettings); } } } // If at least one setting has a matching value, remove all settings with no matching value. // This effectively disambiguates the settings by their available values. // Also remove 'included' settings. IList <SettingMatch> new_setting_matches = new List <SettingMatch>(); foreach (var match in setting_matches) { if ((!has_matching_value_for_any_setting || !string.IsNullOrEmpty(match.Value)) && !setting_names_to_remove.Contains(match.SettingName)) { new_setting_matches.Add(match); } } setting_matches = new_setting_matches; var(opt_amount, isRelative) = OptionalAmount(state, false); foreach (var setting_match in setting_matches) { SettingChange setting_change = new SettingChange { SettingName = setting_match.SettingName }; var value_info = this.settingList.FindSettingValue(setting_match.SettingName, setting_match.Value); if (declarative) { // If the user makes a declarative statement, it means that they're unhappy with the status quo. // So, we use the antonym of the value to get the opposite of the thing they're unhappy with, // which should hopefully make them happy. // If there is no antonym listed, then we want to return an empty value because we were unable to find // the correct value. if (value_info != null) { setting_change.Value = value_info.Antonym; } } else { setting_change.Value = setting_match.Value; } if (opt_amount != null && value_info != null && value_info.ChangesSignOfAmount) { (opt_amount, isRelative) = OptionalAmount(state, true); } setting_change.Amount = opt_amount; setting_change.IsRelativeAmount = isRelative; state.Changes.Add(setting_change); } if (!setting_matches.Any() && opt_amount != null) { SettingChange setting_change = new SettingChange { Amount = opt_amount, IsRelativeAmount = isRelative }; state.Changes.Add(setting_change); } }