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 _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_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 generate_params_continue_recommendation(self, item_in_focus): """ Args: item_in_focus: """ movie_title = item_in_focus[Slots.TITLE.value] results = wikipedia.search(f'I need a film similar to {movie_title}', 20) results = [r.split('(')[0].strip() for r in results] for result in deepcopy(results): if result not in self.slot_values[Slots.TITLE.value]: results.remove(result) if len(results) > 0: return [ ItemConstraint(Slots.TITLE.value, Operator.EQ, str(results)) ] else: results = wikipedia.search( f'I need a movie similar to {movie_title}', 20) results = [r.split('(')[0].strip() for r in results] for result in deepcopy(results): if result not in self.slot_values[Slots.TITLE.value]: results.remove(result) if len(results) > 0: return [ ItemConstraint(Slots.TITLE.value, Operator.EQ, str(results)) ]
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 _title_annotator(self, slot, user_utterance): """This annotator is used to check the movie title. Sometimes the user can just enter a part of the name. Args: slot: utterance: """ tokens = user_utterance.get_tokens() values = self.slot_values[slot] processed_values = set(values.values()) # split into n-grams for ngram_size in range(min(self.ngram_size[slot], len(tokens)), 0, -1): options = {} for gram_list in ngrams(tokens, ngram_size): gram = sum(gram_list).lemma for processed_value in processed_values: if processed_value == gram and len([ x.lemma for x in gram_list if x.lemma in self.stop_words ]) < ngram_size: annotation = SemanticAnnotation.from_span( sum(gram_list), AnnotationType.NAMED_ENTITY, EntityType.TITLE) param = ItemConstraint(slot, Operator.EQ, gram, annotation) return [param] if len([x for x in gram_list if x.lemma in self.stop_words ]) == 0 and len( [int(val) for val in re.findall(r'\b\d+', gram)]) == 0: # check if # all words are in the list of stop words and no numbers if ngram_size == 1: gram_occurrence = len([ value for value in processed_values if gram == value ]) else: gram_occurrence = len([ value for value in processed_values if f' {gram} ' in f' {value} ' ]) if gram_occurrence: options[gram] = gram_occurrence if options: options = { k: v for k, v in sorted(options.items(), key=lambda item: item[1]) } for gram in options: param = ItemConstraint(slot, Operator.EQ, gram.strip()) return [param]
def _filter_genres(self, dact): """ Args: dact: """ for param in dact.params: values = [] if param.slot == Slots.GENRES.value: values = param.value.split() param.value = values[0] if values[0] in self.slot_values[Slots.GENRES.value] else \ self.slot_annotator.genres_alternatives[values[0]] if len(values) > 1: for value in values[1:]: if value not in [ p.value for p in dact.params if p.slot == param.slot ]: if value not in self.slot_values[Slots.GENRES.value]: value = self.slot_annotator.genres_alternatives[ value] dact.params.append( ItemConstraint(Slots.GENRES.value, Operator.EQ, value))
def _get_annotation_relevance(self, dact, raw_utterance, dual_person): """Get the relevance if user really want a preference or wants to remove it from the list Args: dact: raw_utterance: dual_person: """ words_seq = word_tokenize(raw_utterance) # first sequence the params: values = sorted([p.value for p in dact.params], key=raw_utterance.find) words_pre_req = dict.fromkeys(values) next_ind = 0 for val in words_pre_req: end_ind = raw_utterance.find(val) pre_req = raw_utterance[next_ind:end_ind] words_pre_req[val] = self._process_utterance(pre_req) next_ind = end_ind + len(val) param_dontwant = [] for value, pre_req in words_pre_req.items(): if any([ re.search(r'\b{0}\b'.format(pattern), pre_req) for pattern in self.dontwant_pattern ]): param_dontwant.append(value) if dual_person and len(dual_person) > 0: for value in dual_person: if any([ re.search(r'\b{0}\b'.format(pattern), words_pre_req[value]) for pattern in self.tag_words_user_reveal_inquire[ Slots.DIRECTORS.value] ]): self._remove_param( ItemConstraint(Slots.ACTORS.value, Operator.EQ, value), dact) elif any([ re.search(r'\b{0}\b'.format(pattern), words_pre_req[value]) for pattern in self.tag_words_user_reveal_inquire[Slots.ACTORS.value] ]): self._remove_param( ItemConstraint(Slots.DIRECTORS.value, Operator.EQ, value), dact) return param_dontwant
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 _keywords_annotator(self, slot, user_utterance): """This annotator is used to check the movie keywords. If the ngram has only keywords, it will be ignored. Args: slot: utterance: """ tokens = user_utterance.get_tokens() values = self.slot_values[slot] for ngram_size in range(min(self.ngram_size[slot], len(tokens)), 0, -1): for gram_list in ngrams(tokens, ngram_size): gram = sum(gram_list).lemma # TODO (Ivica Kostric): maybe 'no numbers' should be changed # since there are some numbers in keywords (.44, 007, age). # Same goes for stopwords. There are some stopwords as part of # keywords. # Alternatively, there is possibility to put a stopword flag # directly on tokens beforehand. if len([int(val) for val in re.findall(r'\b\d+', gram)]) == 0 and len([ x.lemma for x in gram_list if x.lemma in self.stop_words ]) == 0: for _, lem_value in values.items(): if lem_value == gram: annotation = SemanticAnnotation.from_span( sum(gram_list), AnnotationType.KEYWORD) param = ItemConstraint(slot, Operator.EQ, gram, annotation) return [param] elif (ngram_size == 1 and gram == lem_value) or ( ngram_size > 1 and f' {gram} ' in f' {lem_value} '): annotation = SemanticAnnotation.from_span( sum(gram_list), AnnotationType.KEYWORD) param = ItemConstraint(slot, Operator.EQ, gram, annotation) return [param]
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 _person_name_annotator(self, user_utterance, slots=None): """This annotator is used to check the movie actor and/or director names. Sometimes the user can just enter a part of the name. Args: utterance: slots: (Default value = None) """ tokens = user_utterance.get_tokens() if not slots: slots = [Slots.ACTORS.value, Slots.DIRECTORS.value] person_names = self.person_names else: slots = [slots] person_names = self.slot_values[slots] params = [] for ngram_size in range(self.ngram_size['person'], 0, -1): for gram_list in ngrams(tokens, ngram_size): gram = sum(gram_list).lemma for _, lem_value in person_names.items(): if f' {gram} ' in f' {lem_value} ' and \ gram not in self.stop_words: # gramR = self.find_in_raw_utterance(raw_utterance, # ngram_size, # gram) for slot in slots: if gram in self.slot_values[slot].values(): annotation = SemanticAnnotation.from_span( sum(gram_list), AnnotationType.NAMED_ENTITY, EntityType.PERSON) params.append( ItemConstraint(slot, Operator.EQ, gram, annotation)) break if len(params) > 0: return params
def _genres_annotator(self, slot, user_utterance): """ Args: slot: utterance: """ param = None values = self.slot_values[slot] tokens = user_utterance.get_tokens() for i in range(len(tokens)): for value, lem_value in values.items(): len_lem_value = len(lem_value.split()) token = tokens[i] if len_lem_value == 1 else sum( tokens[i:i + len_lem_value]) if token.lemma.startswith(lem_value): annotation = SemanticAnnotation.from_span( token, AnnotationType.NAMED_ENTITY, EntityType.GENRES) if param: param.add_value(value.lower(), annotation) else: param = ItemConstraint(slot, Operator.EQ, value.lower(), annotation) # TODO(Ivica Kostric): This could be merged with the main genre # dictionary at initialization time. for key, value in self.genres_alternatives.items(): len_key = len(key.split()) token = tokens[i] if len_key == 1 else sum(tokens[i:i + len_key]) if token.lemma.startswith(self._process_value(key)): annotation = SemanticAnnotation.from_span( token, AnnotationType.NAMED_ENTITY, EntityType.GENRES) if param: param.add_value(key.lower(), annotation) else: param = ItemConstraint(slot, Operator.EQ, key.lower(), annotation) if param: return [param]
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 _year_annotator(self, slot, user_utterance): """ Args: slot: user_utterance: utterance: """ tokens = user_utterance.get_tokens() potential_item_constraint = [] for token in tokens: annotation = SemanticAnnotation.from_span(token, AnnotationType.TEMPORAL) if token.lemma.startswith(('new', 'latest')): potential_item_constraint.append( ItemConstraint(slot, Operator.GT, '2010', annotation)) if token.lemma.startswith('old'): potential_item_constraint.append( ItemConstraint(slot, Operator.LT, '2010', annotation)) if not token.lemma[:2].isdigit(): continue if token.text[-1] == 's': year = token.text[:-1] if not year.isdigit(): continue # check if its for example 90s or 1990s if len(year) == 2: year = '20' + year if int(year) <= 20 else '19' + year elif len(year) != 4: continue if year[-1] == '0': return [ ItemConstraint(slot, Operator.BETWEEN, f'{year} AND {str(int(year) + 10)}', annotation) ] else: return [ ItemConstraint(slot, Operator.EQ, year, annotation) ] if token.text[-2:] == 'th': year = token.text[:-2] if year.isdigit() and len(year) == 2: return [ ItemConstraint( slot, Operator.BETWEEN, f'{str(int(year) - 1)}00 AND' f' {year}00', annotation) ] if token.text.isdigit() and len(token.text) == 4: return [ ItemConstraint(slot, Operator.EQ, token.text, annotation) ] return potential_item_constraint[:1]