Rasa课程、Rasa培训、Rasa面试、Rasa实战系列之FormAction(二)

Rasa课程、Rasa培训、Rasa面试、Rasa实战系列之FormAction(二)

FormAction

实现和执行表单逻辑的动作。


class FormAction(LoopAction):
    """Action which implements and executes the form logic."""

    def __init__(
        self, form_name: Text, action_endpoint: Optional[EndpointConfig]
    ) -> None:
        """Creates a `FormAction`.

        Args:
            form_name: Name of the form.
            action_endpoint: Endpoint to execute custom actions.
        """
        self._form_name = form_name
        self.action_endpoint = action_endpoint
        # creating it requires domain, which we don't have in init
        # we'll create it on the first call
        self._unique_entity_mappings: Set[Text] = set()
        self._have_unique_entity_mappings_been_initialized = False

    def name(self) -> Text:
        """Return the form name."""
        return self._form_name

    def required_slots(self, domain: Domain) -> List[Text]:
        """A list of required slots that the form has to fill.

        Returns:
            A list of slot names.
        """
        return domain.required_slots_for_form(self.name())

validate

提取并验证请求词槽和其他词槽的值。

您的用户不会总是回复您要求他们提供的信息。通常,用户会提出问题、闲聊、改变主意,或者以其他方式偏离快乐的道路。

当表单处于活动状态时,如果用户的输入未填充请求的槽,则表单操作的执行将被拒绝,即表单将自动引发ActionExecutionRejection. 这些是表单将引发的特定场景ActionExecutionRejection:

  • 请求了一个词槽,但用户没有用他们的最后一条消息填充词槽,并且您没有定义用于 验证词槽或 提取词槽的自定义操作。
  • 请求了一个词槽,但您用于 验证词槽或 提取词槽的自定义操作未返回任何SlotSet事件。
    def validate(
        self,
        tracker: "DialogueStateTracker",
        domain: Domain,
        output_channel: OutputChannel,
        nlg: NaturalLanguageGenerator,
    ) -> List[Union[SlotSet, Event]]:
        """Extract and validate value of requested slot and other slots.

        Returns:
            The new validation events created by the custom form validation action

        Raises:
            ActionExecutionRejection exception to reject execution of form action
            if nothing was extracted.

        Subclass this method to add custom validation and rejection logic.
        """
        extracted_slot_values = self._get_slot_extractions(tracker, domain)

        validation_events =   self.validate_slots(
            extracted_slot_values, tracker, domain, output_channel, nlg
        )

        some_slots_were_validated = any(
            isinstance(event, SlotSet) and not event.key == REQUESTED_SLOT
            for event in validation_events
            # Ignore `SlotSet`s  for `REQUESTED_SLOT` as that's not a slot which needs
            # to be filled by the user.
        )

        # extract requested slot
        slot_to_fill = self.get_slot_to_fill(tracker)

        if (
            slot_to_fill
            and not extracted_slot_values #用户的输入未填充请求的槽
            and not some_slots_were_validated #没有验证词槽
            and not self._user_rejected_manually(validation_events)
        ):
            # reject to execute the form action
            # if some slot was requested but nothing was extracted
            # it will allow other policies to predict another action
            #
            # don't raise it here if the user rejected manually, to allow slots other
            # than the requested slot to be filled.
            #
            raise ActionExecutionRejection(
                self.name(),
                f"Failed to extract slot {
      
      slot_to_fill} with action {
      
      self.name()}",
            )
        return validation_events

_get_slot_extractions

    def _get_slot_extractions(
        self, tracker: "DialogueStateTracker", domain: Domain
    ) -> Dict[Text, Any]:
        events_since_last_user_uttered = FormAction._get_events_since_last_user_uttered(
            tracker
        )
        slot_values: Dict[Text, Any] = {
    
    }

        required_slots = self._add_dynamic_slots_requested_by_dynamic_forms(
            tracker, domain
        )

        for event in events_since_last_user_uttered:
            if event.key not in required_slots:
                continue

            slot_values = self._update_slot_values(event, tracker, domain, slot_values)

        return slot_values

get_mappings_for_slot

获取请求词槽的映射。如果为None,则将请求的词槽位映射到同名的实体

    def get_mappings_for_slot(
        self, slot_to_fill: Text, domain: Domain
    ) -> List[Dict[Text, Any]]:
        """Get mappings for requested slot.

        If None, map requested slot to an entity with the same name
        """
        domain_slots = domain.as_dict().get(KEY_SLOTS, {
    
    })
        requested_slot_mappings = domain_slots.get(slot_to_fill, {
    
    }).get("mappings", [])

        # check provided slot mappings
        for requested_slot_mapping in requested_slot_mappings:
            if (
                not isinstance(requested_slot_mapping, dict)
                or requested_slot_mapping.get("type") is None
            ):
                raise TypeError("Provided incompatible slot mapping")

        return requested_slot_mappings

_get_events_since_last_user_uttered

    @staticmethod
    def _get_events_since_last_user_uttered(
        tracker: "DialogueStateTracker",
    ) -> List[SlotSet]:
        # TODO: Better way to get this latest_message index is through an instance
        # variable, eg. tracker.latest_message_index
        index_from_end = next(
            (
                i
                for i, event in enumerate(reversed(tracker.events))
                if event == Restarted() or event == tracker.latest_message
            ),
            len(tracker.events) - 1,
        )
        index = len(tracker.events) - index_from_end - 1
        events_since_last_user_uttered = [
            event
            for event in itertools.islice(tracker.events, index, None)
            if isinstance(event, SlotSet)
        ]

        return events_since_last_user_uttered

required_slots

表格必须填写的所需词槽的列表。

    def required_slots(self, domain: Domain) -> List[Text]:
        """A list of required slots that the form has to fill.

        Returns:
            A list of slot names.
        """
        return domain.required_slots_for_form(self.name())

validate_slots

