Example #1
0
    def _raw_action(self, q_res: iter, beliefstate: BeliefState) -> SysAct:
        """Based on the output of the db query and the method, choose
           whether next action should be request or inform

        Args:
            q_res (list): rows (list of dicts) returned by the issued sqlite3
            query
            method (str): the type of user action
                     ('byprimarykey', 'byconstraints', 'byalternatives')

        Returns:
            (SysAct): SysAct object of appropriate type

        --LV
        """
        sys_act = SysAct()
        # if there is more than one result
        if len(q_res) > 1:
            constraints, dontcare = self._get_constraints(beliefstate)
            # Gather all the results for each column
            temp = {key: [] for key in q_res[0].keys()}
            # If any column has multiple values, ask for clarification
            for result in q_res:
                for key in result.keys():
                    if key != self.domain_key:
                        temp[key].append(result[key])
            next_req = self._gen_next_request(temp, beliefstate)
            if next_req:
                sys_act.type = SysActionType.Request
                sys_act.add_value(next_req)
                return sys_act

        # Otherwise action type will be inform, so return an empty inform (to be filled in later)
        sys_act.type = SysActionType.InformByName
        return sys_act
Example #2
0
    def _next_action(self, belief_state: BeliefState):
        """Determines the next system action based on the current belief state and
           previous action.

           When implementing a new type of policy, this method MUST be rewritten

        Args:
            belief_state (HandCraftedBeliefState): system values on liklihood
            of each possible state

        Return:
            (SysAct): the next system action

        --LV
        """
        method = self._get_method(belief_state)
        # Assuming this happens only because domain is not actually active --LV
        if method == 'none':
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
            return sys_act

        elif method == 'byalternatives' and not self._get_constraints(
                belief_state)[0]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
            return sys_act

        elif method == 'byprimarykey' and not self._get_requested_slots(
                belief_state):
            sys_act = SysAct()
            sys_act.type = SysActionType.InformByName
            sys_act.add_value(self.domain.get_primary_key(),
                              self._get_name(belief_state))
            belief_state['system']['lastInformedPrimKeyVal'] = self._get_name(
                belief_state)
            return sys_act

        # Otherwise we need to query the db to determine next action
        results = self._query_db(belief_state)
        sys_act = self._raw_action(results, method, belief_state)

        # requests are fairly easy, if it's a request, return it directly
        if sys_act.type == SysActionType.Request:
            if len(list(sys_act.slot_values.keys())) > 0:
                # update the belief state to reflec the slot we just asked about
                belief_state['system']['lastRequestSlot'] = list(
                    sys_act.slot_values.keys())[0]

        # otherwise we need to convert a raw inform into a one with proper slots and values
        elif sys_act.type == SysActionType.InformByName:
            self._convert_inform(method, results, sys_act, belief_state)
            # update belief state to reflect the offer we just made
            values = sys_act.get_values(self.domain.get_primary_key())
            if values:
                belief_state['system']['lastInformedPrimKeyVal'] = values[0]
            else:
                sys_act.add_value(self.domain.get_primary_key(), 'none')

        return sys_act
Example #3
0
    def _next_action(self, beliefstate: BeliefState):
        """Determines the next system action based on the current belief state and
           previous action.

           When implementing a new type of policy, this method MUST be rewritten

        Args:
            beliefstate (BeliefState): BeliefState object; contains all user constraints to date
            of each possible state

        Return:
            (SysAct): the next system action

        --LV
        """
        sys_state = {}
        # Assuming this happens only because domain is not actually active --LV
        if UserActionType.Bad in beliefstate['user_acts'] or beliefstate['requests'] \
                and not self._get_name(beliefstate):
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
            return sys_act, {'last_act': sys_act}

        elif UserActionType.RequestAlternatives in beliefstate['user_acts'] \
                and not self._get_constraints(beliefstate)[0]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
            return sys_act, {'last_act': sys_act}

        elif self.domain.get_primary_key() in beliefstate['informs'] \
                and not beliefstate['requests']:
            sys_act = SysAct()
            sys_act.type = SysActionType.InformByName
            sys_act.add_value(self.domain.get_primary_key(),
                              self._get_name(beliefstate))
            return sys_act, {'last_act': sys_act}

        # Otherwise we need to query the db to determine next action
        results = self._query_db(beliefstate)
        sys_act = self._raw_action(results, beliefstate)

        # requests are fairly easy, if it's a request, return it directly
        if sys_act.type == SysActionType.Request:
            if len(list(sys_act.slot_values.keys())) > 0:
                sys_state['lastRequestSlot'] = list(
                    sys_act.slot_values.keys())[0]

        # otherwise we need to convert a raw inform into a one with proper slots and values
        elif sys_act.type == SysActionType.InformByName:
            self._convert_inform(results, sys_act, beliefstate)
            # update belief state to reflect the offer we just made
            values = sys_act.get_values(self.domain.get_primary_key())
            if values:
                # belief_state['system']['lastInformedPrimKeyVal'] = values[0]
                sys_state['lastInformedPrimKeyVal'] = values[0]
            else:
                sys_act.add_value(self.domain.get_primary_key(), 'none')

        sys_state['last_act'] = sys_act
        return (sys_act, sys_state)
