def check_inquire_intent(self, user_utterance): """ Args: utterance: """ # matching intents to 'list', 'Summarize', 'Subset', 'Compare' and # 'Similar' tokens = user_utterance.get_tokens() utterance = sum(tokens).lemma if tokens else '' user_dacts = [] dact = DialogueAct(UserIntents.UNK, []) for slot, values in self.tag_words_user_inquire.items(): if any([value for value in values if value in utterance]): dact.intent = UserIntents.INQUIRE dact.params.append(ItemConstraint(slot, Operator.EQ, '')) if dact.intent == UserIntents.UNK: # and self._is_question(raw_utterance): for slot, values in self.tag_words_user_reveal_inquire.items(): if any([value for value in values if value in utterance]): dact.intent = UserIntents.INQUIRE dact.params.append(ItemConstraint(slot, Operator.EQ, '')) if dact.intent != UserIntents.UNK: user_dacts.append(dact) return user_dacts
def check_reveal_intent(self, user_utterance, last_agent_dact): """This function is only called if the intent of agent is ELicit to see if user has answered the query Args: utterance: raw_utterance: last_agent_dact: """ user_dacts = [] for param in last_agent_dact.params: dact = DialogueAct(UserIntents.UNK, []) slot = param.slot params = self.slot_annotator.slot_annotation(slot, user_utterance) if params: dact.intent = UserIntents.REVEAL dact.params.extend(params) if dact.intent == UserIntents.UNK and self.is_dontcare( user_utterance): dact.intent = UserIntents.REVEAL dact.params.append( ItemConstraint(param.slot, Operator.EQ, Values.DONT_CARE)) if dact.intent != UserIntents.UNK: # print(f'All Dacts\n{dact}') self._filter_dact(dact, user_utterance.get_text()) # print(f'Filtered Dacts\n{dact}') if len(dact.params) > 0: user_dacts.append(dact) else: dact.intent = UserIntents.REVEAL dact.params.append( ItemConstraint(param.slot, Operator.EQ, Values.NOT_FOUND)) user_dacts.append(dact) return user_dacts
def _user_options_recommend(self): """Give use button options when making a recommendation Returns: a list of button options """ options = { DialogueAct(UserIntents.REJECT, [ ItemConstraint('reason', Operator.EQ, 'watched') ]): ['I have already watched it.'], # [random.choice(['I have already watched it.', # 'I have seen this already.'])], DialogueAct(UserIntents.REJECT, [ ItemConstraint('reason', Operator.EQ, 'dont_like') ]): ['Recommend me something else please.'], # [random.choice(['I don\'t like this recommendation.', # 'Recommend me something else please.'])], DialogueAct(UserIntents.ACCEPT, []): ['I like this recommendation.'], DialogueAct(UserIntents.INQUIRE, [ ItemConstraint(Slots.MORE_INFO.value, Operator.EQ, '') ]): [ random.choice( ['Tell me something about it.', 'Tell me more about it.']) ] } options.update({'/restart': ['/restart']}) return options
def check_reveal_voluntary_intent(self, user_utterance): """ Args: utterance: raw_utterance: """ user_dacts = [] dact = DialogueAct(UserIntents.UNK, []) person_name_checks = False for slot in self.ontology.slots_annotation: if slot in [x.value for x in [Slots.ACTORS, Slots.DIRECTORS]]: if person_name_checks: continue else: person_name_checks = True # if slot == Slots.TITLE.value and dact.intent!= UserIntents.UNK: # continue params = self.slot_annotator.slot_annotation(slot, user_utterance) if params: dact.intent = UserIntents.REVEAL dact.params.extend(params) # user_dacts.append(dact) # return user_dacts if dact.intent != UserIntents.UNK: # print(f'All Dacts\n{dact}') self._filter_dact(dact, user_utterance.get_text()) # print(f'Filtered Dacts\n{dact}') if len(dact.params) > 0: user_dacts.append(dact) return user_dacts
def _user_options_continue(self, agent_dact): """Give user options to continue when needed Args: agent_dact: """ if agent_dact.intent == AgentIntents.CONTINUE_RECOMMENDATION: options = { DialogueAct(UserIntents.CONTINUE_RECOMMENDATION, []): ["I would like a similar recommendation."], DialogueAct(UserIntents.RESTART, []): ['I want to restart for a new movie.'], DialogueAct(UserIntents.BYE, []): ['I would like to quit now.'] } return options
def check_deny_intent(self, utterance): """ TODO (Ivica Kostric): This is not needed anymore and should be removed. Handling of basic intents is merged into a single method (check_basic_intent) due to similarities. Args: utterance: """ user_dacts = [] dact = DialogueAct(UserIntents.UNK, []) for token in utterance.get_tokens(): for pattern in self.deny_pattern: match = re.search(r'\b{0}\b'.format(pattern), token.lemma) if match: dact.intent = UserIntents.DENY if dact.intent != UserIntents.UNK: user_dacts.append(dact) return user_dacts
def _user_options_inquire(self, dialogue_state): """ Args: dialogue_state: Returns: list of user requestables as buttons """ options = {} requestables = { Slots.GENRES.value: 'genres', Slots.PLOT.value: 'movie plot', Slots.DIRECTORS.value: 'director name', Slots.DURATION.value: 'duration', Slots.ACTORS.value: 'list of actors', Slots.YEAR.value: 'release year', Slots.RATING.value: 'rating' } for slot in dialogue_state.user_requestable: dact = DialogueAct(UserIntents.INQUIRE, [ItemConstraint(slot, Operator.EQ, '')]) options[dact] = requestables.get(slot, 'Invalid slot') options.update({ DialogueAct(UserIntents.REJECT, [ ItemConstraint('reason', Operator.EQ, 'dont_like') ]): ['I don\'t like this recommendation.'], # [random.choice(['I don\'t like this recommendation.', # 'Recommend me something else please.'])], DialogueAct(UserIntents.ACCEPT, []): [ 'I like this recommendation.' ], DialogueAct(UserIntents.CONTINUE_RECOMMENDATION, []): [ "I would like a similar recommendation." ], DialogueAct(UserIntents.RESTART, []): [ 'I want to restart for a new movie.' ], }) return options
def check_reject_intent(self, user_utterance): """ Args: utterance: """ # checking for intent = 'reject' tokens = user_utterance.get_tokens() utterance = sum(tokens).lemma if tokens else '' user_dacts = [] dact = DialogueAct(UserIntents.UNK, []) if any([ re.search(r'\b{0}\b'.format(pattern), utterance) for pattern in self.dontlike_movie_pattern ]): dact.intent = UserIntents.REJECT dact.params = [ItemConstraint('reason', Operator.EQ, 'dont_like')] elif any([ re.search(r'\b{0}\b'.format(pattern), utterance) for pattern in self.watched_pattern ]): dact.intent = UserIntents.REJECT dact.params = [ItemConstraint('reason', Operator.EQ, 'watched')] if dact.intent != UserIntents.UNK: user_dacts.append(dact) return user_dacts
def check_hi_intent(self, utterance): """Checking for a starting message TODO (Ivica Kostric): This is not needed anymore and should be removed. Handling of basic intents is merged into a single method (check_basic_intent) due to similarities. Args: utterance: """ user_dacts = [] dact = DialogueAct(UserIntents.UNK, []) for token in utterance.get_tokens(): if any([ re.search(r'\b{0}\b'.format(pattern), token.lemma) for pattern in self.hi_pattern ]): dact.intent = UserIntents.HI if dact.intent != UserIntents.UNK: user_dacts.append(dact) return user_dacts
def check_basic_intent(self, user_utterance, intent): """Given intent and list of intent patterns checks if any token in user utterance match the pattern. Args: user_utterance (UserUtterance): class containing raw utterance and processed tokens intent (UserIntents): Intent for which to compare patterns. Returns: List[DialogueAct]: If pattern exists returns that intents dialogue act """ user_dacts = [] dact = DialogueAct(UserIntents.UNK, []) for token in user_utterance.get_tokens(): if any([ re.search(r'\b{0}\b'.format(pattern), token.lemma) for pattern in self.basic_patterns.get(intent, []) ]): dact.intent = intent if dact.intent != UserIntents.UNK: user_dacts.append(dact) return user_dacts
def start_dialogue(self, new_user=False): """Starts the dialogue by generating a response from the agent :return: First agent response Args: new_user: (Default value = False) """ self.dialogue_state_tracker.dialogue_state.initialize() self.dialogue_state_tracker.dialogue_context.initialize() agent_dact = DialogueAct(AgentIntents.WELCOME, [ ItemConstraint('new_user', Operator.EQ, new_user), ItemConstraint('is_bot', Operator.EQ, self.isBot) ]) self.dialogue_state_tracker.update_state_agent([agent_dact]) return [agent_dact]
def next_action(self, dialogue_state, dialogue_context=None, restart=False): """Decides the next action to be taken by the agent based on the current state and context. Args: dialogue_state: current dialogue state dialogue_context: context of the dialogue (Default value = None) restart: (Default value = False) Returns: a list of Dialogue Acts """ agent_dacts = [] slots = deepcopy(dialogue_state.agent_requestable) if not dialogue_state.last_user_dacts and not restart: agent_dacts.append( DialogueAct(AgentIntents.WELCOME, [ ItemConstraint('new_user', Operator.EQ, self.new_user), ItemConstraint('is_bot', Operator.EQ, self.isBot) ])) return agent_dacts if not dialogue_state.last_agent_dacts and not restart: if not dialogue_state.last_agent_dacts: agent_dacts.append( DialogueAct(AgentIntents.WELCOME, [ ItemConstraint('new_user', Operator.EQ, self.new_user), ItemConstraint('is_bot', Operator.EQ, self.isBot) ])) if (not dialogue_state.last_user_dacts and restart) or \ (dialogue_state.last_user_dacts and UserIntents.RESTART in [dact.intent for dact in dialogue_state.last_user_dacts]): agent_dacts.append(DialogueAct(AgentIntents.RESTART, [])) agent_dacts.append( DialogueAct(AgentIntents.ELICIT, [ItemConstraint(slots[0], Operator.EQ, '')])) return agent_dacts for user_dact in dialogue_state.last_user_dacts: agent_dact = DialogueAct(AgentIntents.UNK, []) # generating intent = "bye" if user_dact.intent == UserIntents.BYE: agent_dact.intent = AgentIntents.BYE agent_dacts.append(deepcopy(agent_dact)) return agent_dacts # generating intent = "elicit" if user_dact.intent == UserIntents.ACKNOWLEDGE or user_dact.intent == \ UserIntents.UNK: if AgentIntents.WELCOME in [ dact.intent for dact in dialogue_state.last_agent_dacts ]: agent_dact.intent = AgentIntents.ELICIT agent_dact.params.append( ItemConstraint(slots[0], Operator.EQ, '')) agent_dacts.append(deepcopy(agent_dact)) return agent_dacts # deciding between intent "elicit" or "recommend" if dialogue_state.agent_made_partial_offer: # agent will inform about number of CIN_slots = [ key for key in dialogue_state.frame_CIN.keys() if not dialogue_state.frame_CIN[key] and key != Slots.TITLE.value ] if len( CIN_slots ) >= dialogue_state.slot_left_unasked: # if there is a scope of # further questioning # results and will ask next question agent_dact.intent = AgentIntents.COUNT_RESULTS agent_dact.params.append( ItemConstraint('count', Operator.EQ, len(dialogue_state.database_result))) agent_dacts.append(deepcopy(agent_dact)) # adding another dialogue act of ELICIT if dialogue_state.agent_req_filled: random.shuffle(CIN_slots) agent_dact = DialogueAct(AgentIntents.ELICIT, []) agent_dact.params.append( ItemConstraint(CIN_slots[0], Operator.EQ, "")) agent_dacts.append(deepcopy(agent_dact)) else: agent_dact = DialogueAct(AgentIntents.ELICIT, []) random.shuffle(slots) for slot in slots: if not dialogue_state.frame_CIN[slot]: agent_dact.params.append( ItemConstraint(slot, Operator.EQ, '')) break agent_dacts.append(deepcopy(agent_dact)) else: agent_dact = DialogueAct(AgentIntents.RECOMMEND, []) item_in_focus = dialogue_state.database_result[0] agent_dact.params.append( ItemConstraint(Slots.TITLE.value, Operator.EQ, item_in_focus[Slots.TITLE.value])) elif dialogue_state.agent_should_make_offer: agent_dact.intent = AgentIntents.RECOMMEND agent_dact.params.append( ItemConstraint( Slots.TITLE.value, Operator.EQ, dialogue_state.item_in_focus[Slots.TITLE.value])) agent_dacts.append(deepcopy(agent_dact)) elif dialogue_state.agent_offer_no_results: agent_dact.intent = AgentIntents.NO_RESULTS agent_dacts.append(deepcopy(agent_dact)) elif dialogue_state.agent_made_offer: if user_dact.intent == UserIntents.INQUIRE: agent_dact.intent = AgentIntents.INFORM for param in user_dact.params: if param.slot != Slots.MORE_INFO.value: agent_dact.params.append( ItemConstraint( param.slot, Operator.EQ, dialogue_state.item_in_focus[param.slot])) else: agent_dact.params.append( ItemConstraint( param.slot, Operator.EQ, dialogue_state.item_in_focus[ Slots.TITLE.value])) if len(agent_dact.params) == 0: agent_dact.params.append( ItemConstraint( 'deny', Operator.EQ, dialogue_state.item_in_focus[ Slots.TITLE.value])) agent_dacts.append(deepcopy(agent_dact)) # elif user_dact.intent == UserIntents.REVEAL and Slots.TITLE.value in [param.slot for # param in # user_dact.params]: # agent_dact.intent = AgentIntents.INFORM # agent_dact.params.append(ItemConstraint(Slots.MORE_INFO.value, Operator.EQ, # dialogue_state.item_in_focus[ # Slots.TITLE.value])) # agent_dacts.append(deepcopy(agent_dact)) elif user_dact.intent == UserIntents.ACCEPT: agent_dact.intent = AgentIntents.CONTINUE_RECOMMENDATION agent_dact.params.append( ItemConstraint( Slots.TITLE.value, Operator.EQ, dialogue_state.item_in_focus[Slots.TITLE.value])) agent_dacts.append(deepcopy(agent_dact)) if agent_dact.intent == AgentIntents.UNK: if not dialogue_state.agent_req_filled and user_dact.intent != UserIntents.HI: agent_dact.intent = AgentIntents.ELICIT # random.shuffle(slots) for slot in slots: if not dialogue_state.frame_CIN[slot]: agent_dact.params.append( ItemConstraint(slot, Operator.EQ, '')) break elif user_dact.intent == UserIntents.UNK: agent_dact.intent = AgentIntents.CANT_HELP if agent_dact.intent != AgentIntents.UNK: agent_dacts.append(deepcopy(agent_dact)) if len(agent_dacts) == 0: agent_dacts.append(DialogueAct(AgentIntents.CANT_HELP, [])) # Adding example: for agent_dact in agent_dacts: if agent_dact.intent == AgentIntents.ELICIT and \ agent_dact.params[0].slot not in [Slots.YEAR.value]: if dialogue_state.database_result: agent_dact.params[0].value = self._generate_examples( dialogue_state.database_result, agent_dact.params[0].slot) return agent_dacts
def _user_options_remove_preference_CIN(self, CIN): """Generate options for user to select a parameter to remove Args: CIN: The current information needs Returns: a list of button options """ options = {} params = [] for slot, values in CIN.items(): if not values: continue if isinstance(values, list): params.extend([ ItemConstraint(slot, Operator.EQ, value) for value in set(values) if value not in Values.__dict__.values() and not value.startswith('.NOT.') ]) else: if values not in Values.__dict__.values( ) and not values.startswith('.NOT.'): params.append(ItemConstraint(slot, Operator.EQ, values)) for param in params: value = deepcopy(param.value) negative = False if value.startswith('.NOT.'): negative = True # TODO. Add changes here value = value.replace('.NOT.', '') _a_an = 'an' if value[0] in ['a', 'e', 'i', 'o', 'u'] else 'a' param_key = DialogueAct(UserIntents.REMOVE_PREFERENCE, [param]) if param.slot == Slots.GENRES.value: if negative: options[param_key] = [ random.choice([ f'I want {_a_an} "{value}" movie.', f'I would prefer {_a_an} "{value}" ' f'film.' ]) ] else: options[param_key] = [ random.choice([ f'Don\'t want {_a_an} "{value}" ' f'movie.', f'Won\'t prefer {_a_an} "{value}" film.' ]) ] elif param.slot == Slots.TITLE.value: if negative: options[param_key] = [ f'I want movies named like "{value}".' ] else: options[param_key] = [f'No movies named like "{value}".'] elif param.slot == Slots.KEYWORDS.value: if negative: options[param_key] = [f'I need movies based on "{value}".'] else: options[param_key] = [ random.choice([ f'Don\'t need movies based on ' f'"{value}".', f'No need of {_a_an} "{value}" film.' ]) ] elif param.slot == Slots.DIRECTORS.value: if negative: options[param_key] = [ random.choice([ f'I want the director "{value.title()}".', f'Should be directed by "{value.title()}".' ]) ] else: options[param_key] = [ random.choice([ f'Don\'t want the director "{value.title()}".', f'Shouldn\'t be directed by "{value.title()}".' ]) ] elif param.slot == Slots.ACTORS.value: if negative: options[param_key] = [ f'I want the actor "{value.title()}".' ] else: options[param_key] = [ random.choice([ f'Don\'t consider actor "{value.title()}".', f'Remove "{value.title()}" from the list of actors.' ]) ] elif param.slot == Slots.YEAR.value: if negative: options[param_key] = [ random.choice([ f'Release year should be the "' f'{self._summarize_title_year(value)}".', f'Need a movie from the "' f'{self._summarize_title_year(value)}".' ]) ] else: options[param_key] = [ random.choice([ f'Release year shouldn\'t be the "' f'{self._summarize_title_year(value)}".', f'Don\'t need a movie from the "' f'{self._summarize_title_year(value)}".' ]) ] options.update({'/restart': ['/restart']}) return options
def _user_options_remove_preference(self, dual_params): """Generate options for user to select in case of two parameters have same value Args: dual_params: The current parameters with two slots Returns: a list of button options """ options = {} for value, params in dual_params.items(): for param in params: negative = False # TODO (Ivica Kostric): Look into this. Looks like a bug. # value is not a string in some (all?) cases. It can be # of class Values. if value.startswith('.NOT.'): negative = True # TODO. Add changes here value = value.replace('.NOT.', '') _a_an = 'an' if value[0] in ['a', 'e', 'i', 'o', 'u'] else 'a' param_key = DialogueAct(UserIntents.REMOVE_PREFERENCE, [param]) if param == Slots.GENRES.value: if negative: options[param_key] = [ random.choice([ f'I want {_a_an} "{value}" ' f'genre movie.', f'I would prefer {_a_an} "{value}" ' f'genre film.' ]) ] else: options[param_key] = [ random.choice([ f'Don\'t want {_a_an} "{value}" genre' f'movie.', f'Won\'t prefer {_a_an} "{value}" ' f'genre film.' ]) ] elif param == Slots.TITLE.value: if negative: options[param_key] = [ f'I want movies named like "{value}".' ] else: options[param_key] = [ f'No movies named like "{value}".' ] elif param == Slots.KEYWORDS.value: if negative: options[param_key] = [ f'I need movies based on "{value}".' ] else: options[param_key] = [ random.choice([ f'Don\'t need movies based on ' f'"{value}".', f'No need of {_a_an} "{value}" film.' ]) ] elif param == Slots.DIRECTORS.value: if negative: options[param_key] = [ random.choice([ f'I want the director "{value.title()}".', f'Should be directed by "{value.title()}".' ]) ] else: options[param_key] = [ random.choice([ f'Don\'t want the director "{value.title()}".', f'Shouldn\'t be directed by "{value.title()}".' ]) ] elif param == Slots.ACTORS.value: if negative: options[param_key] = [ f'I want the actor "{value.title()}".' ] else: options[param_key] = [ random.choice([ f'Don\'t consider actor "{value.title()}".', f'Remove "{value.title()}" from the list of actors.' ]) ] elif param == Slots.YEAR.value: if negative: options[param_key] = [ random.choice([ f'Release year should be the "' f'{self._summarize_title_year(value)}".', f'Need a movie from the "' f'{self._summarize_title_year(value)}".' ]) ] else: options[param_key] = [ random.choice([ f'Release year shouldn\'t be the "' f'{self._summarize_title_year(value)}".', f'Don\'t need a movie from the "' f'{self._summarize_title_year(value)}".' ]) ] options.update({'/restart': ['/restart']}) return options
def generate_dact(self, user_utterance, options, dialogue_state=None, dialogue_context=None): """Processes the utterance according to dialogue state and context and generate a user dialogue act for Agent to understand. Args: user_utterance: UserUtterance class containing user input options: a list of options provided to the user to choose from dialogue_state: the current dialogue state, if available (Default value = None) dialogue_context: the current dialogue context, if available (Default value = None) Returns: a list of dialogue acts """ # this is the top priority. The agent must check if user selected # any option selected_option = self.get_selected_option( user_utterance, options, dialogue_state.item_in_focus) if selected_option: return selected_option # Define a list of dialogue acts for this specific utterance user_dacts = [] # process the utterance for necessary # utterance = self.intents_checker._lemmatize_value(raw_utterance) self.dialogue_state = dialogue_state # check if user is ending the conversation user_dacts.extend( self.intents_checker.check_basic_intent(user_utterance, UserIntents.BYE)) if len(user_dacts) > 0: return user_dacts # check if it's the start of a conversation if not self.dialogue_state.last_agent_dacts: user_dacts.extend( self.intents_checker.check_reveal_voluntary_intent( user_utterance)) if len(user_dacts) == 0: user_dacts.extend( self.intents_checker.check_basic_intent( user_utterance, UserIntents.HI)) if len(user_dacts) > 0: return user_dacts else: return None for last_agent_dact in self.dialogue_state.last_agent_dacts: if last_agent_dact.intent == AgentIntents.WELCOME: user_dacts.extend( self.intents_checker.check_reveal_voluntary_intent( user_utterance)) if len(user_dacts) == 0: user_dacts.extend( self.intents_checker.check_basic_intent( user_utterance, UserIntents.ACKNOWLEDGE)) if len(user_dacts) > 0: return user_dacts elif last_agent_dact.intent == AgentIntents.ELICIT: user_dacts.extend( self.intents_checker.check_reveal_intent( user_utterance, last_agent_dact)) if len(user_dacts) == 0 or any([ param.value in Values.__dict__.values() for dact in user_dacts for param in dact.params ]): user_dacts.extend( self.intents_checker.check_reveal_voluntary_intent( user_utterance)) if len(user_dacts) > 0: return user_dacts if dialogue_state.agent_made_offer: user_dacts.extend( self.intents_checker.check_reject_intent(user_utterance)) if len(user_dacts) == 0: user_dacts.extend( self.intents_checker.check_inquire_intent(user_utterance)) if len(user_dacts) == 0: user_dacts.extend( self.intents_checker.check_reveal_voluntary_intent( user_utterance)) if len(user_dacts) == 0: deny_dact = self.intents_checker.check_basic_intent( user_utterance, UserIntents.DENY) if len(deny_dact) > 0: deny_dact[0].intent = UserIntents.INQUIRE user_dacts.extend(deny_dact) if len(user_dacts) > 0: return user_dacts if len(user_dacts) == 0: user_dacts.append(DialogueAct(UserIntents.UNK, [])) return user_dacts