def sys_offer(d_state, new_sys_acts, sys_act, sys_acts, sys_acts_copy): # Remove the empty offer sys_acts_copy.remove(sys_act) if d_state.item_in_focus: new_sys_acts.append( DialogueAct( "offer", [ DialogueActItem("name", Operator.EQ, d_state.item_in_focus["name"]) ], )) # Only add these slots if no other acts were output # by the DM if len(sys_acts) == 1: for slot in d_state.slots_filled: if slot in d_state.item_in_focus: if slot not in ["id", "name" ] and slot != d_state.requested_slot: new_sys_acts.append( DialogueAct( "inform", [ DialogueActItem( slot, Operator.EQ, d_state.item_in_focus[slot]) ], )) else: new_sys_acts.append( DialogueAct( "inform", [DialogueActItem(slot, Operator.EQ, "no info")]))
def remove_from_agenda_push_again_if_false_value(self, item): # Remove the inform from the agenda, assuming the # value provided is correct. If it is not, the # act will be pushed again and will be on top of the # agenda (this way we avoid adding / removing # twice. dact = DialogueAct( "inform", [ DialogueActItem( deepcopy(item.slot), deepcopy(self.goal.constraints[item.slot].op), deepcopy(self.goal.constraints[item.slot].value), ) ], ) # Remove and push to make sure the act is on top - # if it already exists self.agenda.remove(dact) if item.value != self.goal.constraints[item.slot].value: meets_constraints = False # For each violated constraint add an inform # TODO: Make this a deny-inform or change # operator to NE self.agenda.push(dact) else: meets_constraints = True return meets_constraints
def build_inform(slot, ds: SlotFillingDialogueState): if slot in ds.item_in_focus: value = ds.item_in_focus[slot] else: value = "no info" return DialogueAct("inform", [DialogueActItem(slot, Operator.EQ, value)])
def get_unfilled_slot(d_state): for slot in d_state.slots_filled: if not d_state.slots_filled[slot]: request_unfilled_slot = DialogueAct( "request", [DialogueActItem(slot, Operator.EQ, "")]) return request_unfilled_slot return None
def next_action(self, ds: SlotFillingDialogueState): if ds.is_terminal_state: dacts = [DialogueAct("bye", [DialogueActItem("", Operator.EQ, "")])] elif ds.requested_slot != "" and ds.item_in_focus and ds.system_made_offer: dacts = build_inform_act(ds) else: dacts = self.request_slots_or_make_offer(ds) return dacts
def build_inform_act(dialogue_state: SlotFillingDialogueState): requested_slot = dialogue_state.requested_slot # Reset request as we attempt to address it dialogue_state.requested_slot = "" value = get_value(dialogue_state.item_in_focus, requested_slot) dact = [ DialogueAct("inform", [DialogueActItem(requested_slot, Operator.EQ, value)]) ] return dact
def make_offer(ds): name = ds.item_in_focus["name"] if "name" in ds.item_in_focus else "unknown" dacts = [DialogueAct("offer", [DialogueActItem("name", Operator.EQ, name)])] inform_acts = [ build_inform(slot, ds) for slot in ds.slots_filled if slot != "requested" and ds.slots_filled[slot] and slot not in ["id", "name"] ] dacts += inform_acts return dacts
def _update_goal_request_value_remove_from_agenda(self, item): self.goal.requests_made[item.slot].value = item.value # Mark the value only if the slot has been # requested and is in the requests if item.slot in self.goal.requests: self.goal.requests[item.slot].value = item.value # Remove any requests from the agenda that ask # for that slot # TODO: Revise this for all operators self.agenda.remove( DialogueAct("request", [DialogueActItem(item.slot, Operator.EQ, "")]))
def decode_action(self, action_enc): if action_enc < len(self.dstc2_acts_sys): return [DialogueAct(self.dstc2_acts_sys[action_enc], [])] if action_enc < len(self.dstc2_acts_sys) + len( self.system_requestable_slots): return [ DialogueAct( "request", [ DialogueActItem( self.system_requestable_slots[ action_enc - len(self.dstc2_acts_sys)], Operator.EQ, "", ) ], ) ] if action_enc < len(self.dstc2_acts_sys) + len( self.system_requestable_slots) + len(self.requestable_slots): index = (action_enc - len(self.dstc2_acts_sys) - len(self.system_requestable_slots)) return [ DialogueAct( "inform", [ DialogueActItem(self.requestable_slots[index], Operator.EQ, "") ], ) ] # Default fall-back action print("Reinforce DialoguePolicy ({0}) policy action decoder warning: " "Selecting default action (index: {1})!".format( 'system', action_enc)) return [DialogueAct("bye", [])]
def build_act_that_requests_random_slot( d_state): # TODO: why would one do such a thing? request_random_slot = DialogueAct( "request", [ DialogueActItem( random.choice(list(d_state.slots_filled.keys())[:-1]), Operator.EQ, "", ) ], ) return request_random_slot
def sys_inform(d_state, new_sys_acts, sys_act): if sys_act.params: slot = sys_act.params[0].slot else: slot = d_state.requested_slot if not slot: slot = random.choice(list(d_state.slots_filled.keys())) if d_state.item_in_focus: if slot not in d_state.item_in_focus or not d_state.item_in_focus[slot]: new_sys_acts.append( DialogueAct("inform", [DialogueActItem(slot, Operator.EQ, "no info")])) else: if slot == "name": new_sys_acts.append( DialogueAct( "offer", [ DialogueActItem(slot, Operator.EQ, d_state.item_in_focus[slot]) ], )) else: new_sys_acts.append( DialogueAct( "inform", [ DialogueActItem(slot, Operator.EQ, d_state.item_in_focus[slot]) ], )) else: new_sys_acts.append( DialogueAct("inform", [DialogueActItem(slot, Operator.EQ, "no info")]))
def cant_help(d_state, new_sys_acts, sys_act, sys_acts_copy): slots = [s for s in d_state.slots_filled if d_state.slots_filled[s]] if slots: slot = random.choice(slots) # Remove the empty canthelp sys_acts_copy.remove(sys_act) new_sys_acts.append( DialogueAct( "canthelp", [ DialogueActItem(slot, Operator.EQ, d_state.slots_filled[slot]) ], ))
def _handle_request(self, items: List[DialogueActItem]): # Push appropriate acts into the agenda for item in items: system_asks_for_slot_in_goal = item.slot in self.goal.constraints if system_asks_for_slot_in_goal: operation = deepcopy(self.goal.constraints[item.slot].op) slot_value = deepcopy(self.goal.constraints[item.slot].value) else: operation = Operator.EQ slot_value = "dontcare" slot_name = deepcopy(item.slot) self.agenda.push( DialogueAct( "inform", [DialogueActItem(slot_name, operation, slot_value)]))
def consistency_check(self): """ Perform some basic checks to ensure that items in the agenda are consistent - i.e. not duplicate, not contradicting with current goal, etc. :return: Nothing """ # Remove all requests for slots that are filled in the goal if self.goal: for slot in self.goal.requests_made: if self.goal.requests_made[slot].value: self.remove( DialogueAct("request", [DialogueActItem(slot, Operator.EQ, "")])) else: print("Warning! Agenda consistency check called without goal. " "Did you forget to initialize?")
def make_request(unfilled_slots): slot = random.choice(unfilled_slots) dacts = [DialogueAct("request", [DialogueActItem(slot, Operator.EQ, "")])] return dacts
def generate(self): if self.goals: return random.choice(self.goals) # Randomly pick an item from the database cursor = self.database.SQL_connection.cursor() sql_command = ( "SELECT * FROM " + self.db_table_name + " WHERE ROWID == (" + str(random.randint(1, self.db_row_count)) + ");" ) cursor.execute(sql_command) db_result = cursor.fetchone() attempt = 0 while attempt < 3 and not db_result: print( "GoalGenerator: Database {0} appears to be empty!".format(self.database) ) print(f"Trying again (attempt {attempt} out of 3)...") sql_command = ( "SELECT * FROM " + self.db_table_name + " WHERE ROWID == (" + str(random.randint(1, self.db_row_count)) + ");" ) cursor.execute(sql_command) db_result = cursor.fetchone() attempt += 1 if not db_result: raise LookupError( "GoalGenerator: Database {0} appears to be " "empty!".format(self.database) ) result = dict(zip(self.slot_names, db_result)) # Generate goal goal = Goal() # TODO: Sample from all available operators, not just '=' # (where applicable) inf_slots = random.sample( list(self.ontology.ontology["informable"].keys()), random.randint(2, len(self.ontology.ontology["informable"])), ) # Sample requests from requestable slots req_slots = random.sample( self.ontology.ontology["requestable"], random.randint(0, len(self.ontology.ontology["requestable"])), ) # Remove slots for which the user places constraints # Note: 'name' may or may not be in inf_slots here, and this is # randomness is desirable for slot in inf_slots: if slot in req_slots: req_slots.remove(slot) # Never ask for specific name unless it is the only constraint # if 'name' in inf_slots and len(inf_slots) > 1: if "name" in inf_slots: inf_slots.remove("name") # Shuffle informable and requestable slots to create some variety # when pushing into the agenda. random.shuffle(inf_slots) random.shuffle(req_slots) for slot in inf_slots: # Check that the slot has a value in the retrieved item if slot in result and result[slot]: goal.constraints[slot] = DialogueActItem( slot, Operator.EQ, result[slot] ) for slot in req_slots: if slot in result: goal.requests[slot] = DialogueActItem(slot, Operator.EQ, []) return goal