验证提取的词槽。如果一个自定义操作可用来验证词槽,我们调用它来验证它们。否则就没有验证

  async def validate_slots(
        self,
        slot_candidates: Dict[Text, Any],
        tracker: "DialogueStateTracker",
        domain: Domain,
        output_channel: OutputChannel,
        nlg: NaturalLanguageGenerator,
    ) -> List[Union[SlotSet, Event]]:
        """Validate the extracted slots.

        If a custom action is available for validating the slots, we call it to validate
        them. Otherwise there is no validation.

        Args:
            slot_candidates: Extracted slots which are candidates to fill the slots
                required by the form.
            tracker: The current conversation tracker.
            domain: The current model domain.
            output_channel: The output channel which can be used to send messages
                to the user.
            nlg:  `NaturalLanguageGenerator` to use for response generation.

        Returns:
            The validation events including potential bot messages and `SlotSet` events
            for the validated slots, if the custom form validation action is present in
            domain actions.
            Otherwise, returns empty list since the extracted slots already have
            corresponding `SlotSet` events in the tracker.
        """
        logger.debug(f"Validating extracted slots: {
      
      slot_candidates}")
        events: List[Union[SlotSet, Event]] = [
            SlotSet(slot_name, value) for slot_name, value in slot_candidates.items()
        ]

        validate_name = f"validate_{
      
      self.name()}"

        if validate_name not in domain.action_names_or_texts:
            return []

        # create temporary tracker with only the SlotSet events added
        # since last user utterance
        _tracker = self._temporary_tracker(tracker, events, domain)

        _action = RemoteAction(validate_name, self.action_endpoint)
        validate_events = await _action.run(output_channel, nlg, _tracker, domain)

        # Only return the validated SlotSet events by the custom form validation action
        # to avoid adding duplicate SlotSet events for slots that are already valid.
        return validate_events
   

do

FormAction类do方法:

1.调用self._validate_if_required方法,从’ self.validate(…)'返回一个事件列表。以下情况需要校验:

  • 表单是活动的
  • 表单在action_listen之后被调用
  • 表单验证没有被取消

2.调用 self.request_next_slot方法,如果需要,请求下一个词槽和响应,否则返回’ None '。调用_ask_for_slot方法,返回词槽提示的 List[Event]事件信息

    def do(
        self,
        output_channel: "OutputChannel",
        nlg: "NaturalLanguageGenerator",
        tracker: "DialogueStateTracker",
        domain: "Domain",
        events_so_far: List[Event],
    ) -> List[Event]:
        events =   self._validate_if_required(tracker, domain, output_channel, nlg)

        if not self._user_rejected_manually(events):
            events +=   self.request_next_slot(
                tracker, domain, output_channel, nlg, events_so_far + events
            )

        return events

get_slot_to_fill

获取接下来要填充的词槽的名称。切换到另一个表单时,请求的词槽位设置仍然来自前一个表单,必须忽略。

    def get_slot_to_fill(self, tracker: "DialogueStateTracker") -> Optional[str]:
        """Gets the name of the slot which should be filled next.

        When switching to another form, the requested slot setting is still from the
        previous form and must be ignored.

        Returns:
            The slot name or `None`
        """
        return (
            tracker.get_slot(REQUESTED_SLOT)
            if tracker.active_loop_name == self.name()
            else None
        )

is_done

如果事件是ActionExecutionRejected,返回false;

如果自定义验证actions操作是以下情况,返回true,可以决定提前终止LoopAction循环:

  • 设置请求的词槽为“None”
  • 或设置“ActiveLoop(None)”。
    def is_done(
        self,
        output_channel: "OutputChannel",
        nlg: "NaturalLanguageGenerator",
        tracker: "DialogueStateTracker",
        domain: "Domain",
        events_so_far: List[Event],
    ) -> bool:
        """Checks if loop can be terminated."""
        if any(isinstance(event, ActionExecutionRejected) for event in events_so_far):
            return False

        # Custom validation actions can decide to terminate the loop early by
        # setting the requested slot to `None` or setting `ActiveLoop(None)`.
        # We explicitly check only the last occurrences for each possible termination
        # event instead of doing `return event in events_so_far` to make it possible
        # to override termination events which were returned earlier.
        return (
            next(
                (
                    event
                    for event in reversed(events_so_far)
                    if isinstance(event, SlotSet) and event.key == REQUESTED_SLOT
                ),
                None,
            )
            == SlotSet(REQUESTED_SLOT, None)
            or next(
                (
                    event
                    for event in reversed(events_so_far)
                    if isinstance(event, ActiveLoop)
                ),
                None,
            )
            == ActiveLoop(None)
        )

_add_dynamic_slots_requested_by_dynamic_forms

    def _add_dynamic_slots_requested_by_dynamic_forms(
        self, tracker: "DialogueStateTracker", domain: Domain
    ) -> Set[Text]:
        required_slots = set(self.required_slots(domain))
        requested_slot = self.get_slot_to_fill(tracker)

        if requested_slot:
            required_slots.add(requested_slot)

        return required_slots

PolicyPrediction

存储有关预测“策略”的信息

