Пример #1
0
 def test_util(self):
     import chatbot.utils as utils
     text = '''My mind is built using Hanson Robotics' character engine, a simulated humanlike brain that runs inside a personal computer. Within this framework, Hanson has modelled Phil's personality and emotions, allowing you to talk with Phil through me, using speech recognition, natural language understanding, and computer vision such as face recognition, and animation of the robotic muscles in my face.'''
     text2 = utils.shorten(text, 123)
     self.assertTrue(len(text2) <= 123)
     text2 = utils.shorten(text, 0)
     self.assertTrue(len(text2) > 0)
     self.assertTrue(utils.str_cleanup(' . ') == '')
     self.assertTrue(utils.str_cleanup(' .ss ') == 'ss')
     self.assertTrue(utils.str_cleanup(' s.ss ') == 's.ss')
     self.assertTrue(utils.str_cleanup(None) is None)
     self.assertTrue(utils.check_online('google.com'))
     self.assertTrue(utils.check_online('google.com', 80))
     self.assertTrue(not utils.check_online('google.com', 81))
Пример #2
0
 def test_util(self):
     import chatbot.utils as utils
     text = '''My mind is built using Hanson Robotics' character engine, a simulated humanlike brain that runs inside a personal computer. Within this framework, Hanson has modelled Phil's personality and emotions, allowing you to talk with Phil through me, using speech recognition, natural language understanding, and computer vision such as face recognition, and animation of the robotic muscles in my face.'''
     text2 = utils.shorten(text, 123)
     self.assertTrue(len(text2) <= 123)
     text2 = utils.shorten(text, 0)
     self.assertTrue(len(text2) > 0)
     self.assertTrue(utils.str_cleanup(' . ') == '')
     self.assertTrue(utils.str_cleanup(' .ss ') == 'ss')
     self.assertTrue(utils.str_cleanup(' s.ss ') == 's.ss')
     self.assertTrue(utils.str_cleanup(None) is None)
     self.assertTrue(utils.check_online('google.com'))
     self.assertTrue(utils.check_online('google.com', 80))
     self.assertTrue(not utils.check_online('google.com', 81))
Пример #3
0
def _ask_character(stage, character, request, response):
    logger.info("Asking character {} \"{}\" in stage {}".format(
        character.id, request.question, stage))

    session = session_manager.get_session(request.sid)
    tier_response = character.respond(request.question, request.lang, session, request.query, request.id)
    answer = str_cleanup(tier_response.get('text', ''))
    answered = False
    trace = tier_response.get('trace')
    category = ''
    if answer:
        if tier_response.get('exact_match') or tier_response.get('ok_match'):
            if tier_response.get('gambit'):
                if random.random() < 0.3:
                    logger.info("{} has gambit but dismissed".format(character.id))
                    response.add_trace((character.id, stage, 'Ignore gambit answer. Answer: {}, Trace: {}'.format(answer, trace)))
                    category = 'gambit'
                else:
                    logger.info("{} has gambit".format(character.id))
                    answered = True
            elif tier_response.get('quibble'):
                response.add_trace((character.id, stage, 'Quibble answer. Answer: {}, Trace: {}'.format(answer, trace)))
                category = 'quibble'
            else:
                logger.info("{} has good match".format(character.id))
                if character.id == 'sc':
                    answered = True
                    response.set_default_response(tier_response)
                if character.id == 'cs':
                    answered = True
                if character.id == 'ddg':
                    answered = True
                    response.set_default_response(tier_response) # choose ddg if it has an answer
                if tier_response.get('exact_match'):
                    answered = True
        else:
            if not tier_response.get('bad'):
                logger.info("{} has no good match".format(character.id))
                response.add_trace((character.id, stage, 'No good match. Answer: {}, Trace: {}'.format(answer, trace)))
                category = 'nogoodmatch'
            else:
                response.add_trace((character.id, stage, 'Bad answer. Answer: {}, Trace: {}'.format(answer, trace)))
                category = 'bad'
        if category:
            response.add_response(category, tier_response)
        else:
            response.add_response(character.id, tier_response)
    else:
        if tier_response.get('repeat'):
            answer = tier_response.get('repeat')
            response.add_trace((character.id, stage, 'Repetitive answer. Answer: {}, Trace: {}'.format(answer, trace)))
            tier_response['text'] = answer
            response.add_response('repeat', tier_response)
        else:
            logger.info("{} has no answer".format(character.id))
            response.add_trace((character.id, stage, 'No answer. Trace: {}'.format(trace)))
    return answered, tier_response
