def test_domain_utterance_actions_deprecated_templates(): new_yaml = """config: store_entities_as_slots: true entities: [] forms: [] intents: [] templates: utter_greet: - text: hey there! utter_goodbye: - text: bye! session_config: carry_over_slots_to_new_session: true session_expiration_time: 60 slots: {}""" old_yaml = """config: store_entities_as_slots: true entities: [] forms: [] intents: [] responses: utter_greet: - text: hey there! utter_goodbye: - text: bye! session_config: carry_over_slots_to_new_session: true session_expiration_time: 60 slots: {}""" old_domain = Domain.from_yaml(old_yaml) new_domain = Domain.from_yaml(new_yaml) assert hash(old_domain) == hash(new_domain)
def test_merge_yaml_domains(): test_yaml_1 = """config: store_entities_as_slots: true entities: [] intents: [] slots: {} responses: utter_greet: - text: hey there!""" test_yaml_2 = """config: store_entities_as_slots: false session_config: session_expiration_time: 20 carry_over_slots: true entities: - cuisine intents: - greet slots: cuisine: type: text responses: utter_goodbye: - text: bye! utter_greet: - text: hey you!""" domain_1 = Domain.from_yaml(test_yaml_1) domain_2 = Domain.from_yaml(test_yaml_2) domain = domain_1.merge(domain_2) # single attribute should be taken from domain_1 assert domain.store_entities_as_slots # conflicts should be taken from domain_1 assert domain.templates == { "utter_greet": [{"text": "hey there!"}], "utter_goodbye": [{"text": "bye!"}], } # lists should be deduplicated and merged assert domain.intents == sorted(["greet", *DEFAULT_INTENTS]) assert domain.entities == ["cuisine"] assert isinstance(domain.slots[0], TextSlot) assert domain.slots[0].name == "cuisine" assert sorted(domain.user_actions) == sorted(["utter_greet", "utter_goodbye"]) assert domain.session_config == SessionConfig(20, True) domain = domain_1.merge(domain_2, override=True) # single attribute should be taken from domain_2 assert not domain.store_entities_as_slots # conflicts should take value from domain_2 assert domain.templates == { "utter_greet": [{"text": "hey you!"}], "utter_goodbye": [{"text": "bye!"}], } assert domain.session_config == SessionConfig(20, True)
def test_merge_yaml_domains(): test_yaml_1 = """actions: - utter_greet config: store_entities_as_slots: true entities: [] intents: [] slots: {} templates: utter_greet: - text: hey there!""" test_yaml_2 = """actions: - utter_greet - utter_goodbye config: store_entities_as_slots: false entities: - cuisine intents: - greet slots: cuisine: type: text templates: utter_greet: - text: hey you!""" domain_1 = Domain.from_yaml(test_yaml_1) domain_2 = Domain.from_yaml(test_yaml_2) domain = domain_1.merge(domain_2) # single attribute should be taken from domain_1 assert domain.store_entities_as_slots # conflicts should be taken from domain_1 assert domain.templates == {"utter_greet": [{"text": "hey there!"}]} # lists should be deduplicated and merged assert domain.intents == ["greet"] assert domain.entities == ["cuisine"] assert isinstance(domain.slots[0], TextSlot) assert domain.slots[0].name == "cuisine" assert sorted(domain.user_actions) == sorted( ["utter_greet", "utter_goodbye"]) domain = domain_1.merge(domain_2, override=True) # single attribute should be taken from domain_2 assert not domain.store_entities_as_slots # conflicts should take value from domain_2 assert domain.templates == {"utter_greet": [{"text": "hey you!"}]}
async def test_failing_form_activation_due_to_no_rule(): form_name = "some_form" other_intent = "bye" domain = Domain.from_yaml(f""" intents: - {GREET_INTENT_NAME} - {other_intent} actions: - {UTTER_GREET_ACTION} - some-action slots: {REQUESTED_SLOT}: type: unfeaturized forms: - {form_name} """) policy = RulePolicy() policy.train([GREET_RULE], domain, RegexInterpreter()) conversation_events = [ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": other_intent}), ] # RulePolicy has no matching rule since no rule for form activation is given action_probabilities = policy.predict_action_probabilities( DialogueStateTracker.from_events("casd", evts=conversation_events, slots=domain.slots), domain, RegexInterpreter(), ) assert max(action_probabilities) == policy._core_fallback_threshold
def validate_and_store_domain_yaml( self, domain_yaml: Text, project_id: Text = config.project_name, path: Optional[Text] = None, store_responses: bool = False, username: Text = config.default_username, should_dump_domain: bool = True, ) -> Optional[Text]: """Store a domain from a yaml dump. Args: domain_yaml: The domain as yaml. project_id: The project_id the domain belongs to. path: File path of the domain. store_responses: Whether or not to store responses. username: Username performing this operation. should_dump_domain: Whether to dump domain to disk after storing it. """ # create Rasa domain object (validation happens automatically) domain = RasaDomain.from_yaml(domain_yaml) cleaned = domain.cleaned_domain() self.store_domain(cleaned, project_id, path, store_responses, username) if should_dump_domain: background_dump_service.add_domain_change() return self.get_domain_yaml(project_id)
def test_domain_to_dict(): test_yaml = """ actions: - action_save_world config: store_entities_as_slots: true entities: [] forms: [] intents: [] responses: utter_greet: - text: hey there! session_config: carry_over_slots_to_new_session: true session_expiration_time: 60 slots: {}""" domain_as_dict = Domain.from_yaml(test_yaml).as_dict() assert domain_as_dict == { "actions": ["action_save_world"], "config": {"store_entities_as_slots": True}, "entities": [], "forms": [], "intents": [], "responses": {"utter_greet": [{"text": "hey there!"}]}, "session_config": { "carry_over_slots_to_new_session": True, "session_expiration_time": 60, }, "slots": {}, }
async def test_activate_with_prefilled_slot(): slot_name = "num_people" slot_value = 5 tracker = DialogueStateTracker.from_events( sender_id="bla", evts=[SlotSet(slot_name, slot_value)]) form_name = "my form" action = FormAction(form_name, None) next_slot_to_request = "next slot to request" domain = f""" forms: - {form_name}: {slot_name}: - type: from_entity entity: {slot_name} {next_slot_to_request}: - type: from_text slots: {slot_name}: type: unfeaturized """ domain = Domain.from_yaml(domain) events = await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) assert events == [ Form(form_name), SlotSet(slot_name, slot_value), SlotSet(REQUESTED_SLOT, next_slot_to_request), ]
def test_predict_core_fallback(rule_policy: RulePolicy, expected_confidence: float, expected_prediction: Text): other_intent = "other" domain = Domain.from_yaml(f""" intents: - {GREET_INTENT_NAME} - {other_intent} actions: - {UTTER_GREET_ACTION} - my_core_fallback """) rule_policy.train([GREET_RULE], domain, RegexInterpreter()) new_conversation = DialogueStateTracker.from_events( "bla2", evts=[ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": other_intent}), ], ) action_probabilities = rule_policy.predict_action_probabilities( new_conversation, domain, RegexInterpreter()) assert_predicted_action(action_probabilities, domain, expected_prediction, expected_confidence)
def test_mapping_wins_over_form(events: List[Event]): domain = """ forms: - test-form """ domain = Domain.from_yaml(domain) tracker = DialogueStateTracker.from_events("test", events, []) ensemble = SimplePolicyEnsemble( [ MappingPolicy(), ConstantPolicy(priority=1, predict_index=0), FormPolicy(), FallbackPolicy(), ] ) result, best_policy = ensemble.probabilities_using_best_policy( tracker, domain, RegexInterpreter() ) max_confidence_index = result.index(max(result)) next_action = domain.action_for_index(max_confidence_index, None) index_of_mapping_policy = 0 assert best_policy == f"policy_{index_of_mapping_policy}_{MappingPolicy.__name__}" assert next_action.name() == ACTION_RESTART_NAME
def test_name_of_utterance(): form_name = "another_form" slot_name = "num_people" full_utterance_name = f"utter_ask_{form_name}_{slot_name}" domain = f""" forms: - {form_name}: {slot_name}: - type: from_text responses: {full_utterance_name}: - text: "How many people?" """ domain = Domain.from_yaml(domain) action_server_url = "http:/my-action-server:5055/webhook" with aioresponses(): action_server = EndpointConfig(action_server_url) action = FormAction(form_name, action_server) assert action._name_of_utterance(domain, slot_name) == full_utterance_name assert (action._name_of_utterance( domain, "another_slot") == "utter_ask_another_slot")
async def test_activate(): tracker = DialogueStateTracker.from_events(sender_id="bla", evts=[]) form_name = "my form" action = FormAction(form_name, None) slot_name = "num_people" domain = f""" forms: - {form_name}: {slot_name}: - type: from_entity entity: number responses: utter_ask_num_people: - text: "How many people?" """ domain = Domain.from_yaml(domain) events = await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) assert events[:-1] == [Form(form_name), SlotSet(REQUESTED_SLOT, slot_name)] assert isinstance(events[-1], BotUttered)
async def test_action_rejection(): form_name = "my form" slot_to_fill = "some slot" tracker = DialogueStateTracker.from_events( sender_id="bla", evts=[ Form(form_name), SlotSet(REQUESTED_SLOT, slot_to_fill), ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": "greet"}), ], ) form_name = "my form" action = FormAction(form_name, None) domain = f""" forms: - {form_name}: {slot_to_fill}: - type: from_entity entity: some_entity slots: {slot_to_fill}: type: unfeaturized """ domain = Domain.from_yaml(domain) with pytest.raises(ActionExecutionRejection): await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, )
async def test_set_slot_and_deactivate(): form_name = "my form" slot_name = "num_people" slot_value = "dasdasdfasdf" events = [ Form(form_name), SlotSet(REQUESTED_SLOT, slot_name), ActionExecuted(ACTION_LISTEN_NAME), UserUttered(slot_value), ] tracker = DialogueStateTracker.from_events(sender_id="bla", evts=events) domain = f""" forms: - {form_name}: {slot_name}: - type: from_text slots: {slot_name}: type: unfeaturized """ domain = Domain.from_yaml(domain) action = FormAction(form_name, None) events = await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) assert events == [ SlotSet(slot_name, slot_value), SlotSet(REQUESTED_SLOT, None), Form(None), ]
async def test_validate_slots_on_activation_with_other_action_after_user_utterance( ): form_name = "my form" slot_name = "num_people" slot_value = "hi" events = [ ActionExecuted(ACTION_LISTEN_NAME), UserUttered(slot_value, entities=[{ "entity": "num_tables", "value": 5 }]), ActionExecuted("action_in_between"), ] tracker = DialogueStateTracker.from_events(sender_id="bla", evts=events) domain = f""" slots: {slot_name}: type: unfeaturized forms: - {form_name}: {slot_name}: - type: from_text actions: - validate_{form_name} """ domain = Domain.from_yaml(domain) action_server_url = "http:/my-action-server:5055/webhook" expected_slot_value = "✅" with aioresponses() as mocked: mocked.post( action_server_url, payload={ "events": [{ "event": "slot", "name": slot_name, "value": expected_slot_value }] }, ) action_server = EndpointConfig(action_server_url) action = FormAction(form_name, action_server) events = await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) assert events == [ Form(form_name), SlotSet(slot_name, expected_slot_value), SlotSet(REQUESTED_SLOT, None), Form(None), ]
def test_immediate_submit(): form_name = "some_form" submit_action_name = "utter_submit" entity = "some_entity" slot = "some_slot" domain = Domain.from_yaml(f""" intents: - {GREET_INTENT_NAME} actions: - {UTTER_GREET_ACTION} - some-action - {submit_action_name} slots: {REQUESTED_SLOT}: type: unfeaturized {slot}: type: unfeaturized forms: - {form_name} entities: - {entity} """) form_activation_rule = _form_activation_rule(domain, form_name, GREET_INTENT_NAME) form_submit_rule = _form_submit_rule(domain, submit_action_name, form_name) policy = RulePolicy() policy.train([GREET_RULE, form_activation_rule, form_submit_rule], domain, RegexInterpreter()) form_conversation = DialogueStateTracker.from_events( "in a form", evts=[ # Form was activated ActionExecuted(ACTION_LISTEN_NAME), # The same intent which activates the form also deactivates it UserUttered( "haha", {"name": GREET_INTENT_NAME}, entities=[{ "entity": entity, "value": "Bruce Wayne" }], ), SlotSet(slot, "Bruce"), ActionExecuted(form_name), SlotSet("bla", "bla"), ActiveLoop(None), SlotSet(REQUESTED_SLOT, None), ], slots=domain.slots, ) # RulePolicy predicts action which handles submit action_probabilities = policy.predict_action_probabilities( form_conversation, domain, RegexInterpreter()) assert_predicted_action(action_probabilities, domain, submit_action_name)
def test_domain_to_yaml(): test_yaml = """actions: - utter_greet config: store_entities_as_slots: true entities: [] forms: [] intents: [] slots: {} templates: utter_greet: - text: hey there!""" domain = Domain.from_yaml(test_yaml) # python 3 and 2 are different here, python 3 will have a leading set # of --- at the beginning of the yml assert domain.as_yaml().strip().endswith(test_yaml.strip()) assert Domain.from_yaml(domain.as_yaml()) is not None
def test_session_config( input_domain, expected_session_expiration_time: float, expected_carry_over_slots: bool, ): domain = Domain.from_yaml(input_domain) assert (domain.session_config.session_expiration_time == expected_session_expiration_time) assert domain.session_config.carry_over_slots == expected_carry_over_slots
def default_domain(self): content = """ intents: - greet - bye - affirm - deny """ return Domain.from_yaml(content)
def test_get_form_action_if_not_in_forms(): form_action_name = "my_business_logic" domain = Domain.from_yaml(""" actions: - my_action """) with pytest.raises(NameError): assert not domain.action_for_name(form_action_name, None)
def test_domain_to_yaml(): test_yaml = f"""config: store_entities_as_slots: true entities: [] forms: [] intents: [] responses: utter_greet: - text: hey there! session_config: carry_over_slots_to_new_session: true session_expiration_time: {DEFAULT_SESSION_EXPIRATION_TIME_IN_MINUTES} slots: {{}}""" domain = Domain.from_yaml(test_yaml) # python 3 and 2 are different here, python 3 will have a leading set # of --- at the beginning of the yml assert domain.as_yaml().strip().endswith(test_yaml.strip()) assert Domain.from_yaml(domain.as_yaml()) is not None
def test_merge_session_config_if_first_is_not_default(): yaml1 = """ session_config: session_expiration_time: 20 carry_over_slots: true""" yaml2 = """ session_config: session_expiration_time: 40 carry_over_slots: true """ domain1 = Domain.from_yaml(yaml1) domain2 = Domain.from_yaml(yaml2) merged = domain1.merge(domain2) assert merged.session_config == SessionConfig(20, True) merged = domain1.merge(domain2, override=True) assert merged.session_config == SessionConfig(40, True)
def test_get_form_action_without_slot_mapping(): form_action_name = "my_business_logic" domain = Domain.from_yaml(f""" actions: - my_action forms: - {form_action_name} """) actual = domain.action_for_name(form_action_name, None) assert isinstance(actual, RemoteAction)
def default_domain(self): content = """ actions: - utter_hello intents: - greet - bye - affirm - deny """ return Domain.from_yaml(content)
def test_merge_domain_with_forms(): test_yaml_1 = """ forms: # Old style form definitions (before RulePolicy) - my_form - my_form2 """ test_yaml_2 = """ forms: - my_form3: slot1: type: from_text """ domain_1 = Domain.from_yaml(test_yaml_1) domain_2 = Domain.from_yaml(test_yaml_2) domain = domain_1.merge(domain_2) expected_number_of_forms = 3 assert len(domain.form_names) == expected_number_of_forms assert len(domain.forms) == expected_number_of_forms
def test_get_form_action(): form_action_name = "my_business_logic" domain = Domain.from_yaml(f""" actions: - my_action forms: - {form_action_name}: my_slot: - type: from_text """) actual = domain.action_for_name(form_action_name, None) assert isinstance(actual, FormAction)
async def test_form_unhappy_path_from_general_rule(): form_name = "some_form" domain = Domain.from_yaml(f""" intents: - {GREET_INTENT_NAME} actions: - {UTTER_GREET_ACTION} - some-action slots: {REQUESTED_SLOT}: type: unfeaturized forms: - {form_name} """) policy = RulePolicy() # RulePolicy should memorize that unhappy_rule overrides GREET_RULE policy.train([GREET_RULE], domain, RegexInterpreter()) # Check that RulePolicy predicts action to handle unhappy path conversation_events = [ ActionExecuted(form_name), ActiveLoop(form_name), SlotSet(REQUESTED_SLOT, "some value"), ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": GREET_INTENT_NAME}), ActionExecutionRejected(form_name), ] action_probabilities = policy.predict_action_probabilities( DialogueStateTracker.from_events("casd", evts=conversation_events, slots=domain.slots), domain, RegexInterpreter(), ) # check that general rule action is predicted assert_predicted_action(action_probabilities, domain, UTTER_GREET_ACTION) # Check that RulePolicy triggers form again after handling unhappy path conversation_events.append(ActionExecuted(UTTER_GREET_ACTION)) action_probabilities = policy.predict_action_probabilities( DialogueStateTracker.from_events("casd", evts=conversation_events, slots=domain.slots), domain, RegexInterpreter(), ) # check that action_listen from general rule is overwritten by form action assert_predicted_action(action_probabilities, domain, form_name)
async def test_validate_slots(validate_return_events: List[Dict], expected_events: List[Event]): form_name = "my form" slot_name = "num_people" slot_value = "hi" events = [ Form(form_name), SlotSet(REQUESTED_SLOT, slot_name), ActionExecuted(ACTION_LISTEN_NAME), UserUttered(slot_value, entities=[{ "entity": "num_tables", "value": 5 }]), ] tracker = DialogueStateTracker.from_events(sender_id="bla", evts=events) domain = f""" slots: {slot_name}: type: unfeaturized num_tables: type: unfeaturized forms: - {form_name}: {slot_name}: - type: from_text num_tables: - type: from_entity entity: num_tables actions: - validate_{form_name} """ domain = Domain.from_yaml(domain) action_server_url = "http:/my-action-server:5055/webhook" with aioresponses() as mocked: mocked.post(action_server_url, payload={"events": validate_return_events}) action_server = EndpointConfig(action_server_url) action = FormAction(form_name, action_server) events = await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) assert events == expected_events
def test_domain_utterance_actions_deprecated_templates(): new_yaml = f"""actions: - utter_greet - utter_goodbye config: store_entities_as_slots: true entities: [] forms: [] intents: [] templates: utter_greet: - text: hey there! utter_goodbye: - text: bye! session_config: carry_over_slots_to_new_session: true session_expiration_time: {DEFAULT_SESSION_EXPIRATION_TIME_IN_MINUTES} slots: {{}}""" old_yaml = f"""config: store_entities_as_slots: true entities: [] forms: [] intents: [] responses: utter_greet: - text: hey there! utter_goodbye: - text: bye! session_config: carry_over_slots_to_new_session: true session_expiration_time: {DEFAULT_SESSION_EXPIRATION_TIME_IN_MINUTES} slots: {{}}""" old_domain = Domain.from_yaml(old_yaml) new_domain = Domain.from_yaml(new_yaml) assert hash(old_domain) == hash(new_domain)
def test_form_submit_rule(): form_name = "some_form" submit_action_name = "utter_submit" domain = Domain.from_yaml( f""" intents: - {GREET_INTENT_NAME} actions: - {UTTER_GREET_ACTION} - some-action - {submit_action_name} slots: {REQUESTED_SLOT}: type: unfeaturized forms: - {form_name} """ ) form_submit_rule = _form_submit_rule(domain, submit_action_name, form_name) policy = RulePolicy() policy.train([GREET_RULE, form_submit_rule], domain, RegexInterpreter()) form_conversation = DialogueStateTracker.from_events( "in a form", evts=[ # Form was activated ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": GREET_INTENT_NAME}), ActionExecuted(form_name), Form(form_name), SlotSet(REQUESTED_SLOT, "some value"), ActionExecuted(ACTION_LISTEN_NAME), # User responds and fills requested slot UserUttered("haha", {"name": GREET_INTENT_NAME}), ActionExecuted(form_name), # Form get's deactivated Form(None), SlotSet(REQUESTED_SLOT, None), ], slots=domain.slots, ) # RulePolicy predicts action which handles submit action_probabilities = policy.predict_action_probabilities( form_conversation, domain ) assert_predicted_action(action_probabilities, domain, submit_action_name)
def test_exception_if_intent_not_present(self, trained_policy): content = """ actions: - utter_hello intents: - greet """ domain = Domain.from_yaml(content) events = [ActionExecuted(ACTION_DEFAULT_FALLBACK_NAME)] tracker = get_tracker(events) with pytest.raises(InvalidDomain): trained_policy.predict_action_probabilities(tracker, domain)