def test_ticket_exists_error(): def mocked_issue_ticket( self, conversation_id: Text, lock_lifetime: Union[float, int] = DEFAULT_LOCK_LIFETIME, ) -> None: # mock LockStore.issue_ticket() so it issues two tickets for the same # conversation ID simultaneously lock = self.get_or_create_lock(conversation_id) lock.issue_ticket(lock_lifetime) self.save_lock(lock) # issue another ticket for this lock lock_2 = copy.deepcopy(lock) lock_2.tickets.append(Ticket(1, time.time() + DEFAULT_LOCK_LIFETIME)) self.ensure_ticket_available(lock_2) lock_store = InMemoryLockStore() conversation_id = "my id 3" with patch.object(InMemoryLockStore, "issue_ticket", mocked_issue_ticket): with pytest.raises(TicketExistsError): lock_store.issue_ticket(conversation_id)
def test_get_next_action_probabilities_pass_policy_predictions_without_interpreter_arg( predict_function: Callable, ): policy = TEDPolicy() policy.predict_action_probabilities = predict_function ensemble = SimplePolicyEnsemble(policies=[policy]) interpreter = Mock() domain = Domain.empty() processor = MessageProcessor( interpreter, ensemble, domain, InMemoryTrackerStore(domain), InMemoryLockStore(), Mock(), ) with pytest.warns(DeprecationWarning): processor._get_next_action_probabilities( DialogueStateTracker.from_events( "lala", [ActionExecuted(ACTION_LISTEN_NAME)] ) )
def test_get_next_action_probabilities_passes_interpreter_to_policies( monkeypatch: MonkeyPatch, ): policy = TEDPolicy() test_interpreter = Mock() def predict_action_probabilities( tracker: DialogueStateTracker, domain: Domain, interpreter: NaturalLanguageInterpreter, **kwargs, ) -> PolicyPrediction: assert interpreter == test_interpreter return PolicyPrediction([1, 0], "some-policy", policy_priority=1) policy.predict_action_probabilities = predict_action_probabilities ensemble = SimplePolicyEnsemble(policies=[policy]) domain = Domain.empty() processor = MessageProcessor( test_interpreter, ensemble, domain, InMemoryTrackerStore(domain), InMemoryLockStore(), Mock(), ) # This should not raise processor._get_next_action_probabilities( DialogueStateTracker.from_events("lala", [ActionExecuted(ACTION_LISTEN_NAME)]) )
def test_predict_next_action_raises_limit_reached_exception(domain: Domain): interpreter = RegexInterpreter() ensemble = SimplePolicyEnsemble( policies=[RulePolicy(), MemoizationPolicy()]) tracker_store = InMemoryTrackerStore(domain) lock_store = InMemoryLockStore() processor = MessageProcessor( interpreter, ensemble, domain, tracker_store, lock_store, TemplatedNaturalLanguageGenerator(domain.responses), max_number_of_predictions=1, ) tracker = DialogueStateTracker.from_events( "test", evts=[ ActionExecuted(ACTION_LISTEN_NAME), UserUttered("Hi!"), ActionExecuted("test_action"), ], ) tracker.set_latest_action({"action_name": "test_action"}) with pytest.raises(ActionLimitReached): processor.predict_next_action(tracker)
def test_lock_expiration(): lock_store = InMemoryLockStore() conversation_id = "my id 2" lock = lock_store.create_lock(conversation_id) lock_store.save_lock(lock) # issue ticket with long lifetime ticket = lock.issue_ticket(10) assert ticket == 0 assert not lock._ticket_for_ticket_number(ticket).has_expired() # issue ticket with short lifetime ticket = lock.issue_ticket(0.00001) time.sleep(0.00002) assert ticket == 1 assert lock._ticket_for_ticket_number(ticket) is None # newly assigned ticket should get number 1 again assert lock.issue_ticket(10) == 1
async def default_processor(default_agent: Agent) -> MessageProcessor: tracker_store = InMemoryTrackerStore(default_agent.domain) lock_store = InMemoryLockStore() return MessageProcessor( default_agent.interpreter, default_agent.policy_ensemble, default_agent.domain, tracker_store, lock_store, TemplatedNaturalLanguageGenerator(default_agent.domain.responses), )
def test_create_lock_store(): lock_store = InMemoryLockStore() conversation_id = "my id 0" # create and lock lock = lock_store.create_lock(conversation_id) lock_store.save_lock(lock) lock = lock_store.get_lock(conversation_id) assert lock assert lock.conversation_id == conversation_id
async def test_processor_logs_text_tokens_in_tracker(mood_agent: Agent): text = "Hello there" tokenizer = WhitespaceTokenizer() tokens = tokenizer.tokenize(Message(data={"text": text}), "text") indices = [(t.start, t.end) for t in tokens] message = UserMessage(text) tracker_store = InMemoryTrackerStore(mood_agent.domain) lock_store = InMemoryLockStore() processor = MessageProcessor( mood_agent.interpreter, mood_agent.policy_ensemble, mood_agent.domain, tracker_store, lock_store, TemplatedNaturalLanguageGenerator(mood_agent.domain.responses), ) tracker = await processor.log_message(message) event = tracker.get_last_event_for(event_type=UserUttered) event_tokens = event.as_dict().get("parse_data").get("text_tokens") assert event_tokens == indices
def _create_lock_store(store: Optional[LockStore]) -> LockStore: if store is not None: return store return InMemoryLockStore()
# issue one long- and one short-lived ticket _ = list(map(lock.issue_ticket, [k for k in [0.01, 10]])) # both tickets are there assert len(lock.tickets) == 2 # sleep and only one ticket should be left time.sleep(0.02) lock.remove_expired_tickets() assert len(lock.tickets) == 1 @pytest.mark.parametrize( "lock_store", [InMemoryLockStore(), FakeRedisLockStore()]) def test_create_lock_store(lock_store: LockStore): conversation_id = "my id 0" # create and lock lock = lock_store.create_lock(conversation_id) lock_store.save_lock(lock) lock = lock_store.get_lock(conversation_id) assert lock assert lock.conversation_id == conversation_id @pytest.mark.parametrize( "lock_store", [InMemoryLockStore(), FakeRedisLockStore()]) def test_serve_ticket(lock_store: LockStore):
@pytest.mark.parametrize( "env_value,lock_store,expected", [ (1, "redis", 1), (4, "redis", 4), (None, "redis", 1), (0, "redis", 1), (-4, "redis", 1), ("illegal value", "redis", 1), (None, None, 1), (None, "in_memory", 1), (5, "in_memory", 1), (2, None, 1), (0, "in_memory", 1), (3, RedisLockStore(), 3), (2, InMemoryLockStore(), 1), ], ) def test_get_number_of_sanic_workers( env_value: Optional[Text], lock_store: Union[LockStore, Text, None], expected: Optional[int], ): # remember pre-test value of SANIC_WORKERS env var pre_test_value = os.environ.get(ENV_SANIC_WORKERS) # set env var to desired value and make assertion if env_value is not None: os.environ[ENV_SANIC_WORKERS] = str(env_value) # lock_store may be string or LockStore object
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
def test_predict_next_action_with_hidden_rules(): rule_intent = "rule_intent" rule_action = "rule_action" story_intent = "story_intent" story_action = "story_action" rule_slot = "rule_slot" story_slot = "story_slot" domain = Domain.from_yaml(f""" version: "2.0" intents: - {rule_intent} - {story_intent} actions: - {rule_action} - {story_action} slots: {rule_slot}: type: text {story_slot}: type: text """) rule = TrackerWithCachedStates.from_events( "rule", domain=domain, slots=domain.slots, evts=[ ActionExecuted(RULE_SNIPPET_ACTION_NAME), ActionExecuted(ACTION_LISTEN_NAME), UserUttered(intent={"name": rule_intent}), ActionExecuted(rule_action), SlotSet(rule_slot, rule_slot), ActionExecuted(ACTION_LISTEN_NAME), ], is_rule_tracker=True, ) story = TrackerWithCachedStates.from_events( "story", domain=domain, slots=domain.slots, evts=[ ActionExecuted(ACTION_LISTEN_NAME), UserUttered(intent={"name": story_intent}), ActionExecuted(story_action), SlotSet(story_slot, story_slot), ActionExecuted(ACTION_LISTEN_NAME), ], ) interpreter = RegexInterpreter() ensemble = SimplePolicyEnsemble( policies=[RulePolicy(), MemoizationPolicy()]) ensemble.train([rule, story], domain, interpreter) tracker_store = InMemoryTrackerStore(domain) lock_store = InMemoryLockStore() processor = MessageProcessor( interpreter, ensemble, domain, tracker_store, lock_store, TemplatedNaturalLanguageGenerator(domain.responses), ) tracker = DialogueStateTracker.from_events( "casd", evts=[ ActionExecuted(ACTION_LISTEN_NAME), UserUttered(intent={"name": rule_intent}), ], slots=domain.slots, ) action, prediction = processor.predict_next_action(tracker) assert action._name == rule_action assert prediction.hide_rule_turn processor._log_action_on_tracker(tracker, action, [SlotSet(rule_slot, rule_slot)], prediction) action, prediction = processor.predict_next_action(tracker) assert isinstance(action, ActionListen) assert prediction.hide_rule_turn processor._log_action_on_tracker(tracker, action, None, prediction) tracker.events.append(UserUttered(intent={"name": story_intent})) # rules are hidden correctly if memo policy predicts next actions correctly action, prediction = processor.predict_next_action(tracker) assert action._name == story_action assert not prediction.hide_rule_turn processor._log_action_on_tracker(tracker, action, [SlotSet(story_slot, story_slot)], prediction) action, prediction = processor.predict_next_action(tracker) assert isinstance(action, ActionListen) assert not prediction.hide_rule_turn
def test_remove_expired_tickets(): lock = TicketLock("random id 1") # issue one long- and one short-lived ticket _ = list(map(lock.issue_ticket, [k for k in [0.01, 10]])) # both tickets are there assert len(lock.tickets) == 2 # sleep and only one ticket should be left time.sleep(0.02) lock.remove_expired_tickets() assert len(lock.tickets) == 1 @pytest.mark.parametrize("lock_store", [InMemoryLockStore(), FakeRedisLockStore()]) def test_create_lock_store(lock_store: LockStore): conversation_id = "my id 0" # create and lock lock = lock_store.create_lock(conversation_id) lock_store.save_lock(lock) lock = lock_store.get_lock(conversation_id) assert lock assert lock.conversation_id == conversation_id @pytest.mark.parametrize("lock_store", [InMemoryLockStore(), FakeRedisLockStore()]) def test_serve_ticket(lock_store: LockStore): conversation_id = "my id 1"
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), InMemoryLockStore(), TemplatedNaturalLanguageGenerator(domain.responses), ) # 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.responses), 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={"utter_action": 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.responses), 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.responses), prediction, ) events_expected.extend([ ActionExecuted(form_2), ActiveLoop(form_2), SlotSet(REQUESTED_SLOT, slot_a), BotUttered( text=utter_ask_form_2, metadata={"utter_action": f"utter_ask_{form_2}_{slot_a}"}, ), ]) assert tracker.applied_events() == events_expected
def test_serve_ticket(): lock_store = InMemoryLockStore() conversation_id = "my id 1" lock = lock_store.create_lock(conversation_id) lock_store.save_lock(lock) # issue ticket with long lifetime ticket_0 = lock_store.issue_ticket(conversation_id, 10) assert ticket_0 == 0 lock = lock_store.get_lock(conversation_id) assert lock.last_issued == ticket_0 assert lock.now_serving == ticket_0 assert lock.is_someone_waiting() # issue another ticket ticket_1 = lock_store.issue_ticket(conversation_id, 10) # finish serving ticket_0 lock_store.finish_serving(conversation_id, ticket_0) lock = lock_store.get_lock(conversation_id) assert lock.last_issued == ticket_1 assert lock.now_serving == ticket_1 assert lock.is_someone_waiting() # serve second ticket and no one should be waiting lock_store.finish_serving(conversation_id, ticket_1) lock = lock_store.get_lock(conversation_id) assert not lock.is_someone_waiting()