Пример #4
0
    def _ask_character(stage,
                       character,
                       weight,
                       good_match=False,
                       reuse=False):
        logger.info("Asking character {} \"{}\" in stage {}".format(
            character.id, _question, stage))

        if not reuse and character.id in used_charaters:
            cross_trace.append((character.id, stage, 'Skip used tier'))
            return False, None, None

        if character.id in used_charaters and character.type == TYPE_CS:
            cross_trace.append((character.id, stage, 'Skip CS tier'))
            return False, None, None

        used_charaters.append(character.id)
        answer = None
        answered = False

        if weight == 0:
            cross_trace.append((character.id, stage, 'Disabled'))
            logger.warn("Character \"{}\" in stage {} is disabled".format(
                character.id, stage))
            return False, None, None

        response = character.respond(_question, lang, sess, query, request_id)
        answer = str_cleanup(response.get('text', ''))
        trace = response.get('trace')

        if answer:
            if 'pickup' in character.id:
                cached_responses['pickup'].append(
                    (response, answer, character))
                return False, None, None
            if good_match:
                if response.get('exact_match') or response.get('ok_match'):
                    logger.info("{} has good match".format(character.id))
                    answered = True
                else:
                    if not response.get('bad'):
                        logger.info("{} has no good match".format(
                            character.id))
                        cross_trace.append(
                            (character.id, stage,
                             'No good match. Answer: {}, Trace: {}'.format(
                                 answer, trace)))
                        cached_responses['nogoodmatch'].append(
                            (response, answer, character))
            elif response.get('bad'):
                cross_trace.append(
                    (character.id, stage,
                     'Bad answer. Answer: {}, Trace: {}'.format(answer,
                                                                trace)))
                cached_responses['bad'].append((response, answer, character))
            elif DISABLE_QUIBBLE and response.get('quibble'):
                cross_trace.append(
                    (character.id, stage,
                     'Quibble answer. Answer: {}, Trace: {}'.format(
                         answer, trace)))
                cached_responses['quibble'].append(
                    (response, answer, character))
            elif response.get('gambit'):
                if random.random() > 0.3:
                    cross_trace.append(
                        (character.id, stage,
                         'Ignore gambit answer. Answer: {}, Trace: {}'.format(
                             answer, trace)))
                    cached_responses['gambit'].append(
                        (response, answer, character))
                else:
                    answered = True
            else:
                answered = True
            if answered:
                if random.random() < weight:
                    cross_trace.append(
                        (character.id, stage, 'Trace: {}'.format(trace)))
                else:
                    answered = False
                    cross_trace.append(
                        (character.id, stage,
                         'Pass through. Answer: {}, Weight: {}, Trace: {}'.
                         format(answer, weight, trace)))
                    logger.info("{} has no answer".format(character.id))
                    if 'markov' not in character.id:
                        cached_responses['pass'].append(
                            (response, answer, character))
                    else:
                        cached_responses['?'].append(
                            (response, answer, character))
        else:
            if response.get('repeat'):
                answer = response.get('repeat')
                cross_trace.append(
                    (character.id, stage,
                     'Repetitive answer. Answer: {}, Trace: {}'.format(
                         answer, trace)))
                cached_responses['repeat'].append(
                    (response, answer, character))
            else:
                logger.info("{} has no answer".format(character.id))
                cross_trace.append((character.id, stage,
                                    'No answer. Trace: {}'.format(trace)))
        return answered, answer, response
