Example #1
0
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"
Example #2
0
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'
Example #3
0
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
Example #4
0
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"
Example #5
0
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"
Example #6
0
 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 []
Example #7
0
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"
Example #8
0
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"
Example #9
0
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"
Example #10
0
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
Example #11
0
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'
Example #12
0
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
Example #13
0
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)