class PolicyPrediction:
    """Stores information about the prediction of a `Policy`."""

    def __init__(
        self,
        probabilities: List[float],
        policy_name: Optional[Text],
        policy_priority: int = 1,
        events: Optional[List[Event]] = None,
        optional_events: Optional[List[Event]] = None,
        is_end_to_end_prediction: bool = False,
        is_no_user_prediction: bool = False,
        diagnostic_data: Optional[Dict[Text, Any]] = None,
        hide_rule_turn: bool = False,
        action_metadata: Optional[Dict[Text, Any]] = None,
    ) -> None:
        """Creates a `PolicyPrediction`.

        Args:
            probabilities: The probabilities for each action.
            policy_name: Name of the policy which made the prediction.
            policy_priority: The priority of the policy which made the prediction.
            events: Events which the `Policy` needs to have applied to the tracker
                after the prediction. These events are applied independent of whether
                the policy wins against other policies or not. Be careful which events
                you return as they can potentially influence the conversation flow.
            optional_events: Events which the `Policy` needs to have applied to the
                tracker after the prediction in case it wins. These events are only
                applied in case the policy's prediction wins. Be careful which events
                you return as they can potentially influence the conversation flow.
            is_end_to_end_prediction: `True` if the prediction used the text of the
                user message instead of the intent.
            is_no_user_prediction: `True` if the prediction uses neither the text
                of the user message nor the intent. This is for the example the case
                for happy loop paths.
            diagnostic_data: Intermediate results or other information that is not
                necessary for Rasa to function, but intended for debugging and
                fine-tuning purposes.
            hide_rule_turn: `True` if the prediction was made by the rules which
                do not appear in the stories
            action_metadata: Specifies additional metadata that can be passed
                by policies.
        """
        self.probabilities = probabilities
        self.policy_name = policy_name
        self.policy_priority = (policy_priority,)
        self.events = events or []
        self.optional_events = optional_events or []
        self.is_end_to_end_prediction = is_end_to_end_prediction
        self.is_no_user_prediction = is_no_user_prediction
        self.diagnostic_data = diagnostic_data or {
    
    }
        self.hide_rule_turn = hide_rule_turn
        self.action_metadata = action_metadata

    @staticmethod
    def for_action_name(
        domain: Domain,
        action_name: Text,
        policy_name: Optional[Text] = None,
        confidence: float = 1.0,
        action_metadata: Optional[Dict[Text, Any]] = None,
    ) -> "PolicyPrediction":
        """Create a prediction for a given action.

        Args:
            domain: The current model domain
            action_name: The action which should be predicted.
            policy_name: The policy which did the prediction.
            confidence: The prediction confidence.
            action_metadata: Additional metadata to be attached with the prediction.

        Returns:
            The prediction.
        """
        probabilities = confidence_scores_for(action_name, confidence, domain)

        return PolicyPrediction(
            probabilities, policy_name, action_metadata=action_metadata
        )

    def __eq__(self, other: Any) -> bool:
        """Checks if the two objects are equal.

        Args:
            other: Any other object.

        Returns:
            `True` if other has the same type and the values are the same.
        """
        if not isinstance(other, PolicyPrediction):
            return False

        return (
            self.probabilities == other.probabilities
            and self.policy_name == other.policy_name
            and self.policy_priority == other.policy_priority
            and self.events == other.events
            and self.optional_events == other.optional_events
            and self.is_end_to_end_prediction == other.is_end_to_end_prediction
            and self.is_no_user_prediction == other.is_no_user_prediction
            and self.hide_rule_turn == other.hide_rule_turn
            and self.action_metadata == other.action_metadata
            # We do not compare `diagnostic_data`, because it has no effect on the
            # action prediction.
        )

    @property
    def max_confidence_index(self) -> int:
        """Gets the index of the action prediction with the highest confidence.

        Returns:
            The index of the action with the highest confidence.
        """
        return self.probabilities.index(self.max_confidence)

    @property
    def max_confidence(self) -> float:
        """Gets the highest predicted confidence.

        Returns:
            The highest predicted confidence.
        """
        return max(self.probabilities, default=0.0)

Agent