Example #4
0
    def _convert_inform_by_constraints(self, q_results: iter, sys_act: SysAct,
                                       belief_state: BeliefState):
        """
            Helper function for filling in slots and values of a raw inform act when the system is
            ready to make the user an offer

            Args:
                q_results (iter): the results from the databse query
                sys_act (SysAct): the raw infor act to be filled in
                belief_state (BeliefState): the current system beliefs

        """
        # altered to add all results to the SysAct
        if list(q_results):
            self.current_suggestions = []
            self.s_index = 0
            for result in q_results:
                sys_act.add_value(self.domain_key, result[self.domain_key])

        else:
            sys_act.add_value(self.domain_key, 'none')

        sys_act.type = SysActionType.InformByName
        constraints, dontcare = self._get_constraints(belief_state)
        for c in constraints:
            # Using constraints here rather than results to deal with empty
            # results sets (eg. user requests something impossible) --LV
            sys_act.add_value(c, constraints[c])

        # altered to the changes above
        if list(q_results):
            for slot in belief_state['requests']:
                if slot not in sys_act.slot_values:
                    for result in q_results:
                        sys_act.add_value(slot, result[slot])
Example #5
0
    def _convert_inform_by_primkey(self, q_results: iter, sys_act: SysAct,
                                   belief_state: BeliefState):
        """
            Helper function that adds the values for slots to a SysAct object when the system
            is answering a request for information about an entity from the user

            Args:
                q_results (iterable): list of query results from the database
                sys_act (SysAct): current raw sys_act to be filled in
                belief_state (BeliefState)

        """
        sys_act.type = SysActionType.InformByName
        if q_results:
            result = q_results[0]  # currently return just the first result
            keys = list(result.keys()
                        )  # should represent all user specified constraints

            # add slots + values (where available) to the sys_act
            for k in keys:
                res = result[k] if result[k] else 'not available'
                sys_act.add_value(k, res)
            # Name might not be a constraint in request queries, so add it
            if self.domain_key not in keys:
                name = self._get_name(belief_state)
                sys_act.add_value(self.domain_key, name)
            # Add default Inform slots
            for slot in self.domain.get_default_inform_slots():
                if slot not in sys_act.slot_values:
                    sys_act.add_value(slot, result[slot])
        else:
            sys_act.add_value(self.domain_key, 'none')
Example #6
0
    def _convert_inform_by_constraints(self, q_results: iter, sys_act: SysAct,
                                       belief_state: BeliefState):
        """
            Helper function for filling in slots and values of a raw inform act when the system is
            ready to make the user an offer

            Args:
                q_results (iter): the results from the databse query
                sys_act (SysAct): the raw infor act to be filled in
                belief_state (BeliefState): the current system beliefs

        """
        # TODO: Do we want some way to allow users to scroll through
        # result set other than to type 'alternatives'? --LV
        if q_results:
            self.current_suggestions = []
            self.s_index = 0
            for result in q_results:
                self.current_suggestions.append(result)
            result = self.current_suggestions[0]
            sys_act.add_value(self.domain_key, result[self.domain_key])
        else:
            sys_act.add_value(self.domain_key, 'none')

        sys_act.type = SysActionType.InformByName
        constraints, dontcare = self._get_constraints(belief_state)
        for c in constraints:
            # Using constraints here rather than results to deal with empty
            # results sets (eg. user requests something impossible) --LV
            sys_act.add_value(c, constraints[c])
