Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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)
Example #6
0
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)))
Example #7
0
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
Example #8
0
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)))