def user_transition(self, natural_language: str, state: Union[Enum, str, tuple], debugging=False): """ :param state: :param natural_language: :param debugging: :return: the successor state representing the highest score user transition that matches natural_language, or None if none match """ if '__gate__' in self._vars: del self._vars['__gate__'] if '__user_utterance__' in self.vars() and self.vars()['__user_utterance__'] is not None: natural_language = self.vars()['__user_utterance__'] else: natural_language = ''.join([c.lower() for c in natural_language if c.isalpha() or c == ' ']) state = module_state(state) self._error_transitioned = False ti = time() if state is None: state = self.state() else: state = State(state) transition_options = [] transition_items = [] for transition in self.transitions(state, Speaker.USER): natex = self.transition_natex(*transition) score = self.transition_settings(*transition).score transition_items.append((natex, transition, score)) while self._transitions: natex, transition, score = self._transitions.pop() transition_items.append((natex, transition, score)) ngrams = Ngrams(natural_language, n=10) for natex, transition, score in transition_items: self._potential_transition = transition if not self.is_module() and isinstance(transition[1], tuple): continue t1 = time() if debugging: print('Evaluating transition {}'.format(transition[:2])) vars = HashableDict(self._vars) try: match = natex.match(natural_language, vars, self._macros, ngrams, debugging) except Exception as e: print() print('Transition {}: {} failed'.format(str(transition), natex)) traceback.print_exc(file=sys.stdout) print() match = None source, target, speaker = transition if '__source__' in vars: source = State(module_state(vars['__source__'])) del vars['__source__'] if '__target__' in vars: target = State(module_state(vars['__target__'])) del vars['__target__'] transition = source, target, speaker if self.is_module() and isinstance(target, tuple): enter_natex = self.composite_dialogue_flow().state_settings(*target).enter else: enter_natex = self.state_settings(target).enter enter_natex_pass = True if enter_natex is not None: try: enter_natex_pass = enter_natex.generate(vars=vars, macros=self._macros, debugging=debugging) except Exception as e: print() print(e) print('Enter Natex {}: {} failed'.format(str(target), enter_natex)) print() enter_natex_pass = None if match and enter_natex_pass is not None: if debugging: print('Transition {} matched "{}"'.format(transition[:2], natural_language)) if '__score__' in vars: score = vars['__score__'] del vars['__score__'] gate_closed = False gate_var_config = None gate_target_id = None if '__gate__' in vars: gate_var_config = vars['__gate__'] gate_target_id = (self.namespace(), target) if ( not isinstance(target, tuple) and self.is_module()) else target for vc in self.gates()[gate_target_id]: if gate_var_config == vc: gate_closed = True del vars['__gate__'] if not gate_closed: transition_options.append((score, natex, transition, vars, gate_var_config, gate_target_id)) t2 = time() if debugging: print('Transition {} evaluated in {:.5f}'.format(transition, t2-t1)) while self._transitions: natex, transition, score = self._transitions.pop() transition_items.append((natex, transition, score)) self._transitions.clear() if transition_options: if debugging: print('Transition options: ------------') for option in transition_options: print('{} {}: {}'.format(option[0], option[2][1], option[1])) print('--------------------------------') score, natex, transition, vars, gate_var_config, gate_target_id = random_max(transition_options, key=lambda x: x[0]) if gate_var_config is not None: self.gates()[gate_target_id].append(gate_var_config) if debugging: updates = {} for k, v in vars.items(): if k not in self._vars or v != self._vars[k]: updates[k] = v if updates: print('Updating vars:') for k, v in updates.items(): if k in self._vars: print(' {} = {} -> {}'.format(k, self._vars[k], v)) else: print(' {} = None -> {}'.format(k, v)) self.update_vars(vars) next_state = transition[1] if debugging: print('User transition in {:.5f}'.format(time() - ti)) print('Transitioning {} -> {}'.format(self.state(), next_state)) return next_state else: self._error_transitioned = True next_state = self.error_successor(self.state()) if debugging: print('User transition in {:.5f}'.format(time() - ti)) print('Error transition {} -> {}'.format(self.state(), next_state)) return next_state
def system_transition(self, state: Union[Enum, str, tuple], debugging=False): """ :param state: :param debugging: :return: a <state, response> tuple representing the successor state and response """ if '__gate__' in self._vars: del self._vars['__gate__'] state = module_state(state) ti = time() if state is None: state = self.state() else: state = State(state) transition_options = [] transitions = list(self.transitions(state, Speaker.SYSTEM)) transition_items = [] for transition in transitions: natex = self.transition_natex(*transition) score = self.transition_settings(*transition).score transition_items.append((natex, transition, score)) while self._transitions: natex, transition, score = self._transitions.pop() transition_items.append((natex, transition, score)) while self._update_transitions: natex, transition, score = self._update_transitions.pop() transition_items.append((natex, transition, score)) for natex, transition, score in transition_items: t1 = time() transition_transition_enter = None vars = HashableDict(self._vars) self._potential_transition = transition # MOVED, todo try: generation = natex.generate(vars=vars, macros=self._macros, debugging=debugging) except Exception as e: print() print('Transition {}: {} failed'.format(str(transition), natex)) traceback.print_exc(file=sys.stdout) print() generation = None source, target, speaker = transition if '__source__' in vars: source = State(module_state(vars['__source__'])) del vars['__source__'] if '__target__' in vars: target = State(module_state(vars['__target__'])) del vars['__target__'] transition = source, target, speaker # if not self.is_module() and isinstance(target, tuple): # continue if '->' in transition[1]: _src, _tar = target.split('->')[0], target.split('->')[1] _tar = State(module_state(_tar)) transition = (_src, _tar, speaker) try: appended_generation = self.transition_natex(*transition).generate(vars=vars, macros=self._macros, debugging=debugging) if appended_generation is None: generation = None else: generation = generation + ' ' + appended_generation except Exception as e: print() print('Transition {}: {} failed'.format(str(transition), natex)) traceback.print_exc(file=sys.stdout) print() generation = None elif isinstance(transition[1], tuple) and '->' in transition[1][1]: namespace = transition[1][0] source, target = (namespace, target[1].split('->')[0]), target[1].split('->')[1] target = State(module_state(target)) transition_transition_enter = source transition = (source, target, speaker) try: appended_generation = self.composite_dialogue_flow().transition_natex( namespace, *transition).generate(vars=vars, macros=self._macros, debugging=debugging) if generation is None or appended_generation is None: generation = None else: generation = generation + ' ' + appended_generation except Exception as e: print() print('Transition {}: {} failed'.format(str(transition), natex)) traceback.print_exc(file=sys.stdout) print() generation = None source, target, speaker = transition if '__source__' in vars: source = State(module_state(vars['__source__'])) del vars['__source__'] if '__target__' in vars: target = State(module_state(vars['__target__'])) del vars['__target__'] transition = source, target, speaker enter_natex_pass = True transition_transition_enter_vars = vars if transition_transition_enter is not None: if self.is_module() and isinstance(transition_transition_enter, tuple): enter_natex = self.composite_dialogue_flow().state_settings(*transition_transition_enter).enter else: enter_natex = self.state_settings(transition_transition_enter).enter if enter_natex is not None: try: enter_natex_pass = enter_natex.generate(vars=transition_transition_enter_vars, macros=self._macros, debugging=debugging) except Exception as e: print() print(e) print('Enter Natex {}: {} failed'.format(str(transition_transition_enter), enter_natex)) print() enter_natex_pass = None if enter_natex_pass: if self.is_module() and isinstance(target, tuple): enter_natex = self.composite_dialogue_flow().state_settings(*target).enter else: enter_natex = self.state_settings(target).enter if enter_natex is not None: try: enter_natex_pass = enter_natex.generate(vars=vars, macros=self._macros, debugging=debugging) except Exception as e: print() print(e) print('Enter Natex {}: {} failed'.format(str(target), enter_natex)) print() enter_natex_pass = None if generation is not None and enter_natex_pass is not None: if '__score__' in vars: score = vars['__score__'] del vars['__score__'] gate_closed = False gate_var_config = None gate_target_id = None if '__gate__' in vars: gate_var_config = vars['__gate__'] gate_target_id = (self.namespace(), target) if (not isinstance(target, tuple) and self.is_module()) else target for vc in self.gates()[gate_target_id]: if gate_var_config == vc: gate_closed = True del vars['__gate__'] tt_gate_var_config = None tt_gate_target_id = None if transition_transition_enter is not None and '__gate__' in transition_transition_enter_vars: tt_gate_var_config = transition_transition_enter_vars['__gate__'] tt_gate_target_id = (self.namespace(), transition_transition_enter) if \ (not isinstance(transition_transition_enter, tuple) and self.is_module()) else transition_transition_enter for vc in self.gates()[tt_gate_target_id]: if tt_gate_var_config == vc: gate_closed = True del transition_transition_enter_vars['__gate__'] transition_transition_enter_vars.update(vars) vars = transition_transition_enter_vars if not gate_closed: transition_options.append((score, natex, generation, transition, vars, gate_var_config, gate_target_id, tt_gate_var_config, tt_gate_target_id)) t2 = time() if debugging: print('Transition {} evaluated in {:.5f}'.format(transition, t2-t1)) while self._transitions: natex, transition, score = self._transitions.pop() transition_items.append((natex, transition, score)) self._transitions.clear() if transition_options: if debugging: print('Transition options: ------------') for option in transition_options: print('{} {}: {}'.format(option[0], option[3][1], option[1])) print('--------------------------------') score, natex, response, transition, vars, gate_var_config, gate_target_id, tt_gate_var_config, tt_gate_target_id =\ random_max(transition_options, key=lambda x: x[0]) if gate_var_config is not None: self.gates()[gate_target_id].append(gate_var_config) if tt_gate_var_config is not None: self.gates()[tt_gate_target_id].append(tt_gate_var_config) if debugging: updates = {} for k, v in vars.items(): if k not in self._vars or v != self._vars[k]: updates[k] = v if updates: print('Updating vars:') for k, v in updates.items(): if k in self._vars: print(' {} = {} -> {}'.format(k, self._vars[k], v)) else: print(' {} = None -> {}'.format(k, v)) self.update_vars(vars) next_state = transition[1] if debugging: tf = time() print('System transition in {:.5f}'.format(tf-ti)) print('Transitioning {} -> {}'.format(self.state(), next_state)) if '__response_prefix__' in self.vars() and self.vars()['__response_prefix__'] != 'None': response = self.vars()['__response_prefix__'] + ' ' + response self.vars()['__response_prefix__'] = 'None' return response, next_state else: if self._default_state is not None: self.set_state(self._default_state) if debugging: print('No valid system transitions found, going to default state...') return self.system_transition(self.state(), debugging=debugging) raise AssertionError('dialogue flow system transition found no valid options from state {}'.format(state))