Example #7
0
    def _convert_inform_by_alternatives(self, sys_act: SysAct, q_res: iter,
                                        belief_state: BeliefState):
        """
            Helper Function, scrolls through the list of alternative entities which match the
            user's specified constraints and uses the next item in the list to fill in the raw
            inform act.

            When the end of the list is reached, currently continues to give last item in the list
            as a suggestion

            Args:
                sys_act (SysAct): the raw inform to be filled in
                belief_state (BeliefState): current system belief state ()

        """
        if q_res and not self.current_suggestions:
            self.current_suggestions = []
            self.s_index = -1
            for result in q_res:
                self.current_suggestions.append(result)

        self.s_index += 1
        # here we should scroll through possible offers presenting one each turn the user asks
        # for alternatives
        if self.s_index <= len(self.current_suggestions) - 1:
            # the first time we inform, we should inform by name, so we use the right template
            if self.s_index == 0:
                sys_act.type = SysActionType.InformByName
            else:
                sys_act.type = SysActionType.InformByAlternatives
            result = self.current_suggestions[self.s_index]
            # Inform by alternatives according to our current templates is
            # just a normal inform apparently --LV
            sys_act.add_value(self.domain_key, result[self.domain_key])
        else:
            sys_act.type = SysActionType.InformByAlternatives
            # default to last suggestion in the list
            self.s_index = len(self.current_suggestions) - 1
            sys_act.add_value(self.domain.get_primary_key(), 'none')

        # in addition to the name, add the constraints the user has specified, so they know the
        # offer is relevant to them
        constraints, dontcare = self._get_constraints(belief_state)
        for c in constraints:
            sys_act.add_value(c, constraints[c])