Agent类为最重要的Rasa功能提供了一个接口。这包括训练、处理消息、加载对话模型、获取下一个操作和处理通道

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[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[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 isinstance(self.nlg, TemplatedNaturalLanguageGenerator):
            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)  # type: ignore[union-attr]

    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(  # type: ignore[union-attr]
                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(  # type: ignore[union-attr] # noqa:E501
            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(  # type: ignore[union-attr]
            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)  # type: ignore[union-attr]

    @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(  # type: ignore[union-attr]
            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(  # type: ignore[union-attr]
            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}'."
            )

MessageProcessor

消息处理器是与机器人模型通信的接口。


class MessageProcessor:
    """The message processor is interface for communicating with a bot model."""

    def __init__(
        self,
        model_path: Union[Text, Path],
        tracker_store: rasa.core.tracker_store.TrackerStore,
        lock_store: LockStore,
        generator: NaturalLanguageGenerator,
        action_endpoint: Optional[EndpointConfig] = None,
        max_number_of_predictions: int = MAX_NUMBER_OF_PREDICTIONS,
        on_circuit_break: Optional[LambdaType] = None,
        http_interpreter: Optional[RasaNLUHttpInterpreter] = None,
    ) -> None:
        """Initializes a `MessageProcessor`."""
        self.nlg = generator
        self.tracker_store = tracker_store
        self.lock_store = lock_store
        self.max_number_of_predictions = max_number_of_predictions
        self.on_circuit_break = on_circuit_break
        self.action_endpoint = action_endpoint
        self.model_filename, self.model_metadata, self.graph_runner = self._load_model(
            model_path
        )
        self.model_path = Path(model_path)
        self.domain = self.model_metadata.domain
        self.http_interpreter = http_interpreter

    @staticmethod
    def _load_model(
        model_path: Union[Text, Path]
    ) -> Tuple[Text, ModelMetadata, GraphRunner]:
        """Unpacks a model from a given path using the graph model loader."""
        try:
            if os.path.isfile(model_path):
                model_tar = model_path
            else:
                model_tar = get_latest_model(model_path)
                if not model_tar:
                    raise ModelNotFound(f"No model found at path '{
      
      model_path}'.")
        except TypeError:
            raise ModelNotFound(f"Model {
      
      model_path} can not be loaded.")

        logger.info(f"Loading model {
      
      model_tar}...")
        with tempfile.TemporaryDirectory() as temporary_directory:
            try:
                metadata, runner = loader.load_predict_graph_runner(
                    Path(temporary_directory),
                    Path(model_tar),
                    LocalModelStorage,
                    DaskGraphRunner,
                )
                return os.path.basename(model_tar), metadata, runner
            except tarfile.ReadError:
                raise ModelNotFound(f"Model {
      
      model_path} can not be loaded.")

    async def handle_message(
        self, message: UserMessage
    ) -> Optional[List[Dict[Text, Any]]]:
        """Handle a single message with this processor."""
        # preprocess message if necessary
        tracker = await self.log_message(message, should_save_tracker=False)

        if self.model_metadata.training_type == TrainingType.NLU:
            self.save_tracker(tracker)
            rasa.shared.utils.io.raise_warning(
                "No core model. Skipping action prediction and execution.",
                docs=DOCS_URL_POLICIES,
            )
            return None

        tracker = await self.run_action_extract_slots(message.output_channel, tracker)

        await self._run_prediction_loop(message.output_channel, tracker)

        self.save_tracker(tracker)

        if isinstance(message.output_channel, CollectingOutputChannel):
            return message.output_channel.messages

        return None

_run_action

事件和返回值用于在操作执行后更新跟踪器状态

    async def _run_action(
        self,
        action: rasa.core.actions.action.Action,
        tracker: DialogueStateTracker,
        output_channel: OutputChannel,
        nlg: NaturalLanguageGenerator,
        prediction: PolicyPrediction,
    ) -> bool:
        # events and return values are used to update
        # the tracker state after an action has been taken
        try:
            # Use temporary tracker as we might need to discard the policy events in
            # case of a rejection.
            temporary_tracker = tracker.copy()
            temporary_tracker.update_with_events(prediction.events, self.domain)
            events = await action.run(
                output_channel, nlg, temporary_tracker, self.domain
            )
        except rasa.core.actions.action.ActionExecutionRejection:
            events = [
                ActionExecutionRejected(
                    action.name(), prediction.policy_name, prediction.max_confidence
                )
            ]
            tracker.update(events[0])
            return self.should_predict_another_action(action.name())
        except Exception:
            logger.exception(
                f"Encountered an exception while running action '{
      
      action.name()}'."
                "Bot will continue, but the actions events are lost. "
                "Please check the logs of your action server for "
                "more information."
            )
            events = []

        self._log_action_on_tracker(tracker, action, events, prediction)
        if action.name() != ACTION_LISTEN_NAME and not action.name().startswith(
            UTTER_PREFIX
        ):
            self._log_slots(tracker)

        await self.execute_side_effects(events, tracker, output_channel)

        return self.should_predict_another_action(action.name())

_send_bot_messages

发送事件数组中记录的所有bot消息

    @staticmethod
    def  _send_bot_messages(
        events: List[Event],
        tracker: DialogueStateTracker,
        output_channel: OutputChannel,
    ) -> None:
        """Send all the bot messages that are logged in the events array."""

        for e in events:
            if not isinstance(e, BotUttered):
                continue

            output_channel.send_response(tracker.sender_id, e.message())

_schedule_reminders

使用调度器为作业计时,以触发传递的提醒。具有相同’ id '属性的提醒会相互覆盖(也就是说,最终只有一个会运行)。

   def  _schedule_reminders(
        self,
        events: List[Event],
        tracker: DialogueStateTracker,
        output_channel: OutputChannel,
    ) -> None:
        """Uses the scheduler to time a job to trigger the passed reminder.

        Reminders with the same `id` property will overwrite one another
        (i.e. only one of them will eventually run).
        """
        for e in events:
            if not isinstance(e, ReminderScheduled):
                continue

            (jobs.scheduler()).add_job(
                self.handle_reminder,
                "date",
                run_date=e.trigger_date_time,
                args=[e, tracker.sender_id, output_channel],
                id=e.name,
                replace_existing=True,
                name=e.scheduled_job_name(tracker.sender_id),
            )

_cancel_reminders

取消匹配’ ReminderCancelled '事件的提醒。由ReminderCancelled事件指定的所有提醒将被取消


    @staticmethod
    def  _cancel_reminders(
        events: List[Event], tracker: DialogueStateTracker
    ) -> None:
        """Cancel reminders that match the `ReminderCancelled` event."""
        # All Reminders specified by ReminderCancelled events will be cancelled
        for event in events:
            if isinstance(event, ReminderCancelled):
                scheduler = jobs.scheduler()
                for scheduled_job in scheduler.get_jobs():
                    if event.cancels_job_with_name(
                        scheduled_job.name, tracker.sender_id
                    ):
                        scheduler.remove_job(scheduled_job.id)

should_predict_another_action

如果传入参数action_name不在ACTION_LISTEN_NAME, ACTION_SESSION_START_NAME里面,确定处理器是否应该预测另一个操作action。

    @staticmethod
    def should_predict_another_action(action_name: Text) -> bool:
        """Determine whether the processor should predict another action.

        Args:
            action_name: Name of the latest executed action.

        Returns:
            `False` if `action_name` is `ACTION_LISTEN_NAME` or
            `ACTION_SESSION_START_NAME`, otherwise `True`.
        """

        return action_name not in (ACTION_LISTEN_NAME, ACTION_SESSION_START_NAME)

execute_side_effects

发送bot消息、安排和取消事件数组中记录的提醒

    def  execute_side_effects(
        self,
        events: List[Event],
        tracker: DialogueStateTracker,
        output_channel: OutputChannel,
    ) -> None:
        """Send bot messages, schedule and cancel reminders that are logged
        in the events array."""

        self._send_bot_messages(events, tracker, output_channel)
        self._schedule_reminders(events, tracker, output_channel)
        self._cancel_reminders(events, tracker)

_log_slots

日志当前设置的词槽

    @staticmethod
    def _log_slots(tracker: DialogueStateTracker) -> None:
        # Log currently set slots
        slot_values = "\n".join(
            [f"\t{
      
      s.name}: {
      
      s.value}" for s in tracker.slots.values()]
        )
        if slot_values.strip():
            logger.debug(f"Current slot values: \n{
      
      slot_values}")

_log_action_on_tracker

确保即使lazy programmer在操作结束时没有输入’ return[] ‘,或者由于其他原因run方法返回’ None ',代码仍然可以工作。

    def _log_action_on_tracker(
        self,
        tracker: DialogueStateTracker,
        action: Action,
        events: Optional[List[Event]],
        prediction: PolicyPrediction,
    ) -> None:
        # Ensures that the code still works even if a lazy programmer missed
        # to type `return []` at the end of an action or the run method
        # returns `None` for some other reason.
        if events is None:
            events = []

        action_was_rejected_manually = any(
            isinstance(event, ActionExecutionRejected) for event in events
        )
        if not action_was_rejected_manually:
            logger.debug(f"Policy prediction ended with events '{
      
      prediction.events}'.")
            tracker.update_with_events(prediction.events, self.domain)

            # log the action and its produced events
            tracker.update(action.event_for_successful_execution(prediction))

        logger.debug(f"Action '{
      
      action.name()}' ended with events '{
      
      events}'.")
        tracker.update_with_events(events, self.domain)

ActionExecuted

操作描述了所采取的action+其结果。

 class ActionExecuted(Event):
    """An operation describes an action taken + its result.

    It comprises an action and a list of events. operations will be appended
    to the latest `Turn`` in `Tracker.turns`.
    """

    type_name = "action"

    def __init__(
        self,
        action_name: Optional[Text] = None,
        policy: Optional[Text] = None,
        confidence: Optional[float] = None,
        timestamp: Optional[float] = None,
        metadata: Optional[Dict] = None,
        action_text: Optional[Text] = None,
        hide_rule_turn: bool = False,
    ) -> None:
        """Creates event for a successful event execution.

        Args:
            action_name: Name of the action which was executed. `None` if it was an
                end-to-end prediction.
            policy: Policy which predicted action.
            confidence: Confidence with which policy predicted action.
            timestamp: When the event was created.
            metadata: Additional event metadata.
            action_text: In case it's an end-to-end action prediction, the text which
                was predicted.
            hide_rule_turn: If `True`, this action should be hidden in the dialogue
                history created for ML-based policies.
        """
        self.action_name = action_name
        self.policy = policy
        self.confidence = confidence
        self.unpredictable = False
        self.action_text = action_text
        self.hide_rule_turn = hide_rule_turn

        if self.action_name is None and self.action_text is None:
            raise ValueError(
                "Both the name of the action and the end-to-end "
                "predicted text are missing. "
                "The `ActionExecuted` event cannot be initialised."
            )

        super().__init__(timestamp, metadata)

    def __members__(self) -> Tuple[Optional[Text], Optional[Text], Text]:
        meta_no_nones = {
    
    k: v for k, v in self.metadata.items() if v is not None}
        return (self.action_name, self.action_text, jsonpickle.encode(meta_no_nones))

    def __repr__(self) -> Text:
        """Returns event as string for debugging."""
        return "ActionExecuted(action: {}, policy: {}, confidence: {})".format(
            self.action_name, self.policy, self.confidence
        )

    def __str__(self) -> Text:
        """Returns event as human readable string."""
        return str(self.action_name) or str(self.action_text)

    def __hash__(self) -> int:
        """Returns unique hash for event."""
        return hash(self.__members__())

    def __eq__(self, other: Any) -> bool:
        """Compares object with other object."""
        if not isinstance(other, ActionExecuted):
            return NotImplemented

        return self.__members__() == other.__members__()

    def as_story_string(self) -> Optional[Text]:
        """Returns event in Markdown format."""
        if self.action_text:
            raise UnsupportedFeatureException(
                f"Printing end-to-end bot utterances is not supported in the "
                f"Markdown training format. Please use the YAML training data format "
                f"instead. Please see {
      
      DOCS_URL_TRAINING_DATA} for more information."
            )

        return self.action_name

    @classmethod
    def _from_story_string(
        cls, parameters: Dict[Text, Any]
    ) -> Optional[List["ActionExecuted"]]:
        return [
            ActionExecuted(
                parameters.get("name"),
                parameters.get("policy"),
                parameters.get("confidence"),
                parameters.get("timestamp"),
                parameters.get("metadata"),
                parameters.get("action_text"),
                parameters.get("hide_rule_turn", False),
            )
        ]

    def as_dict(self) -> Dict[Text, Any]:
        """Returns serialized event."""
        d = super().as_dict()
        d.update(
            {
    
    
                "name": self.action_name,
                "policy": self.policy,
                "confidence": self.confidence,
                "action_text": self.action_text,
                "hide_rule_turn": self.hide_rule_turn,
            }
        )
        return d

    def as_sub_state(self) -> Dict[Text, Text]:
        """Turns ActionExecuted into a dictionary containing action name or action text.

        One action cannot have both set at the same time

        Returns:
            a dictionary containing action name or action text with the corresponding
            key.
        """
        if self.action_name:
            return {
    
    ACTION_NAME: self.action_name}
        else:
            # FIXME: we should define the type better here, and require either
            #        `action_name` or `action_text`
            return {
    
    ACTION_TEXT: cast(Text, self.action_text)}

    def apply_to(self, tracker: "DialogueStateTracker") -> None:
        """Applies event to current conversation state."""
        tracker.set_latest_action(self.as_sub_state())
        tracker.clear_followup_action()



OutputChannel

输出通道基类。为文本输出通道提供正常的发送方法实现。


class OutputChannel:
    """Output channel base class.

    Provides sane implementation of the send methods
    for text only output channels.
    """

    @classmethod
    def name(cls) -> Text:
        """Every output channel needs a name to identify it."""
        return cls.__name__

    async def send_response(self, recipient_id: Text, message: Dict[Text, Any]) -> None:
        """Send a message to the client."""

        if message.get("quick_replies"):
            await self.send_quick_replies(
                recipient_id,
                message.pop("text"),
                message.pop("quick_replies"),
                **message,
            )
        elif message.get("buttons"):
            await self.send_text_with_buttons(
                recipient_id, message.pop("text"), message.pop("buttons"), **message
            )
        elif message.get("text"):
            await self.send_text_message(recipient_id, message.pop("text"), **message)

        if message.get("custom"):
            await self.send_custom_json(recipient_id, message.pop("custom"), **message)

        # if there is an image we handle it separately as an attachment
        if message.get("image"):
            await self.send_image_url(recipient_id, message.pop("image"), **message)

        if message.get("attachment"):
            await self.send_attachment(
                recipient_id, message.pop("attachment"), **message
            )

        if message.get("elements"):
            await self.send_elements(recipient_id, message.pop("elements"), **message)

DialogueStateTracker

维护会话的状态。


class DialogueStateTracker:
    """Maintains the state of a conversation.

    The field max_event_history will only give you these last events,
    it can be set in the tracker_store"""

    @classmethod
    def from_dict(
        cls,
        sender_id: Text,
        events_as_dict: List[Dict[Text, Any]],
        slots: Optional[Iterable[Slot]] = None,
        max_event_history: Optional[int] = None,
    ) -> "DialogueStateTracker":
        """Create a tracker from dump.

        The dump should be an array of dumped events. When restoring
        the tracker, these events will be replayed to recreate the state.
        """
        evts = events.deserialise_events(events_as_dict)

        return cls.from_events(sender_id, evts, slots, max_event_history)

applied_events

返回所有应该应用的动作

   def applied_events(self) -> List[Event]:
        """Returns all actions that should be applied - w/o reverted events.

        Returns:
            The events applied to the tracker.
        """
        loop_names = [
            event.name
            for event in self.events
            if isinstance(event, ActiveLoop) and event.name
        ]

        applied_events: List[Event] = []

        for event in self.events:
            if isinstance(event, (Restarted, SessionStarted)):
                applied_events = []
            elif isinstance(event, ActionReverted):
                self._undo_till_previous(ActionExecuted, applied_events)
            elif isinstance(event, UserUtteranceReverted):
                # Seeing a user uttered event automatically implies there was
                # a listen event right before it, so we'll first rewind the
                # user utterance, then get the action right before it (also removes
                # the `action_listen` action right before it).
                self._undo_till_previous(UserUttered, applied_events)
                self._undo_till_previous(ActionExecuted, applied_events)
            elif (
                isinstance(event, ActionExecuted)
                and event.action_name in loop_names
                and not self._first_loop_execution_or_unhappy_path(
                    event.action_name, applied_events
                )
            ):
                self._undo_till_previous_loop_execution(
                    event.action_name, applied_events
                )
            else:
                applied_events.append(event)

        return applied_events

_first_loop_execution_or_unhappy_path


    def _first_loop_execution_or_unhappy_path(
        self, loop_action_name: Text, applied_events: List[Event]
    ) -> bool:
        next_action: Optional[Text] = None

        for event in reversed(applied_events):
            # Stop looking for a previous loop execution if there is a loop deactivation
            # event because it means that the current loop is running for the first
            # time and previous loop events belong to different loops.
            if isinstance(event, ActiveLoop) and event.name is None:
                return True

            if self._is_within_unhappy_path(loop_action_name, event, next_action):
                return True

            if isinstance(event, ActionExecuted):
                # We found a previous execution of the loop and we are not within an
                # unhappy path.
                if event.action_name == loop_action_name:
                    return False

                # Remember the action as we need that to check whether we might be
                # within an unhappy path.
                next_action = event.action_name

        return True

ActionExecutionRejected

通知Core操作的执行已被拒绝


class ActionExecutionRejected(SkipEventInMDStoryMixin):
    """Notify Core that the execution of the action has been rejected."""

    type_name = "action_execution_rejected"

    def __init__(
        self,
        action_name: Text,
        policy: Optional[Text] = None,
        confidence: Optional[float] = None,
        timestamp: Optional[float] = None,
        metadata: Optional[Dict[Text, Any]] = None,
    ) -> None:
        """Creates event.

        Args:
            action_name: Action which was rejected.
            policy: Policy which predicted the rejected action.
            confidence: Confidence with which the reject action was predicted.
            timestamp: When the event was created.
            metadata: Additional event metadata.
        """
        self.action_name = action_name
        self.policy = policy
        self.confidence = confidence
        super().__init__(timestamp, metadata)

    def __str__(self) -> Text:
        """Returns text representation of event."""
        return (
            "ActionExecutionRejected("
            "action: {}, policy: {}, confidence: {})"
            "".format(self.action_name, self.policy, self.confidence)
        )

    def __hash__(self) -> int:
        """Returns unique hash for event."""
        return hash(self.action_name)
    def __eq__(self, other: Any) -> bool:
        """Compares object with other object."""
        if not isinstance(other, ActionExecutionRejected):
            return NotImplemented

        return self.action_name == other.action_name

ActionExecutionRejection

报一个异常 将允许其他策略预测不同的行动

class ActionExecutionRejection(RasaException):
    """Raising this exception will allow other policies
    to predict a different action"""

    def __init__(self, action_name: Text, message: Optional[Text] = None) -> None:
        self.action_name = action_name
        self.message = message or "Custom action '{}' rejected to run".format(
            action_name
        )
        super(ActionExecutionRejection, self).__init__()

    def __str__(self) -> Text:
        return self.message

ActionExtractSlots

每个用户回合后运行的默认Action操作

class ActionExtractSlots(Action):
    """Default action that runs after each user turn.

    Action is executed automatically in MessageProcessor.handle_message(...)
    before the next predicted action is run.

    Sets slots to extracted values from user message
    according to assigned slot mappings.
    """

    def __init__(self, action_endpoint: Optional[EndpointConfig]) -> None:
        """Initializes default action extract slots."""
        self._action_endpoint = action_endpoint

    def name(self) -> Text:
        """Returns action_extract_slots name."""
        return ACTION_EXTRACT_SLOTS

_verify_mapping_conditions

    @staticmethod
    def _verify_mapping_conditions(
        mapping: Dict[Text, Any], tracker: "DialogueStateTracker", slot_name: Text
    ) -> bool:
        if mapping.get(MAPPING_CONDITIONS) and mapping[MAPPING_TYPE] != str(
            SlotMappingType.FROM_TRIGGER_INTENT
        ):
            if not ActionExtractSlots._matches_mapping_conditions(
                mapping, tracker, slot_name
            ):
                return False

        return True

_fails_unique_entity_mapping_check

    def _fails_unique_entity_mapping_check(
        self,
        slot_name: Text,
        mapping: Dict[Text, Any],
        tracker: "DialogueStateTracker",
        domain: "Domain",
    ) -> bool:
        from rasa.core.actions.forms import FormAction

        if mapping[MAPPING_TYPE] != str(SlotMappingType.FROM_ENTITY):
            return False

        form_name = tracker.active_loop_name

        if not form_name:
            return False

        if tracker.get_slot(REQUESTED_SLOT) == slot_name:
            return False

        form = FormAction(form_name, self._action_endpoint)

        if slot_name not in form.required_slots(domain):
            return False

        if form.entity_mapping_is_unique(mapping, domain):
            return False

        return True

SlotMapping


class SlotMapping:
    """Defines functionality for the available slot mappings."""

    @staticmethod
    def validate(mapping: Dict[Text, Any], slot_name: Text) -> None:
        """Validates a slot mapping.

        Args:
            mapping: The mapping which is validated.
            slot_name: The name of the slot which is mapped by this mapping.

        Raises:
            InvalidDomain: In case the slot mapping is not valid.
        """
        from rasa.shared.core.domain import InvalidDomain

        if not isinstance(mapping, dict):
            raise InvalidDomain(
                f"Please make sure that the slot mappings for slot '{
      
      slot_name}' in "
                f"your domain are valid dictionaries. Please see "
                f"{
      
      DOCS_URL_SLOTS} for more information."
            )

        try:
            mapping_type = SlotMappingType(mapping.get(MAPPING_TYPE))
        except ValueError:
            raise InvalidDomain(
                f"Your domain uses an invalid slot mapping of type "
                f"'{
      
      mapping.get(MAPPING_TYPE)}' for slot '{
      
      slot_name}'. Please see "
                f"{
      
      DOCS_URL_SLOTS} for more information."
            )

        validations: Dict[SlotMappingType, List[Text]] = {
    
    
            SlotMappingType.FROM_ENTITY: ["entity"],
            SlotMappingType.FROM_INTENT: ["value"],
            SlotMappingType.FROM_TRIGGER_INTENT: ["value"],
            SlotMappingType.FROM_TEXT: [],
            SlotMappingType.CUSTOM: [],
        }

        required_keys = validations[mapping_type]
        for required_key in required_keys:
            if mapping.get(required_key) is None:
                raise InvalidDomain(
                    f"You need to specify a value for the key "
                    f"'{
      
      required_key}' in the slot mapping of type '{
      
      mapping_type}' "
                    f"for slot '{
      
      slot_name}'. Please see "
                    f"{
      
      DOCS_URL_SLOTS} for more information."
                )

