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_rule_with_condition(rule_steps_without_stories: List[StoryStep]): rule = rule_steps_without_stories[0] assert rule.block_name == "Rule with condition" assert rule.events == [ Form("loop_q_form"), SlotSet("requested_slot", "some_slot"), ActionExecuted(RULE_SNIPPET_ACTION_NAME), UserUttered( "inform", {"name": "inform", "confidence": 1.0}, [{"entity": "some_slot", "value": "bla"}], ), ActionExecuted("loop_q_form"), ]
async def test_update_tracker_session_with_slots( default_channel: CollectingOutputChannel, default_processor: MessageProcessor, monkeypatch: MonkeyPatch, ): sender_id = uuid.uuid4().hex tracker = default_processor.tracker_store.get_or_create_tracker(sender_id) # apply a user uttered and five slots user_event = UserUttered("some utterance") tracker.update(user_event) slot_set_events = [ SlotSet(f"slot key {i}", f"test value {i}") for i in range(5) ] for event in slot_set_events: tracker.update(event) # patch `_has_session_expired()` so the `_update_tracker_session()` call actually # does something monkeypatch.setattr(default_processor, "_has_session_expired", lambda _: True) await default_processor._update_tracker_session(tracker, default_channel) # the save is not called in _update_tracker_session() default_processor._save_tracker(tracker) # inspect tracker and make sure all events are present tracker = default_processor.tracker_store.retrieve(sender_id) events = list(tracker.events) # the first three events should be up to the user utterance assert events[:2] == [ActionExecuted(ACTION_LISTEN_NAME), user_event] # next come the five slots assert events[2:7] == slot_set_events # the next two events are the session start sequence assert events[7:9] == [ ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted() ] # the five slots should be reapplied assert events[9:14] == slot_set_events # finally an action listen, this should also be the last event assert events[14] == events[-1] == ActionExecuted(ACTION_LISTEN_NAME)
def test_invalid_slot_mapping(): form_name = "my_form" form = FormAction(form_name, None) slot_name = "test" tracker = DialogueStateTracker.from_events( "sender", [SlotSet(REQUESTED_SLOT, slot_name)] ) domain = Domain.from_dict( {"forms": [{form_name: {slot_name: [{"type": "invalid"}]}}]} ) with pytest.raises(ValueError): form.extract_requested_slot(tracker, domain)
def test_tracker_serialisation(): slot_key = "location" slot_val = "Easter Island" store = InMemoryTrackerStore(domain) tracker = store.get_or_create_tracker(UserMessage.DEFAULT_SENDER_ID) ev = SlotSet(slot_key, slot_val) tracker.update(ev) serialised = store.serialise_tracker(tracker) assert tracker == store.deserialise_tracker(UserMessage.DEFAULT_SENDER_ID, serialised)
def test_get_or_create(): slot_key = "location" slot_val = "Easter Island" store = InMemoryTrackerStore(domain) tracker = store.get_or_create_tracker(UserMessage.DEFAULT_SENDER_ID) ev = SlotSet(slot_key, slot_val) tracker.update(ev) assert tracker.get_slot(slot_key) == slot_val store.save(tracker) again = store.get_or_create_tracker(UserMessage.DEFAULT_SENDER_ID) assert again.get_slot(slot_key) == slot_val
def _tracker_store_and_tracker_with_slot_set() -> Tuple[ InMemoryTrackerStore, DialogueStateTracker ]: # returns an InMemoryTrackerStore containing a tracker with a slot set slot_key = "cuisine" slot_val = "French" store = InMemoryTrackerStore(domain) tracker = store.get_or_create_tracker(UserMessage.DEFAULT_SENDER_ID) ev = SlotSet(slot_key, slot_val) tracker.update(ev) return store, tracker
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), Form(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, ) # 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, ) # check that action_listen from general rule is overwritten by form action assert_predicted_action(action_probabilities, domain, form_name)
def test_common_action_prefix(): this = [ ActionExecuted("action_listen"), ActionExecuted("greet"), UserUttered("hey"), ActionExecuted("amazing"), # until this point they are the same SlotSet("my_slot", "a"), ActionExecuted("a"), ActionExecuted("after_a"), ] other = [ ActionExecuted("action_listen"), ActionExecuted("greet"), UserUttered("hey"), ActionExecuted("amazing"), # until this point they are the same SlotSet("my_slot", "b"), ActionExecuted("b"), ActionExecuted("after_b"), ] num_common = visualization._length_of_common_action_prefix(this, other) assert num_common == 3
def _merge_slots( self, entities: Optional[List[Dict[Text, Any]]] = None) -> List[SlotSet]: """Take a list of entities and create tracker slot set events. If an entity type matches a slots name, the entities value is set as the slots value by creating a ``SlotSet`` event. """ entities = entities if entities else self.latest_message.entities new_slots = [ SlotSet(e["entity"], e["value"]) for e in entities if e["entity"] in self.slots.keys() ] return new_slots
async def test_action_session_start_with_slots( default_channel: CollectingOutputChannel, template_nlg: TemplatedNaturalLanguageGenerator, template_sender_tracker: DialogueStateTracker, default_domain: Domain, session_config: SessionConfig, expected_events: List[Event], ): # set a few slots on tracker slot_set_event_1 = SlotSet("my_slot", "value") slot_set_event_2 = SlotSet("another-slot", "value2") for event in [slot_set_event_1, slot_set_event_2]: template_sender_tracker.update(event) default_domain.session_config = session_config events = await ActionSessionStart().run(default_channel, template_nlg, template_sender_tracker, default_domain) assert events == expected_events # make sure that the list of events has ascending timestamps assert sorted(events, key=lambda x: x.timestamp) == events
def run(self,dispatcher,tracker,domain): feedback = tracker.get_slot("confirm_feedback") print("hello") print(feedback) #if response == "no 👎": if feedback == "Not Satisfied": name= next(tracker.get_latest_entity_values("person"), None) if not name: dispatcher.utter_template("utter_name",tracker) UserUtteranceReverted() #return [UserUtteranceReverted()] return [SlotSet('person',name)] else: dispatcher.utter_template("utter_goodbye",tracker) return[Restarted()]
def test_extract_requested_slot_default(): """Test default extraction of a slot value from entity with the same name.""" form = FormAction("some form", None) tracker = DialogueStateTracker.from_events( "default", [ SlotSet(REQUESTED_SLOT, "some_slot"), UserUttered( "bla", entities=[{"entity": "some_slot", "value": "some_value"}] ), ActionExecuted(ACTION_LISTEN_NAME), ], ) slot_values = form.extract_requested_slot(tracker, Domain.empty()) assert slot_values == {"some_slot": "some_value"}
def test_dynamo_tracker_floats(): conversation_id = uuid.uuid4().hex tracker_store = DynamoTrackerStore(domain) tracker = tracker_store.get_or_create_tracker(conversation_id, append_action_listen=False) # save `slot` event with known `float`-type timestamp timestamp = 13423.23434623 tracker.update(SlotSet("key", "val", timestamp=timestamp)) tracker_store.save(tracker) # retrieve tracker and the event timestamp is retrieved as a `float` tracker = tracker_store.get_or_create_tracker(conversation_id) retrieved_timestamp = tracker.events[0].timestamp assert isinstance(retrieved_timestamp, float) assert retrieved_timestamp == timestamp
async def test_remote_action_utterances_with_none_values( default_channel, default_tracker, default_domain): endpoint = EndpointConfig("https://example.com/webhooks/actions") remote_action = action.RemoteAction("my_action", endpoint) response = { "events": [ { "event": "form", "name": "restaurant_form", "timestamp": None }, { "event": "slot", "timestamp": None, "name": "requested_slot", "value": "cuisine", }, ], "responses": [{ "text": None, "buttons": None, "elements": [], "custom": None, "template": "utter_ask_cuisine", "image": None, "attachment": None, }], } nlg = TemplatedNaturalLanguageGenerator( {"utter_ask_cuisine": [{ "text": "what dou want to eat?" }]}) with aioresponses() as mocked: mocked.post("https://example.com/webhooks/actions", payload=response) events = await remote_action.run(default_channel, nlg, default_tracker, default_domain) assert events == [ BotUttered("what dou want to eat?", metadata={"template_name": "utter_ask_cuisine"}), Form("restaurant_form"), SlotSet("requested_slot", "cuisine"), ]
async def test_form_unhappy_path(): 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() policy.train([GREET_RULE], domain, RegexInterpreter()) unhappy_form_conversation = DialogueStateTracker.from_events( "in a form", evts=[ # We are in an active form ActionExecuted(form_name), Form(form_name), SlotSet(REQUESTED_SLOT, "some value"), # User responds to slot request ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": GREET_INTENT_NAME}), # Form isn't happy with the answer and rejects execution ActionExecutionRejected(form_name), ], slots=domain.slots, ) # RulePolicy doesn't trigger form but FAQ action_probabilities = policy.predict_action_probabilities( unhappy_form_conversation, domain ) assert_predicted_action(action_probabilities, domain, UTTER_GREET_ACTION)
def _form_submit_rule(domain: Domain, submit_action_name: Text, form_name: Text) -> DialogueStateTracker: return TrackerWithCachedStates.from_events( "bla", domain=domain, slots=domain.slots, evts=[ ActiveLoop(form_name), # Any events in between ActionExecuted(RULE_SNIPPET_ACTION_NAME), # Form runs and deactivates itself ActionExecuted(form_name), ActiveLoop(None), SlotSet(REQUESTED_SLOT, None), ActionExecuted(submit_action_name), ActionExecuted(ACTION_LISTEN_NAME), ], is_rule_tracker=True, )
def test_extract_requested_slot_from_entity( mapping_not_intent: Optional[Text], mapping_intent: Optional[Text], mapping_role: Optional[Text], mapping_group: Optional[Text], entities: List[Dict[Text, Any]], intent: Text, expected_slot_values: Dict[Text, Text], ): """Test extraction of a slot value from entity with the different restrictions.""" form_name = "some form" form = FormAction(form_name, None) mapping = form.from_entity( entity="some_entity", role=mapping_role, group=mapping_group, intent=mapping_intent, not_intent=mapping_not_intent, ) domain = Domain.from_dict( {"forms": [{ form_name: { "some_slot": [mapping] } }]}) tracker = DialogueStateTracker.from_events( "default", [ SlotSet(REQUESTED_SLOT, "some_slot"), UserUttered("bla", intent={ "name": intent, "confidence": 1.0 }, entities=entities), ], ) slot_values = form.extract_requested_slot(tracker, domain) assert slot_values == expected_slot_values
async def test_predict_action_listen_after_form(): 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() policy.train([GREET_RULE], domain, RegexInterpreter()) form_conversation = DialogueStateTracker.from_events( "in a form", evts=[ # We are in an activate form ActionExecuted(form_name), Form(form_name), SlotSet(REQUESTED_SLOT, "some value"), ActionExecuted(ACTION_LISTEN_NAME), # User sends message as response to a requested slot UserUttered("haha", {"name": GREET_INTENT_NAME}), # Form is running again ActionExecuted(form_name), ], slots=domain.slots, ) # RulePolicy predicts action listen action_probabilities = policy.predict_action_probabilities( form_conversation, domain ) assert_predicted_action(action_probabilities, domain, ACTION_LISTEN_NAME)
async def test_trigger_slot_mapping_applies( trigger_slot_mapping: Dict, expected_value: Text ): form_name = "some_form" entity_name = "some_slot" slot_filled_by_trigger_mapping = "other_slot" form = FormAction(form_name, None) domain = Domain.from_dict( { "forms": [ { form_name: { entity_name: [ { "type": "from_entity", "entity": entity_name, "intent": "some_intent", } ], slot_filled_by_trigger_mapping: [trigger_slot_mapping], } } ] } ) tracker = DialogueStateTracker.from_events( "default", [ SlotSet(REQUESTED_SLOT, "some_slot"), UserUttered( "bla", intent={"name": "greet", "confidence": 1.0}, entities=[{"entity": entity_name, "value": "some_value"}], ), ActionExecuted(ACTION_LISTEN_NAME), ], ) slot_values = form.extract_other_slots(tracker, domain) assert slot_values == {slot_filled_by_trigger_mapping: expected_value}
async def test_read_rules_without_stories(default_domain: Domain): story_steps = await loading.load_data_from_files( ["data/test_stories/rules_without_stories.md"], default_domain, RegexInterpreter(), ) # this file contains three rules and two ML stories assert len(story_steps) == 3 ml_steps = [s for s in story_steps if not s.is_rule] rule_steps = [s for s in story_steps if s.is_rule] assert len(ml_steps) == 0 assert len(rule_steps) == 3 assert rule_steps[0].block_name == "rule 1" assert rule_steps[1].block_name == "rule 2" assert rule_steps[2].block_name == "rule 3" # inspect the first rule and make sure all events were picked up correctly events = rule_steps[0].events assert len(events) == 5 assert events[0] == ActiveLoop("loop_q_form") assert events[1] == SlotSet("requested_slot", "some_slot") assert events[2] == ActionExecuted("...") assert events[3] == UserUttered( 'inform{"some_slot":"bla"}', { "name": "inform", "confidence": 1.0 }, [{ "entity": "some_slot", "start": 6, "end": 25, "value": "bla" }], ) assert events[4] == ActionExecuted("loop_q_form")
async def test_form_unhappy_path_without_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(form_name), Form(form_name), SlotSet(REQUESTED_SLOT, "some value"), ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": other_intent}), Form(form_name), ActionExecutionRejected(form_name), ] # Unhappy path is not handled. No rule matches. Let's hope ML fixes our problems 🤞 action_probabilities = policy.predict_action_probabilities( DialogueStateTracker.from_events( "casd", evts=conversation_events, slots=domain.slots ), domain, ) assert max(action_probabilities) == policy._core_fallback_threshold
def test_session_start_is_not_serialised(default_domain: Domain): tracker = DialogueStateTracker("default", default_domain.slots) # the retrieved tracker should be empty assert len(tracker.events) == 0 # add SlotSet event tracker.update(SlotSet("slot", "value")) # add the two SessionStarted events and a user event tracker.update(ActionExecuted(ACTION_SESSION_START_NAME)) tracker.update(SessionStarted()) tracker.update(UserUttered("say something")) # make sure session start is not serialised story = Story.from_events(tracker.events, "some-story01") expected = """## some-story01 - slot{"slot": "value"} * say something """ assert story.as_story_string(flat=True) == expected
def test_extract_other_slots_with_entity( some_other_slot_mapping: List[Dict[Text, Any]], some_slot_mapping: List[Dict[Text, Any]], entities: List[Dict[Text, Any]], intent: Text, expected_slot_values: Dict[Text, Text], ): """Test extraction of other not requested slots values from entities.""" form_name = "some_form" form = FormAction(form_name, None) domain = Domain.from_dict( { "forms": [ { form_name: { "some_other_slot": some_other_slot_mapping, "some_slot": some_slot_mapping, } } ] } ) tracker = DialogueStateTracker.from_events( "default", [ SlotSet(REQUESTED_SLOT, "some_slot"), UserUttered( "bla", intent={"name": intent, "confidence": 1.0}, entities=entities ), ActionExecuted(ACTION_LISTEN_NAME), ], ) slot_values = form.extract_other_slots(tracker, domain) # check that the value was extracted for non requested slot assert slot_values == expected_slot_values
def push_slots_into_current_frame( tracker: "DialogueStateTracker") -> List[Event]: events = [] framed_slots = { key: slot.value for key, slot in tracker.slots.items() if slot.frame_slot } if tracker.frames.current_frame: for key, value in framed_slots.items(): events.append( FrameUpdated( frame_idx=tracker.frames.current_frame_idx, name=key, value=value, )) else: events.append(FrameCreated(slots=framed_slots, switch_to=True)) # Reset ref field events.append(SlotSet("ref", None)) return events
def test_extract_requested_slot_mapping_does_not_apply(slot_mapping: Dict): form_name = "some_form" entity_name = "some_slot" form = FormAction(form_name, None) domain = Domain.from_dict({"forms": [{form_name: {entity_name: [slot_mapping]}}]}) tracker = DialogueStateTracker.from_events( "default", [ SlotSet(REQUESTED_SLOT, "some_slot"), UserUttered( "bla", intent={"name": "greet", "confidence": 1.0}, entities=[{"entity": entity_name, "value": "some_value"}], ), ActionExecuted(ACTION_LISTEN_NAME), ], ) slot_values = form.extract_requested_slot(tracker, domain) # check that the value was not extracted for incorrect intent assert slot_values == {}
async def test_can_read_test_story_with_entities_slot_autofill(default_domain: Domain): trackers = await training.load_data( "data/test_yaml_stories/story_with_or_and_entities.yml", default_domain, use_story_concatenation=False, tracker_limit=1000, remove_duplicates=False, ) assert len(trackers) == 2 assert trackers[0].events[-3] == UserUttered( "greet", intent={"name": "greet", "confidence": 1.0}, parse_data={ "text": "/greet", "intent_ranking": [{"confidence": 1.0, "name": "greet"}], "intent": {"confidence": 1.0, "name": "greet"}, "entities": [], }, ) assert trackers[0].events[-2] == ActionExecuted("utter_greet") assert trackers[0].events[-1] == ActionExecuted("action_listen") assert trackers[1].events[-4] == UserUttered( "greet", intent={"name": "greet", "confidence": 1.0}, entities=[{"entity": "name", "value": "peter"}], parse_data={ "text": "/greet", "intent_ranking": [{"confidence": 1.0, "name": "greet"}], "intent": {"confidence": 1.0, "name": "greet"}, "entities": [{"entity": "name", "value": "peter"}], }, ) assert trackers[1].events[-3] == SlotSet(key="name", value="peter") assert trackers[1].events[-2] == ActionExecuted("utter_greet") assert trackers[1].events[-1] == ActionExecuted("action_listen")
# a couple of event instances that we can use for testing test_events = [ Event.from_parameters({ "event": UserUttered.type_name, "text": "/goodbye", "parse_data": { "intent": { "confidence": 1.0, "name": "greet" }, "entities": [], }, }), BotUttered("Welcome!", {"test": True}), SlotSet("cuisine", 34), SlotSet("cuisine", "34"), SlotSet("location", None), SlotSet("location", [34, "34", None]), ] @pytest.fixture def rasa_app_without_api(rasa_server_without_api: Sanic) -> SanicTestClient: return rasa_server_without_api.test_client @pytest.fixture def rasa_app(rasa_server: Sanic) -> SanicTestClient: return rasa_server.test_client
async def test_remote_action_logs_events(default_dispatcher_collecting, default_domain): tracker = DialogueStateTracker("default", default_domain.slots) endpoint = EndpointConfig("https://example.com/webhooks/actions") remote_action = action.RemoteAction("my_action", endpoint) response = { "events": [{ "event": "slot", "value": "rasa", "name": "name" }], "responses": [ { "text": "test text", "buttons": [{ "title": "cheap", "payload": "cheap" }] }, { "template": "utter_greet" }, ], } with aioresponses() as mocked: mocked.post("https://example.com/webhooks/actions", payload=response) events = await remote_action.run(default_dispatcher_collecting, tracker, default_domain) r = latest_request(mocked, "post", "https://example.com/webhooks/actions") assert r assert json_of_latest_request(r) == { "domain": default_domain.as_dict(), "next_action": "my_action", "sender_id": "default", "version": rasa.__version__, "tracker": { "latest_message": { "entities": [], "intent": {}, "text": None }, "active_form": {}, "latest_action_name": None, "sender_id": "default", "paused": False, "followup_action": "action_listen", "latest_event_time": None, "slots": { "name": None }, "events": [], "latest_input_channel": None, }, } assert events == [SlotSet("name", "rasa")] channel = default_dispatcher_collecting.output_channel assert channel.messages == [ { "text": "test text", "recipient_id": "my-sender", "buttons": [{ "title": "cheap", "payload": "cheap" }], }, { "text": "hey there None!", "recipient_id": "my-sender" }, ]
import json from rasa.core import broker from rasa.core.broker import FileProducer, PikaProducer, KafkaProducer from rasa.core.events import Event, Restarted, SlotSet, UserUttered from rasa.utils.endpoints import EndpointConfig, read_endpoint_config from tests.core.conftest import DEFAULT_ENDPOINTS_FILE TEST_EVENTS = [ UserUttered("/greet", { "name": "greet", "confidence": 1.0 }, []), SlotSet("name", "rasa"), Restarted() ] def test_pika_broker_from_config(): cfg = read_endpoint_config( 'data/test_endpoints/event_brokers/' 'pika_endpoint.yml', "event_broker") actual = broker.from_endpoint_config(cfg) assert isinstance(actual, PikaProducer) assert actual.host == "localhost" assert actual.credentials.username == "username" assert actual.queue == "queue" def test_no_broker_in_config():