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
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