check_mapping_validity

检查映射的有效性

    @staticmethod
    def check_mapping_validity(
        slot_name: Text,
        mapping_type: SlotMappingType,
        mapping: Dict[Text, Any],
        domain: "Domain",
    ) -> bool:
        """Checks the mapping for validity.

        Args:
            slot_name: The name of the slot to be validated.
            mapping_type: The type of the slot mapping.
            mapping: Slot mapping.
            domain: The domain to check against.

        Returns:
            True, if intent and entity specified in a mapping exist in domain.
        """
        if (
            mapping_type == SlotMappingType.FROM_ENTITY
            and mapping.get(ENTITY_ATTRIBUTE_TYPE) not in domain.entities
        ):
            rasa.shared.utils.io.raise_warning(
                f"Slot '{
      
      slot_name}' uses a 'from_entity' mapping "
                f"for a non-existent entity '{
      
      mapping.get(ENTITY_ATTRIBUTE_TYPE)}'. "
                f"Skipping slot extraction because of invalid mapping."
            )
            return False

        if (
            mapping_type == SlotMappingType.FROM_INTENT
            and mapping.get(INTENT) not in domain.intents
        ):
            rasa.shared.utils.io.raise_warning(
                f"Slot '{
      
      slot_name}' uses a 'from_intent' mapping for "
                f"a non-existent intent '{
      
      mapping.get('intent')}'. "
                f"Skipping slot extraction because of invalid mapping."
            )
            return False

        return True

