async def test_action_utter_retrieved_response( default_channel, default_nlg, default_tracker, default_domain ): from rasa.core.channels.channel import UserMessage action_name = "utter_chitchat" default_tracker.latest_message = UserMessage( "Who are you?", parse_data={ "response_selector": { "chitchat": { "response": { "intent_response_key": "chitchat/ask_name", "response_templates": [{"text": "I am a bot."}], "template_name": "utter_chitchat/ask_name", } } } }, ) default_domain.templates.update( {"utter_chitchat/ask_name": [{"text": "I am a bot."}]} ) events = await ActionRetrieveResponse(action_name).run( default_channel, default_nlg, default_tracker, default_domain ) assert events[0].as_dict().get("text") == BotUttered("I am a bot.").as_dict().get( "text" ) assert ( events[0].as_dict().get("metadata").get("template_name") == "utter_chitchat/ask_name" )
async def test_action_default_ask_affirmation_on_empty_conversation( default_channel, default_nlg, default_tracker, domain: Domain): events = await ActionDefaultAskAffirmation().run(default_channel, default_nlg, default_tracker, domain) assert events == [ BotUttered( "Did you mean 'None'?", { "buttons": [ { "title": "Yes", "payload": "/None" }, { "title": "No", "payload": "/out_of_scope" }, ] }, {"utter_action": "action_default_ask_affirmation"}, ) ]
UserUttered("/greet", {"name": "greet", "confidence": 1.0}, []), UserUttered("/goodbye", {"name": "goodbye", "confidence": 1.0}, []), ), (SlotSet("my_slot", "value"), SlotSet("my__other_slot", "value")), (Restarted(), None), (AllSlotsReset(), None), (ConversationPaused(), None), (ConversationResumed(), None), (StoryExported(), None), (ActionReverted(), None), (UserUtteranceReverted(), None), (SessionStarted(), None), (ActionExecuted("my_action"), ActionExecuted("my_other_action")), (FollowupAction("my_action"), FollowupAction("my_other_action")), ( BotUttered("my_text", {"my_data": 1}), BotUttered("my_other_test", {"my_other_data": 1}), ), ( AgentUttered("my_text", "my_data"), AgentUttered("my_other_test", "my_other_data"), ), ( ReminderScheduled("my_intent", datetime.now()), ReminderScheduled("my_other_intent", datetime.now()), ), ], ) def test_event_has_proper_implementation(one_event, another_event): # equals tests assert (
from ruamel.yaml import StringIO # 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, INTENT_NAME_KEY: "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 get_test_client(rasa_server_without_api) @pytest.fixture def rasa_app(rasa_server: Sanic) -> SanicTestClient: return get_test_client(rasa_server)
async def test_handle_message_with_session_start( default_channel: CollectingOutputChannel, default_processor: MessageProcessor, monkeypatch: MonkeyPatch, ): sender_id = uuid.uuid4().hex entity = "name" slot_1 = {entity: "Core"} await default_processor.handle_message( UserMessage(f"/greet{json.dumps(slot_1)}", default_channel, sender_id)) assert default_channel.latest_output() == { "recipient_id": sender_id, "text": "hey there Core!", } # patch processor so a session start is triggered monkeypatch.setattr(default_processor, "_has_session_expired", lambda _: True) slot_2 = {entity: "post-session start hello"} # handle a new message await default_processor.handle_message( UserMessage(f"/greet{json.dumps(slot_2)}", default_channel, sender_id)) tracker = default_processor.tracker_store.get_or_create_tracker(sender_id) # make sure the sequence of events is as expected expected = [ ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), ActionExecuted(ACTION_LISTEN_NAME), UserUttered( f"/greet{json.dumps(slot_1)}", { INTENT_NAME_KEY: "greet", "confidence": 1.0 }, [{ "entity": entity, "start": 6, "end": 22, "value": "Core" }], ), SlotSet(entity, slot_1[entity]), ActionExecuted("utter_greet"), BotUttered("hey there Core!", metadata={"template_name": "utter_greet"}), ActionExecuted(ACTION_LISTEN_NAME), ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), # the initial SlotSet is reapplied after the SessionStarted sequence SlotSet(entity, slot_1[entity]), ActionExecuted(ACTION_LISTEN_NAME), UserUttered( f"/greet{json.dumps(slot_2)}", { INTENT_NAME_KEY: "greet", "confidence": 1.0 }, [{ "entity": entity, "start": 6, "end": 42, "value": "post-session start hello", }], ), SlotSet(entity, slot_2[entity]), ActionExecuted("utter_greet"), BotUttered( "hey there post-session start hello!", metadata={"template_name": "utter_greet"}, ), ActionExecuted(ACTION_LISTEN_NAME), ] assert list(tracker.events) == expected
def _two_stage_clarification_request() -> List[Event]: return [ ActionExecuted(ACTION_TWO_STAGE_FALLBACK_NAME), BotUttered("please affirm") ]
user_uttered("greet"), ActionExecuted("loop"), ActiveLoop("loop"), SlotSet(REQUESTED_SLOT, "bla"), SlotSet("slot", "value"), ActiveLoop(None), SlotSet(REQUESTED_SLOT, None), ], ), ( [ user_uttered("trigger form"), ActionExecuted("form"), ActiveLoop("form"), SlotSet(REQUESTED_SLOT, "some slot"), BotUttered("ask slot"), ActionExecuted(ACTION_LISTEN_NAME), user_uttered("fill requested slots"), SlotSet("some slot", "value"), ActionExecuted("form"), SlotSet("some slot", "value"), SlotSet(REQUESTED_SLOT, None), ActiveLoop(None), ], [ user_uttered("trigger form"), ActionExecuted("form"), ActiveLoop("form"), SlotSet(REQUESTED_SLOT, "some slot"), BotUttered("ask slot"), SlotSet("some slot", "value"),
async def test_switch_forms_with_same_slot(default_agent: Agent): """Tests switching of forms, where the first slot is the same in both forms. Tests the fix for issue 7710""" # Define two forms in the domain, with same first slot slot_a = "my_slot_a" form_1 = "my_form_1" utter_ask_form_1 = f"Please provide the value for {slot_a} of form 1" form_2 = "my_form_2" utter_ask_form_2 = f"Please provide the value for {slot_a} of form 2" domain = f""" version: "2.0" nlu: - intent: order_status examples: | - check status of my order - when are my shoes coming in - intent: return examples: | - start a return - I don't want my shoes anymore forms: {form_1}: {slot_a}: - type: from_entity entity: number {form_2}: {slot_a}: - type: from_entity entity: number responses: utter_ask_{form_1}_{slot_a}: - text: {utter_ask_form_1} utter_ask_{form_2}_{slot_a}: - text: {utter_ask_form_2} """ domain = Domain.from_yaml(domain) # Driving it like rasa/core/processor processor = MessageProcessor( default_agent.interpreter, default_agent.policy_ensemble, domain, InMemoryTrackerStore(domain), TemplatedNaturalLanguageGenerator(domain.templates), ) # activate the first form tracker = DialogueStateTracker.from_events( "some-sender", evts=[ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("order status", { "name": "form_1", "confidence": 1.0 }), DefinePrevUserUtteredFeaturization(False), ], ) # rasa/core/processor.predict_next_action prediction = PolicyPrediction([], "some_policy") action_1 = FormAction(form_1, None) await processor._run_action( action_1, tracker, CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), prediction, ) events_expected = [ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("order status", { "name": "form_1", "confidence": 1.0 }), DefinePrevUserUtteredFeaturization(False), ActionExecuted(form_1), ActiveLoop(form_1), SlotSet(REQUESTED_SLOT, slot_a), BotUttered( text=utter_ask_form_1, metadata={"template_name": f"utter_ask_{form_1}_{slot_a}"}, ), ] assert tracker.applied_events() == events_expected next_events = [ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("return my shoes", { "name": "form_2", "confidence": 1.0 }), DefinePrevUserUtteredFeaturization(False), ] tracker.update_with_events( next_events, domain, ) events_expected.extend(next_events) # form_1 is still active, and bot will first validate if the user utterance # provides valid data for the requested slot, which is rejected await processor._run_action( action_1, tracker, CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), prediction, ) events_expected.extend([ActionExecutionRejected(action_name=form_1)]) assert tracker.applied_events() == events_expected # Next, bot predicts form_2 action_2 = FormAction(form_2, None) await processor._run_action( action_2, tracker, CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), prediction, ) events_expected.extend([ ActionExecuted(form_2), ActiveLoop(form_2), SlotSet(REQUESTED_SLOT, slot_a), BotUttered( text=utter_ask_form_2, metadata={"template_name": f"utter_ask_{form_2}_{slot_a}"}, ), ]) assert tracker.applied_events() == events_expected
async def test_logging_of_end_to_end_action(): end_to_end_action = "hi, how are you?" domain = Domain( intents=["greet"], entities=[], slots=[], templates={}, action_names=[], forms={}, action_texts=[end_to_end_action], ) conversation_id = "test_logging_of_end_to_end_action" user_message = "/greet" class ConstantEnsemble(PolicyEnsemble): def __init__(self) -> None: super().__init__([]) self.number_of_calls = 0 def probabilities_using_best_policy( self, tracker: DialogueStateTracker, domain: Domain, interpreter: NaturalLanguageInterpreter, **kwargs: Any, ) -> PolicyPrediction: if self.number_of_calls == 0: prediction = PolicyPrediction.for_action_name( domain, end_to_end_action, "some policy" ) prediction.is_end_to_end_prediction = True self.number_of_calls += 1 return prediction else: return PolicyPrediction.for_action_name(domain, ACTION_LISTEN_NAME) tracker_store = InMemoryTrackerStore(domain) lock_store = InMemoryLockStore() processor = MessageProcessor( RegexInterpreter(), ConstantEnsemble(), domain, tracker_store, lock_store, NaturalLanguageGenerator.create(None, domain), ) await processor.handle_message(UserMessage(user_message, sender_id=conversation_id)) tracker = tracker_store.retrieve(conversation_id) expected_events = [ ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), ActionExecuted(ACTION_LISTEN_NAME), UserUttered(user_message, intent={"name": "greet"}), ActionExecuted(action_text=end_to_end_action), BotUttered("hi, how are you?", {}, {}, 123), ActionExecuted(ACTION_LISTEN_NAME), ] for event, expected in zip(tracker.events, expected_events): assert event == expected
async def test_restart_triggers_session_start( default_channel: CollectingOutputChannel, default_processor: MessageProcessor, monkeypatch: MonkeyPatch, ): # The rule policy is trained and used so as to allow the default action # ActionRestart to be predicted rule_policy = RulePolicy() rule_policy.train([], default_processor.domain, RegexInterpreter()) monkeypatch.setattr( default_processor.policy_ensemble, "policies", [rule_policy, *default_processor.policy_ensemble.policies], ) sender_id = uuid.uuid4().hex entity = "name" slot_1 = {entity: "name1"} await default_processor.handle_message( UserMessage(f"/greet{json.dumps(slot_1)}", default_channel, sender_id)) assert default_channel.latest_output() == { "recipient_id": sender_id, "text": "hey there name1!", } # This restarts the chat await default_processor.handle_message( UserMessage("/restart", default_channel, sender_id)) tracker = default_processor.tracker_store.get_or_create_tracker(sender_id) expected = [ ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), ActionExecuted(ACTION_LISTEN_NAME), UserUttered( f"/greet{json.dumps(slot_1)}", { INTENT_NAME_KEY: "greet", "confidence": 1.0 }, [{ "entity": entity, "start": 6, "end": 23, "value": "name1" }], ), SlotSet(entity, slot_1[entity]), DefinePrevUserUtteredFeaturization(use_text_for_featurization=False), ActionExecuted("utter_greet"), BotUttered("hey there name1!", metadata={"utter_action": "utter_greet"}), ActionExecuted(ACTION_LISTEN_NAME), UserUttered("/restart", { INTENT_NAME_KEY: "restart", "confidence": 1.0 }), DefinePrevUserUtteredFeaturization(use_text_for_featurization=False), ActionExecuted(ACTION_RESTART_NAME), Restarted(), ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), # No previous slot is set due to restart. ActionExecuted(ACTION_LISTEN_NAME), ] for actual, expected in zip(tracker.events, expected): assert actual == expected
"confidence": 1.0 }, []), ), (SlotSet("my_slot", "value"), SlotSet("my__other_slot", "value")), (Restarted(), None), (AllSlotsReset(), None), (ConversationPaused(), None), (ConversationResumed(), None), (StoryExported(), None), (ActionReverted(), None), (UserUtteranceReverted(), None), (SessionStarted(), None), (ActionExecuted("my_action"), ActionExecuted("my_other_action")), (FollowupAction("my_action"), FollowupAction("my_other_action")), ( BotUttered("my_text", {"my_data": 1}), BotUttered("my_other_test", {"my_other_data": 1}), ), ( AgentUttered("my_text", "my_data"), AgentUttered("my_other_test", "my_other_data"), ), ( ReminderScheduled("my_intent", datetime.now()), ReminderScheduled("my_other_intent", datetime.now()), ), ], ) def test_event_has_proper_implementation(one_event, another_event): # equals tests assert (one_event != another_event
"message_id": None, "metadata": {}, "text": "Hi my name is Peter", }, text="Hi my name is Peter", timestamp=1551953035.076376, ), SlotSet(key="name", timestamp=1551953035.076385, value="Peter"), ActionExecuted(action_name="utter_greet", timestamp=1551953040.607782), BotUttered( data={ "attachment": None, "buttons": None, "elements": None }, text="hey there Peter!", timestamp=1551953040.60779, ), ], ) TEST_FORMBOT_DIALOGUE = Dialogue( name="formbot", events=[ ActionExecuted(action_name="action_listen", timestamp=1551884035.892855), UserUttered( intent={ "confidence": 0.3748943507671356, "name": "greet"
async def test_remote_action_logs_events( default_channel: OutputChannel, default_nlg: NaturalLanguageGenerator, default_tracker: DialogueStateTracker, domain: Domain, ): 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", "response": None, "buttons": [{ "title": "cheap", "payload": "cheap" }], }, { "response": "utter_greet" }, ], } with aioresponses() as mocked: mocked.post("https://example.com/webhooks/actions", payload=response) events = await remote_action.run(default_channel, default_nlg, default_tracker, domain) r = latest_request(mocked, "post", "https://example.com/webhooks/actions") assert r assert json_of_latest_request(r) == { "domain": domain.as_dict(), "next_action": "my_action", "sender_id": "my-sender", "version": rasa.__version__, "tracker": { "latest_message": { "entities": [], "intent": {}, "text": None, "message_id": None, "metadata": {}, }, ACTIVE_LOOP: {}, "latest_action": {}, "latest_action_name": None, "sender_id": "my-sender", "paused": False, FOLLOWUP_ACTION: ACTION_LISTEN_NAME, "latest_event_time": None, "slots": { "name": None, REQUESTED_SLOT: None, SESSION_START_METADATA_SLOT: None, }, "events": [], "latest_input_channel": None, }, } assert len(events) == 3 # first two events are bot utterances assert events[0] == BotUttered( "test text", {"buttons": [{ "title": "cheap", "payload": "cheap" }]}) assert events[1] == BotUttered("hey there None!", metadata={"utter_action": "utter_greet"}) assert events[2] == SlotSet("name", "rasa")
LoopInterrupted(timestamp=None, is_interrupted=False), ActiveLoop(name="loop"), LegacyForm(name="my_form"), AllSlotsReset(), SlotSet(key="my_slot", value={}), SlotSet(key="my slot", value=[]), SlotSet(key="test", value=1), SlotSet(key="test", value="text"), ConversationResumed(), ConversationPaused(), FollowupAction(name="test"), StoryExported(), Restarted(), ActionReverted(), UserUtteranceReverted(), BotUttered(text="Test bot utterance"), UserUttered( parse_data={ "entities": [], "response_selector": { "all_retrieval_intents": [], "chitchat/ask_weather": { "response": {}, "ranking": [] }, }, }), UserUttered( text="hello", parse_data={ "intent": {
def test_json_parse_bot(): evt = {"event": "bot", "text": "Hey there!", "data": {}} assert Event.from_parameters(evt) == BotUttered("Hey there!", {})
async def test_remote_action_logs_events(default_channel, default_nlg, default_tracker, default_domain): 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", "template": None, "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_channel, default_nlg, default_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": "my-sender", "version": rasa.__version__, "tracker": { "latest_message": { "entities": [], "intent": {}, "text": None, "message_id": None, "metadata": {}, }, ACTIVE_LOOP: {}, "latest_action": {}, "latest_action_name": None, "sender_id": "my-sender", "paused": False, "followup_action": "action_listen", "latest_event_time": None, "slots": { "name": None }, "events": [], "latest_input_channel": None, }, } assert len(events) == 3 # first two events are bot utterances assert events[0] == BotUttered( "test text", {"buttons": [{ "title": "cheap", "payload": "cheap" }]}) assert events[1] == BotUttered("hey there None!", metadata={"template_name": "utter_greet"}) assert events[2] == SlotSet("name", "rasa")
events = await action.run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) assert events == expected_events @pytest.mark.parametrize( "custom_events", [ # Custom action returned no events [], # Custom action returned events but no `SlotSet` events [BotUttered("some text").as_dict()], # Custom action returned only `SlotSet` event for `required_slot` [SlotSet(REQUESTED_SLOT, "some value").as_dict()], ], ) async def test_no_slots_extracted_with_custom_slot_mappings( custom_events: List[Event]): form_name = "my form" events = [ ActiveLoop(form_name), SlotSet(REQUESTED_SLOT, "num_tables"), ActionExecuted(ACTION_LISTEN_NAME), UserUttered("off topic"), ] tracker = DialogueStateTracker.from_events(sender_id="bla", evts=events)