Пример #5
0
def _ask_characters(characters, question, lang, sid, query, request_id,
                    **kwargs):
    sess = session_manager.get_session(sid)
    if sess is None:
        return

    used_charaters = []
    data = sess.get_session_data()
    user = getattr(data, 'user')
    botname = getattr(data, 'botname')
    weights = get_weights(characters, sess)
    weighted_characters = zip(characters, weights)
    logger.info("Weights {}".format(weights))

    _question = preprocessing(question, lang, sess)
    response = {}
    hit_character = None
    answer = None
    cross_trace = []
    cached_responses = defaultdict(list)

    control = get_character('control')
    if control is not None:
        _response = control.respond(_question, lang, sess, query, request_id)
        _answer = _response.get('text')
        if _answer == '[tell me more]':
            cross_trace.append((control.id, 'control', _response.get('trace')
                                or 'No trace'))
            if sess.last_used_character:
                if sess.cache.that_question is None:
                    sess.cache.that_question = sess.cache.last_question
                context = sess.last_used_character.get_context(sess)
                if 'continue' in context and context.get('continue'):
                    _answer, res = shorten(context.get('continue'), 140)
                    response['text'] = answer = _answer
                    response['botid'] = sess.last_used_character.id
                    response['botname'] = sess.last_used_character.name
                    sess.last_used_character.set_context(
                        sess, {'continue': res})
                    hit_character = sess.last_used_character
                    cross_trace.append((sess.last_used_character.id,
                                        'continuation', 'Non-empty'))
                else:
                    _question = sess.cache.that_question.lower().strip()
                    cross_trace.append(
                        (sess.last_used_character.id, 'continuation', 'Empty'))
        elif _answer.startswith('[weather]'):
            template = _answer.replace('[weather]', '')
            cross_trace.append((control.id, 'control', _response.get('trace')
                                or 'No trace'))
            context = control.get_context(sess)
            if context:
                location = context.get('querylocation')
                prop = parse_weather(get_weather(location))
                if prop:
                    try:
                        _answer = template.format(location=location, **prop)
                        if _answer:
                            answer = _answer
                            response['text'] = _answer
                            response['botid'] = control.id
                            response['botname'] = control.name
                    except Exception as ex:
                        cross_trace.append(
                            (control.id, 'control', 'No answer'))
                        logger.error(ex)
                        logger.error(traceback.format_exc())
                else:
                    cross_trace.append((control.id, 'control', 'No answer'))
        elif _answer in OPERATOR_MAP.keys():
            opt = OPERATOR_MAP[_answer]
            cross_trace.append((control.id, 'control', _response.get('trace')
                                or 'No trace'))
            context = control.get_context(sess)
            if context:
                item1 = context.get('item1')
                item2 = context.get('item2')
                item1 = words2num(item1)
                item2 = words2num(item2)
                if item1 is not None and item2 is not None:
                    try:
                        result = opt(item1, item2)
                        img = math.modf(result)[0]
                        if img < 1e-6:
                            result_str = '{:d}'.format(int(result))
                        else:
                            result_str = 'about {:.4f}'.format(result)
                        if result > 1e20:
                            answer = "The number is too big. You should use a calculator."
                        else:
                            answer = "The answer is {result}".format(
                                result=result_str)
                    except ZeroDivisionError:
                        answer = "Oh, the answer is not a number"
                    except Exception as ex:
                        logger.error(ex)
                        logger.error(traceback.format_exc())
                        answer = "Sorry, something goes wrong. I can't calculate it."
                    response['text'] = answer
                    response['botid'] = control.id
                    response['botname'] = control.name
                else:
                    cross_trace.append((control.id, 'control', 'No answer'))
        else:
            if _answer and not re.findall(r'\[.*\].*', _answer):
                cross_trace.append(
                    (control.id, 'control', _response.get('trace')
                     or 'No trace'))
                hit_character = control
                answer = _answer
                response = _response
            else:
                cross_trace.append((control.id, 'control', 'No answer'))
            for c in characters:
                try:
                    c.remove_context(sess, 'continue')
                except NotImplementedError:
                    pass
            sess.cache.that_question = None

    def _ask_character(stage,
                       character,
                       weight,
                       good_match=False,
                       reuse=False):
        logger.info("Asking character {} \"{}\" in stage {}".format(
            character.id, _question, stage))

        if not reuse and character.id in used_charaters:
            cross_trace.append((character.id, stage, 'Skip used tier'))
            return False, None, None

        if character.id in used_charaters and character.type == TYPE_CS:
            cross_trace.append((character.id, stage, 'Skip CS tier'))
            return False, None, None

        used_charaters.append(character.id)
        answer = None
        answered = False

        if weight == 0:
            cross_trace.append((character.id, stage, 'Disabled'))
            logger.warn("Character \"{}\" in stage {} is disabled".format(
                character.id, stage))
            return False, None, None

        response = character.respond(_question, lang, sess, query, request_id)
        answer = str_cleanup(response.get('text', ''))
        trace = response.get('trace')

        if answer:
            if 'pickup' in character.id:
                cached_responses['pickup'].append(
                    (response, answer, character))
                return False, None, None
            if good_match:
                if response.get('exact_match') or response.get('ok_match'):
                    logger.info("{} has good match".format(character.id))
                    answered = True
                else:
                    if not response.get('bad'):
                        logger.info("{} has no good match".format(
                            character.id))
                        cross_trace.append(
                            (character.id, stage,
                             'No good match. Answer: {}, Trace: {}'.format(
                                 answer, trace)))
                        cached_responses['nogoodmatch'].append(
                            (response, answer, character))
            elif response.get('bad'):
                cross_trace.append(
                    (character.id, stage,
                     'Bad answer. Answer: {}, Trace: {}'.format(answer,
                                                                trace)))
                cached_responses['bad'].append((response, answer, character))
            elif DISABLE_QUIBBLE and response.get('quibble'):
                cross_trace.append(
                    (character.id, stage,
                     'Quibble answer. Answer: {}, Trace: {}'.format(
                         answer, trace)))
                cached_responses['quibble'].append(
                    (response, answer, character))
            elif response.get('gambit'):
                if random.random() > 0.3:
                    cross_trace.append(
                        (character.id, stage,
                         'Ignore gambit answer. Answer: {}, Trace: {}'.format(
                             answer, trace)))
                    cached_responses['gambit'].append(
                        (response, answer, character))
                else:
                    answered = True
            else:
                answered = True
            if answered:
                if random.random() < weight:
                    cross_trace.append(
                        (character.id, stage, 'Trace: {}'.format(trace)))
                else:
                    answered = False
                    cross_trace.append(
                        (character.id, stage,
                         'Pass through. Answer: {}, Weight: {}, Trace: {}'.
                         format(answer, weight, trace)))
                    logger.info("{} has no answer".format(character.id))
                    if 'markov' not in character.id:
                        cached_responses['pass'].append(
                            (response, answer, character))
                    else:
                        cached_responses['?'].append(
                            (response, answer, character))
        else:
            if response.get('repeat'):
                answer = response.get('repeat')
                cross_trace.append(
                    (character.id, stage,
                     'Repetitive answer. Answer: {}, Trace: {}'.format(
                         answer, trace)))
                cached_responses['repeat'].append(
                    (response, answer, character))
            else:
                logger.info("{} has no answer".format(character.id))
                cross_trace.append((character.id, stage,
                                    'No answer. Trace: {}'.format(trace)))
        return answered, answer, response

    # If the last input is a question, then try to use the same tier to
    # answer it.
    if not answer:
        if sess.open_character in characters:
            answered, _answer, _response = _ask_character('question',
                                                          sess.open_character,
                                                          1,
                                                          good_match=True)
            if answered:
                hit_character = sess.open_character
                answer = _answer
                response = _response

    # Try the first tier to see if there is good match
    if not answer:
        c, weight = weighted_characters[0]
        answered, _answer, _response = _ask_character('priority',
                                                      c,
                                                      weight,
                                                      good_match=True)
        if answered:
            hit_character = c
            answer = _answer
            response = _response

    # Select tier that is designed to be proper to answer the question
    if not answer:
        for c, weight in weighted_characters:
            if c.is_favorite(_question):
                answered, _answer, _response = _ask_character('favorite', c, 1)
                if answered:
                    hit_character = c
                    answer = _answer
                    response = _response

    # Check the last used character
    if not answer:
        if sess.last_used_character and sess.last_used_character.dynamic_level:
            for c, weight in weighted_characters:
                if sess.last_used_character.id == c.id:
                    answered, _answer, _response = _ask_character(
                        'last used', c, weight)
                    if answered:
                        hit_character = c
                        answer = _answer
                        response = _response
                    break

    # Check the loop
    if not answer:
        for c, weight in weighted_characters:
            answered, _answer, _response = _ask_character('loop',
                                                          c,
                                                          weight,
                                                          reuse=True)
            if answered:
                hit_character = c
                answer = _answer
                response = _response
                break

    if not answer:
        for response_type in [
                'pass', 'nogoodmatch', 'quibble', 'repeat', 'gambit', 'pickup',
                '?'
        ]:
            if cached_responses.get(response_type):
                response, answer, hit_character = cached_responses.get(
                    response_type)[0]
                if response_type == 'repeat':
                    pass
                response['text'] = answer
                cross_trace.append(
                    (hit_character.id, response_type, response.get('trace')
                     or 'No trace'))
                break

    if answer and re.match('.*{.*}.*', answer):
        logger.info("Template answer {}".format(answer))
        try:
            response['orig_text'] = answer
            answer = render(answer)
            response['text'] = answer
        except Exception as ex:
            answer = ''
            response['text'] = ''
            logger.error("Error in rendering template, {}".format(ex))

    dummy_character = get_character('dummy', lang)
    if not answer and dummy_character:
        if response.get('repeat'):
            response = dummy_character.respond("REPEAT_ANSWER", lang, sid,
                                               query)
        else:
            response = dummy_character.respond("NO_ANSWER", lang, sid, query)
        hit_character = dummy_character
        answer = str_cleanup(response.get('text', ''))

    if not query and hit_character is not None:
        response['AnsweredBy'] = hit_character.id
        sess.last_used_character = hit_character

        if is_question(answer.lower().strip()):
            if hit_character.dynamic_level:
                sess.open_character = hit_character
                logger.info("Set open dialog character {}".format(
                    hit_character.id))
        else:
            sess.open_character = None

    response['ModQuestion'] = _question
    response['trace'] = cross_trace
    return response
