def as_story_string(self, flat=False, e2e=False): # if the result should be flattened, we # will exclude the caption and any checkpoints. for s in self.start_checkpoints: if s.name == STORY_START: # first story step in the story, so reset helper self.story_string_helper = StoryStringHelper() if flat: result = "" else: result = "\n## {}\n".format(self.block_name) for s in self.start_checkpoints: if s.name != STORY_START: result += self._checkpoint_string(s) for s in self.events: if isinstance(s, UserUttered): if self.story_string_helper.active_form is None: result += self._user_string(s, e2e) else: # form is active # it is not known whether the form will be # successfully executed, so store this # story string for later self._store_user_strings(s, e2e, FORM_PREFIX) elif isinstance(s, Form): # form got either activated or deactivated self.story_string_helper.active_form = s.name if self.story_string_helper.active_form is None: # form deactivated, so form succeeded, # so add story string with form prefix result += self.story_string_helper.form_prefix_string # remove all stored story strings self._reset_stored_strings() result += self._bot_string(s) elif isinstance(s, FormValidation): self.story_string_helper.form_validation = s.validate elif isinstance(s, ActionExecutionRejected): if s.action_name == self.story_string_helper.active_form: # form rejected self.story_string_helper.form_rejected = True elif isinstance(s, ActionExecuted): if self._is_action_listen(s): pass elif self.story_string_helper.active_form is None: result += self._bot_string(s) else: # form is active if self.story_string_helper.form_rejected: if (self.story_string_helper.form_validation and s.action_name == self.story_string_helper.active_form): result += self._bot_string( ActionExecuted(ACTION_LISTEN_NAME)) result += (self.story_string_helper. form_prefix_string) else: result += (self.story_string_helper. no_form_prefix_string) # form rejected, add story string without form prefix result += self._bot_string(s) else: # form succeeded, so add story string with form prefix result += (self.story_string_helper. form_prefix_string) result += self._bot_string(s, FORM_PREFIX) # remove all stored story strings self._reset_stored_strings() if s.action_name == self.story_string_helper.active_form: # form was successfully executed self.story_string_helper.form_rejected = False self.story_string_helper.form_validation = True elif isinstance(s, SlotSet): if self.story_string_helper.active_form is None: result += self._bot_string(s) else: # form is active # it is not known whether the form will be # successfully executed, so store this # story string for later # slots should be always printed without prefix self._store_bot_strings(s) elif isinstance(s, Event): converted = s.as_story_string() if converted: if self.story_string_helper.active_form is None: result += self._bot_string(s) else: # form is active # it is not known whether the form will be # successfully executed, so store this # story string for later self._store_bot_strings(s, FORM_PREFIX) else: raise Exception("Unexpected element in story step: " "{}".format(s)) if (not self.end_checkpoints and self.story_string_helper.active_form is not None): # there are no end checkpoints # form is active # add story string with form prefix result += self.story_string_helper.form_prefix_string # remove all stored story strings self._reset_stored_strings() if not flat: for e in self.end_checkpoints: result += "> {}\n".format(e.as_story_string()) return result
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 assert list(tracker.events) == [ ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), ActionExecuted(ACTION_LISTEN_NAME), UserUttered( f"/greet{json.dumps(slot_1)}", {"name": "greet", "confidence": 1.0}, [{"entity": entity, "start": 6, "end": 22, "value": "Core"}], ), SlotSet(entity, slot_1[entity]), ActionExecuted("utter_greet"), BotUttered("hey there Core!"), 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)}", {"name": "greet", "confidence": 1.0}, [ { "entity": entity, "start": 6, "end": 42, "value": "post-session start hello", } ], ), SlotSet(entity, slot_2[entity]), ActionExecuted(ACTION_LISTEN_NAME), ]
async def test_persist_form_story(tmpdir): domain = Domain.load("data/test_domains/form.yml") tracker = DialogueStateTracker("", domain.slots) story = ("* greet\n" " - utter_greet\n" "* start_form\n" " - some_form\n" ' - form{"name": "some_form"}\n' "* default\n" " - utter_default\n" " - some_form\n" "* stop\n" " - utter_ask_continue\n" "* affirm\n" " - some_form\n" "* stop\n" " - utter_ask_continue\n" " - action_listen\n" "* form: inform\n" " - some_form\n" ' - form{"name": null}\n' "* goodbye\n" " - utter_goodbye\n") # simulate talking to the form events = [ UserUttered(intent={"name": "greet"}), ActionExecuted("utter_greet"), ActionExecuted("action_listen"), # start the form UserUttered(intent={"name": "start_form"}), ActionExecuted("some_form"), Form("some_form"), ActionExecuted("action_listen"), # out of form input UserUttered(intent={"name": "default"}), ActionExecutionRejected("some_form"), ActionExecuted("utter_default"), ActionExecuted("some_form"), ActionExecuted("action_listen"), # out of form input UserUttered(intent={"name": "stop"}), ActionExecutionRejected("some_form"), ActionExecuted("utter_ask_continue"), ActionExecuted("action_listen"), # out of form input but continue with the form UserUttered(intent={"name": "affirm"}), FormValidation(False), ActionExecuted("some_form"), ActionExecuted("action_listen"), # out of form input UserUttered(intent={"name": "stop"}), ActionExecutionRejected("some_form"), ActionExecuted("utter_ask_continue"), ActionExecuted("action_listen"), # form input UserUttered(intent={"name": "inform"}), FormValidation(True), ActionExecuted("some_form"), ActionExecuted("action_listen"), Form(None), UserUttered(intent={"name": "goodbye"}), ActionExecuted("utter_goodbye"), ActionExecuted("action_listen"), ] [tracker.update(e) for e in events] assert story in tracker.export_stories()
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")
) # retrieve the updated tracker t = default_processor.tracker_store.retrieve(sender_id) assert len(t.events) == 4 # nothing should have been executed @pytest.mark.parametrize( "event_to_apply,session_expiration_time_in_minutes,has_expired", [ # last user event is way in the past (UserUttered(timestamp=1), 60, True), # user event are very recent (UserUttered("hello", timestamp=time.time()), 60, False,), # there is user event (ActionExecuted(ACTION_LISTEN_NAME, timestamp=time.time()), 60, False), # Old event, but sessions are disabled (UserUttered("hello", timestamp=1), 0, False), # there is no event (None, 1, False), ], ) async def test_has_session_expired( event_to_apply: Optional[Event], session_expiration_time_in_minutes: float, has_expired: bool, default_processor: MessageProcessor, ): sender_id = uuid.uuid4().hex default_processor.domain.session_config = SessionConfig(
}, []), 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_json_parse_action(): evt = {"event": "action", "name": "my_action"} assert Event.from_parameters(evt) == ActionExecuted("my_action")
def test_get_last_event_with_reverted(): events = [ActionExecuted("one"), ActionReverted(), user_uttered("two", 1)] tracker = get_tracker(events) assert tracker.get_last_event_for(ActionExecuted) is None
async def record_messages( endpoint: EndpointConfig, sender_id: Text = UserMessage.DEFAULT_SENDER_ID, max_message_limit: Optional[int] = None, finetune: bool = False, stories: Optional[Text] = None, skip_visualization: bool = False, ): """Read messages from the command line and print bot responses.""" from rasa.core import training try: _print_help(skip_visualization) try: domain = await retrieve_domain(endpoint) except ClientError: logger.exception( "Failed to connect to Rasa Core server at '{}'. " "Is the server running?".format(endpoint.url) ) return trackers = await training.load_data( stories, Domain.from_dict(domain), augmentation_factor=0, use_story_concatenation=False, ) intents = [next(iter(i)) for i in (domain.get("intents") or [])] num_messages = 0 sender_ids = [t.events for t in trackers] + [sender_id] if not skip_visualization: plot_file = "story_graph.dot" await _plot_trackers(sender_ids, plot_file, endpoint) else: plot_file = None while not utils.is_limit_reached(num_messages, max_message_limit): try: if await is_listening_for_message(sender_id, endpoint): await _enter_user_message(sender_id, endpoint) await _validate_nlu(intents, endpoint, sender_id) await _predict_till_next_listen( endpoint, sender_id, finetune, sender_ids, plot_file ) num_messages += 1 except RestartConversation: await send_event(endpoint, sender_id, Restarted().as_dict()) await send_event( endpoint, sender_id, ActionExecuted(ACTION_LISTEN_NAME).as_dict() ) logger.info("Restarted conversation, starting a new one.") except UndoLastStep: await _undo_latest(sender_id, endpoint) await _print_history(sender_id, endpoint) except ForkTracker: await _print_history(sender_id, endpoint) evts_fork = await _request_fork_from_user(sender_id, endpoint) await send_event(endpoint, sender_id, Restarted().as_dict()) if evts_fork: for evt in evts_fork: await send_event(endpoint, sender_id, evt) logger.info("Restarted conversation at fork.") await _print_history(sender_id, endpoint) await _plot_trackers(sender_ids, plot_file, endpoint) except Abort: return except Exception: logger.exception("An exception occurred while recording messages.") raise
tracker._set_slot(slot_name, value_to_set) # assert that the tracker contains the slot with the modified value assert tracker.get_slot(slot_name) == value_to_set # assert that the initial slot has not been affected assert slot.value == initial_value @pytest.mark.parametrize( "events, expected_applied_events", [ ( [ # Form gets triggered. ActionExecuted(ACTION_LISTEN_NAME), user_uttered("fill_whole_form"), # Form executes and fills slots. ActionExecuted("loop"), ActiveLoop("loop"), SlotSet("slot1", "value"), SlotSet("slot2", "value2"), ], [ ActionExecuted(ACTION_LISTEN_NAME), user_uttered("fill_whole_form"), ActionExecuted("loop"), ActiveLoop("loop"), SlotSet("slot1", "value"), SlotSet("slot2", "value2"), ],
def test_get_last_event_for(): events = [ActionExecuted("one"), user_uttered("two", 1)] tracker = get_tracker(events) assert tracker.get_last_event_for(ActionExecuted).action_name == "one"
async def test_one_stage_fallback_rule(): domain = Domain.from_yaml(f""" intents: - {GREET_INTENT_NAME} - {DEFAULT_NLU_FALLBACK_INTENT_NAME} actions: - {UTTER_GREET_ACTION} """) fallback_recover_rule = TrackerWithCachedStates.from_events( "bla", domain=domain, slots=domain.slots, evts=[ ActionExecuted(RULE_SNIPPET_ACTION_NAME), ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": DEFAULT_NLU_FALLBACK_INTENT_NAME}), ActionExecuted(ACTION_DEFAULT_FALLBACK_NAME), ActionExecuted(ACTION_LISTEN_NAME), ], is_rule_tracker=True, ) greet_rule_which_only_applies_at_start = TrackerWithCachedStates.from_events( "bla", domain=domain, evts=[ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": GREET_INTENT_NAME}), ActionExecuted(UTTER_GREET_ACTION), ActionExecuted(ACTION_LISTEN_NAME), ], is_rule_tracker=True, ) policy = RulePolicy() policy.train( [greet_rule_which_only_applies_at_start, fallback_recover_rule], domain, RegexInterpreter(), ) # RulePolicy predicts fallback action conversation_events = [ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("dasdakl;fkasd", {"name": DEFAULT_NLU_FALLBACK_INTENT_NAME}), ] tracker = DialogueStateTracker.from_events("casd", evts=conversation_events, slots=domain.slots) action_probabilities = policy.predict_action_probabilities(tracker, domain) assert_predicted_action(action_probabilities, domain, ACTION_DEFAULT_FALLBACK_NAME) # Fallback action reverts fallback events, next action is `ACTION_LISTEN` conversation_events += await ActionDefaultFallback().run( CollectingOutputChannel(), TemplatedNaturalLanguageGenerator(domain.templates), tracker, domain, ) # Rasa is back on track when user rephrased intent conversation_events += [ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("haha", {"name": GREET_INTENT_NAME}), ] tracker = DialogueStateTracker.from_events("casd", evts=conversation_events, slots=domain.slots) action_probabilities = policy.predict_action_probabilities(tracker, domain) assert_predicted_action(action_probabilities, domain, UTTER_GREET_ACTION)
async def test_form_unhappy_path_triggering_form_again(): form_name = "some_form" handle_rejection_action_name = "utter_handle_rejection" domain = Domain.from_yaml(f""" intents: - {GREET_INTENT_NAME} actions: - {UTTER_GREET_ACTION} - {handle_rejection_action_name} - some-action slots: {REQUESTED_SLOT}: type: unfeaturized forms: - {form_name} """) unhappy_rule = TrackerWithCachedStates.from_events( "bla", domain=domain, slots=domain.slots, evts=[ # We are in an active form Form(form_name), SlotSet(REQUESTED_SLOT, "bla"), ActionExecuted(RULE_SNIPPET_ACTION_NAME), ActionExecuted(ACTION_LISTEN_NAME), # When a user says "hi", and the form is unhappy, we want to run a specific # action UserUttered("haha", {"name": GREET_INTENT_NAME}), ActionExecuted(handle_rejection_action_name), ActionExecuted(form_name), ActionExecuted(ACTION_LISTEN_NAME), ], is_rule_tracker=True, ) policy = RulePolicy() policy.train([unhappy_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, ) assert_predicted_action(action_probabilities, domain, handle_rejection_action_name) # Check that RulePolicy triggers form again after handling unhappy path conversation_events.append(ActionExecuted(handle_rejection_action_name)) action_probabilities = policy.predict_action_probabilities( DialogueStateTracker.from_events("casd", evts=conversation_events, slots=domain.slots), domain, ) assert_predicted_action(action_probabilities, domain, form_name)
Form, SlotSet, ActionExecutionRejected, ) from rasa.core.interpreter import RegexInterpreter from rasa.core.nlg import TemplatedNaturalLanguageGenerator from rasa.core.policies.rule_policy import RulePolicy from rasa.core.trackers import DialogueStateTracker from rasa.core.training.generator import TrackerWithCachedStates UTTER_GREET_ACTION = "utter_greet" GREET_INTENT_NAME = "greet" GREET_RULE = DialogueStateTracker.from_events( "bla", evts=[ ActionExecuted(RULE_SNIPPET_ACTION_NAME), ActionExecuted(ACTION_LISTEN_NAME), # Greet is a FAQ here and gets triggered in any context UserUttered("haha", {"name": GREET_INTENT_NAME}), ActionExecuted(UTTER_GREET_ACTION), ], ) GREET_RULE.is_rule_tracker = True def test_rule_policy_has_max_history_none(): policy = RulePolicy() assert policy.featurizer.max_history is None def _form_submit_rule(domain: Domain, submit_action_name: Text,
def _add_action_listen(self, events) -> None: if not events or not self._is_action_listen(events[-1]): # do not add second action_listen events.append(ActionExecuted(ACTION_LISTEN_NAME))
def _two_stage_clarification_request() -> List[Event]: return [ActionExecuted(ACTION_TWO_STAGE_FALLBACK_NAME), BotUttered("please affirm")]