Пример #1
0
def test_tracker_store_storage_and_retrieval(store):
    tracker = store.get_or_create_tracker("some-id")
    # the retrieved tracker should be empty
    assert tracker.sender_id == "some-id"

    # Action listen should be in there
    assert list(tracker.events) == [ActionExecuted(ActionListen().name())]

    # lets log a test message
    intent = {"name": "greet", "confidence": 1.0}
    tracker.update(UserUttered("_greet", intent, []))
    assert tracker.latest_message.intent.get("name") == "greet"
    store.save(tracker)

    # retrieving the same tracker should result in the same tracker
    retrieved_tracker = store.get_or_create_tracker("some-id")
    assert retrieved_tracker.sender_id == "some-id"
    assert len(retrieved_tracker.events) == 2
    assert retrieved_tracker.latest_message.intent.get("name") == "greet"

    # getting another tracker should result in an empty tracker again
    other_tracker = store.get_or_create_tracker("some-other-id")
    assert other_tracker.sender_id == "some-other-id"
    assert len(other_tracker.events) == 1
Пример #2
0
class Domain(with_metaclass(abc.ABCMeta, object)):
    """The domain specifies the universe in which the bot's policy acts.

    A Domain subclass provides the actions the bot can take, the intents
    and entities it can recognise, and the topics it knows about."""

    DEFAULT_ACTIONS = [ActionListen(), ActionRestart()]

    def __init__(self, topics=None, store_entities_as_slots=True,
                 restart_intent="restart"):
        self.default_topic = DefaultTopic
        self.topics = topics if topics is not None else []
        self.store_entities_as_slots = store_entities_as_slots
        self.restart_intent = restart_intent

    @utils.lazyproperty
    def num_actions(self):
        """Returns the number of available actions."""

        # noinspection PyTypeChecker
        return len(self.actions)

    @utils.lazyproperty
    def action_names(self):
        # type: () -> List[Text]
        """Returns the name of available actions."""

        return [a.name() for a in self.actions]

    @utils.lazyproperty
    def action_map(self):
        # type: () -> Dict[Text, Tuple[int, Action]]
        """Provides a mapping from action names to indices and actions."""
        return {a.name(): (i, a) for i, a in enumerate(self.actions)}

    @utils.lazyproperty
    def num_features(self):
        """Number of used input features for the action prediction."""

        return len(self.input_features)

    def action_for_name(self, action_name):
        # type: (Text) -> Optional[Action]
        """Looks up which action corresponds to this action name."""

        if action_name in self.action_map:
            return self.action_map.get(action_name)[1]
        else:
            self._raise_action_not_found_exception(action_name)

    def action_for_index(self, index):
        """Integer index corresponding to an actions index in the action list.

        This method resolves the index to the actions name."""

        if len(self.actions) <= index or index < 0:
            raise Exception(
                    "Can not access action at index {}. "
                    "Domain has {} actions.".format(index, len(self.actions)))
        return self.actions[index]

    def index_for_action(self, action_name):
        # type: (Text) -> Optional[int]
        """Looks up which action index corresponds to this action name"""

        if action_name in self.action_map:
            return self.action_map.get(action_name)[0]
        else:
            self._raise_action_not_found_exception(action_name)

    def _raise_action_not_found_exception(self, action_name):
        actions = "\n".join(["\t - {}".format(a)
                             for a in sorted(self.action_map)])
        raise Exception(
                "Can not access action '{}', "
                "as that name is not a registered action for this domain. "
                "Available actions are: \n{}".format(action_name, actions))

    @staticmethod
    def _is_predictable_event(event):
        return isinstance(event, ActionExecuted) and not event.unpredictable

    def slice_feature_history(self,
                              featurizer,
                              tracker_history,
                              slice_length):
        # type: (Featurizer, List[Dict[Text, float]], int) -> np.ndarray
        """Slices a featurization from the trackers history.

        If the slice is at the array borders, padding will be added to ensure
        he slice length."""

        slice_end = len(tracker_history)
        slice_start = max(0, slice_end - slice_length)
        padding = [None] * max(0, slice_length - slice_end)
        state_features = padding + tracker_history[slice_start:]
        encoded_features = [featurizer.encode(f, self.input_feature_map)
                            for f in state_features]
        return np.vstack(encoded_features)

    def features_for_tracker_history(self, tracker):
        """Array of features for each state of the trackers history."""

        return [self.get_active_features(tr) for tr in
                tracker.generate_all_prior_states()]

    def feature_vector_for_tracker(self, featurizer, tracker, max_history):
        """Creates a 2D array of shape (max_history,num_features)

        max_history specifies the number of previous steps to be included
        in the input. Each row in the array corresponds to the binarised
        features of each state. Result is padded with default values if
        there are fewer than `max_history` states present."""

        all_features = self.features_for_tracker_history(tracker)
        return self.slice_feature_history(featurizer, all_features, max_history)

    def random_template_for(self, utter_action):
        if utter_action in self.templates:
            return np.random.choice(self.templates[utter_action])
        else:
            return None

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def slot_features(self):
        # type: () -> List[Text]
        """Returns all available slot feature strings."""

        return ["slot_{}_{}".format(s.name, i)
                for s in self.slots
                for i in range(0, s.feature_dimensionality())]

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def prev_action_features(self):
        # type: () -> List[Text]
        """Returns all available previous action feature strings."""

        return ["prev_{0}".format(a.name())
                for a in self.actions]

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def intent_features(self):
        # type: () -> List[Text]
        """Returns all available previous action feature strings."""

        return ["intent_{0}".format(i)
                for i in self.intents]

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def entity_features(self):
        # type: () -> List[Text]
        """Returns all available previous action feature strings."""

        return ["entity_{0}".format(e)
                for e in self.entities]

    def index_of_feature(self, feature_name):
        # type: (Text) -> Optional[int]
        """Provides the index of a feature."""

        return self.input_feature_map.get(feature_name)

    @utils.lazyproperty
    def input_feature_map(self):
        # type: () -> Dict[Text, int]
        """Provides a mapping from feature names to indices."""
        return {f: i for i, f in enumerate(self.input_features)}

    @utils.lazyproperty
    def input_features(self):
        # type: () -> List[Text]
        """Returns all available features."""

        return \
            self.intent_features + \
            self.entity_features + \
            self.slot_features + \
            self.prev_action_features

    def get_active_features(self, tracker):
        # type: (DialogueStateTracker) -> Dict[Text, float]
        """Return a bag of active features from the tracker state"""
        feature_dict = self.get_parsing_features(tracker)
        feature_dict.update(self.get_prev_action_features(tracker))
        return feature_dict

    def get_prev_action_features(self, tracker):
        # type: (DialogueStateTracker) -> Dict[Text, float]
        """Turns the previous taken action into a feature name."""

        latest_action = tracker.latest_action_name
        if latest_action:
            prev_action_name = "prev_{}".format(latest_action)
            if prev_action_name in self.input_feature_map:
                return {prev_action_name: 1}
            else:
                raise Exception(
                        "Failed to use action '{}' in history. "
                        "Please make sure all actions are listed in the "
                        "domains action list.".format(latest_action))
        else:
            return {}

    def get_parsing_features(self, tracker):
        # type: (DialogueStateTracker) -> Dict[Text, float]

        feature_dict = {}

        # Set all found entities with the feature value 1.0
        for entity in tracker.latest_message.entities:
            key = "entity_{0}".format(entity["entity"])
            feature_dict[key] = 1.

        # Set all set slots with the featurization of the stored value
        for key, slot in tracker.slots.items():
            if slot is not None:
                for i, slot_value in enumerate(slot.as_feature()):
                    slot_id = "slot_{}_{}".format(key, i)
                    feature_dict[slot_id] = slot_value

        latest_msg = tracker.latest_message

        if "intent_ranking" in latest_msg.parse_data:
            for intent in latest_msg.parse_data["intent_ranking"]:
                if intent.get("name"):
                    intent_id = "intent_{}".format(intent["name"])
                    feature_dict[intent_id] = intent["confidence"]

        elif latest_msg.intent.get("name"):
            intent_id = "intent_{}".format(latest_msg.intent["name"])
            feature_dict[intent_id] = latest_msg.intent.get("confidence", 1.0)

        return feature_dict

    def slots_for_entities(self, entities):
        if self.store_entities_as_slots:
            return [SlotSet(entity['entity'], entity['value'])
                    for entity in entities
                    for s in self.slots
                    if entity['entity'] == s.name]
        else:
            return []

    def persist(self, file_name):
        raise NotImplementedError

    @classmethod
    def load(cls, file_name):
        raise NotImplementedError

    def persist_specification(self, model_path):
        # type: (Text, List[Text]) -> None
        """Persists the domain specification to storage."""

        domain_spec_path = os.path.join(model_path, 'domain.json')
        utils.create_dir_for_file(domain_spec_path)
        metadata = {
            "features": self.input_features
        }
        with io.open(domain_spec_path, 'w') as f:
            f.write(str(json.dumps(metadata, indent=2)))

    @classmethod
    def load_specification(cls, path):
        matadata_path = os.path.join(path, 'domain.json')
        with io.open(matadata_path) as f:
            specification = json.loads(f.read())
        return specification

    def compare_with_specification(self, path):
        # type: (Text) -> bool
        """Compares the domain spec of the current and the loaded domain.

        Throws exception if the loaded domain specification is different
        to the current domain are different."""

        loaded_domain_spec = self.load_specification(path)
        features = loaded_domain_spec["features"]
        if features != self.input_features:
            missing = ",".join(set(features) - set(self.input_features))
            additional = ",".join(set(self.input_features) - set(features))
            raise Exception(
                    "Domain specification has changed. "
                    "You MUST retrain the policy. " +
                    "Detected mismatch in domain specification. " +
                    "The following features have been \n"
                    "\t - removed: {} \n"
                    "\t - added:   {} ".format(missing, additional))
        else:
            return True

    # Abstract Methods : These have to be implemented in any domain subclass

    @abc.abstractproperty
    def slots(self):
        # type: () -> List[Slot]
        """Domain subclass must provide a list of slots"""
        pass

    @abc.abstractproperty
    def entities(self):
        # type: () -> List[Text]
        raise NotImplementedError(
                "domain must provide a list of entities")

    @abc.abstractproperty
    def intents(self):
        # type: () -> List[Text]
        raise NotImplementedError(
                "domain must provide a list of intents")

    @abc.abstractproperty
    def actions(self):
        # type: () -> List[Action]
        raise NotImplementedError(
                "domain must provide a list of possible actions")

    @abc.abstractproperty
    def templates(self):
        # type: () -> List[Dict[Text, Any]]
        raise NotImplementedError(
                "domain must provide a dictionary of response templates")
