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