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( session_expiration_time_in_minutes, True ) # create new tracker without events tracker = default_processor.tracker_store.get_or_create_tracker(sender_id) tracker.events.clear() # apply desired event if event_to_apply: tracker.update(event_to_apply) # noinspection PyProtectedMember assert default_processor._has_session_expired(tracker) == has_expired
def create_processor( self, preprocessor: Optional[Callable[[Text], Text]] = None) -> MessageProcessor: """Instantiates a processor based on the set state of the agent.""" # Checks that the interpreter and tracker store are set and # creates a processor if not self.is_ready(): raise AgentNotReady( "Agent needs to be prepared before usage. You need to set an " "interpreter and a tracker store.") return MessageProcessor( self.interpreter, self.policy_ensemble, self.domain, self.tracker_store, self.nlg, action_endpoint=self.action_endpoint, message_preprocessor=preprocessor, )
async def test_fetch_tracker_with_initial_session_does_not_update_session( default_channel: CollectingOutputChannel, default_processor: MessageProcessor, monkeypatch: MonkeyPatch, ): conversation_id = uuid.uuid4().hex # the domain has a session expiration time of one second monkeypatch.setattr( default_processor.tracker_store.domain, "session_config", SessionConfig(carry_over_slots=True, session_expiration_time=1 / 60), ) now = time.time() # the tracker initially contains events initial_events = [ ActionExecuted(ACTION_SESSION_START_NAME, timestamp=now - 10), SessionStarted(timestamp=now - 9), ActionExecuted(ACTION_LISTEN_NAME, timestamp=now - 8), UserUttered( "/greet", {INTENT_NAME_KEY: "greet", "confidence": 1.0}, timestamp=now - 7 ), ] tracker = DialogueStateTracker.from_events(conversation_id, initial_events) default_processor.tracker_store.save(tracker) tracker = await default_processor.fetch_tracker_with_initial_session( conversation_id, default_channel ) # the conversation session has expired, but calling # `fetch_tracker_with_initial_session()` did not update it assert default_processor._has_session_expired(tracker) assert [event.as_dict() for event in tracker.events] == [ event.as_dict() for event in initial_events ]
async def get_response(self, request): """Train the engine. """ if self.config.get('domain') is None: self.config.setdefault( 'domain', Domain.from_file("data/" + self.config['skill-id'] + "/core/model")) self.config.setdefault( 'tracker_store', ArcusTrackerStore(self.config.get('domain'), self.asm)) domain = self.config.get('domain') tracker_store = self.config.get('tracker_store') nlg = NaturalLanguageGenerator.create(None, domain) policy_ensemble = SimplePolicyEnsemble.load("data/" + self.config['skill-id'] + "/core") interpreter = LocalNLUInterpreter(request) url = 'http://localhost:8080/api/v1/skill/generic_action' processor = MessageProcessor(interpreter, policy_ensemble, domain, tracker_store, nlg, action_endpoint=EndpointConfig(url), message_preprocessor=None) message_nlu = UserMessage(request['text'], None, request['user'], input_channel=request['channel']) result = await processor.handle_message(message_nlu) if result is not None and len(result) > 0: return {"text": result[0]['text']} else: _LOGGER.info(result) return {"text": "error"}
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
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
class Agent: """The Agent class provides an interface for the most important Rasa functionality. This includes training, handling messages, loading a dialogue model, getting the next action, and handling a channel. """ def __init__( self, domain: Optional[Union[Text, Domain]] = None, generator: Union[EndpointConfig, NaturalLanguageGenerator, None] = None, tracker_store: Optional[TrackerStore] = None, lock_store: Optional[LockStore] = None, action_endpoint: Optional[EndpointConfig] = None, fingerprint: Optional[Text] = None, model_server: Optional[EndpointConfig] = None, remote_storage: Optional[Text] = None, http_interpreter: Optional[RasaNLUHttpInterpreter] = None, ): """Initializes an `Agent`.""" self.domain = domain self.processor: Optional[MessageProcessor] = None self.nlg = NaturalLanguageGenerator.create(generator, self.domain) self.tracker_store = self._create_tracker_store( tracker_store, self.domain) self.lock_store = self._create_lock_store(lock_store) self.action_endpoint = action_endpoint self.http_interpreter = http_interpreter self._set_fingerprint(fingerprint) self.model_server = model_server self.remote_storage = remote_storage @classmethod def load( cls, model_path: Union[Text, Path], domain: Optional[Union[Text, Domain]] = None, generator: Union[EndpointConfig, NaturalLanguageGenerator, None] = None, tracker_store: Optional[TrackerStore] = None, lock_store: Optional[LockStore] = None, action_endpoint: Optional[EndpointConfig] = None, fingerprint: Optional[Text] = None, model_server: Optional[EndpointConfig] = None, remote_storage: Optional[Text] = None, http_interpreter: Optional[RasaNLUHttpInterpreter] = None, ) -> Agent: """Constructs a new agent and loads the processer and model.""" agent = Agent( domain=domain, generator=generator, tracker_store=tracker_store, lock_store=lock_store, action_endpoint=action_endpoint, fingerprint=fingerprint, model_server=model_server, remote_storage=remote_storage, http_interpreter=http_interpreter, ) agent.load_model(model_path=model_path, fingerprint=fingerprint) return agent def load_model(self, model_path: Union[Text, Path], fingerprint: Optional[Text] = None) -> None: """Loads the agent's model and processor given a new model path.""" self.processor = MessageProcessor( model_path=model_path, tracker_store=self.tracker_store, lock_store=self.lock_store, action_endpoint=self.action_endpoint, generator=self.nlg, http_interpreter=self.http_interpreter, ) self.domain = self.processor.domain self._set_fingerprint(fingerprint) # update domain on all instances self.tracker_store.domain = self.domain if hasattr(self.nlg, "responses"): self.nlg.responses = self.domain.responses if self.domain else {} @property def model_id(self) -> Optional[Text]: """Returns the model_id from processor's model_metadata.""" return self.processor.model_metadata.model_id if self.processor else None @property def model_name(self) -> Optional[Text]: """Returns the model name from processor's model_path.""" return self.processor.model_path.name if self.processor else None def is_ready(self) -> bool: """Check if all necessary components are instantiated to use agent.""" return self.tracker_store is not None and self.processor is not None @agent_must_be_ready async def parse_message(self, message_data: Text) -> Dict[Text, Any]: """Handles message text and intent payload input messages. The return value of this function is parsed_data. Args: message_data (Text): Contain the received message in text or\ intent payload format. Returns: The parsed message. Example: {\ "text": '/greet{"name":"Rasa"}',\ "intent": {"name": "greet", "confidence": 1.0},\ "intent_ranking": [{"name": "greet", "confidence": 1.0}],\ "entities": [{"entity": "name", "start": 6,\ "end": 21, "value": "Rasa"}],\ } """ message = UserMessage(message_data) return await self.processor.parse_message(message) async def handle_message( self, message: UserMessage) -> Optional[List[Dict[Text, Any]]]: """Handle a single message.""" if not self.is_ready(): logger.info("Ignoring message as there is no agent to handle it.") return None async with self.lock_store.lock(message.sender_id): return await self.processor.handle_message(message) @agent_must_be_ready async def predict_next_for_sender_id( self, sender_id: Text) -> Optional[Dict[Text, Any]]: """Predict the next action for a sender id.""" return await self.processor.predict_next_for_sender_id(sender_id) @agent_must_be_ready def predict_next_with_tracker( self, tracker: DialogueStateTracker, verbosity: EventVerbosity = EventVerbosity.AFTER_RESTART, ) -> Optional[Dict[Text, Any]]: """Predicts the next action.""" return self.processor.predict_next_with_tracker(tracker, verbosity) @agent_must_be_ready async def log_message(self, message: UserMessage) -> DialogueStateTracker: """Append a message to a dialogue - does not predict actions.""" return await self.processor.log_message(message) @agent_must_be_ready async def execute_action( self, sender_id: Text, action: Text, output_channel: OutputChannel, policy: Optional[Text], confidence: Optional[float], ) -> Optional[DialogueStateTracker]: """Executes an action.""" prediction = PolicyPrediction.for_action_name(self.domain, action, policy, confidence or 0.0) return await self.processor.execute_action(sender_id, action, output_channel, self.nlg, prediction) @agent_must_be_ready async def trigger_intent( self, intent_name: Text, entities: List[Dict[Text, Any]], output_channel: OutputChannel, tracker: DialogueStateTracker, ) -> None: """Trigger a user intent, e.g. triggered by an external event.""" await self.processor.trigger_external_user_uttered( intent_name, entities, tracker, output_channel) @agent_must_be_ready async def handle_text( self, text_message: Union[Text, Dict[Text, Any]], output_channel: Optional[OutputChannel] = None, sender_id: Optional[Text] = DEFAULT_SENDER_ID, ) -> Optional[List[Dict[Text, Any]]]: """Handle a single message. If a message preprocessor is passed, the message will be passed to that function first and the return value is then used as the input for the dialogue engine. The return value of this function depends on the ``output_channel``. If the output channel is not set, set to ``None``, or set to ``CollectingOutputChannel`` this function will return the messages the bot wants to respond. :Example: >>> from rasa.core.agent import Agent >>> agent = Agent.load("examples/moodbot/models") >>> await agent.handle_text("hello") [u'how can I help you?'] """ if isinstance(text_message, str): text_message = {"text": text_message} msg = UserMessage(text_message.get("text"), output_channel, sender_id) return await self.handle_message(msg) def _set_fingerprint(self, fingerprint: Optional[Text] = None) -> None: if fingerprint: self.fingerprint = fingerprint else: self.fingerprint = uuid.uuid4().hex @staticmethod def _create_tracker_store(store: Optional[TrackerStore], domain: Domain) -> TrackerStore: if store is not None: store.domain = domain tracker_store = store else: tracker_store = InMemoryTrackerStore(domain) return FailSafeTrackerStore(tracker_store) @staticmethod def _create_lock_store(store: Optional[LockStore]) -> LockStore: if store is not None: return store return InMemoryLockStore() def load_model_from_remote_storage(self, model_name: Text) -> None: """Loads an Agent from remote storage.""" from rasa.nlu.persistor import get_persistor persistor = get_persistor(self.remote_storage) if persistor is not None: with tempfile.TemporaryDirectory() as temporary_directory: persistor.retrieve(model_name, temporary_directory) self.load_model(temporary_directory) else: raise RasaException( f"Persistor not found for remote storage: '{self.remote_storage}'." )
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_policy_events_are_applied_to_tracker( default_processor: MessageProcessor, monkeypatch: MonkeyPatch ): expected_action = ACTION_LISTEN_NAME policy_events = [LoopInterrupted(True)] conversation_id = "test_policy_events_are_applied_to_tracker" user_message = "/greet" expected_events = [ ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), ActionExecuted(ACTION_LISTEN_NAME), UserUttered(user_message, intent={"name": "greet"}), *policy_events, ] class ConstantEnsemble(PolicyEnsemble): def probabilities_using_best_policy( self, tracker: DialogueStateTracker, domain: Domain, interpreter: NaturalLanguageInterpreter, **kwargs: Any, ) -> PolicyPrediction: prediction = PolicyPrediction.for_action_name( default_processor.domain, expected_action, "some policy" ) prediction.events = policy_events return prediction monkeypatch.setattr(default_processor, "policy_ensemble", ConstantEnsemble([])) action_received_events = False async def mocked_run( self, output_channel: "OutputChannel", nlg: "NaturalLanguageGenerator", tracker: "DialogueStateTracker", domain: "Domain", ) -> List[Event]: # The action already has access to the policy events nonlocal action_received_events action_received_events = list(tracker.events) == expected_events return [] monkeypatch.setattr(ActionListen, ActionListen.run.__name__, mocked_run) await default_processor.handle_message( UserMessage(user_message, sender_id=conversation_id) ) assert action_received_events tracker = default_processor.get_tracker(conversation_id) # The action was logged on the tracker as well expected_events.append(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
async def test_default_intent_recognized(default_processor: MessageProcessor): message = UserMessage("/restart") parsed = await default_processor._parse_message(message) with pytest.warns(None) as record: default_processor._log_unseen_features(parsed) assert len(record) == 0