Пример #3
0
 def apply_to(self, tracker):
     from rasa_core.actions.action import ActionListen
     tracker._reset()
     tracker.follow_up_action = ActionListen()
def test_text_format():
    assert "{}".format(ActionListen()) == \
           "Action('action_listen')"
    assert "{}".format(UtterAction("my_action_name")) == \
           "UtterAction('my_action_name')"
Пример #5
0
 def apply_to(self, tracker):
     from rasa_core.actions.action import ActionListen
     tracker._reset()
     # will be the index of the first event after the restart
     tracker.latest_restart_event = len(tracker.events) + 1
     tracker.follow_up_action = ActionListen()
Пример #6
0
class Domain(with_metaclass(abc.ABCMeta, object)):
    """The domain specifies the universe in which the bot's policy acts.

    A Domain subclass provides the actions the bot can take, the intents
    and entities it can recognise, and the topics it knows about."""

    DEFAULT_ACTIONS = [ActionListen(), ActionRestart()]

    def __init__(self,
                 topics=None,
                 store_entities_as_slots=True,
                 restart_intent="restart"):
        self.default_topic = DefaultTopic
        self.topics = topics if topics is not None else []
        self.store_entities_as_slots = store_entities_as_slots
        self.restart_intent = restart_intent

    @utils.lazyproperty
    def num_actions(self):
        """Returns the number of available actions."""

        # noinspection PyTypeChecker
        return len(self.actions)

    @utils.lazyproperty
    def action_names(self):
        # type: () -> List[Text]
        """Returns the name of available actions."""

        return [a.name() for a in self.actions]

    @utils.lazyproperty
    def action_map(self):
        # type: () -> Dict[Text, Tuple[int, Action]]
        """Provides a mapping from action names to indices and actions."""
        return {a.name(): (i, a) for i, a in enumerate(self.actions)}

    @utils.lazyproperty
    def num_states(self):
        """Number of used input states for the action prediction."""

        return len(self.input_states)

    def action_for_name(self, action_name):
        # type: (Text) -> Optional[Action]
        """Looks up which action corresponds to this action name."""

        if action_name in self.action_map:
            return self.action_map.get(action_name)[1]
        else:
            self._raise_action_not_found_exception(action_name)

    def action_for_index(self, index):
        """Integer index corresponding to an actions index in the action list.

        This method resolves the index to the actions name."""

        if len(self.actions) <= index or index < 0:
            raise Exception("Can not access action at index {}. "
                            "Domain has {} actions.".format(
                                index, len(self.actions)))
        return self.actions[index]

    def index_for_action(self, action_name):
        # type: (Text) -> Optional[int]
        """Looks up which action index corresponds to this action name"""

        if action_name in self.action_map:
            return self.action_map.get(action_name)[0]
        else:
            self._raise_action_not_found_exception(action_name)

    def _raise_action_not_found_exception(self, action_name):
        actions = "\n".join(
            ["\t - {}".format(a) for a in sorted(self.action_map)])
        raise Exception(
            "Can not access action '{}', "
            "as that name is not a registered action for this domain. "
            "Available actions are: \n{}".format(action_name, actions))

    def random_template_for(self, utter_action):
        if utter_action in self.templates:
            return np.random.choice(self.templates[utter_action])
        else:
            return None

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def slot_states(self):
        # type: () -> List[Text]
        """Returns all available slot state strings."""

        return [
            "slot_{}_{}".format(s.name, i) for s in self.slots
            for i in range(0, s.feature_dimensionality())
        ]

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def prev_action_states(self):
        # type: () -> List[Text]
        """Returns all available previous action state strings."""

        return [PREV_PREFIX + a.name() for a in self.actions]

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def intent_states(self):
        # type: () -> List[Text]
        """Returns all available previous action state strings."""

        return ["intent_{0}".format(i) for i in self.intents]

    # noinspection PyTypeChecker
    @utils.lazyproperty
    def entity_states(self):
        # type: () -> List[Text]
        """Returns all available previous action state strings."""

        return ["entity_{0}".format(e) for e in self.entities]

    def index_of_state(self, state_name):
        # type: (Text) -> Optional[int]
        """Provides the index of a state."""

        return self.input_state_map.get(state_name)

    @utils.lazyproperty
    def input_state_map(self):
        # type: () -> Dict[Text, int]
        """Provides a mapping from state names to indices."""
        return {f: i for i, f in enumerate(self.input_states)}

    @utils.lazyproperty
    def input_states(self):
        # type: () -> List[Text]
        """Returns all available states."""

        return \
            self.intent_states + \
            self.entity_states + \
            self.slot_states + \
            self.prev_action_states

    @staticmethod
    def get_parsing_states(tracker):
        # type: (DialogueStateTracker) -> Dict[Text, float]

        state_dict = {}

        # Set all found entities with the state value 1.0
        for entity in tracker.latest_message.entities:
            key = "entity_{0}".format(entity["entity"])
            state_dict[key] = 1.0

        # Set all set slots with the featurization of the stored value
        for key, slot in tracker.slots.items():
            if slot is not None:
                for i, slot_value in enumerate(slot.as_feature()):
                    if slot_value != 0:
                        slot_id = "slot_{}_{}".format(key, i)
                        state_dict[slot_id] = slot_value

        latest_msg = tracker.latest_message

        if "intent_ranking" in latest_msg.parse_data:
            for intent in latest_msg.parse_data["intent_ranking"]:
                if intent.get("name"):
                    intent_id = "intent_{}".format(intent["name"])
                    state_dict[intent_id] = intent["confidence"]

        elif latest_msg.intent.get("name"):
            intent_id = "intent_{}".format(latest_msg.intent["name"])
            state_dict[intent_id] = latest_msg.intent.get("confidence", 1.0)

        return state_dict

    def get_prev_action_states(self, tracker):
        # type: (DialogueStateTracker) -> Dict[Text, float]
        """Turns the previous taken action into a state name."""

        latest_action = tracker.latest_action_name
        if latest_action:
            prev_action_name = PREV_PREFIX + latest_action
            if prev_action_name in self.input_state_map:
                return {prev_action_name: 1.0}
            else:
                logger.warning(
                    "Failed to use action '{}' in history. "
                    "Please make sure all actions are listed in the "
                    "domains action list. If you recently removed an "
                    "action, don't worry about this warning. It "
                    "should stop appearing after a while. "
                    "".format(latest_action))
                return {}
        else:
            return {}

    def get_active_states(self, tracker):
        # type: (DialogueStateTracker) -> Dict[Text, float]
        """Return a bag of active states from the tracker state"""
        state_dict = self.get_parsing_states(tracker)
        state_dict.update(self.get_prev_action_states(tracker))
        return state_dict

    def states_for_tracker_history(self, tracker):
        # type: (DialogueStateTracker) -> List[Dict[Text, float]]
        """Array of states for each state of the trackers history."""
        return [
            self.get_active_states(tr)
            for tr in tracker.generate_all_prior_trackers()
        ]

    def slots_for_entities(self, entities):
        if self.store_entities_as_slots:
            slot_events = []
            for s in self.slots:
                matching_entities = [
                    e['value'] for e in entities if e['entity'] == s.name
                ]
                if matching_entities:
                    if s.type_name == 'list':
                        slot_events.append(SlotSet(s.name, matching_entities))
                    else:
                        slot_events.append(
                            SlotSet(s.name, matching_entities[-1]))
            return slot_events
        else:
            return []

    def persist(self, filename):
        raise NotImplementedError

    @classmethod
    def load(cls, filename):
        raise NotImplementedError

    def persist_specification(self, model_path):
        # type: (Text) -> None
        """Persists the domain specification to storage."""

        domain_spec_path = os.path.join(model_path, 'domain.json')
        utils.create_dir_for_file(domain_spec_path)

        metadata = {"states": self.input_states}
        utils.dump_obj_as_json_to_file(domain_spec_path, metadata)

    @classmethod
    def load_specification(cls, path):
        # type: (Text) -> Dict[Text, Any]
        """Load a domains specification from a dumped model directory."""

        matadata_path = os.path.join(path, 'domain.json')
        with io.open(matadata_path) as f:
            specification = json.loads(f.read())
        return specification

    def compare_with_specification(self, path):
        # type: (Text) -> bool
        """Compares the domain spec of the current and the loaded domain.

        Throws exception if the loaded domain specification is different
        to the current domain are different."""

        loaded_domain_spec = self.load_specification(path)
        states = loaded_domain_spec["states"]
        if states != self.input_states:
            missing = ",".join(set(states) - set(self.input_states))
            additional = ",".join(set(self.input_states) - set(states))
            raise Exception("Domain specification has changed. "
                            "You MUST retrain the policy. " +
                            "Detected mismatch in domain specification. " +
                            "The following states have been \n"
                            "\t - removed: {} \n"
                            "\t - added:   {} ".format(missing, additional))
        else:
            return True

    # Abstract Methods : These have to be implemented in any domain subclass
    @abc.abstractproperty
    def slots(self):
        # type: () -> List[Slot]
        """Domain subclass must provide a list of slots"""
        pass

    @abc.abstractproperty
    def entities(self):
        # type: () -> List[Text]
        raise NotImplementedError("domain must provide a list of entities")

    @abc.abstractproperty
    def intents(self):
        # type: () -> List[Text]
        raise NotImplementedError("domain must provide a list of intents")

    @abc.abstractproperty
    def actions(self):
        # type: () -> List[Action]
        raise NotImplementedError(
            "domain must provide a list of possible actions")

    @abc.abstractproperty
    def templates(self):
        # type: () -> List[Dict[Text, Any]]
        raise NotImplementedError(
            "domain must provide a dictionary of response templates")
Пример #7
0
 def _restart_tracker(proc, sender_id):
     proc._get_tracker(sender_id)._reset()
     proc._get_tracker(sender_id).follow_up_action = ActionListen()