예제 #1
0
def test_fill_with_constraints(agenda, goal, constraintA, constraintB):
    """
    Tests whether filling the stack with constraints will add all constraints from the goal as
    user actions of type inform to the stack.

    Args:
        agenda: Agenda object (given in conftest.py)
        goal: Goal object (given in conftest.py)
        constraintA (dict): an existing slot-value pair in the domain (given in
        conftest_<domain>.py)
        constraintB (dict): another existing slot-value pair in the domain (given in
        conftest_<domain>.py)
    """
    goal.constraints = [
        Constraint(slot=constraintA['slot'], value=constraintA['value']),
        Constraint(slot=constraintB['slot'], value=constraintB['value'])
    ]
    agenda_size = len(agenda.stack)
    agenda.fill_with_constraints(goal)
    assert len(agenda.stack) == agenda_size + len(goal.constraints)
    assert any(
        item.type == UserActionType.Inform and item.slot == constraintA['slot']
        and item.value == constraintA['value'] for item in agenda.stack)
    assert any(
        item.type == UserActionType.Inform and item.slot == constraintB['slot']
        and item.value == constraintB['value'] for item in agenda.stack)
예제 #2
0
파일: simulator.py 프로젝트: kdbz/adviser
    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
예제 #3
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_is_inconsistent_constraint_for_consistent_constraint(goal):
    """
    Tests whether a consistent constraint is recognized as such by the base function.

    Args:
        goal: Goal object (given in conftest.py)
    """
    goal.constraints = [Constraint('foo', 'bar')]
    is_inconsistent = goal.is_inconsistent_constraint(Constraint('foo', 'bar'))
    assert is_inconsistent is False
예제 #4
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_is_inconsistent_constraint_strict_for_unknown_constraint(goal):
    """
    Tests whether an unknown constraint is not recognized as inconsistent by the strict function.

    Args:
        goal: Goal object (given in conftest.py)
    """
    goal.constraints = [Constraint('foo', 'bar')]
    is_inconsistent = goal.is_inconsistent_constraint_strict(
        Constraint('bar', 'foo'))
    assert is_inconsistent is True
예제 #5
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_is_inconsistent_constraint_for_dontcare_constraint(goal):
    """
    Tests whether an inconsistent constraint is not recognized as such by the base function if
    the value for the corresponding goal constraint is 'dontcare'.

    Args:
        goal: Goal object (given in conftest.py)
    """
    goal.constraints = [Constraint('foo', 'dontcare')]
    is_inconsistent = goal.is_inconsistent_constraint(Constraint('foo', 'bar'))
    assert is_inconsistent is False
예제 #6
0
def test_fill_agenda_on_initialization(agenda, goal, constraintA, constraintB):
    """
    Tests whether the agenda is emptied and filled with the constraints of the given goal on
    initialization.

    Args:
        agenda: Agenda object (given in conftest.py)
        goal: Goal object (given in conftest.py)
        constraintA (dict): an existing slot-value pair in the domain (given in
        conftest_<domain>.py)
        constraintB (dict): another existing slot-value pair in the domain (given in
        conftest_<domain>.py)
    """
    stored_action = UserAct(act_type=UserActionType.Inform,
                            slot=constraintA['slot'],
                            value=constraintA['value'])
    goal_constraint = Constraint(slot=constraintB['slot'],
                                 value=constraintB['value'])
    agenda.stack = [stored_action]
    goal.constraints = [goal_constraint]
    agenda.init(goal)
    assert len(agenda.stack) == len(goal.constraints)
    assert stored_action not in agenda.stack
    assert any(item.slot == goal_constraint.slot and item.value ==
               goal_constraint.value and item.type == UserActionType.Inform
               for item in agenda.stack)
예제 #7
0
파일: simulator.py 프로젝트: kdbz/adviser
 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))
예제 #8
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."
            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))
