def test_swap_intent_with2(): swap_rules = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['intent_substitution']) # make sure intent not swapped after another action than the one specified in the rule parse_data = {"intent": {"name": "whatever", "confidence": 1.0}} Rules._swap_intent(parse_data, None, swap_rules.rules[1]) assert parse_data["intent"]["name"] == "whatever"
def test_validator_intent_none(): validator = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['input_validation']) previous = "utter_garantie_type_bien" parse_data = {'intent': {'name': None}, 'entities': []} assert validator._get_error(parse_data, previous) == 'utter_general_validation_options'
def test_validator_dummy_valid(): validator = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['input_validation']) previous = "not_in_file" parse_data = {'intent': {'name': 'affirm'}, 'entities': []} assert validator._get_error(parse_data, previous) is None
def test_swap_intent_with1(): swap_rules = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['intent_substitution']) # make sure intent swapped parse_data = {"intent": {"name": "chitchat.i_am_angry", "confidence": 1.0}} Rules._swap_intent(parse_data, None, swap_rules.rules[1]) assert parse_data["intent"]["name"] == "request.handover"
def test_swap_intent_after1(): swap_rules = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['intent_substitution']) # make sure intent swapped parse_data = {"intent": {"name": "whatever", "confidence": 1.0}} Rules._swap_intent(parse_data, "utter_something", swap_rules.rules[0]) assert parse_data["intent"]["name"] == "intent_something"
def __init__(self, rules_file): data = self._load_yaml(rules_file) self.actions_to_ignore = ['action_listen', 'action_invalid_utterance'] self.allowed_entities = data["allowed_entities"] if data and "allowed_entities" in data else {} self.intent_substitutions = data["intent_substitutions"] if data and "intent_substitutions" in data else [] self.input_validation = InputValidator(data["input_validation"]) if data and "input_validation" in data else [] self.disambiguation_policy = Disambiguator(data["disambiguation_policy"]) if data and "disambiguation_policy" in data else []
def test_swap_intent_with3(): swap_rules = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['intent_substitution']) # make sure intent is swapped and entity is added parse_data = {"intent": {"name": "chitchat.bye", "confidence": 1.0}} Rules._swap_intent(parse_data, None, swap_rules.rules[2]) assert parse_data["intent"]["name"] == "chitchat" assert parse_data["entities"][0]["entity"] == "intent" assert parse_data["entities"][0]["value"] == "chitchat.bye"
def test_swap_intent_with4(): swap_rules = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['intent_substitution']) # just checking regex is ok parse_data = { "intent": { "name": "chitchat.this_is_frustrating", "confidence": 1.0 } } Rules._swap_intent(parse_data, None, swap_rules.rules[2]) assert parse_data["intent"]["name"] == "chitchat.this_is_frustrating"
def test_swap_intent_after2(): swap_rules = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['intent_substitution']) # make sure intent is not swapped when in unless list parse_data = { "intent": { "name": "chitchat.this_is_frustrating", "confidence": 1.0 } } Rules._swap_intent(parse_data, "utter_something", swap_rules.rules[0]) assert parse_data["intent"]["name"] == "chitchat.this_is_frustrating"
def test_validator_intent_and_entity_ok(): validator = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['input_validation']) previous = "utter_garantie_type_bien" parse_data = { 'intent': { 'name': 'garantie' }, 'entities': [{ 'entity': 'product_type' }] } assert validator._get_error(parse_data, previous) is None
def test_validator_regex(): validator = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['input_validation']) previous = "utter_garantie_confirm_particulier" parse_data = { 'intent': { 'name': 'cancel' }, 'entities': [{ 'entity': 'product_type' }] } # having an entity in parse_data that is not expected is ok assert validator._get_error(parse_data, previous) is None previous = "utter_garantie_confirm_particulier" parse_data = {'intent': {'name': 'cancel'}, 'entities': []} assert validator._get_error(parse_data, previous) is None previous = "utter_garantie_confirm_particulier" parse_data = {'intent': {'name': 'lakjshdflkjashdf'}, 'entities': []} assert validator._get_error( parse_data, previous) == 'utter_general_validation_affirm_deny'
def test_validator_intent_no_entity(): validator = InputValidator( InputValidator._load_yaml(VALIDATOR_RULES_YAML)['input_validation']) previous = "utter_garantie_type_bien" parse_data = {'intent': {'name': 'cancel'}, 'entities': []} assert validator._get_error(parse_data, previous) is None
class Rules(object): def __init__(self, rules_file): data = self._load_yaml(rules_file) self.actions_to_ignore = ['action_listen', 'action_invalid_utterance'] self.allowed_entities = data["allowed_entities"] if data and "allowed_entities" in data else {} self.intent_substitutions = data["intent_substitutions"] if data and "intent_substitutions" in data else [] self.input_validation = InputValidator(data["input_validation"]) if data and "input_validation" in data else [] self.disambiguation_policy = Disambiguator(data["disambiguation_policy"]) if data and "disambiguation_policy" in data else [] def interrupts(self, dispatcher, parse_data, tracker, run_action): self.run_swap_intent_rules(parse_data, tracker) if self.disambiguation_policy.disambiguate(parse_data, tracker, dispatcher, run_action): return True self.filter_entities(parse_data) if self.input_validation: error_template = self.input_validation.get_error(parse_data, tracker) if error_template is not None: self._utter_error_and_roll_back(dispatcher, tracker, error_template, run_action) return True @staticmethod def _utter_error_and_roll_back(dispatcher, tracker, template, run_action): action = ActionInvalidUtterance(template) run_action(action, tracker, dispatcher) def filter_entities(self, parse_data): if parse_data['intent']['name'] in self.allowed_entities.keys(): filtered = list(filter(lambda ent: ent['entity'] in self.allowed_entities[parse_data['intent']['name']], parse_data['entities'])) else: filtered = parse_data['entities'] if len(filtered) < len(parse_data['entities']): # logging first logger.warn("entity(ies) were removed from parse stories") parse_data['entities'] = filtered def run_swap_intent_rules(self, parse_data, tracker): # don't do anything if no intent is present if parse_data["intent"]["name"] is None or parse_data["intent"]["name"] == "": return previous_action = self._get_previous_action(tracker) for rule in self.intent_substitutions: if Rules._swap_intent(parse_data, previous_action, rule): break @staticmethod def _swap_intent(parse_data, previous_action, rule): # don't do anything if no intent is present if parse_data["intent"]["name"] is None or parse_data["intent"]["name"] == "": return # for an after rule if previous_action and 'after' in rule and re.match(rule['after'], previous_action): return Rules._swap_intent_after(parse_data, rule) # for a general substitution elif 'after' not in rule and re.match(rule['intent'], parse_data['intent']['name']): return Rules.swap_intent_with(parse_data, rule) @staticmethod def _swap_intent_after(parse_data, rule): rule['unless'] = rule['unless'] if 'unless' in rule else [] if parse_data['intent']['name'] not in rule['unless']: logger.warn( "intent '{}' was replaced with '{}'".format(parse_data['intent']['name'], rule['intent'])) parse_data['intent']['name'] = rule['intent'] parse_data.pop('intent_ranking', None) return True @staticmethod def swap_intent_with(parse_data, rule): def format(text, parse_data): return text.format(intent=parse_data["intent"]["name"]) pd_copy = copy.deepcopy(parse_data) parse_data['intent']['name'] = rule['with'] parse_data['intent_ranking'] = [{"name": rule['with'], "confidence": 1.0}] if 'entities' in rule and 'add' in rule["entities"]: for entity in rule["entities"]["add"]: if 'entities' not in parse_data: parse_data['entities'] = [] parse_data['entities'].append( {"entity": format(entity["name"], pd_copy), "value": format(entity["value"], pd_copy)}) return True def _get_previous_action(self, tracker): action_listen_found = False for i in range(len(tracker.events) - 1, -1, -1): if i == 0: return None if type(tracker.events[i]) is ActionExecuted \ and action_listen_found is False \ and tracker.events[i].action_name not in self.actions_to_ignore: return tracker.events[i].action_name return None @staticmethod def _load_yaml(rules_file): with io.open(rules_file, 'r', encoding='utf-8') as stream: try: return yaml.load(stream) except yaml.YAMLError as exc: raise ValueError(exc)