def extract_from_dialog(dialog): prev_news_outputs = get_skill_outputs_from_dialog( dialog["utterances"][-3:], "news_api_skill", activated=True) if len(prev_news_outputs) > 0: prev_news_output = prev_news_outputs[-1] else: prev_news_output = {} no_detected = is_no(dialog["human_utterances"][-1]) nounphrases = get_entities(dialog["human_utterances"][-1], only_named=False, with_labels=False) if prev_news_output.get( "news_status", "finished") == OPINION_REQUEST_STATUS or (prev_news_output.get( "news_status", "finished") == OFFERED_NEWS_DETAILS_STATUS and no_detected): verb_noun_phrases, sources = extract_verb_noun_phrases( prev_news_outputs[-1].get("text", "nothing"), only_i_do_that=False, nounphrases=nounphrases) else: verb_noun_phrases, sources = extract_verb_noun_phrases( dialog["utterances"][-1]["text"], only_i_do_that=False, nounphrases=nounphrases) return verb_noun_phrases, sources
def get_used_attributes_by_name( utterances, attribute_name="meta_script_topic", value_by_default=None, activated=True, skill_name="meta_script_skill", ): """ Find among given utterances values of particular attribute of `meta_script_skill` outputs. `meta_script_skill` should be active skill if `activated` Args: utterances: list of utterances. the first one is user's one. attribute_name: name of the attribute to collect value_by_default: if not None will also be added to the returned list activated: whether `meta_script_skill` should be active or not Returns: list of attribute values """ used = [] meta_script_outputs = get_skill_outputs_from_dialog(utterances, skill_name=skill_name, activated=activated) for output in meta_script_outputs: value = output.get(attribute_name, value_by_default) if value is not None: used.append(value) logger.info(f"Found used attribute `{attribute_name}` values:`{used}`") return used
def comment_using_atomic(dialog): responses, confidences, attrs = [], [], [] if get_toxic(dialog["human_utterances"][-1], probs=False): return [""], [0.0], [{}] used_templates = get_used_attributes_by_name( dialog["utterances"], attribute_name="atomic_comment_template", value_by_default=None, activated=True, skill_name="comet_dialog_skill", )[-3:] prev_comet_outputs = get_skill_outputs_from_dialog( dialog["utterances"][-3:], skill_name="comet_dialog_skill", activated=True ) prev_best_sent = prev_comet_outputs[-1].get("atomic_best_sent", "") if len(prev_comet_outputs) > 0 else "" comet_comment_templates = get_all_not_used_templates(used_templates, ATOMIC_COMMENT_TEMPLATES) for template in comet_comment_templates[:NUMBER_OF_HYPOTHESES_COMET_DIALOG]: confidence, attr = 0.0, {} attr["atomic_comment_template"] = template relation = ATOMIC_COMMENT_TEMPLATES[template]["attribute"] logger.info(f"Choose template: {template}") response = fill_comet_atomic_template(prev_best_sent, template, relation) if response == "": continue confidence = DEFAULT_ATOMIC_CONTINUE_CONFIDENCE attr["can_continue"] = CAN_NOT_CONTINUE attr["atomic_dialog"] = "comment" attr["atomic_best_sent"] = prev_best_sent if get_nltk_sentiment(response) == "negative": response = "" confidence = 0.0 attr = {} responses.append(response) confidences.append(confidence) attrs.append(attr) return responses, confidences, attrs
def get_statuses_and_topics(dialog): """ Find prevously discussed meta-script topics, the last met-script status, determine current step meta-script status and topic. Args: dialog: dialog itself Returns: tuple of current status and topic """ # deeper2 and opinion could be randomly skipped in dialog flow dialog_flow = ["starting", "deeper1", "deeper2", "opinion", "comment"] dialog_flow_user_topic = ["starting", "deeper1", "comment"] curr_meta_script_statuses = [] curr_meta_script_topics = [] source_topics = [] if len(dialog["utterances"]) >= 3: # if dialog is not empty used_topics = get_used_attributes_by_name( dialog["utterances"], attribute_name="meta_script_topic", value_by_default="", activated=True) # this determines how many replies back we assume active meta script skill to continue dialog. # let's assume we can continue if meta_scrip skill was active on up to 2 steps back prev_reply_output = get_skill_outputs_from_dialog( dialog["utterances"][-5:], skill_name="meta_script_skill", activated=True) # get last meta script output even if it was not activated but right after it was active last_all_meta_script_outputs = get_skill_outputs_from_dialog( dialog["utterances"][-5:], skill_name="meta_script_skill", activated=False) prev_topic_finished = False for out in last_all_meta_script_outputs: if out.get("meta_script_status", "") == "finished": logger.info( f"Found finished dialog on meta_script_topic: `{out.get('meta_script_status', '')}`" ) prev_topic_finished = True if len(prev_reply_output) > 0: # previously active skill was `meta_script_skill` curr_meta_script_status = prev_reply_output[-1].get( "meta_script_status", "") else: # previous active skill was not `meta_script_skill` curr_meta_script_status = "" logger.info(f"Found meta_script_status: `{curr_meta_script_status}`") if curr_meta_script_status in ["comment", "", FINISHED_SCRIPT ] or prev_topic_finished: # if previous meta script is finished (comment given) in previous bot reply # or if no meta script in previous reply or script was forcibly topics, curr_source_topics = get_not_used_topics( used_topics, dialog) if curr_source_topics != [PREDEFINED_SOURCE]: # if topic is extracted from utterances pass elif if_choose_topic(dialog["human_utterances"][-1], dialog["bot_utterances"][-1]): # len(utterances) >3 so at least 1 bot utterance exists # one of the predefined topics (wiki or hand-written) curr_meta_script_statuses += [dialog_flow[0]] * len(topics) curr_meta_script_topics += topics source_topics += curr_source_topics else: pass else: # some meta script is already in progress # we define it here as predefined because we do not care about this variable if it's not script starting source_topic = PREDEFINED_SOURCE curr_meta_script_topic = used_topics[-1] logger.info( f"Found meta_script_status: `{curr_meta_script_status}` " f"on previous meta_script_topic: `{curr_meta_script_topic}`") # getting the next dialog flow status if is_custom_topic(curr_meta_script_topic): curr_meta_script_status = dialog_flow_user_topic[ dialog_flow_user_topic.index(curr_meta_script_status) + 1] else: curr_meta_script_status = dialog_flow[ dialog_flow.index(curr_meta_script_status) + 1] if curr_meta_script_status == "opinion": # randomly skip third deeper question if uniform(0, 1) <= 0.5: curr_meta_script_status = "comment" if curr_meta_script_status == "deeper2": # randomly skip third deeper question if uniform(0, 1) <= 0.5: curr_meta_script_status = "opinion" logger.info(f"New meta_script_status: `{curr_meta_script_status}` " f"on meta_script_topic: `{curr_meta_script_topic}`") curr_meta_script_statuses += [curr_meta_script_status] curr_meta_script_topics += [curr_meta_script_topic] source_topics += [source_topic] else: # start of the dialog, pick up a topic of meta script curr_meta_script_topics, source_topics = get_not_used_topics([], dialog) if source_topics != [PREDEFINED_SOURCE]: curr_meta_script_statuses = [dialog_flow_user_topic[0] ] * len(curr_meta_script_topics) else: curr_meta_script_statuses = [dialog_flow[0] ] * len(curr_meta_script_topics) logger.info(f"Final new meta_script_status: `{curr_meta_script_statuses}` " f"on meta_script_topic: `{curr_meta_script_topics}`") return curr_meta_script_statuses, curr_meta_script_topics, source_topics
def respond(): dialogs_batch = [None] st_time = time.time() dialogs_batch = request.json["dialogs"] rand_seed = request.json.get("rand_seed") responses = [] for dialog in dialogs_batch: prev_skill_outputs = get_skill_outputs_from_dialog( dialog["utterances"][-MEMORY_LENGTH:], "game_cooperative_skill", activated=True ) is_active_last_answer = bool(prev_skill_outputs) human_attr = dialog["human"]["attributes"] prev_state = human_attr.get("game_cooperative_skill", {}).get("state", {}) try: state = copy.deepcopy(prev_state) if state and not is_active_last_answer: state["messages"] = [] # pre_len = len(state.get("messages", [])) last_utter = dialog["human_utterances"][-1] last_utter_text = last_utter["text"].lower() agent_intents = get_agent_intents(last_utter) # for tests attr = {} if rand_seed: random.seed(int(rand_seed)) response, state = skill([last_utter_text], state, agent_intents) # logger.info(f"state = {state}") # logger.info(f"last_utter_text = {last_utter_text}") # logger.info(f"response = {response}") bot_utterance = dialog["bot_utterances"][-1] if dialog["bot_utterances"] else {} text = response.get("text", "Sorry") if not response.get("confidence"): confidence = 0 elif ( not is_active_last_answer and if_chat_about_particular_topic( dialog["human_utterances"][-1], bot_utterance, compiled_pattern=GAMES_COMPILED_PATTERN, ) and find_games_in_text(last_utter_text) ): confidence = 0 elif not is_active_last_answer and if_chat_about_particular_topic( dialog["human_utterances"][-1], bot_utterance, compiled_pattern=GAMES_COMPILED_PATTERN, ): confidence = 1 elif is_active_last_answer: confidence = 1 elif is_yes(dialog["human_utterances"][-1]) and game_skill_was_proposed(bot_utterance): confidence = 1 elif not is_yes(dialog["human_utterances"][-1]) and game_skill_was_proposed(bot_utterance): confidence = 0.95 text = FALLBACK_ACKN_TEXT state = prev_state elif GAMES_COMPILED_PATTERN.search(last_utter_text) and not is_active_last_answer: confidence = 0.98 else: confidence = 0 curr_text = clean_text(text.lower()) last_text = clean_text(bot_utterance.get("text", "").lower()) ratio = difflib.SequenceMatcher(None, curr_text.split(), last_text.split()).ratio() if ratio > 0.95: confidence = 0 if confidence == 1: can_continue = MUST_CONTINUE elif confidence > 0.95: can_continue = CAN_CONTINUE_SCENARIO else: can_continue = CAN_NOT_CONTINUE human_attr["game_cooperative_skill"] = {"state": state} attr["can_continue"] = can_continue except Exception as exc: sentry_sdk.capture_exception(exc) logger.exception(exc) text = "" confidence = 0.0 human_attr["game_cooperative_skill"] = {"state": prev_state} attr = {} bot_attr = {} responses.append((text, confidence, human_attr, bot_attr, attr)) total_time = time.time() - st_time logger.info(f"game_cooperative_skill exec time = {total_time:.3f}s") return jsonify(responses)
def respond(): st_time = time() dialogs = request.json["dialogs"] responses = [] confidences = [] human_attributes = [] bot_attributes = [] attributes = [] topics, statuses, curr_news_samples = collect_topics_and_statuses(dialogs) topics = [remove_punct_and_articles(topic) for topic in topics] topics = np.array(topics) statuses = np.array(statuses) curr_news_samples = np.array(curr_news_samples) for dialog, curr_topic, curr_status, result in zip(dialogs, topics, statuses, curr_news_samples): logger.info( f"Composing answer for topic: {curr_topic} and status: {curr_status}." ) logger.info(f"Result: {result}.") human_attr = {} human_attr["used_links"] = dialog["human"]["attributes"].get( "used_links", defaultdict(list)) human_attr["disliked_skills"] = dialog["human"]["attributes"].get( "disliked_skills", []) human_attr["news_api_skill"] = dialog["human"]["attributes"].get( "news_api_skill", {}) human_attr["news_api_skill"]["discussed_news"] = human_attr[ "news_api_skill"].get("discussed_news", []) bot_attr = {} # the only difference is that result is already is a dictionary with news. lets_chat_about_particular_topic = if_chat_about_particular_topic( dialog["human_utterances"][-1], dialog["bot_utterances"][-1] if len(dialog["bot_utterances"]) else {}) curr_uttr = dialog["human_utterances"][-1] about_news = ({"News"} & set( get_topics(curr_uttr, which="cobot_topics"))) or re.search( NEWS_TEMPLATES, curr_uttr["text"].lower()) about_news = about_news and not re.search(FALSE_NEWS_TEMPLATES, curr_uttr["text"].lower()) prev_bot_uttr_lower = dialog["bot_utterances"][-1]["text"].lower( ) if len(dialog["bot_utterances"]) > 0 else "" if lets_chat_about_particular_topic: prev_news_skill_output = get_skill_outputs_from_dialog( dialog["utterances"][-3:], skill_name="news_api_skill", activated=True) if result and len(prev_news_skill_output) == 0: # it was a lets chat about topic and we found appropriate news if curr_topic == "all": if about_news: response = OFFER_BREAKING_NEWS confidence = DEFAULT_NEWS_OFFER_CONFIDENCE # 1.0 attr = { "news_status": OFFERED_BREAKING_NEWS_STATUS, "news_topic": "all", "can_continue": CAN_CONTINUE_PROMPT, "curr_news": result, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] else: response = "" confidence = 0.0 attr = {} else: response = SAY_TOPIC_SPECIFIC_NEWS.replace( "TOPIC", curr_topic) response = f"{response} {result['title']}.. {OFFER_MORE}" confidence = LINKTO_CONFIDENCE attr = { "news_status": OFFERED_NEWS_DETAILS_STATUS, "news_topic": curr_topic, "curr_news": result, "can_continue": CAN_CONTINUE_PROMPT, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] responses.append(response) confidences.append(confidence) human_attributes.append(human_attr) bot_attributes.append(bot_attr) attributes.append(attr) continue else: responses.append("") confidences.append(0.0) human_attributes.append(human_attr) bot_attributes.append(bot_attr) attributes.append({}) continue if result: logger.info("Topic: {}".format(curr_topic)) logger.info("News found: {}".format(result)) if curr_status == "headline": if len(dialog["human_utterances"]) > 0: curr_uttr = dialog["human_utterances"][-1] else: curr_uttr = {"text": ""} if OFFER_BREAKING_NEWS.lower( ) in prev_bot_uttr_lower and is_yes(curr_uttr): response = f"Here it is: {result['title']}.. {OFFER_MORE}" confidence = DEFAULT_NEWS_OFFER_CONFIDENCE attr = { "news_status": OFFERED_NEWS_DETAILS_STATUS, "news_topic": curr_topic, "curr_news": result, "can_continue": MUST_CONTINUE, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] elif curr_topic == "all": prev_news_skill_output = get_skill_outputs_from_dialog( dialog["utterances"][-3:], skill_name="news_api_skill", activated=True) if (len(prev_news_skill_output) > 0 and prev_news_skill_output[-1].get( "news_status", "") == OFFERED_NEWS_TOPIC_CATEGORIES_STATUS): # topic was not detected response = "" confidence = 0.0 attr = {} else: response = f"Here is one of the latest news that I found: {result['title']}.. {OFFER_MORE}" confidence = DEFAULT_NEWS_OFFER_CONFIDENCE attr = { "news_status": OFFERED_NEWS_DETAILS_STATUS, "news_topic": curr_topic, "curr_news": result, "can_continue": MUST_CONTINUE, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] else: response = ( f"Here is one of the latest news on topic {curr_topic}: " f"{result['title']}.. {OFFER_MORE}") confidence = DEFAULT_NEWS_OFFER_CONFIDENCE attr = { "news_status": OFFERED_NEWS_DETAILS_STATUS, "news_topic": curr_topic, "curr_news": result, "can_continue": MUST_CONTINUE, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] elif curr_status == "details": response = f"In details: {result['description']}. {ASK_OPINION}" confidence = DEFAULT_NEWS_DETAILS_CONFIDENCE attr = { "news_status": OPINION_REQUEST_STATUS, "news_topic": curr_topic, "curr_news": result, "can_continue": MUST_CONTINUE, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] elif curr_status == "declined": # user declined to get latest news, topical news, or we did not find news request response, confidence, human_attr, bot_attr, attr = "", 0.0, {}, {}, {} else: prev_news_skill_output = get_skill_outputs_from_dialog( dialog["utterances"][-3:], skill_name="news_api_skill", activated=True) curr_uttr = dialog["human_utterances"][-1] # status finished is here if len(prev_news_skill_output ) > 0 and prev_news_skill_output[-1].get( "news_status", "") not in [ OFFERED_NEWS_DETAILS_STATUS, OFFERED_NEWS_TOPIC_CATEGORIES_STATUS, ]: result = prev_news_skill_output[-1].get("curr_news", {}) # try to offer more news topics_list = NEWS_TOPICS[:] random.shuffle(topics_list) offered_topics = [] for topic in topics_list: curr_topic_result = get_news_for_current_entity( topic, curr_uttr, human_attr["news_api_skill"]["discussed_news"]) if len(curr_topic_result) > 0: offered_topics.append(topic) logger.info("Topic: {}".format(topic)) logger.info("Result: {}".format(curr_topic_result)) if len(offered_topics) == 2: break if len(offered_topics) == 2: # two topics with result news were found response = ( f"{random.choice(WHAT_TYPE_OF_NEWS)} " f"{offered_topics[0]} or {offered_topics[1].lower()}?" ) confidence = WHAT_TYPE_OF_NEWS_CONFIDENCE attr = { "news_status": OFFERED_NEWS_TOPIC_CATEGORIES_STATUS, "can_continue": CAN_CONTINUE_PROMPT, "news_topic": " ".join(offered_topics), "curr_news": result, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] else: # can't find enough topics for the user to offer response, confidence, human_attr, bot_attr, attr = link_to_other_skills( human_attr, bot_attr, curr_uttr) else: # news was offered previously but the user refuse to get it # or false news request was detected response, confidence, human_attr, bot_attr, attr = "", 0.0, {}, {}, {} else: # no found news logger.info("No particular news found.") new_result = get_news_for_current_entity( "all", curr_uttr, human_attr["news_api_skill"]["discussed_news"]) if curr_topic != "all" and len(new_result.get("title", "")) > 0: logger.info("Offer latest news.") response = f"Sorry, I could not find some specific news. {OFFER_BREAKING_NEWS}" confidence = NOT_SPECIFIC_NEWS_OFFER_CONFIDENCE attr = { "news_status": OFFERED_BREAKING_NEWS_STATUS, "news_topic": "all", "can_continue": MUST_CONTINUE, "curr_news": new_result, } if attr["curr_news"]["url"] not in human_attr[ "news_api_skill"]["discussed_news"]: human_attr["news_api_skill"]["discussed_news"] += [ attr["curr_news"]["url"] ] elif OFFER_BREAKING_NEWS.lower() in prev_bot_uttr_lower and is_yes( curr_uttr): logger.info("No latest news found.") response = ( "Sorry, seems like all the news slipped my mind. Let's chat about something else. " "What do you want to talk about?") confidence = NOT_SPECIFIC_NEWS_OFFER_CONFIDENCE attr = { "news_status": OFFERED_BREAKING_NEWS_STATUS, "can_continue": MUST_CONTINUE } else: response, confidence, human_attr, bot_attr, attr = "", 0.0, {}, {}, {} responses.append(response) confidences.append(confidence) human_attributes.append(human_attr) bot_attributes.append(bot_attr) attributes.append(attr) total_time = time() - st_time logger.info(f"news_api_skill exec time: {total_time:.3f}s") return jsonify( list( zip(responses, confidences, human_attributes, bot_attributes, attributes)))
def collect_topics_and_statuses(dialogs): topics = [] statuses = [] curr_news_samples = [] for dialog in dialogs: curr_uttr = dialog["human_utterances"][-1] prev_uttr = dialog["bot_utterances"][-1] if len( dialog["bot_utterances"]) else {} human_attr = {} human_attr["news_api_skill"] = dialog["human"]["attributes"].get( "news_api_skill", {}) discussed_news = human_attr["news_api_skill"].get("discussed_news", []) prev_bot_uttr = dialog["bot_utterances"][-1] if len( dialog["bot_utterances"]) > 0 else {} prev_bot_uttr_lower = prev_bot_uttr.get("text", "").lower() prev_news_skill_output = get_skill_outputs_from_dialog( dialog["utterances"][-3:], skill_name="news_api_skill", activated=True) if len(prev_news_skill_output) > 0 and len( prev_news_skill_output[-1]) > 0: logger.info(f"News skill was prev active.") prev_news_skill_output = prev_news_skill_output[-1] prev_status = prev_news_skill_output.get("news_status", "") prev_topic = prev_news_skill_output.get("news_topic", "all") last_news = prev_news_skill_output.get("curr_news", {}) if prev_status == OFFERED_NEWS_DETAILS_STATUS: topics.append(prev_topic) if is_yes(curr_uttr): logger.info(f"Detected topic for news: {prev_topic}") statuses.append("details") else: logger.info("User refused to get news details") statuses.append("finished") curr_news_samples.append(last_news) elif prev_status == OFFERED_BREAKING_NEWS_STATUS or OFFER_BREAKING_NEWS.lower( ) in prev_bot_uttr_lower: topics.append("all") if is_yes(curr_uttr): logger.info("Detected topic for news: all.") statuses.append("headline") else: logger.info("User refuse to get latest news") statuses.append("declined") curr_news_samples.append(last_news) elif re.search(TELL_MORE_NEWS_TEMPLATES, curr_uttr["text"].lower()): prev_news_skill_output = get_skill_outputs_from_dialog( dialog["utterances"][-7:], skill_name="news_api_skill", activated=True) for prev_news_out in prev_news_skill_output: if prev_news_out.get("curr_news", {}) != {}: last_news = prev_news_out.get("curr_news", {}) logger.info( f"User requested more news. Prev news was: {last_news}") topics.append(prev_topic) statuses.append("headline") curr_news_samples.append( get_news_for_current_entity(prev_topic, curr_uttr, discussed_news)) elif prev_status == OFFERED_NEWS_TOPIC_CATEGORIES_STATUS: if not (news_rejection(curr_uttr["text"].lower()) or is_no(curr_uttr)): logger.info("User chose the topic for news") if ANY_TOPIC_PATTERN.search(curr_uttr["text"]): topics.append(prev_topic.split()[0]) curr_news_samples.append( get_news_for_current_entity( prev_topic.split()[0], curr_uttr, discussed_news)) elif SECOND_TOPIC_PATTERN.search(curr_uttr["text"]): topics.append(prev_topic.split()[1]) curr_news_samples.append( get_news_for_current_entity( prev_topic.split()[1], curr_uttr, discussed_news)) else: entities = extract_topics(curr_uttr) if len(entities) != 0: topics.append(entities[-1]) curr_news_samples.append( get_news_for_current_entity( entities[-1], curr_uttr, discussed_news)) else: topics.append("all") curr_news_samples.append( get_news_for_current_entity( "all", curr_uttr, discussed_news)) logger.info(f"Chosen topic: {topics}") statuses.append("headline") else: logger.info("User doesn't want to get any news") topics.append("all") statuses.append("declined") curr_news_samples.append({}) elif prev_status == OFFER_TOPIC_SPECIFIC_NEWS_STATUS: topics.append(prev_topic) if is_yes(curr_uttr): logger.info( f"User wants to listen news about {prev_topic}.") statuses.append("headline") else: logger.info( f"User doesn't want to listen news about {prev_topic}." ) statuses.append("declined") curr_news_samples.append(last_news) else: logger.info( "News skill was active and now can offer more news.") topics.append("all") statuses.append("finished") curr_news_samples.append( get_news_for_current_entity("all", curr_uttr, discussed_news)) else: logger.info(f"News skill was NOT active.") about_news = ( ({"News"} & set(get_topics(curr_uttr, which="cobot_topics"))) or re.search(NEWS_TEMPLATES, curr_uttr["text"].lower()) ) and not re.search(FALSE_NEWS_TEMPLATES, curr_uttr["text"].lower()) lets_chat_about_particular_topic = if_chat_about_particular_topic( curr_uttr, prev_uttr) lets_chat_about_news = if_chat_about_particular_topic( curr_uttr, prev_uttr, compiled_pattern=NEWS_TEMPLATES) _was_offer_news = was_offer_news_about_topic(prev_bot_uttr_lower) _offered_by_bot_entities = EXTRACT_OFFERED_NEWS_TOPIC_TEMPLATE.findall( prev_bot_uttr_lower) if about_news: # the request contains something about news entities = extract_topics(curr_uttr) logger.info(f"News request on entities: `{entities}`") if re.search(TELL_MORE_NEWS_TEMPLATES, curr_uttr["text"].lower()): # user requestd more news. # look for the last 3 turns and find last discussed news sample logger.info("Tell me more news request.") prev_news_skill_output = get_skill_outputs_from_dialog( dialog["utterances"][-7:], skill_name="news_api_skill", activated=True) if len(prev_news_skill_output) > 0 and len( prev_news_skill_output[-1]) > 0: prev_news_skill_output = prev_news_skill_output[-1] prev_topic = prev_news_skill_output.get( "news_topic", "all") else: prev_topic = "all" logger.info( "News skill was NOT prev active. User requested more news." ) topics.append(prev_topic) statuses.append("headline") curr_news_samples.append( get_news_for_current_entity(prev_topic, curr_uttr, discussed_news)) elif len(entities) == 0: # no entities or nounphrases -> no special news request, get all news logger.info("News request, no entities and nounphrases.") topics.append("all") statuses.append("headline") curr_news_samples.append( get_news_for_current_entity("all", curr_uttr, discussed_news)) else: # found entities or nounphrases -> special news request, # get the last mentioned entity # if no named entities, get the last mentioned nounphrase logger.info(f"Detected topic for news: {entities[-1]}") topics.append(entities[-1]) statuses.append("headline") curr_news_samples.append( get_news_for_current_entity(entities[-1], curr_uttr, discussed_news)) elif OFFER_BREAKING_NEWS.lower() in prev_bot_uttr_lower: # news skill was not previously active topics.append("all") if is_yes(curr_uttr) or lets_chat_about_news: logger.info("Detected topic for news: all.") statuses.append("headline") else: logger.info( "Detected topic for news: all. Refused to get latest news" ) statuses.append("declined") curr_news_samples.append( get_news_for_current_entity("all", curr_uttr, discussed_news)) elif _was_offer_news and _offered_by_bot_entities: topics.append(_offered_by_bot_entities[-1]) if is_yes(curr_uttr): logger.info( f"Bot offered news on entities: `{_offered_by_bot_entities}`" ) statuses.append("headline") else: logger.info( f"Bot offered news on entities: `{_offered_by_bot_entities}`. User refused." ) statuses.append("declined") curr_news_samples.append( get_news_for_current_entity(_offered_by_bot_entities[-1], curr_uttr, discussed_news)) elif lets_chat_about_particular_topic: # the request contains something about news entities = extract_topics(curr_uttr) logger.info(f"News request on entities: `{entities}`") if len(entities) == 0: # no entities or nounphrases & lets_chat_about_particular_topic logger.info( "No news request, no entities and nounphrases, but lets chat." ) topics.append("all") statuses.append("declined") curr_news_samples.append({}) else: # found entities or nounphrases -> special news request, # get the last mentioned entity # if no named entities, get the last mentioned nounphrase logger.info(f"Detected topic for news: {entities[-1]}") topics.append(entities[-1]) statuses.append("headline") curr_news_samples.append( get_news_for_current_entity(entities[-1], curr_uttr, discussed_news)) else: logger.info("Didn't detected news request.") topics.append("all") statuses.append("declined") curr_news_samples.append({}) return topics, statuses, curr_news_samples
def respond(): st_time = time.time() dialogs_batch = request.json["dialogs"] confidences = [] responses = [] human_attributes = [] bot_attributes = [] attributes = [] for dialog in dialogs_batch: used_topics = dialog["human"]["attributes"].get( "small_talk_topics", []) human_attr = {} bot_attr = {} attr = {} skill_outputs = get_skill_outputs_from_dialog( dialog["utterances"][-3:], skill_name="small_talk_skill", activated=True) if len(skill_outputs) > 0: # small_talk_skill was active on the previous step topic = skill_outputs[0].get("small_talk_topic", "") script_step = skill_outputs[0].get("small_talk_step", 0) script = skill_outputs[0].get("small_talk_script", []) logger.info(f"Found previous step topic: `{topic}`.") else: topic = "" script_step = 0 script = [] _, new_user_topic, new_conf = pickup_topic_and_start_small_talk(dialog) logger.info( f"From current user utterance: `{dialog['human_utterances'][-1]['text']}` " f"extracted topic: `{new_user_topic}`.") sentiment = get_sentiment(dialog["human_utterances"][-1], probs=False)[0] if (len(topic) > 0 and len(script) > 0 and (len(new_user_topic) == 0 or new_conf == FOUND_WORD_START_CONFIDENCE or new_user_topic == topic)): # we continue dialog if new topic was not found or was found just as the key word in user sentence. # because we can start a conversation picking up topic with key word with small proba user_dont_like = NOT_LIKE_PATTERN.search( dialog["human_utterances"][-1]["text"]) user_stop_talking = COMPILE_NOT_WANT_TO_TALK_ABOUT_IT.search( dialog["human_utterances"][-1]["text"]) if sentiment == "negative" or user_dont_like or user_stop_talking: logger.info( "Found negative sentiment to small talk phrase. Finish script." ) response, confidence, attr = ( "", 0.0, { "can_continue": CAN_NOT_CONTINUE, "small_talk_topic": "", "small_talk_step": 0, "small_talk_script": [], }, ) else: response, confidence, attr = get_next_response_on_topic( topic, dialog["human_utterances"][-1], curr_step=script_step + 1, topic_script=script) if response != "": logger.info( f"Continue script on topic: `{topic}`.\n" f"User utterance: `{dialog['human_utterances'][-1]['text']}`.\n" f"Bot response: `{response}`.") else: logger.info( "Try to extract topic from user utterance or offer if requested." ) response, topic, confidence = pickup_topic_and_start_small_talk( dialog) _is_quesion = is_any_question_sentence_in_utterance( dialog["human_utterances"][-1]) _is_lets_chat = if_chat_about_particular_topic( dialog["human_utterances"][-1], dialog["bot_utterances"][-1] if dialog["bot_utterances"] else {}) if len(topic) > 0 and topic not in used_topics and ( not _is_quesion or _is_lets_chat): logger.info( f"Starting script on topic: `{topic}`.\n" f"User utterance: `{dialog['human_utterances'][-1]['text']}`.\n" f"Bot response: `{response}`.") # topic script start, response is already formulated human_attr["small_talk_topics"] = used_topics + [topic] attr["response_parts"] = ["prompt"] attr["can_continue"] = CAN_CONTINUE_PROMPT attr["small_talk_topic"] = topic attr["small_talk_step"] = 0 attr["small_talk_script"] = TOPIC_SCRIPTS.get(topic, []) else: logger.info(f"Can not extract or offer NEW topic.") response = "" if len(response) == 0: confidence = 0.0 responses.append(response) confidences.append(confidence) human_attributes.append(human_attr) bot_attributes.append(bot_attr) attributes.append(attr) total_time = time.time() - st_time logger.info(f"small_talk_skill exec time: {total_time:.3f}s") return jsonify( list( zip(responses, confidences, human_attributes, bot_attributes, attributes)))