Ejemplo n.º 1
0
    def respond(self, question, lang, session, query, request_id=None):
        ret = {}
        ret['text'] = ''
        ret['botid'] = self.id
        ret['botname'] = self.name
        ret['repeat'] = ''

        sid = session.sid
        answer, res = '', ''
        if lang not in self.languages:
            return ret
        elif re.search(r'\[.*\]', question):
            return ret

        chat_tries = 0
        answer = self.kernel.respond(question, sid, query)
        answer, res = shorten(answer, self.response_limit)

        if self.non_repeat:
            while chat_tries < self.max_chat_tries:
                if answer and session.check(question, answer):
                    break
                answer = self.kernel.respond(question, sid, query)
                answer, res = shorten(answer, self.response_limit)
                chat_tries += 1
        if answer:
            if not session.check(question, answer):
                ret['repeat'] = answer
                answer = ''
                self.logger.warn("Repeat answer")
        if res:
            self.kernel.setPredicate('continue', res, sid)
            self.logger.info("Set predicate continue={}".format(res))
        ret['text'] = answer
        ret['emotion'] = self.kernel.getPredicate('emotion', sid)
        ret['performance'] = self.kernel.getPredicate('performance', sid)
        traces = self.kernel.getTraceDocs()
        if traces:
            self.logger.debug("Trace: {}".format(traces))
            patterns = []
            for trace in traces:
                match_obj = self.trace_pattern.match(trace)
                if match_obj:
                    patterns.append(match_obj.group('pname'))
            ret['pattern'] = patterns
            if patterns:
                first = patterns[0]
                if '*' in first or '_' in first:
                    pattern_len = len(first.strip().split())
                    if '*' not in first:
                        ret['ok_match'] = True
                    if pattern_len > 3 and pattern_len > 0.9 * len(
                            question.strip().split()):
                        ret['ok_match'] = True
                else:
                    ret['exact_match'] = True
            traces = replace_aiml_abs_path(traces)
            ret['trace'] = '\n'.join(traces)
        return ret
Ejemplo n.º 2
0
    def respond(self, question, lang, session, query):
        ret = {}
        ret['text'] = ''
        ret['botid'] = self.id
        ret['botname'] = self.name
        ret['repeat'] = ''

        sid = session.sid
        answer, res = '', ''
        if lang not in self.languages:
            return ret
        elif re.search(r'\[.*\]', question):
            return ret

        chat_tries = 0
        answer = self.kernel.respond(question, sid, query)
        answer, res = shorten(answer, self.response_limit)

        if self.non_repeat:
            while chat_tries < self.max_chat_tries:
                if answer and session.check(question, answer):
                    break
                answer = self.kernel.respond(question, sid, query)
                answer, res = shorten(answer, self.response_limit)
                chat_tries += 1
        if answer:
            if not session.check(question, answer):
                ret['repeat'] = answer
                answer = ''
                self.logger.warn("Repeat answer")
        if res:
            self.kernel.setPredicate('continue', res, sid)
            self.logger.info("Set predicate continue={}".format(res))
        ret['text'] = answer
        ret['emotion'] = self.kernel.getPredicate('emotion', sid)
        ret['performance'] = self.kernel.getPredicate('performance', sid)
        traces = self.kernel.getTraceDocs()
        if traces:
            self.logger.debug("Trace: {}".format(traces))
            patterns = []
            for trace in traces:
                match_obj = self.trace_pattern.match(trace)
                if match_obj:
                    patterns.append(match_obj.group('pname'))
            ret['pattern'] = patterns
            if patterns:
                first = patterns[0]
                if '*' in first or '_' in first:
                    pattern_len = len(first.strip().split())
                    if '*' not in first:
                        ret['ok_match'] = True
                    if pattern_len>3 and pattern_len>0.9*len(question.strip().split()):
                        ret['ok_match'] = True
                else:
                    ret['exact_match'] = True
            traces = replace_aiml_abs_path(traces)
            ret['trace'] = '\n'.join(traces)
        return ret
Ejemplo n.º 3
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))
Ejemplo n.º 4
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))
Ejemplo n.º 5
0
    def respond(self, question, lang, session, query, request_id, *args,
                **kwargs):
        ret = {}
        ret['botid'] = self.id
        ret['botname'] = self.name
        sid = self.get_csuser(session)
        if lang not in self.languages:
            ret['text'] = ''
        else:
            state_snapshot = os.path.join(CS_STATE_DIR,
                                          '{}/{}.txt'.format(sid, request_id))
            if not session.test:
                self.save_state(session, state_snapshot)
            answer = self.say(sid, question)
            if not answer:
                ret['trace'] = 'Not responsive'

            answer, res = shorten(answer, self.response_limit)
            trace = self.say(sid, ':why')

            if self.non_repeat and session and answer:
                if not session.check(question, answer):
                    ret['repeat'] = answer
                    ret['trace'] = trace
                    answer = ''
                    self.logger.warn("Repeat answer")

            if session and res:
                self.set_context(session, {'continue': res})
                self.logger.info("Set continue={}".format(res))

            if self.is_command(question):
                if question == ':reset':
                    ret['text'] = 'Hi there'
                else:
                    ret['text'] = ''
            else:
                ret['text'] = answer

            if answer:
                ret['trace'] = trace
                ret['quibble'] = 'xquibble_' in trace or 'keywordlessquibbles' in trace
                ret['gambit'] = 'gambit' in trace
                ret['repeat_input'] = 'repeatinput' in trace
                if not ret['quibble'] and not ret['repeat_input']:
                    ret['ok_match'] = True
                if ret['repeat_input']:
                    ret['bad'] = 'repeatinput' in trace

        return ret
Ejemplo n.º 6
0
Archivo: ddg.py Proyecto: JacobE293/agi
    def respond(self, question, lang, session=None, *args, **kwargs):
        ret = {}
        ret['botid'] = self.id
        ret['botname'] = self.name
        ret['text'] = ''
        if lang not in self.languages:
            return ret
        elif re.search(r'\[.*\]', question):
            return ret

        if self.last_check_time is None or (
                dt.datetime.now() - self.last_check_time).total_seconds() > 60:
            self.last_check_time = dt.datetime.now()
            self.online = check_online('duckduckgo.com')

        if self.online:
            if self.is_favorite(question.lower()):
                answer = self.ask(question)
                answer, res = shorten(answer, self.response_limit)

                if self.non_repeat and session and answer:
                    if not session.check(question, answer):
                        ret['repeat'] = answer
                        answer = ''
                        self.logger.warn("Repeat answer")

                if res and session is not None:
                    self.set_context(session, {'continue': res})
                    self.logger.info("Set continue={}".format(res))

                ret['text'] = answer
            else:
                ret['trace'] = "Can't answer"
        else:
            ret['trace'] = "Offline"
        return ret
Ejemplo n.º 7
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
Ejemplo n.º 8
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
Ejemplo n.º 9
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