Пример #6
0
def _ask_characters(characters, question, lang, sid, query):
    sess = session_manager.get_session(sid)
    if sess is None:
        return

    used_charaters = []
    data = sess.get_session_data()
    user = getattr(data, 'user')
    botname = getattr(data, 'botname')
    weights = get_weights(characters, sess)
    weighted_characters = zip(characters, weights)
    logger.info("Weights {}".format(weighted_characters))

    _question = preprocessing(question)
    response = {}
    hit_character = None
    answer = None
    cross_trace = []

    cached_responses = defaultdict(list)

    reduction = get_character('reduction')
    if reduction is not None:
        _response = reduction.respond(_question, lang, sess, query=True)
        reducted_text = _response.get('text')
        if reducted_text:
            _question = reducted_text

    control = get_character('control')
    if control is not None:
        _response = control.respond(_question, lang, sess, query)
        _answer = _response.get('text')
        if _answer == '[tell me more]':
            cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
            if sess.last_used_character:
                if sess.cache.that_question is None:
                    sess.cache.that_question = sess.cache.last_question
                context = sess.last_used_character.get_context(sess)
                if 'continue' in context and context.get('continue'):
                    _answer, res = shorten(context.get('continue'), 140)
                    response['text'] = answer = _answer
                    response['botid'] = sess.last_used_character.id
                    response['botname'] = sess.last_used_character.name
                    sess.last_used_character.set_context(sess, {'continue': res})
                    hit_character = sess.last_used_character
                    cross_trace.append((sess.last_used_character.id, 'continuation', 'Non-empty'))
                else:
                    _question = sess.cache.that_question.lower().strip()
                    cross_trace.append((sess.last_used_character.id, 'continuation', 'Empty'))
        elif _answer.startswith('[weather]'):
            template = _answer.replace('[weather]', '')
            cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
            context = control.get_context(sess)
            if context:
                location = context.get('querylocation')
                prop = parse_weather(get_weather(location))
                if prop:
                    try:
                        _answer = template.format(location=location, **prop)
                        if _answer:
                            answer = _answer
                            response['text'] = _answer
                            response['botid'] = control.id
                            response['botname'] = control.name
                    except Exception as ex:
                        cross_trace.append((control.id, 'control', 'No answer'))
                        logger.error(ex)
                else:
                    cross_trace.append((control.id, 'control', 'No answer'))
        elif _answer in OPERATOR_MAP.keys():
            opt = OPERATOR_MAP[_answer]
            cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
            context = control.get_context(sess)
            if context:
                item1 = context.get('item1')
                item2 = context.get('item2')
                item1 = words2num(item1)
                item2 = words2num(item2)
                if item1 is not None and item2 is not None:
                    try:
                        result = opt(item1, item2)
                        img = math.modf(result)[0]
                        if img < 1e-6:
                            result_str = '{:d}'.format(int(result))
                        else:
                            result_str = 'about {:.4f}'.format(result)
                        if result > 1e20:
                            answer = "The number is too big. You should use a calculator."
                        else:
                            answer = "The answer is {result}".format(result=result_str)
                    except ZeroDivisionError:
                        answer = "Oh, the answer is not a number"
                    except Exception as ex:
                        logger.error(ex)
                        answer = "Sorry, something goes wrong. I can't calculate it."
                    response['text'] = answer
                    response['botid'] = control.id
                    response['botname'] = control.name
                else:
                    cross_trace.append((control.id, 'control', 'No answer'))
        else:
            if _answer and not re.findall(r'\[.*\].*', _answer):
                cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
                hit_character = control
                answer = _answer
                response = _response
            else:
                cross_trace.append((control.id, 'control', 'No answer'))
            for c in characters:
                try:
                    c.remove_context(sess, 'continue')
                except NotImplementedError:
                    pass
            sess.cache.that_question = None

    # If the last input is a question, then try to use the same tier to
    # answer it.
    if not answer:
        if sess.open_character and sess.open_character in characters \
                and sess.open_character.weight != 0:
            logger.info("Using open dialog character {}".format(sess.open_character.id))
            response = sess.open_character.respond(_question, lang, sess, query)
            used_charaters.append(sess.open_character.id)
            _answer = str_cleanup(response.get('text', ''))
            if _answer:
                if response.get('repeat'):
                    cross_trace.append((sess.open_character.id, 'question', 'Repetitive answer'))
                    cached_responses['repeat'].append((response, response.get('repeat'), sess.open_character))
                elif response.get('bad'):
                    cross_trace.append((sess.open_character.id, 'question', 'Bad answer: {}'.format(response.get('trace'))))
                    cached_responses['bad'].append((response, _answer, sess.open_character))
                else:
                    answer = _answer
                    hit_character = sess.open_character
                    cross_trace.append((sess.open_character.id, 'question', response.get('trace') or 'No trace'))
            else:
                cross_trace.append((sess.open_character.id, 'question', 'No answer'))

    # Try the first tier to see if there is good match
    if not answer:
        c, weight = weighted_characters[0]
        if c.id == botname and weight != 0:
            _response = c.respond(_question, lang, sess, query=True)
            _answer = str_cleanup(_response.get('text', ''))
            if _response.get('exact_match') or _response.get('ok_match'):
                logger.info("{} has good match".format(c.id))
                response = c.respond(_question, lang, sess, query)
                _answer = str_cleanup(response.get('text', ''))
                used_charaters.append(c.id)
                if _answer:
                    if random.random() < weight:
                        hit_character = c
                        answer = _answer
                        cross_trace.append((c.id, 'priority', response.get('trace') or 'No trace'))
                    else:
                        cross_trace.append((c.id, 'priority', 'Pass through'))
                        cached_responses['pass'].append((response, _answer, c))
                elif response.get('repeat'):
                    cross_trace.append((c.id, 'priority', 'Repetitive answer'))
                    cached_responses['repeat'].append((response, response.get('repeat'), c))
                else:
                    cross_trace.append((c.id, 'priority', 'No answer'))
            elif _answer:
                logger.info("{} has no good match".format(c.id))
                cross_trace.append((c.id, 'priority', 'No good match: {}'.format(_response.get('trace') or 'No trace')))
                cached_responses['nogoodmatch'].append((_response, _answer, c))
            else:
                cross_trace.append((c.id, 'priority', 'No answer'))

    # Select tier that is designed to be proper to answer the question
    if not answer:
        for c, weight in weighted_characters:
            if weight != 0 and c.is_favorite(_question):
                _response = c.respond(_question, lang, sess, query)
                _answer = str_cleanup(_response.get('text'))
                if _answer:
                    hit_character = c
                    cross_trace.append((c.id, 'favorite', response.get('trace') or 'No trace'))
                    answer = _answer
                    response = _response
                    break
                else:
                    if _response.get('repeat'):
                        cross_trace.append((c.id, 'favorite', 'Repetitive answer'))
                        cached_responses['repeat'].append((_response, _response.get('repeat'), c))
                    else:
                        cross_trace.append((c.id, 'favorite', 'No answer'))

    # Check the last used character
    if not answer:
        if sess.last_used_character and sess.last_used_character.dynamic_level \
                and sess.last_used_character.weight != 0:
            if sess.last_used_character.id in used_charaters:
                cross_trace.append((sess.last_used_character.id, 'last used', 'Skip used tier'))
            else:
                for c, weight in weighted_characters:
                    if sess.last_used_character.id == c.id:
                        _response = c.respond(_question, lang, sess, query=True)
                        _answer = str_cleanup(_response.get('text', ''))
                        if _response.get('exact_match') or _response.get('ok_match'):
                            logger.info("Last used tier {} has good match".format(c.id))
                            if sess.last_used_character.type == TYPE_CS:
                                response = _response
                            else:
                                response = c.respond(_question, lang, sess, query)
                                _answer = str_cleanup(response.get('text', ''))
                            used_charaters.append(c.id)
                            if _answer:
                                if random.random() < weight:
                                    hit_character = c
                                    answer = _answer
                                    cross_trace.append((c.id, 'last used', response.get('trace') or 'No trace'))
                                else:
                                    cross_trace.append((c.id, 'last used', 'Pass through'))
                                    cached_responses['pass'].append((response, _answer, c))
                            else:
                                if response.get('repeat'):
                                    cross_trace.append((c.id, 'last used', 'Repetitive answer'))
                                    cached_responses['repeat'].append((response, response.get('repeat'), c))
                                else:
                                    cross_trace.append((c.id, 'last used', 'No answer'))
                        elif _answer:
                            logger.info("{} has no good match".format(c.id))
                            cross_trace.append((c.id, 'last used', 'No good match: {}'.format(_response.get('trace') or 'No trace')))
                            cached_responses['nogoodmatch'].append((_response, _answer, c))
                        else:
                            cross_trace.append((c.id, 'last used', 'No answer'))
                        break

    # Check the loop
    if not answer:
        for c, weight in weighted_characters:
            if weight == 0:
                logger.info("Ignore zero weighted character {}".format(c.id))
                continue

            if c.id in used_charaters:
                logger.info("Ignore used tiers {}".format(c.id))
                cross_trace.append((c.id, 'loop', 'Skip used tier'))
                continue

            response = c.respond(_question, lang, sess, query)
            used_charaters.append(c.id)
            assert isinstance(response, dict), "Response must be a dict"

            _answer = str_cleanup(response.get('text', ''))
            if not _answer:
                if response.get('repeat'):
                    cross_trace.append((c.id, 'loop', 'Repetitive answer'))
                    cached_responses['repeat'].append((response, response.get('repeat'), c))
                else:
                    cross_trace.append((c.id, 'loop', 'No answer'))
                continue

            if response.get('bad'):
                cross_trace.append((c.id, 'loop', 'Bad answer: {}'.format(response.get('trace'))))
                cached_responses['bad'].append((response, _answer, c))
                continue

            if DISABLE_QUIBBLE and response.get('quibble'):
                logger.info("Ignore quibbled answer by {}".format(c.id))
                cross_trace.append((c.id, 'loop', 'Quibble answer'))
                cached_responses['quibble'].append((response, _answer, c))
                continue

            if response.get('gambit'):
                if random.random() > 0.3:
                    cached_responses['gambit'].append((response, _answer, c))
                    cross_trace.append((c.id, 'loop', 'Ignore gambit answer'))
                    logger.info("Ignore gambit response")
                    continue

            if 'pickup' in c.id:
                cached_responses['pickup'].append((response, _answer, c))

            # Each tier has weight*100% chance to be selected.
            # If the chance goes to the last tier, it will be selected anyway.
            if random.random() < weight:
                answer = _answer
                hit_character = c
                cross_trace.append((c.id, 'loop', response.get('trace') or 'No trace'))
                break
            else:
                cross_trace.append((c.id, 'loop', 'Pass through'))
                if 'pickup' not in c.id and 'markov' not in c.id:
                    cached_responses['pass'].append((response, _answer, c))
                else:
                    cached_responses['?'].append((response, _answer, c))

    if not answer:
        for response_type in ['pass', 'nogoodmatch', 'quibble', 'repeat', 'gambit', 'pickup', 'bad', '?']:
            if cached_responses.get(response_type):
                response, answer, hit_character = cached_responses.get(response_type)[0]
                if response_type == 'repeat':
                    if len(answer) < 80:
                        answer = "Again. " + answer
                    elif 80 < len(answer) < 200:
                        answer = "Let me say again. " + answer
                    else:
                        continue
                response['text'] = answer
                cross_trace.append(
                    (hit_character.id, response_type,
                    response.get('trace') or 'No trace'))
                break

    dummy_character = get_character('dummy', lang)
    if not answer and dummy_character:
        if response.get('repeat'):
            response = dummy_character.respond("REPEAT_ANSWER", lang, sid, query)
        else:
            response = dummy_character.respond("NO_ANSWER", lang, sid, query)
        hit_character = dummy_character
        answer = str_cleanup(response.get('text', ''))

    if not query and hit_character is not None:
        sess.add(question, answer, AnsweredBy=hit_character.id,
                    User=user, BotName=botname, Trace=cross_trace,
                    Revision=REVISION, Lang=lang, ModQuestion=_question)

        sess.last_used_character = hit_character

        if answer.lower().strip().endswith('?'):
            if hit_character.dynamic_level:
                sess.open_character = hit_character
                logger.info("Set open dialog character {}".format(
                            hit_character.id))
        else:
            sess.open_character = None

    response['trace'] = cross_trace
    return response