Example #8
0
    def choose_sys_act(self, beliefstate: BeliefState = None, sys_act: SysAct = None)\
            -> dict(sys_act=SysAct):
        """
            Responsible for walking the policy through a single turn. Uses the current user
            action and system belief state to determine what the next system action should be.

            To implement an alternate policy, this method may need to be overwritten

            Args:
                belief_state (BeliefState): a BeliefState object representing current system
                                           knowledge
                user_acts (list): a list of UserAct objects mapped from the user's last utterance
                sys_act (SysAct): this should be None

            Returns:
                (dict): a dictionary with the key "sys_act" and the value that of the systems next
                        action

        """
        # variables for general (non-domain specific) actions
        # self.turn = dialog_graph.num_turns
        self.prev_sys_act = sys_act
        self._remove_gen_actions(beliefstate)
        sys_state = {}

        # do nothing on the first turn --LV
        if self.first_turn and not beliefstate['user_acts']:
            self.first_turn = False
            sys_act = SysAct()
            sys_act.type = SysActionType.Welcome
            return {'sys_act': sys_act}
        elif UserActionType.Bad in beliefstate["user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
        # if the action is 'bye' tell system to end dialog
        elif UserActionType.Bye in beliefstate["user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bye
        # if user only says thanks, ask if they want anything else
        elif UserActionType.Thanks in beliefstate["user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.RequestMore
        # If user only says hello, request a random slot to move dialog along
        elif UserActionType.Hello in beliefstate[
                "user_acts"] or UserActionType.SelectDomain in beliefstate[
                    "user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Request
            slot = self._get_open_slot(beliefstate)
            sys_act.add_value(slot)
            # prepare sys_state info
            sys_state['lastRequestSlot'] = slot

            # If we switch to the domain, start a new dialog
            if UserActionType.SelectDomain in beliefstate["user_acts"]:
                self.dialog_start()
            self.first_turn = False

        # handle domain specific actions
        else:
            sys_act, sys_state = self._next_action(beliefstate)

        self.logger.dialog_turn("System Action: " + str(sys_act))
        if 'last_act' not in sys_state:
            sys_state['last_act'] = sys_act
        return {'sys_act': sys_act, 'sys_state': sys_state}
Example #9
0
    def forward(self,
                dialog_graph,
                beliefstate: BeliefState = None,
                user_acts: List[u.UserAct] = None,
                sys_act: SysAct = None,
                **kwargs) -> dict(sys_act=SysAct):
        """
            Responsible for walking the policy through a single turn. Uses the current user
            action and system belief state to determine what the next system action should be.

            To implement an alternate policy, this method may need to be overwritten

            Args:
                dialog_graph (DialogSystem): the graph to which the policy belongs
                belief_state (BeliefState): a BeliefState obejct representing current system
                                           knowledge
                user_acts (list): a list of UserAct objects mapped from the user's last utterance
                sys_act (SysAct): this should be None

            Returns:
                (dict): a dictionary with the key "sys_act" and the value that of the systems next
                        action

        """
        belief_state = beliefstate
        # variables for general (non-domain specific) actions
        self.turn = dialog_graph.num_turns
        self.prev_sys_act = sys_act
        self._check_for_gen_actions(user_acts)

        # do nothing on the first turn --LV
        if user_acts is None and self.turn == 0:
            sys_act = SysAct()
            sys_act.type = SysActionType.Welcome
            return sys_act
        # if there is no user act, and it's not the first turn, this is bad
        elif not self.act_types_lst and self.turn > 0:
            # if not self.is_user_act and self.turn > 0:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
        # if the user has not said anything which can be parsed
        elif u.UserActionType.Bad in self.act_types_lst:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
        # if the action is 'bye' tell system to end dialog
        elif u.UserActionType.Bye in self.act_types_lst:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bye
        # if user only says thanks, ask if they want anything else
        elif u.UserActionType.Thanks in self.act_types_lst:
            sys_act = SysAct()
            sys_act.type = SysActionType.RequestMore
        # If user only says hello, request a random slot to move dialog along
        elif u.UserActionType.Hello in self.act_types_lst:
            sys_act = SysAct()
            sys_act.type = SysActionType.Request
            sys_act.add_value(self._get_open_slot(belief_state))
        # handle domain specific actions
        else:
            sys_act = self._next_action(belief_state)

        self.logger.dialog_turn("System Action: " + str(sys_act))
        return {'sys_act': sys_act}
Example #10
0
    def choose_sys_act(self, beliefstate: BeliefState = None) \
            -> dict(sys_act=SysAct):
        """
            Responsible for walking the policy through a single turn. Uses the current user
            action and system belief state to determine what the next system action should be.

            To implement an alternate policy, this method may need to be overwritten

            Args:
                belief_state (BeliefState): a BeliefState obejct representing current system
                                           knowledge

            Returns:
                (dict): a dictionary with the key "sys_act" and the value that of the systems next
                        action

        """
        self.turns += 1
        # do nothing on the first turn --LV
        sys_state = {}
        if self.first_turn and not beliefstate['user_acts']:
            self.first_turn = False
            sys_act = SysAct()
            sys_act.type = SysActionType.Welcome
            sys_state["last_act"] = sys_act
            return {'sys_act': sys_act, "sys_state": sys_state}

        if self.turns >= self.max_turns:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bye
            sys_state["last_act"] = sys_act
            return {'sys_act': sys_act, "sys_state": sys_state}

        # removes hello and thanks if there are also domain specific actions
        self._remove_gen_actions(beliefstate)

        if UserActionType.Bad in beliefstate["user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bad
        # if the action is 'bye' tell system to end dialog
        elif UserActionType.Bye in beliefstate["user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Bye
        # if user only says thanks, ask if they want anything else
        elif UserActionType.Thanks in beliefstate["user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.RequestMore
        # If user only says hello, request a random slot to move dialog along
        elif UserActionType.Hello in beliefstate[
                "user_acts"] or UserActionType.SelectDomain in beliefstate[
                    "user_acts"]:
            sys_act = SysAct()
            sys_act.type = SysActionType.Request
            slot = self._get_open_slot(beliefstate)
            sys_act.add_value(slot)

            # If we switch to the domain, start a new dialog
            if UserActionType.SelectDomain in beliefstate["user_acts"]:
                self.dialog_start()
            self.first_turn = False
        # handle domain specific actions
        else:
            sys_act, sys_state = self._next_action(beliefstate)
        if self.logger:
            self.logger.dialog_turn("System Action: " + str(sys_act))
        if "last_act" not in sys_state:
            sys_state["last_act"] = sys_act
        return {'sys_act': sys_act, "sys_state": sys_state}