コード例 #1
0
def _replay_tracker_events(tracker: DialogueStateTracker,
                           event_service: EventService) -> None:
    """Migrates the `events`, `logs`, `sessions` collections."""

    for event in tracker.events:
        event_dict = event.as_dict()
        # add sender id to event
        event_dict["sender_id"] = tracker.sender_id
        stringified_event = json.dumps(event_dict)
        # Update events + most of conversations metadata
        _ = event_service.save_event(stringified_event)
コード例 #2
0
def _replay_tracker_events(
    tracker: DialogueStateTracker,
    event_service: EventService,
    logs_service: LogsService,
    analytics_service: AnalyticsService,
) -> None:
    """Migrates the `events`, `logs`, `sessions` collections."""

    for event in tracker.events:
        event_dict = event.as_dict()
        # add sender id to event
        event_dict["sender_id"] = tracker.sender_id
        stringified_event = json.dumps(event_dict)
        # Update events + most of conversations metadata
        event = event_service.save_event(stringified_event)
        # Save logs from conversations
        _save_nlu_log(logs_service, event_dict, event.id)
        # Update analytics
        analytics_service.save_analytics(stringified_event,
                                         sender_id=event.conversation_id)
コード例 #3
0
class EventConsumer:
    """Abstract base class for all event consumers."""

    type_name = None

    def __init__(
        self,
        should_run_liveness_endpoint: bool = False,
        session: Optional["Session"] = None,
    ) -> None:
        """Abstract event consumer that implements a liveness endpoint.

        Args:
            should_run_liveness_endpoint: If `True`, runs a Sanic server as a
                background process that can be used to probe liveness of this service.
                The service will be exposed at a port defined by the
                `SELF_PORT` environment variable (5673 by default).
            session: SQLAlchemy session to use.

        """
        self.liveness_endpoint: Optional["Process"] = None
        self.start_liveness_endpoint_process(should_run_liveness_endpoint)

        self._session = session or db_utils.get_database_session(config.LOCAL_MODE)

        self.event_service = EventService(self._session)
        self.analytics_service = AnalyticsService(self._session)
        self.logs_service = LogsService(self._session)

        self.pending_events: Deque[PendingEvent] = deque(maxlen=MAX_PENDING_EVENTS)

    def __enter__(self) -> None:
        pass

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        self._session.close()

    @staticmethod
    def _run_liveness_endpoint_process(consumer_type: Text) -> "Process":
        """Run a Sanic app as a multiprocessing.Process and return it.

        Args:
            consumer_type: Event consumer type.

        Returns:
            Sanic endpoint app as a multiprocessing.Process.

        """
        port = int(os.environ.get("SELF_PORT", "5673"))
        p = utils.run_in_process(
            fn=_run_liveness_app, args=(port, consumer_type), daemon=True
        )

        logger.info(f"Started Sanic liveness endpoint at port '{port}'.")

        return p

    def start_liveness_endpoint_process(
        self, should_run_liveness_endpoint: bool
    ) -> None:
        """Start liveness endpoint multiprocessing.Process if
        `should_run_liveness_endpoint` is `True`, else do nothing."""

        if should_run_liveness_endpoint:
            self.liveness_endpoint = self._run_liveness_endpoint_process(self.type_name)

    def kill_liveness_endpoint_process(self) -> None:
        """Kill liveness endpoint multiprocessing.Process if it is active."""

        if self.liveness_endpoint and self.liveness_endpoint.is_alive():
            self.liveness_endpoint.terminate()
            logger.info(
                f"Terminated event consumer liveness endpoint process "
                f"with PID '{self.liveness_endpoint.pid}'."
            )

    def log_event(
        self,
        data: Union[Text, bytes],
        sender_id: Optional[Text] = None,
        event_number: Optional[int] = None,
        origin: Optional[Text] = None,
        import_process_id: Optional[Text] = None,
    ) -> None:
        """Handle an incoming event forwarding it to necessary services and handlers.

        Args:
            data: Event to be logged.
            sender_id: Conversation ID sending the event.
            event_number: Event number associated with the event.
            origin: Rasa environment origin of the event.
            import_process_id: Unique ID if the event comes from a `rasa export`
                process.

        """

        log_operation = self._event_log_operation(
            data, sender_id, event_number, origin, import_process_id
        )

        try:
            log_operation()

            self._session.commit()

            self._process_pending_events()
        except sqlalchemy.exc.IntegrityError as e:
            logger.warning(
                f"Saving event failed due to an 'IntegrityError'. This "
                f"means that the event is already stored in the "
                f"database. The event data was '{data}'. {e}"
            )
            self._session.rollback()
        except Exception as e:
            logger.error(e)
            self._save_event_as_pending(data, log_operation)
            self._session.rollback()

    def _event_log_operation(
        self,
        data: Union[Text, bytes],
        sender_id: Optional[Text] = None,
        event_number: Optional[int] = None,
        origin: Optional[Text] = None,
        import_process_id: Optional[Text] = None,
    ) -> Callable[[], None]:
        def _log() -> None:
            event = self.event_service.save_event(
                data,
                sender_id=sender_id,
                event_number=event_number,
                origin=origin,
                import_process_id=import_process_id,
            )
            self.logs_service.save_nlu_logs_from_event(data, event.id)
            self.analytics_service.save_analytics(data, sender_id=event.conversation_id)

            if utils.is_enterprise_installed():
                from rasax.enterprise import reporting  # pytype: disable=import-error

                reporting.report_event(json.loads(data), event.conversation_id)

        return _log

    def _save_event_as_pending(
        self,
        raw_event: Union[Text, bytes],
        on_save: Optional[Callable[[], None]] = None,
    ) -> None:
        """Add `ConversationEvent` to pending events.

        Args:
            raw_event: Consumed event which has to be saved later since the last try
                failed.
            on_save: `Callable` that will be called to persist the event.
        """
        if len(self.pending_events) >= MAX_PENDING_EVENTS:
            pending_event = self.pending_events.popleft()
            warnings.warn(
                f"`PendingEvents` deque has exceeded its maximum length of "
                f"{MAX_PENDING_EVENTS}. The oldest event with data "
                f"{pending_event.raw_event} was removed."
            )

        self.pending_events.append(PendingEvent(raw_event, on_save))

    def _process_pending_events(self) -> None:
        """Process all pending events."""

        for pending_event in list(self.pending_events):
            try:
                pending_event.on_save()
                self._session.commit()
                self.pending_events.remove(pending_event)
            except Exception as e:
                self._session.rollback()
                logger.debug(
                    f"Cannot process the pending event with "
                    f"the following data: '{pending_event.raw_event}'."
                    f"Exception: {e}."
                )

    def consume(self):
        """Consume events."""
        raise NotImplementedError(
            "Each event consumer needs to implement the `consume()` method."
        )