Exemplo n.º 1
0
    def clean(self, goal: Goal):
        """Cleans the agenda, i.e. makes sure that actions are consistent with goal and in the
        correct order.

        Args:
            goal (Goal): The goal which is needed to determine the consistent actions.

        """
        cleaned_stack = []
        # reverse order since most recent actions are on top of agenda
        for action in self.stack[::-1]:
            if action not in cleaned_stack:
                # NOTE sufficient if there is only one slot per (request) action
                # remove accomplished requests
                if (action.type is not UserActionType.Request
                        or (action.slot in goal.requests
                            and goal.requests[action.slot] is None)
                        or action.slot not in goal.requests):
                    # make sure to remove "old" inform actions
                    if action.type is UserActionType.Inform:
                        if not goal.is_inconsistent_constraint(
                                Constraint(action.slot, action.value)):
                            cleaned_stack.insert(0, action)
                    else:
                        cleaned_stack.insert(0, action)
        self.stack = cleaned_stack
Exemplo n.º 2
0
def goal(domain):
    return Goal(domain)
Exemplo n.º 3
0
    def __init__(self, domain: Domain, logger: DiasysLogger = DiasysLogger()):
        super(HandcraftedUserSimulator, self).__init__(domain)

        # possible system actions
        self.receive_options = {
            SysActionType.Welcome: self._receive_welcome,
            SysActionType.InformByName: self._receive_informbyname,
            SysActionType.InformByAlternatives:
            self._receive_informbyalternatives,
            SysActionType.Request: self._receive_request,
            SysActionType.Confirm: self._receive_confirm,
            SysActionType.Select: self._receive_select,
            SysActionType.RequestMore: self._receive_requestmore,
            SysActionType.Bad: self._receive_bad,
            SysActionType.ConfirmRequest: self._receive_confirmrequest
        }

        # parse config file
        self.logger = logger
        self.config = configparser.ConfigParser(inline_comment_prefixes=('#',
                                                                         ';'))
        self.config.optionxform = str
        self.config.read(
            os.path.join(os.path.abspath(os.path.dirname(__file__)),
                         'usermodel.cfg'))

        self.parameters = {}
        # goal
        self.parameters['goal'] = {}
        for key in self.config["goal"]:
            val = self.config.get("goal", key)
            self.parameters['goal'][key] = float(val)

        # usermodel
        self.parameters['usermodel'] = {}
        for key in self.config["usermodel"]:
            val = self.config.get("usermodel", key)
            if key in ['patience']:
                # patience will be sampled on begin of each dialog
                self.parameters['usermodel'][key] = [
                    int(x)
                    for x in (val.replace(' ', '').strip('[]').split(','))
                ]
            else:
                if val.startswith("[") and val.endswith("]"):
                    # value is a list to sample the probability from
                    self.parameters['usermodel'][
                        key] = common.numpy.random.uniform(*[
                            float(x) for x in val.replace(' ', '').strip(
                                '[]').split(',')
                        ])
                else:
                    # value is the probability
                    self.parameters['usermodel'][key] = float(val)

        # member declarations
        self.turn = 0
        self.domain = domain
        self.dialog_patience = None
        self.patience = None
        self.last_user_actions = None
        self.last_system_action = None
        self.excluded_venues = []

        # member definitions
        self.goal = Goal(domain, self.parameters['goal'])
        self.agenda = Agenda()
        self.num_actions_next_turn = -1