Пример #7
0
    def _ask_character(stage, character, weight, good_match=False, reuse=False):
        logger.info("Asking character {} in stage {}".format(character.id, stage))

        if not reuse and character.id in used_charaters:
            cross_trace.append((character.id, stage, 'Skip used tier'))
            return False, None, None

        if character.id in used_charaters and character.type == TYPE_CS:
            cross_trace.append((character.id, stage, 'Skip CS tier'))
            return False, None, None

        used_charaters.append(character.id)
        answer = None
        answered = False

        if weight == 0:
            cross_trace.append((character.id, stage, 'Disabled'))
            return False, None, None

        response = character.respond(_question, lang, sess, query, request_id)
        answer = str_cleanup(response.get('text', ''))
        trace = response.get('trace')

        if answer:
            if 'pickup' in character.id:
                cached_responses['pickup'].append((response, answer, character))
                return False, None, None
            if good_match:
                if response.get('exact_match') or response.get('ok_match'):
                    logger.info("{} has good match".format(character.id))
                    answered = True
                else:
                    logger.info("{} has no good match".format(character.id))
                    cross_trace.append((character.id, stage, 'No good match. Answer: {}, Trace: {}'.format(answer, trace)))
                    cached_responses['nogoodmatch'].append((response, answer, character))
            elif response.get('bad'):
                cross_trace.append((character.id, stage, 'Bad answer. Answer: {}, Trace: {}'.format(answer, trace)))
                cached_responses['bad'].append((response, answer, character))
            elif DISABLE_QUIBBLE and response.get('quibble'):
                cross_trace.append((character.id, stage, 'Quibble answer. Answer: {}, Trace: {}'.format(answer, trace)))
                cached_responses['quibble'].append((response, answer, character))
            elif response.get('gambit'):
                if random.random() > 0.3:
                    cross_trace.append((character.id, stage, 'Ignore gambit answer. Answer: {}, Trace: {}'.format(answer, trace)))
                    cached_responses['gambit'].append((response, answer, character))
                else:
                    answered = True
            else:
                answered = True
            if answered:
                if random.random() < weight:
                    cross_trace.append((character.id, stage, 'Trace: {}'.format(trace)))
                else:
                    answered = False
                    cross_trace.append((character.id, stage, 'Pass through. Answer: {}, Weight: {}, Trace: {}'.format(answer, weight, trace)))
                    logger.info("{} has no answer".format(character.id))
                    if 'markov' not in character.id:
                        cached_responses['pass'].append((response, answer, character))
                    else:
                        cached_responses['?'].append((response, answer, character))
        else:
            if response.get('repeat'):
                answer = response.get('repeat')
                cross_trace.append((character.id, stage, 'Repetitive answer. Answer: {}, Trace: {}'.format(answer, trace)))
                cached_responses['repeat'].append((response, answer, character))
            else:
                logger.info("{} has no answer".format(character.id))
                cross_trace.append((character.id, stage, 'No answer. Trace: {}'.format(trace)))
        return answered, answer, response