intent_is_desired

检查用户意图是否与词槽映射意图规范匹配。

    @staticmethod
    def intent_is_desired(
        mapping: Dict[Text, Any], tracker: "DialogueStateTracker", domain: "Domain"
    ) -> bool:
        """Checks whether user intent matches slot mapping intent specifications."""
        mapping_intents = SlotMapping.to_list(mapping.get(INTENT, []))
        mapping_not_intents = SlotMapping.to_list(mapping.get(NOT_INTENT, []))

        active_loop_name = tracker.active_loop_name
        if active_loop_name:
            mapping_not_intents = set(
                mapping_not_intents
                + SlotMapping._get_active_loop_ignored_intents(
                    mapping, domain, active_loop_name
                )
            )

        if tracker.latest_message:
            intent = tracker.latest_message.intent.get(INTENT_NAME_KEY)
        else:
            intent = None

        intent_not_blocked = not mapping_intents and intent not in mapping_not_intents

        return intent_not_blocked or intent in mapping_intents

_get_active_loop_ignored_intents

    @staticmethod
    def _get_active_loop_ignored_intents(
        mapping: Dict[Text, Any], domain: "Domain", active_loop_name: Text
    ) -> List[Text]:
        from rasa.shared.core.constants import ACTIVE_LOOP

        mapping_conditions = mapping.get(MAPPING_CONDITIONS)
        active_loop_match = True
        ignored_intents = []

        if mapping_conditions:
            match_list = [
                condition.get(ACTIVE_LOOP) == active_loop_name
                for condition in mapping_conditions
            ]
            active_loop_match = any(match_list)

        if active_loop_match:
            form_ignored_intents = domain.forms[active_loop_name].get(
                IGNORED_INTENTS, []
            )
            ignored_intents = SlotMapping.to_list(form_ignored_intents)

        return ignored_intents

