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
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
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)
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])
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')
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])
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])
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}
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}
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}