Пример #8
0
def _ask_characters(characters, question, lang, sid, query, request_id, **kwargs):
    sess = session_manager.get_session(sid)
    if sess is None:
        return

    used_charaters = []
    data = sess.get_session_data()
    user = getattr(data, 'user')
    botname = getattr(data, 'botname')
    weights = get_weights(characters, sess)
    weighted_characters = zip(characters, weights)
    logger.info("Weights {}".format(weighted_characters))

    _question = preprocessing(question, lang, sess)
    response = {}
    hit_character = None
    answer = None
    cross_trace = []
    cached_responses = defaultdict(list)

    control = get_character('control')
    if control is not None:
        _response = control.respond(_question, lang, sess, query, request_id)
        _answer = _response.get('text')
        if _answer == '[tell me more]':
            cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
            if sess.last_used_character:
                if sess.cache.that_question is None:
                    sess.cache.that_question = sess.cache.last_question
                context = sess.last_used_character.get_context(sess)
                if 'continue' in context and context.get('continue'):
                    _answer, res = shorten(context.get('continue'), 140)
                    response['text'] = answer = _answer
                    response['botid'] = sess.last_used_character.id
                    response['botname'] = sess.last_used_character.name
                    sess.last_used_character.set_context(sess, {'continue': res})
                    hit_character = sess.last_used_character
                    cross_trace.append((sess.last_used_character.id, 'continuation', 'Non-empty'))
                else:
                    _question = sess.cache.that_question.lower().strip()
                    cross_trace.append((sess.last_used_character.id, 'continuation', 'Empty'))
        elif _answer.startswith('[weather]'):
            template = _answer.replace('[weather]', '')
            cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
            context = control.get_context(sess)
            if context:
                location = context.get('querylocation')
                prop = parse_weather(get_weather(location))
                if prop:
                    try:
                        _answer = template.format(location=location, **prop)
                        if _answer:
                            answer = _answer
                            response['text'] = _answer
                            response['botid'] = control.id
                            response['botname'] = control.name
                    except Exception as ex:
                        cross_trace.append((control.id, 'control', 'No answer'))
                        logger.error(ex)
                else:
                    cross_trace.append((control.id, 'control', 'No answer'))
        elif _answer in OPERATOR_MAP.keys():
            opt = OPERATOR_MAP[_answer]
            cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
            context = control.get_context(sess)
            if context:
                item1 = context.get('item1')
                item2 = context.get('item2')
                item1 = words2num(item1)
                item2 = words2num(item2)
                if item1 is not None and item2 is not None:
                    try:
                        result = opt(item1, item2)
                        img = math.modf(result)[0]
                        if img < 1e-6:
                            result_str = '{:d}'.format(int(result))
                        else:
                            result_str = 'about {:.4f}'.format(result)
                        if result > 1e20:
                            answer = "The number is too big. You should use a calculator."
                        else:
                            answer = "The answer is {result}".format(result=result_str)
                    except ZeroDivisionError:
                        answer = "Oh, the answer is not a number"
                    except Exception as ex:
                        logger.error(ex)
                        answer = "Sorry, something goes wrong. I can't calculate it."
                    response['text'] = answer
                    response['botid'] = control.id
                    response['botname'] = control.name
                else:
                    cross_trace.append((control.id, 'control', 'No answer'))
        else:
            if _answer and not re.findall(r'\[.*\].*', _answer):
                cross_trace.append((control.id, 'control', _response.get('trace') or 'No trace'))
                hit_character = control
                answer = _answer
                response = _response
            else:
                cross_trace.append((control.id, 'control', 'No answer'))
            for c in characters:
                try:
                    c.remove_context(sess, 'continue')
                except NotImplementedError:
                    pass
            sess.cache.that_question = None

    def _ask_character(stage, character, weight, good_match=False, reuse=False):
        logger.info("Asking character {} in stage {}".format(character.id, stage))

        if not reuse and character.id in used_charaters:
            cross_trace.append((character.id, stage, 'Skip used tier'))
            return False, None, None

        if character.id in used_charaters and character.type == TYPE_CS:
            cross_trace.append((character.id, stage, 'Skip CS tier'))
            return False, None, None

        used_charaters.append(character.id)
        answer = None
        answered = False

        if weight == 0:
            cross_trace.append((character.id, stage, 'Disabled'))
            return False, None, None

        response = character.respond(_question, lang, sess, query, request_id)
        answer = str_cleanup(response.get('text', ''))
        trace = response.get('trace')

        if answer:
            if 'pickup' in character.id:
                cached_responses['pickup'].append((response, answer, character))
                return False, None, None
            if good_match:
                if response.get('exact_match') or response.get('ok_match'):
                    logger.info("{} has good match".format(character.id))
                    answered = True
                else:
                    logger.info("{} has no good match".format(character.id))
                    cross_trace.append((character.id, stage, 'No good match. Answer: {}, Trace: {}'.format(answer, trace)))
                    cached_responses['nogoodmatch'].append((response, answer, character))
            elif response.get('bad'):
                cross_trace.append((character.id, stage, 'Bad answer. Answer: {}, Trace: {}'.format(answer, trace)))
                cached_responses['bad'].append((response, answer, character))
            elif DISABLE_QUIBBLE and response.get('quibble'):
                cross_trace.append((character.id, stage, 'Quibble answer. Answer: {}, Trace: {}'.format(answer, trace)))
                cached_responses['quibble'].append((response, answer, character))
            elif response.get('gambit'):
                if random.random() > 0.3:
                    cross_trace.append((character.id, stage, 'Ignore gambit answer. Answer: {}, Trace: {}'.format(answer, trace)))
                    cached_responses['gambit'].append((response, answer, character))
                else:
                    answered = True
            else:
                answered = True
            if answered:
                if random.random() < weight:
                    cross_trace.append((character.id, stage, 'Trace: {}'.format(trace)))
                else:
                    answered = False
                    cross_trace.append((character.id, stage, 'Pass through. Answer: {}, Weight: {}, Trace: {}'.format(answer, weight, trace)))
                    logger.info("{} has no answer".format(character.id))
                    if 'markov' not in character.id:
                        cached_responses['pass'].append((response, answer, character))
                    else:
                        cached_responses['?'].append((response, answer, character))
        else:
            if response.get('repeat'):
                answer = response.get('repeat')
                cross_trace.append((character.id, stage, 'Repetitive answer. Answer: {}, Trace: {}'.format(answer, trace)))
                cached_responses['repeat'].append((response, answer, character))
            else:
                logger.info("{} has no answer".format(character.id))
                cross_trace.append((character.id, stage, 'No answer. Trace: {}'.format(trace)))
        return answered, answer, response

    # If the last input is a question, then try to use the same tier to
    # answer it.
    if not answer:
        if sess.open_character in characters:
            answered, _answer, _response = _ask_character(
                'question', sess.open_character, 1, good_match=True)
            if answered:
                hit_character = sess.open_character
                answer = _answer
                response = _response

    # Try the first tier to see if there is good match
    if not answer:
        c, weight = weighted_characters[0]
        answered, _answer, _response = _ask_character(
            'priority', c, weight, good_match=True)
        if answered:
            hit_character = c
            answer = _answer
            response = _response

    # Select tier that is designed to be proper to answer the question
    if not answer:
        for c, weight in weighted_characters:
            if c.is_favorite(_question):
                answered, _answer, _response = _ask_character(
                    'favorite', c, 1)
                if answered:
                    hit_character = c
                    answer = _answer
                    response = _response

    # Check the last used character
    if not answer:
        if sess.last_used_character and sess.last_used_character.dynamic_level:
            for c, weight in weighted_characters:
                if sess.last_used_character.id == c.id:
                    answered, _answer, _response = _ask_character(
                        'last used', c, weight)
                    if answered:
                        hit_character = c
                        answer = _answer
                        response = _response
                    break

    # Check the loop
    if not answer:
        for c, weight in weighted_characters:
            answered, _answer, _response = _ask_character(
                'loop', c, weight, reuse=True)
            if answered:
                hit_character = c
                answer = _answer
                response = _response
                break

    if not answer:
        for response_type in ['pass', 'nogoodmatch', 'quibble', 'repeat', 'gambit', 'pickup', 'bad', '?']:
            if cached_responses.get(response_type):
                response, answer, hit_character = cached_responses.get(response_type)[0]
                if response_type == 'repeat':
                    if len(answer) < 80:
                        answer = "Again. " + answer
                    elif 80 < len(answer) < 200:
                        answer = "Let me say again. " + answer
                    else:
                        continue
                response['text'] = answer
                cross_trace.append(
                    (hit_character.id, response_type,
                    response.get('trace') or 'No trace'))
                break

    if answer and re.match('.*{.*}.*', answer):
        logger.info("Template answer {}".format(answer))
        try:
            response['orig_text'] = answer
            answer = render(answer)
            response['text'] = answer
        except Exception as ex:
            answer = ''
            response['text'] = ''
            logger.error("Error in rendering template, {}".format(ex))

    dummy_character = get_character('dummy', lang)
    if not answer and dummy_character:
        if response.get('repeat'):
            response = dummy_character.respond("REPEAT_ANSWER", lang, sid, query)
        else:
            response = dummy_character.respond("NO_ANSWER", lang, sid, query)
        hit_character = dummy_character
        answer = str_cleanup(response.get('text', ''))

    if not query and hit_character is not None:
        sess.add(question, answer, AnsweredBy=hit_character.id,
                    User=user, BotName=botname, Trace=cross_trace,
                    Revision=REVISION, Lang=lang, ModQuestion=_question,
                    RequestId=request_id,Marker=kwargs.get('marker'))

        sess.last_used_character = hit_character

        if is_question(answer.lower().strip()):
            if hit_character.dynamic_level:
                sess.open_character = hit_character
                logger.info("Set open dialog character {}".format(
                            hit_character.id))
        else:
            sess.open_character = None

    response['trace'] = cross_trace
    return response