extract_slot_value_from_predefined_mapping

如果词槽具有适用的预定义映射,则提取词槽值

def extract_slot_value_from_predefined_mapping(
    mapping_type: SlotMappingType,
    mapping: Dict[Text, Any],
    tracker: "DialogueStateTracker",
) -> List[Any]:
    """Extracts slot value if slot has an applicable predefined mapping."""
    should_fill_entity_slot = (
        mapping_type == SlotMappingType.FROM_ENTITY
        and SlotMapping.entity_is_desired(mapping, tracker)
    )

    should_fill_intent_slot = mapping_type == SlotMappingType.FROM_INTENT

    should_fill_text_slot = mapping_type == SlotMappingType.FROM_TEXT

    active_loops_in_mapping_conditions = [
        active_loop.get(ACTIVE_LOOP)
        for active_loop in mapping.get(MAPPING_CONDITIONS, [])
    ]
    should_fill_trigger_slot = (
        mapping_type == SlotMappingType.FROM_TRIGGER_INTENT
        and tracker.active_loop_name not in active_loops_in_mapping_conditions
    )

    value: List[Any] = []
    if should_fill_entity_slot:
        value = list(
            tracker.get_latest_entity_values(
                mapping.get(ENTITY_ATTRIBUTE_TYPE),
                mapping.get(ENTITY_ATTRIBUTE_ROLE),
                mapping.get(ENTITY_ATTRIBUTE_GROUP),
            )
        )
    elif should_fill_intent_slot or should_fill_trigger_slot:
        value = [mapping.get("value")]
    elif should_fill_text_slot:
        value = [
            tracker.latest_message.text if tracker.latest_message is not None else None
        ]

    return value