예제 #9
0
def test_remove_inconsistencies_on_clean(agenda, goal, constraintA,
                                         constraintB, constraintA_alt):
    """
    Tests whether cleaning the stack removes inconsistent inform constraints.

    Args:
        agenda: Agenda object (given in conftest.py)
        goal: Goal object (given in conftest.py)
        constraintA (dict): an existing slot-value pair in the domain (given in
        conftest_<domain>.py)
        constraintB (dict): another existing slot-value pair in the domain (given in
        conftest_<domain>.py)
        constraintA_alt (dict): as constraint A, but with an alternative value (given in
        conftest_<domain>.py)
    """
    inconsistent_item = UserAct(act_type=UserActionType.Inform,
                                slot=constraintA['slot'],
                                value=constraintA['value'])
    stack = [
        inconsistent_item,
        UserAct(act_type=UserActionType.Confirm,
                slot=constraintB['slot'],
                value=constraintB['value'])
    ]
    agenda.stack = stack.copy()
    goal.constraints = [
        Constraint(constraintA_alt['slot'], constraintA_alt['value'])
    ]
    agenda.clean(goal)
    assert len(agenda.stack) < len(stack)
    assert all(item.slot != inconsistent_item.slot
               and item.value != inconsistent_item.value
               for item in agenda.stack)
예제 #10
0
    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
예제 #11
0
파일: simulator.py 프로젝트: kdbz/adviser
    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
예제 #12
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_update_unknown_constraint(goal):
    """
    Tests whether updating a constraint is skipped if the constraint is not present in the goal's constraints.

    Args:
        goal: Goal object (given in conftest.py)
    """
    goal.constraints = [Constraint('foo', 'bar')]
    update_successful = goal.update_constraint('bar', 'foo')
    assert update_successful is False
예제 #13
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_get_unknown_constraint(goal):
    """
    Tests whether the get_constraint function returns a 'dontcare' if the goal has no constraint
    with that slot.

    Args:
        goal: Goal object (given in conftest.py)
    """
    goal.constraints = [Constraint('foo', 'bar')]
    constraint_value = goal.get_constraint('baz')
    assert constraint_value == 'dontcare'
예제 #14
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_get_known_constraint(goal):
    """
    Tests whether the get_constraint function returns the corresponding value if the goal has a
    constraint with that slot.

    Args:
        goal: Goal object (given in conftest.py)
    """
    slot = 'foo'
    value = 'bar'
    goal.constraints = [Constraint(slot, value)]
    constraint_value = goal.get_constraint(slot)
    assert constraint_value == value
예제 #15
0
파일: goal_test.py 프로젝트: kdbz/adviser
def test_update_known_constraint(goal):
    """
    Tests whether updating a constraint works correctly if the constraint is actually present in
    the goal's constraints.

    Args:
        goal: Goal object (given in conftest.py)
    """
    slot = 'foo'
    value = 'baz'
    goal.constraints = [Constraint(slot, 'bar')]
    update_successful = goal.update_constraint(slot, value)
    matching_constraint = next(constraint for constraint in goal.constraints
                               if constraint.slot == slot)
    assert update_successful is True
    assert matching_constraint.value == value
예제 #16
0
    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))

        # Emotiongoal 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):
            emotionadd(self.emotion_list, 1)
            self._finish_dialog()
        else:
            emotionadd(self.emotion_list, 0)
예제 #17
0
파일: goal_test.py 프로젝트: kdbz/adviser
    assert goal.constraints != []
    assert goal.requests != {}
    assert len(goal.constraints) >= min_constraints
    assert len(goal.constraints) <= max_constraints
    assert len(goal.requests) >= min_requests
    assert len(goal.requests
               ) <= max_requests + 1  # primary key slot is added separately


@pytest.mark.parametrize(
    'constraints',
    [
        lambda x, y: [(x['slot'], x['value']),
                      (y['slot'], y['value'])],  # as list of tuples
        lambda x, y:
        [Constraint(x['slot'], x['value']),
         Constraint(y['slot'], y['value'])],  # as list of Constraints
        lambda x, y: {
            x['slot']: x['value'],
            y['slot']: y['value']
        }  # as dict
    ])
@pytest.mark.parametrize(
    'requests',
    [
        lambda x, y: [x['slot'], y['slot']],  # as list of strings (slots)
        lambda x, y: {
            x['slot']: None,
            y['slot']: None
        }  # as dict
    ])
예제 #18
0
파일: simulator.py 프로젝트: kdbz/adviser
    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