Exemplo n.º 4
0
class HandcraftedUserSimulator(Service):
    """The class for a handcrafted (agenda-based) user simulator.

    Args:
        domain (Domain): The domain for which the user simulator will be instantiated. It will use
        this domain to generate the goals.
    """
    def __init__(self, domain: Domain, logger: DiasysLogger = DiasysLogger()):
        super(HandcraftedUserSimulator, self).__init__(domain)

        # possible system actions
        self.receive_options = {
            SysActionType.Welcome: self._receive_welcome,
            SysActionType.InformByName: self._receive_informbyname,
            SysActionType.InformByAlternatives:
            self._receive_informbyalternatives,
            SysActionType.Request: self._receive_request,
            SysActionType.Confirm: self._receive_confirm,
            SysActionType.Select: self._receive_select,
            SysActionType.RequestMore: self._receive_requestmore,
            SysActionType.Bad: self._receive_bad,
            SysActionType.ConfirmRequest: self._receive_confirmrequest
        }

        # parse config file
        self.logger = logger
        self.config = configparser.ConfigParser(inline_comment_prefixes=('#',
                                                                         ';'))
        self.config.optionxform = str
        self.config.read(
            os.path.join(os.path.abspath(os.path.dirname(__file__)),
                         'usermodel.cfg'))

        self.parameters = {}
        # goal
        self.parameters['goal'] = {}
        for key in self.config["goal"]:
            val = self.config.get("goal", key)
            self.parameters['goal'][key] = float(val)

        # usermodel
        self.parameters['usermodel'] = {}
        for key in self.config["usermodel"]:
            val = self.config.get("usermodel", key)
            if key in ['patience']:
                # patience will be sampled on begin of each dialog
                self.parameters['usermodel'][key] = [
                    int(x)
                    for x in (val.replace(' ', '').strip('[]').split(','))
                ]
            else:
                if val.startswith("[") and val.endswith("]"):
                    # value is a list to sample the probability from
                    self.parameters['usermodel'][
                        key] = common.numpy.random.uniform(*[
                            float(x) for x in val.replace(' ', '').strip(
                                '[]').split(',')
                        ])
                else:
                    # value is the probability
                    self.parameters['usermodel'][key] = float(val)

        # member declarations
        self.turn = 0
        self.domain = domain
        self.dialog_patience = None
        self.patience = None
        self.last_user_actions = None
        self.last_system_action = None
        self.excluded_venues = []

        # member definitions
        self.goal = Goal(domain, self.parameters['goal'])
        self.agenda = Agenda()
        self.num_actions_next_turn = -1

    def dialog_start(self):
        """Resets the user model at the beginning of a dialog, e.g. draws a new goal and populates
        the agenda according to the goal."""
        # self.goal = Goal(self.domain, self.parameters['goal'])

        self.goal.init()
        self.agenda.init(self.goal)
        if self.logger:
            self.logger.dialog_turn(
                "New goal has constraints {} and requests {}.".format(
                    self.goal.constraints, self.goal.requests))
            self.logger.dialog_turn("New agenda initialized: {}".format(
                self.agenda))

        # add hello action with some probability
        if common.random.random() < self.parameters['usermodel']['Greeting']:
            self.agenda.push(UserAct(act_type=UserActionType.Hello, score=1.0))

        # needed for possibility to reset patience
        if len(self.parameters['usermodel']['patience']) == 1:
            self.dialog_patience = self.parameters['usermodel']['patience'][0]
        else:
            self.dialog_patience = common.random.randint(
                *self.parameters['usermodel']['patience'])
        self.patience = self.dialog_patience
        self.last_user_actions = None
        self.last_system_action = None
        self.excluded_venues = []
        self.turn = 0

    @PublishSubscribe(sub_topics=["sys_act", "sys_turn_over"],
                      pub_topics=["user_acts", "sim_goal"])
    def user_turn(self, sys_act: SysAct = None, sys_turn_over=False) \
            -> dict(user_acts=List[UserAct], sim_goal=Goal):
        """
        Determines the next user actions based on the given system actions and the user simulator's own goal

        Args:
            sys_act (SysAct): The system action for which a user response will be retrieved.
            sys_turn_over (bool): signal to start the user turn
        Returns:
            (dict): Dictionary including the user acts as a list and the current user's goal.

        """
        # self.turn = dialog_graph.num_turns
        if sys_act is not None and sys_act.type == SysActionType.Bye:
            # if self.goal.is_fulfilled():
            #     self._finish_dialog()
            return {"sim_goal": self.goal}

        if sys_act is not None:
            self.receive(sys_act)

        user_acts = self.respond()

        # user_acts = [UserAct(text="Hi!", act_type=UserActionType.Hello, score=1.)]

        self.logger.dialog_turn("User Action: " + str(user_acts))
        # input()
        return {'user_acts': user_acts}

    def receive(self, sys_act: SysAct):
        """
        This function makes sure that the agenda reflects all changes needed for the received
        system action.
        
        Args:
            sys_act (SysAct): The action the system took
        """

        if self.last_system_action is not None:
            # check whether system action is the same as before
            if sys_act == self.last_system_action:
                self.patience -= 1
            elif self.parameters['usermodel']['resetPatience']:
                self.patience = self.dialog_patience

        self.last_system_action = sys_act

        if self.patience == 0:
            self.logger.dialog_turn("User patience run out, ending dialog.")
            self.agenda.clear()
            self._finish_dialog(ungrateful=True)
        else:
            ignored_requests, ignored_requests_alt = self._check_system_ignored_request(
                self.last_user_actions, sys_act)

            # first stage: push operations on top of agenda
            if sys_act.type in self.receive_options:
                self.receive_options[sys_act.type](sys_act)

                # handle missing requests
                if ignored_requests:
                    # repeat unanswered requests from user from last turn
                    self.agenda.push(ignored_requests)

                if ignored_requests_alt:
                    self.agenda.push(ignored_requests_alt)
                    # make sure to pick only the requestalt actions (should be 1)
                    self.num_actions_next_turn = len(ignored_requests_alt)
                    # make sure that old request actions verifying an offer are removed
                    self.agenda.remove_actions_of_type(
                        act_type=UserActionType.Request)

                # second stage: clean agenda
                self.agenda.clean(self.goal)
                # agenda might be empty -> add requests again
                if self.agenda.is_empty():
                    if self.goal.is_fulfilled():
                        self._finish_dialog()
                    else:
                        self.agenda.fill_with_requests(self.goal,
                                                       exclude_name=False)

            else:
                self.logger.error(
                    "System Action Type is {}, but I don't know how to handle it!"
                    .format(sys_act.type))

    def _receive_welcome(self, sys_act: SysAct):
        """
        Processes a welcome action from the system. In this case do nothing
        
        Args:
            sys_act (SysAct): the last system action
        """
        # do nothing as the first turn is already intercepted
        # also, the 'welcome' action is never used in reinforcement learning from the policy
        # -> will only, if at all, occur at first turn

    def _receive_informbyname(self, sys_act: SysAct):
        """
        Processes an informbyname action from the system; checks if the inform matches the
        goal constraints and if yes, will add unanswered requests to the agenda 
        
        Args:
            sys_act (SysAct): the last system action        
        """
        # check all system informs for offer
        inform_list = []
        offers = []
        for slot, value_list in sys_act.slot_values.items():
            for value in value_list:
                if slot == 'name':
                    offers.append(value)
                else:
                    inform_list.append(Constraint(slot, value))

        # check offer
        if offers:
            if self._check_offer(offers, inform_list):
                # valid offer
                for slot, value in inform_list:
                    self.goal.fulfill_request(slot, value)

        # needed to make sure that not informed constraints (which have been turned into requests)
        # will be asked first (before ending the dialog too early)
        req_actions_not_in_goal = []
        for action in self.agenda.get_actions_of_type(UserActionType.Request):
            if action.slot not in self.goal.requests:
                req_actions_not_in_goal.append(copy.deepcopy(action))

        # goal might be fulfilled now
        if (self.goal.is_fulfilled() and
                not self.agenda.contains_action_of_type(UserActionType.Inform)
                and not req_actions_not_in_goal):
            self._finish_dialog()

    def _receive_informbyalternatives(self, sys_act: SysAct):
        """
        Processes an informbyalternatives action from the system; this is treated like
        an inform by name
        
        Args:
            sys_act (SysAct): the last system action        
        """
        # same as inform by name
        if self.excluded_venues and self.goal.requests[
                self.domain.get_primary_key()] is None:
            self._receive_informbyname(sys_act)
        else:
            self._repeat_last_actions()

    def _receive_request(self, sys_act: SysAct):
        """
        Processes a request action from the system by adding the corresponding answer based
        on the current simulator goal.
        
        Args:
            sys_act (SysAct): the last system action        
        """
        for slot, _ in sys_act.slot_values.items():
            self.agenda.push(
                UserAct(act_type=UserActionType.Inform,
                        slot=slot,
                        value=self.goal.get_constraint(slot),
                        score=1.0))

    def _receive_confirm(self, sys_act: SysAct):
        """
        Processes a confirm action from the system based on information in the user goal
        
        Args:
            sys_act (SysAct): the last system action        
        """
        for slot, _value in sys_act.slot_values.items():
            value = _value[0]  # there is always only one value
            if self.goal.is_inconsistent_constraint_strict(
                    Constraint(slot, value)):
                # inform about correct value with some probability, otherwise deny value
                if common.random.random(
                ) < self.parameters['usermodel']['InformOnConfirm']:
                    self.agenda.push(
                        UserAct(act_type=UserActionType.Inform,
                                slot=slot,
                                value=self.goal.get_constraint(slot),
                                score=1.0))
                else:
                    self.agenda.push(
                        UserAct(act_type=UserActionType.NegativeInform,
                                slot=slot,
                                value=value,
                                score=1.0))
            else:
                # NOTE using inform currently since NLU currently does not support Affirm here and
                # NLU would tinker it into an Inform action anyway
                # self.agenda.push(
                #     UserAct(act_type=UserActionType.Affirm, score=1.0))
                self.agenda.push(
                    UserAct(act_type=UserActionType.Inform,
                            slot=slot,
                            value=value,
                            score=1.0))

    def _receive_select(self, sys_act: SysAct):
        """
        Processes a select action from the system based on the simulation goal
        
        Args:
            sys_act (SysAct): the last system action        
        """
        # handle as request
        value_in_goal = False
        for slot, values in sys_act.slot_values.items():
            for value in values:
                # do not consider 'dontcare' as any value
                if not self.goal.is_inconsistent_constraint_strict(
                        Constraint(slot, value)):
                    value_in_goal = True

        if value_in_goal:
            self._receive_request(sys_act)
        else:
            assert len(sys_act.slot_values.keys()) == 1, \
                "There shall be only one slot in a select action."
            # NOTE: currently we support only one slot for select action,
            # but this could be changed in the future

            slot = list(sys_act.slot_values.keys())[0]
            # inform about correct value with some probability
            if common.random.random(
            ) < self.parameters['usermodel']['InformOnSelect']:
                self.agenda.push(
                    UserAct(act_type=UserActionType.Inform,
                            slot=slot,
                            value=self.goal.get_constraint(slot),
                            score=1.0))

            for slot, values in sys_act.slot_values.items():
                for value in values:
                    self.agenda.push(
                        UserAct(act_type=UserActionType.NegativeInform,
                                slot=slot,
                                value=value,
                                score=1.0))

    def _receive_requestmore(self, sys_act: SysAct):
        """
        Processes a requestmore action from the system.
        
        Args:
            sys_act (SysAct): the last system action        
        """
        if self.goal.is_fulfilled():
            # end dialog
            self._finish_dialog()
        elif (not self.agenda.contains_action_of_type(UserActionType.Inform)
              and self.goal.requests['name'] is not None):
            # venue has been offered and all informs have been issued, but atleast one request slot
            # is missing
            if self.agenda.is_empty():
                self.agenda.fill_with_requests(self.goal)
        else:
            # make sure that dialog becomes longer
            self._repeat_last_actions()

    def _receive_bad(self, sys_act: SysAct):
        """
        Processes a bad action from the system; repeats the last user action
        
        Args:
            sys_act (SysAct): the last system action        
        """
        # NOTE repeat last action, should never occur on intention-level as long no noise is used
        self._repeat_last_actions()

    def _receive_confirmrequest(self, sys_act: SysAct):
        """
        Processes a confirmrequest action from the system.
        
        Args:
            sys_act (SysAct): the last system action        
        """
        # first slot is confirm, second slot is request
        for slot, value in sys_act.slot_values.items():
            if value is None:
                # system's request action
                self._receive_request(
                    SysAct(act_type=SysActionType.Request,
                           slot_values={slot: None}))
            else:
                # system's confirm action
                # NOTE SysActionType Confirm has single value only
                self._receive_confirm(
                    SysAct(act_type=SysActionType.Confirm,
                           slot_values={slot: [value]}))

    def respond(self):
        """
        Gets n actions from the agenda, where n is drawn depending on the agenda or a pdf.
        """
        # get some actions from the agenda

        assert len(
            self.agenda
        ) > 0, "Agenda is empty, this must not happen at this point!"

        if self.num_actions_next_turn > 0:
            # use and reset self.num_actions_next_turn if set
            num_actions = self.num_actions_next_turn
            self.num_actions_next_turn = -1
        elif self.agenda.stack[-1].type == UserActionType.Bye:
            # pop all actions from agenda since agenda can only contain thanks (optional) and
            # bye action
            num_actions = -1
        else:
            # draw amount of actions
            num_actions = min(len(self.agenda),
                              common.numpy.random.choice(
                                  [1, 2, 3], p=[.6, .3, .1]))  # hardcoded pdf

        # get actions from agenda
        user_actions = self.agenda.get_actions(num_actions)
        # copy needed for repeat action since they might be changed in other modules
        self.last_user_actions = copy.deepcopy(user_actions)

        for action in user_actions:
            if action.type == UserActionType.Inform:
                _constraint = Constraint(action.slot, action.value)
                # if _constraint in self.goal.constraints:
                if action in self.goal.missing_informs:
                    self.goal.missing_informs.remove(action)

        return user_actions

    def _finish_dialog(self, ungrateful=False):
        """
        Pushes a bye action ontop of the agenda in order to end a dialog. Depending on the user
        model, a thankyou action might be added too.

        Args:
            ungrateful (bool): determines if the user should also say "thanks"; if the dialog ran
                               too long or the user ran out of patience, ungrateful will be true
        """
        self.agenda.clear()  # empty agenda
        # thank with some probability
        # NOTE bye has to be the topmost action on the agenda since we check for it in the
        # respond() method
        if not ungrateful and common.random.random(
        ) < self.parameters['usermodel']['Thank']:
            self.agenda.push(UserAct(act_type=UserActionType.Thanks,
                                     score=1.0))
        self.agenda.push(UserAct(act_type=UserActionType.Bye, score=1.0))

    def _repeat_last_actions(self):
        """
        Pushes the last user actions ontop of the agenda.
        """
        if self.last_user_actions is not None:
            self.agenda.push(self.last_user_actions[::-1])
            self.num_actions_next_turn = len(self.last_user_actions)

    def _alter_constraints(self, constraints, count):
        """
        Alters *count* constraints from the given constraints by choosing a new value
        (could be also 'dontcare').
        """
        constraints_candidates = constraints[:]  # copy list
        if not constraints_candidates:
            for _constraint in self.goal.constraints:
                if _constraint.value != 'dontcare':
                    constraints_candidates.append(
                        Constraint(_constraint.slot, _constraint.value))
        else:
            # any constraint from the current system actions has to be taken into consideration
            # make sure that constraints are part of the goal since noise could have influenced the
            # dialog -> given constraints must conform to the current goal
            constraints_candidates = list(
                filter(
                    lambda x: not self.goal.is_inconsistent_constraint_strict(
                        x), constraints_candidates))

        if not constraints_candidates:
            return []

        constraints_to_alter = common.numpy.random.choice(
            constraints_candidates, count, replace=False)

        new_constraints = []
        for _constraint in constraints_to_alter:
            self.goal.excluded_inf_slot_values[_constraint.slot].add(
                _constraint.value)
            possible_values = self.goal.inf_slot_values[_constraint.slot][:]
            for _value in self.goal.excluded_inf_slot_values[_constraint.slot]:
                # remove values which have been tried already
                # NOTE values in self.excluded_inf_slot_values should always be in possible_values
                # because the same source is used for both and to initialize the goal
                possible_values.remove(_value)

            if not possible_values:
                # add 'dontcare' as last option
                possible_values.append('dontcare')

            # 'dontcare' value with some probability
            if common.random.random(
            ) < self.parameters['usermodel']['DontcareIfNoVenue']:
                value = 'dontcare'
            else:
                value = common.numpy.random.choice(possible_values)
            if not self.goal.update_constraint(_constraint.slot, value):
                # NOTE: this case should never happen!
                print(
                    "The given constraints (probably by the system) are not part of the goal!"
                )
            new_constraints.append(Constraint(_constraint.slot, value))

        self.logger.dialog_turn("Goal altered! {} -> {}.".format(
            constraints_to_alter, new_constraints))

        return new_constraints

    def _check_informs(self, informed_constraints_by_system):
        """ Checks whether the informs by the system are consistent with the goal and pushes
        appropriate actions onto the agenda for inconsistent constraints. """

        # check for inconsistent constraints and remove informs of consistent constraints from
        # agenda
        consistent_with_goal = True
        for _constraint in informed_constraints_by_system:
            if self.goal.is_inconsistent_constraint(_constraint):
                consistent_with_goal = False
                self.agenda.push(
                    UserAct(act_type=UserActionType.Inform,
                            slot=_constraint.slot,
                            value=self.goal.get_constraint(_constraint.slot),
                            score=1.0))
            else:
                self.agenda.remove_actions(UserActionType.Inform, *_constraint)

        return consistent_with_goal

    def _check_offer(self, offers, informed_constraints_by_system):
        """ Checks for an offer and returns True if the offer is valid. """

        if not self._check_informs(informed_constraints_by_system):
            # reset offer in goal since inconsistencies have been detected and covered
            self.goal.requests[self.domain.get_primary_key()] = None
            return False

        # TODO maybe check for current offer first since alternative with name='none' by system
        # would trigger goal change -> what is the correct action in this case?
        if offers:
            if 'none' not in offers:
                # offer was given

                # convert informs of values != 'dontcare' to requests
                actions_to_convert = list(
                    self.agenda.get_actions_of_type(UserActionType.Inform,
                                                    consider_dontcare=False))
                if len(self.goal.constraints) > 1 and len(
                        actions_to_convert) == len(self.goal.constraints):
                    # penalise too early offers
                    self._repeat_last_actions()
                    self.num_actions_next_turn = len(self.last_user_actions)
                    return False

                # ask for values of remaining inform slots on agenda - this has two purposes:
                #   1. making sure that offer is consistent with goal
                #   2. making sure that inconsistent offers prolongate a dialog
                for action in actions_to_convert:
                    self.agenda.push(
                        UserAct(act_type=UserActionType.Request,
                                slot=action.slot,
                                value=None,
                                score=1.0))
                self.agenda.remove_actions_of_type(UserActionType.Inform)

                if self.goal.requests[
                        self.domain.get_primary_key()] is not None:
                    if self.goal.requests[
                            self.domain.get_primary_key()] in offers:
                        # offer is the same, don't change anything but treat offer as valid
                        return True
                    else:
                        # offer is not the same, but did not request a new one
                        # NOTE with current bst do not (negative) inform about the offer, because
                        # it will only set the proability to zero -> will not be excluded
                        # self.agenda.push(UserAct(act_type=UserActionType.NegativeInform,\
                        #     slot=self.domain.get_primary_key(), value=offers[0]))
                        return False
                else:
                    for _offer in offers:
                        if _offer not in self.excluded_venues:
                            # offer is not on the exclusion list (e.g. from reqalt action) and
                            # there is no current offer

                            # sometimes ask for alternative
                            if common.random.random(
                            ) < self.parameters['usermodel']['ReqAlt']:
                                self._request_alt(_offer)
                                return False
                            else:
                                self.goal.requests[
                                    self.domain.get_primary_key()] = _offer
                                for _action in self.goal.missing_informs:
                                    # informed constraints by system are definitely consistent with
                                    # goal at this point
                                    if Constraint(
                                            _action.slot, _action.value
                                    ) not in informed_constraints_by_system:
                                        self.agenda.push(
                                            UserAct(act_type=UserActionType.
                                                    Request,
                                                    slot=_action.slot,
                                                    value=None))
                                return True

                    # no valid offer was given
                    self._request_alt()
                    return False
            else:
                # no offer was given
                # TODO add probability to choose number of alternations
                altered_constraints = self._alter_constraints(
                    informed_constraints_by_system, 1)
                # reset goal push new actions on top of agenda
                self.goal.reset()
                self.goal.missing_informs = [
                    UserAct(act_type=UserActionType.Inform,
                            slot=_constraint.slot,
                            value=_constraint.value)
                    for _constraint in self.goal.constraints
                ]
                for _constraint in altered_constraints:
                    self.agenda.push(
                        UserAct(act_type=UserActionType.Inform,
                                slot=_constraint.slot,
                                value=_constraint.value,
                                score=1.0))
                self.agenda.clean(self.goal)
                return False
        return False

    def _request_alt(self, offer=None):
        """
        Handles the case where a user might want to ask for an alternative offer
        """
        # add current offer to exclusion list, reset current offer and request alternative
        if offer is not None:
            self.excluded_venues.append(offer)
        if self.goal.requests[self.domain.get_primary_key()] is not None:
            self.excluded_venues.append(
                self.goal.requests[self.domain.get_primary_key()])
            self.goal.requests[self.domain.get_primary_key()] = None
        self.goal.reset()
        self.agenda.push(UserAct(act_type=UserActionType.RequestAlternatives))

    def _check_system_ignored_request(self, user_actions: List[UserAct],
                                      sys_act: SysAct):
        """
        Make sure that there are no unanswered requests/constraints that got turned into requests
        """
        if not user_actions:
            # no user_actions -> system ignored nothing
            return [], []

        requests = [
            action for action in user_actions
            if action.type == UserActionType.Request
        ]

        if not requests:
            # no requests -> system ignored nothing
            return [], []

        if sys_act.type in [SysActionType.InformByName]:
            requests = [
                request for request in requests
                if request.slot not in sys_act.slot_values
            ]

        requests_alt = [
            action for action in user_actions
            if action.type == UserActionType.RequestAlternatives
        ]
        if sys_act.type == SysActionType.InformByAlternatives:
            offer = sys_act.slot_values[self.domain.get_primary_key()]

            if (
                    set(offer) - set(self.excluded_venues)
            ):  # and self.goal.requests[self.domain.get_primary_key()] is None:
                requests_alt = []

        return requests, requests_alt