constants常量

E:\starspace\GavinNLP\rasa-3.1.0\rasa\shared\core\constants.py

slotmap的类型

  • FROM_ENTITY = “from_entity”
  • FROM_INTENT = “from_intent”
  • FROM_TRIGGER_INTENT = “from_trigger_intent”
  • FROM_TEXT = “from_text”
  • CUSTOM = “custom”

SLOT_MAPPINGS = "mappings"
MAPPING_CONDITIONS = "conditions"
MAPPING_TYPE = "type"


class SlotMappingType(Enum):
    """Slot mapping types."""

    FROM_ENTITY = "from_entity"
    FROM_INTENT = "from_intent"
    FROM_TRIGGER_INTENT = "from_trigger_intent"
    FROM_TEXT = "from_text"
    CUSTOM = "custom"

    def __str__(self) -> str:
        """Returns the string representation that should be used in config files."""
        return self.value

    def is_predefined_type(self) -> bool:
        """Returns True iff the mapping type is predefined.

        That is, to evaluate the mapping no custom action execution is needed.
        """
        return self != SlotMappingType.CUSTOM


RasaException

import json
from typing import Optional, Text

import jsonschema
from ruamel.yaml.error import (
    MarkedYAMLError,
    MarkedYAMLWarning,
    MarkedYAMLFutureWarning,
)


class RasaException(Exception):
    """Base exception class for all errors raised by Rasa Open Source.

    These exceptions results from invalid use cases and will be reported
    to the users, but will be ignored in telemetry.
    """


class RasaCoreException(RasaException):
    """Basic exception for errors raised by Rasa Core."""


class RasaXTermsError(RasaException):
    """Error in case the user didn't accept the Rasa X terms."""


class InvalidParameterException(RasaException, ValueError):
    """Raised when an invalid parameter is used."""


class YamlException(RasaException):
    """Raised if there is an error reading yaml."""

    def __init__(self, filename: Optional[Text] = None) -> None:
        """Create exception.

        Args:
            filename: optional file the error occurred in"""
        self.filename = filename


class YamlSyntaxException(YamlException):
    """Raised when a YAML file can not be parsed properly due to a syntax error."""

    def __init__(
        self,
        filename: Optional[Text] = None,
        underlying_yaml_exception: Optional[Exception] = None,
    ) -> None:
        super(YamlSyntaxException, self).__init__(filename)

        self.underlying_yaml_exception = underlying_yaml_exception

    def __str__(self) -> Text:
        if self.filename:
            exception_text = f"Failed to read '{
      
      self.filename}'."
        else:
            exception_text = "Failed to read YAML."

        if self.underlying_yaml_exception:
            if isinstance(
                self.underlying_yaml_exception,
                (MarkedYAMLError, MarkedYAMLWarning, MarkedYAMLFutureWarning),
            ):
                self.underlying_yaml_exception.note = None
            if isinstance(
                self.underlying_yaml_exception,
                (MarkedYAMLWarning, MarkedYAMLFutureWarning),
            ):
                self.underlying_yaml_exception.warn = None
            exception_text += f" {
      
      self.underlying_yaml_exception}"

        if self.filename:
            exception_text = exception_text.replace(
                'in "<unicode string>"', f'in "{
      
      self.filename}"'
            )

        exception_text += (
            "\n\nYou can use https://yamlchecker.com/ to validate the "
            "YAML syntax of your file."
        )
        return exception_text


class FileNotFoundException(RasaException, FileNotFoundError):
    """Raised when a file, expected to exist, doesn't exist."""


class FileIOException(RasaException):
    """Raised if there is an error while doing file IO."""


class InvalidConfigException(ValueError, RasaException):
    """Raised if an invalid configuration is encountered."""


class UnsupportedFeatureException(RasaCoreException):
    """Raised if a requested feature is not supported."""


class SchemaValidationError(RasaException, jsonschema.ValidationError):
    """Raised if schema validation via `jsonschema` failed."""


class InvalidEntityFormatException(RasaException, json.JSONDecodeError):
    """Raised if the format of an entity is invalid."""

    @classmethod
    def create_from(
        cls, other: json.JSONDecodeError, msg: Text
    ) -> "InvalidEntityFormatException":
        """Creates `InvalidEntityFormatException` from `JSONDecodeError`."""
        return cls(msg, other.doc, other.pos)


class ConnectionException(RasaException):
    """Raised when a connection to a 3rd party service fails.

    It's used by our broker and tracker store classes, when
    they can't connect to services like postgres, dynamoDB, mongo.
    """

Rasa 3.x系列博客分享

猜你喜欢

转载自blog.csdn.net/duan_zhihua/article/details/124169857