def __init__(self, configuration): """ Initialize the internal structures of this agent. :param configuration: a dictionary representing the configuration file """ super(ConversationalSingleAgent, self).__init__() self.configuration = configuration # There is only one agent in this setting self.agent_id = 0 # dialogue statistics self.dialogue_episode = 0 self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 self.total_dialogue_turns = 0 # Default meta-parameter values self.minibatch_length = 500 self.train_interval = 50 self.train_epochs = 3 # True values here would imply some default modules self.USE_USR_SIMULATOR = False self.USER_SIMULATOR_NLU = False self.USER_SIMULATOR_NLG = False self.USE_NLG = False self.USE_SPEECH = False self.USER_HAS_INITIATIVE = True self.SAVE_LOG = True self.SAVE_INTERVAL = 10000 # The dialogue will terminate after MAX_TURNS (this agent will issue # a bye() dialogue act. self.MAX_TURNS = 15 self.dialogue_turn = -1 self.ontology = None self.database = None self.domain = None self.global_args = {} self.dialogue_manager = None self.user_model = None self.user_simulator = None self.user_simulator_args = {} self.nlu = None self.nlg = None self.agent_role = None self.agent_goal = None self.goal_generator = None self.curr_state = None self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None self.user_model = UserModel() self.recorder = DialogueEpisodeRecorder() # TODO: Handle this properly - get reward function type from config self.reward_func = SlotFillingReward() # self.reward_func = SlotFillingGoalAdvancementReward() if self.configuration: # Error checks for options the config must have if not self.configuration['GENERAL']: raise ValueError('Cannot run Plato without GENERAL settings!') elif not self.configuration['GENERAL']['interaction_mode']: raise ValueError('Cannot run Plato without an ' 'interaction mode!') elif not self.configuration['DIALOGUE']: raise ValueError('Cannot run Plato without DIALOGUE settings!') elif not self.configuration['AGENT_0']: raise ValueError('Cannot run Plato without at least ' 'one agent!') # dialogue domain self.settings if 'DIALOGUE' in self.configuration and \ self.configuration['DIALOGUE']: if 'initiative' in self.configuration['DIALOGUE']: self.USER_HAS_INITIATIVE = bool( self.configuration['DIALOGUE']['initiative'] == 'user' ) self.user_simulator_args['us_has_initiative'] = \ self.USER_HAS_INITIATIVE if self.configuration['DIALOGUE']['domain']: self.domain = self.configuration['DIALOGUE']['domain'] if 'ontology_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['ontology_path'] ): self.ontology = ontology.Ontology( self.configuration['DIALOGUE']['ontology_path'] ) else: raise FileNotFoundError( 'domain file %s not found' % self.configuration['DIALOGUE']['ontology_path']) # Alternatively, look at global_arguments for ontology path elif 'global_arguments' in self.configuration['GENERAL'] \ and 'ontology' in \ self.configuration['GENERAL']['global_arguments']: if os.path.isfile( self.configuration['GENERAL'][ 'global_arguments']['ontology'] ): self.ontology = ontology.Ontology( self.configuration['GENERAL'][ 'global_arguments']['ontology'] ) else: raise FileNotFoundError( 'domain file %s not found' % self.configuration['GENERAL'][ 'global_arguments']['ontology']) if 'db_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['db_path'] ): if 'db_type' in self.configuration['DIALOGUE']: if self.configuration['DIALOGUE']['db_type'] == \ 'sql': self.database = database.SQLDataBase( self.configuration['DIALOGUE']['db_path'] ) else: self.database = database.DataBase( self.configuration['DIALOGUE']['db_path'] ) else: # Default to SQL self.database = database.SQLDataBase( self.configuration['DIALOGUE']['db_path'] ) else: raise FileNotFoundError( 'Database file %s not found' % self.configuration['DIALOGUE']['db_path'] ) # Alternatively, look at global arguments for db path elif 'global_arguments' in self.configuration['GENERAL'] \ and 'database' in \ self.configuration['GENERAL']['global_arguments']: if os.path.isfile( self.configuration['GENERAL'][ 'global_arguments']['database'] ): self.database = database.DataBase( self.configuration['GENERAL'][ 'global_arguments']['database'] ) else: raise FileNotFoundError( 'domain file %s not found' % self.configuration['GENERAL'][ 'global_arguments']['ontology']) if 'goals_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['goals_path'] ): self.goals_path = \ self.configuration['DIALOGUE']['goals_path'] else: raise FileNotFoundError( 'Goals file %s not found' % self.configuration['DIALOGUE']['goals_path'] ) # General settings if 'GENERAL' in self.configuration and \ self.configuration['GENERAL']: if 'global_arguments' in self.configuration['GENERAL']: self.global_args = \ self.configuration['GENERAL']['global_arguments'] if 'experience_logs' in self.configuration['GENERAL']: dialogues_path = None if 'path' in \ self.configuration['GENERAL']['experience_logs']: dialogues_path = \ self.configuration['GENERAL'][ 'experience_logs']['path'] if 'load' in \ self.configuration['GENERAL']['experience_logs'] \ and bool(self.configuration['GENERAL'][ 'experience_logs']['load']): if dialogues_path and os.path.isfile(dialogues_path): self.recorder.load(dialogues_path) else: raise FileNotFoundError( 'dialogue Log file %s not found (did you ' 'provide one?)' % dialogues_path) if 'save' in \ self.configuration['GENERAL']['experience_logs']: self.recorder.set_path(dialogues_path) self.SAVE_LOG = bool( self.configuration['GENERAL'][ 'experience_logs']['save'] ) if self.configuration['GENERAL']['interaction_mode'] == \ 'simulation': self.USE_USR_SIMULATOR = True elif self.configuration['GENERAL']['interaction_mode'] == \ 'speech': self.USE_SPEECH = True self.asr = speech_rec.Recognizer() # Agent Settings # Retrieve agent role if 'role' in self.configuration['AGENT_0']: self.agent_role = self.configuration['AGENT_0']['role'] else: raise ValueError( 'agent: No role assigned for agent {0} in ' 'config!'.format(self.agent_id) ) if self.agent_role == 'user': if self.ontology and self.database: self.goal_generator = GoalGenerator({ 'ontology': self.ontology, 'database': self.database }) else: raise ValueError( 'Conversational Single Agent (user): Cannot generate ' 'goal without ontology and database.' ) # Retrieve agent parameters if 'max_turns' in self.configuration['AGENT_0']: self.MAX_TURNS = self.configuration['AGENT_0']['max_turns'] if 'train_interval' in self.configuration['AGENT_0']: self.train_interval = \ self.configuration['AGENT_0']['train_interval'] if 'train_minibatch' in self.configuration['AGENT_0']: self.minibatch_length = \ self.configuration['AGENT_0']['train_minibatch'] if 'train_epochs' in self.configuration['AGENT_0']: self.train_epochs = \ self.configuration['AGENT_0']['train_epochs'] if 'save_interval' in self.configuration['AGENT_0']: self.SAVE_INTERVAL = \ self.configuration['AGENT_0']['save_interval'] # usr Simulator # Check for specific simulator self.settings, otherwise # default to agenda if 'USER_SIMULATOR' in self.configuration['AGENT_0']: # Agent 0 simulator configuration if 'package' in \ self.configuration['AGENT_0']['USER_SIMULATOR'] and \ 'class' in \ self.configuration['AGENT_0']['USER_SIMULATOR']: if 'arguments' in \ self.configuration['AGENT_0']['USER_SIMULATOR']: self.user_simulator_args =\ self.configuration[ 'AGENT_0']['USER_SIMULATOR']['arguments'] self.user_simulator_args.update(self.global_args) self.user_simulator = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['USER_SIMULATOR'][ 'package'], self.configuration['AGENT_0']['USER_SIMULATOR'][ 'class'], self.user_simulator_args ) if hasattr(self.user_simulator, 'nlu'): self.USER_SIMULATOR_NLU = self.user_simulator.nlu if hasattr(self.user_simulator, 'nlg'): self.USER_SIMULATOR_NLG = self.user_simulator.nlg else: # Fallback to agenda based simulator with default settings self.user_simulator = AgendaBasedUS( self.user_simulator_args ) # NLU Settings if 'NLU' in self.configuration['AGENT_0']: nlu_args = {} if 'package' in self.configuration['AGENT_0']['NLU'] and \ 'class' in self.configuration['AGENT_0']['NLU']: if 'arguments' in \ self.configuration['AGENT_0']['NLU']: nlu_args = \ self.configuration['AGENT_0']['NLU']['arguments'] nlu_args.update(self.global_args) self.nlu = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['NLU'][ 'package'], self.configuration['AGENT_0']['NLU'][ 'class'], nlu_args ) # DM Settings if 'DM' in self.configuration['AGENT_0']: dm_args = dict( zip( ['settings', 'ontology', 'database', 'domain', 'agent_id', 'agent_role'], [self.configuration, self.ontology, self.database, self.domain, self.agent_id, self.agent_role ] ) ) if 'package' in self.configuration['AGENT_0']['DM'] and \ 'class' in self.configuration['AGENT_0']['DM']: if 'arguments' in \ self.configuration['AGENT_0']['DM']: dm_args.update( self.configuration['AGENT_0']['DM']['arguments'] ) dm_args.update(self.global_args) self.dialogue_manager = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['DM'][ 'package'], self.configuration['AGENT_0']['DM'][ 'class'], dm_args ) # NLG Settings if 'NLG' in self.configuration['AGENT_0']: nlg_args = {} if 'package' in self.configuration['AGENT_0']['NLG'] and \ 'class' in self.configuration['AGENT_0']['NLG']: if 'arguments' in \ self.configuration['AGENT_0']['NLG']: nlg_args = \ self.configuration['AGENT_0']['NLG']['arguments'] nlg_args.update(self.global_args) self.nlg = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['NLG'][ 'package'], self.configuration['AGENT_0']['NLG'][ 'class'], nlg_args ) if self.nlg: self.USE_NLG = True # True if at least one module is training self.IS_TRAINING = self.nlu and self.nlu.training or \ self.dialogue_manager and self.dialogue_manager.training or \ self.nlg and self.nlg.training
class ConversationalSingleAgent(ConversationalAgent): """ Essentially the dialogue system. Will be able to interact with: - Simulated Users via: - dialogue Acts - Text - Human Users via: - Text - Speech - Online crowd? - parser """ def __init__(self, configuration): """ Initialize the internal structures of this agent. :param configuration: a dictionary representing the configuration file """ super(ConversationalSingleAgent, self).__init__() self.configuration = configuration # There is only one agent in this setting self.agent_id = 0 # dialogue statistics self.dialogue_episode = 0 self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 self.total_dialogue_turns = 0 # Default meta-parameter values self.minibatch_length = 500 self.train_interval = 50 self.train_epochs = 3 # True values here would imply some default modules self.USE_USR_SIMULATOR = False self.USER_SIMULATOR_NLU = False self.USER_SIMULATOR_NLG = False self.USE_NLG = False self.USE_SPEECH = False self.USER_HAS_INITIATIVE = True self.SAVE_LOG = True self.SAVE_INTERVAL = 10000 # The dialogue will terminate after MAX_TURNS (this agent will issue # a bye() dialogue act. self.MAX_TURNS = 15 self.dialogue_turn = -1 self.ontology = None self.database = None self.domain = None self.global_args = {} self.dialogue_manager = None self.user_model = None self.user_simulator = None self.user_simulator_args = {} self.nlu = None self.nlg = None self.agent_role = None self.agent_goal = None self.goal_generator = None self.curr_state = None self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None self.user_model = UserModel() self.recorder = DialogueEpisodeRecorder() # TODO: Handle this properly - get reward function type from config self.reward_func = SlotFillingReward() # self.reward_func = SlotFillingGoalAdvancementReward() if self.configuration: # Error checks for options the config must have if not self.configuration['GENERAL']: raise ValueError('Cannot run Plato without GENERAL settings!') elif not self.configuration['GENERAL']['interaction_mode']: raise ValueError('Cannot run Plato without an ' 'interaction mode!') elif not self.configuration['DIALOGUE']: raise ValueError('Cannot run Plato without DIALOGUE settings!') elif not self.configuration['AGENT_0']: raise ValueError('Cannot run Plato without at least ' 'one agent!') # dialogue domain self.settings if 'DIALOGUE' in self.configuration and \ self.configuration['DIALOGUE']: if 'initiative' in self.configuration['DIALOGUE']: self.USER_HAS_INITIATIVE = bool( self.configuration['DIALOGUE']['initiative'] == 'user' ) self.user_simulator_args['us_has_initiative'] = \ self.USER_HAS_INITIATIVE if self.configuration['DIALOGUE']['domain']: self.domain = self.configuration['DIALOGUE']['domain'] if 'ontology_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['ontology_path'] ): self.ontology = ontology.Ontology( self.configuration['DIALOGUE']['ontology_path'] ) else: raise FileNotFoundError( 'domain file %s not found' % self.configuration['DIALOGUE']['ontology_path']) # Alternatively, look at global_arguments for ontology path elif 'global_arguments' in self.configuration['GENERAL'] \ and 'ontology' in \ self.configuration['GENERAL']['global_arguments']: if os.path.isfile( self.configuration['GENERAL'][ 'global_arguments']['ontology'] ): self.ontology = ontology.Ontology( self.configuration['GENERAL'][ 'global_arguments']['ontology'] ) else: raise FileNotFoundError( 'domain file %s not found' % self.configuration['GENERAL'][ 'global_arguments']['ontology']) if 'db_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['db_path'] ): if 'db_type' in self.configuration['DIALOGUE']: if self.configuration['DIALOGUE']['db_type'] == \ 'sql': self.database = database.SQLDataBase( self.configuration['DIALOGUE']['db_path'] ) else: self.database = database.DataBase( self.configuration['DIALOGUE']['db_path'] ) else: # Default to SQL self.database = database.SQLDataBase( self.configuration['DIALOGUE']['db_path'] ) else: raise FileNotFoundError( 'Database file %s not found' % self.configuration['DIALOGUE']['db_path'] ) # Alternatively, look at global arguments for db path elif 'global_arguments' in self.configuration['GENERAL'] \ and 'database' in \ self.configuration['GENERAL']['global_arguments']: if os.path.isfile( self.configuration['GENERAL'][ 'global_arguments']['database'] ): self.database = database.DataBase( self.configuration['GENERAL'][ 'global_arguments']['database'] ) else: raise FileNotFoundError( 'domain file %s not found' % self.configuration['GENERAL'][ 'global_arguments']['ontology']) if 'goals_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['goals_path'] ): self.goals_path = \ self.configuration['DIALOGUE']['goals_path'] else: raise FileNotFoundError( 'Goals file %s not found' % self.configuration['DIALOGUE']['goals_path'] ) # General settings if 'GENERAL' in self.configuration and \ self.configuration['GENERAL']: if 'global_arguments' in self.configuration['GENERAL']: self.global_args = \ self.configuration['GENERAL']['global_arguments'] if 'experience_logs' in self.configuration['GENERAL']: dialogues_path = None if 'path' in \ self.configuration['GENERAL']['experience_logs']: dialogues_path = \ self.configuration['GENERAL'][ 'experience_logs']['path'] if 'load' in \ self.configuration['GENERAL']['experience_logs'] \ and bool(self.configuration['GENERAL'][ 'experience_logs']['load']): if dialogues_path and os.path.isfile(dialogues_path): self.recorder.load(dialogues_path) else: raise FileNotFoundError( 'dialogue Log file %s not found (did you ' 'provide one?)' % dialogues_path) if 'save' in \ self.configuration['GENERAL']['experience_logs']: self.recorder.set_path(dialogues_path) self.SAVE_LOG = bool( self.configuration['GENERAL'][ 'experience_logs']['save'] ) if self.configuration['GENERAL']['interaction_mode'] == \ 'simulation': self.USE_USR_SIMULATOR = True elif self.configuration['GENERAL']['interaction_mode'] == \ 'speech': self.USE_SPEECH = True self.asr = speech_rec.Recognizer() # Agent Settings # Retrieve agent role if 'role' in self.configuration['AGENT_0']: self.agent_role = self.configuration['AGENT_0']['role'] else: raise ValueError( 'agent: No role assigned for agent {0} in ' 'config!'.format(self.agent_id) ) if self.agent_role == 'user': if self.ontology and self.database: self.goal_generator = GoalGenerator({ 'ontology': self.ontology, 'database': self.database }) else: raise ValueError( 'Conversational Single Agent (user): Cannot generate ' 'goal without ontology and database.' ) # Retrieve agent parameters if 'max_turns' in self.configuration['AGENT_0']: self.MAX_TURNS = self.configuration['AGENT_0']['max_turns'] if 'train_interval' in self.configuration['AGENT_0']: self.train_interval = \ self.configuration['AGENT_0']['train_interval'] if 'train_minibatch' in self.configuration['AGENT_0']: self.minibatch_length = \ self.configuration['AGENT_0']['train_minibatch'] if 'train_epochs' in self.configuration['AGENT_0']: self.train_epochs = \ self.configuration['AGENT_0']['train_epochs'] if 'save_interval' in self.configuration['AGENT_0']: self.SAVE_INTERVAL = \ self.configuration['AGENT_0']['save_interval'] # usr Simulator # Check for specific simulator self.settings, otherwise # default to agenda if 'USER_SIMULATOR' in self.configuration['AGENT_0']: # Agent 0 simulator configuration if 'package' in \ self.configuration['AGENT_0']['USER_SIMULATOR'] and \ 'class' in \ self.configuration['AGENT_0']['USER_SIMULATOR']: if 'arguments' in \ self.configuration['AGENT_0']['USER_SIMULATOR']: self.user_simulator_args =\ self.configuration[ 'AGENT_0']['USER_SIMULATOR']['arguments'] self.user_simulator_args.update(self.global_args) self.user_simulator = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['USER_SIMULATOR'][ 'package'], self.configuration['AGENT_0']['USER_SIMULATOR'][ 'class'], self.user_simulator_args ) if hasattr(self.user_simulator, 'nlu'): self.USER_SIMULATOR_NLU = self.user_simulator.nlu if hasattr(self.user_simulator, 'nlg'): self.USER_SIMULATOR_NLG = self.user_simulator.nlg else: # Fallback to agenda based simulator with default settings self.user_simulator = AgendaBasedUS( self.user_simulator_args ) # NLU Settings if 'NLU' in self.configuration['AGENT_0']: nlu_args = {} if 'package' in self.configuration['AGENT_0']['NLU'] and \ 'class' in self.configuration['AGENT_0']['NLU']: if 'arguments' in \ self.configuration['AGENT_0']['NLU']: nlu_args = \ self.configuration['AGENT_0']['NLU']['arguments'] nlu_args.update(self.global_args) self.nlu = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['NLU'][ 'package'], self.configuration['AGENT_0']['NLU'][ 'class'], nlu_args ) # DM Settings if 'DM' in self.configuration['AGENT_0']: dm_args = dict( zip( ['settings', 'ontology', 'database', 'domain', 'agent_id', 'agent_role'], [self.configuration, self.ontology, self.database, self.domain, self.agent_id, self.agent_role ] ) ) if 'package' in self.configuration['AGENT_0']['DM'] and \ 'class' in self.configuration['AGENT_0']['DM']: if 'arguments' in \ self.configuration['AGENT_0']['DM']: dm_args.update( self.configuration['AGENT_0']['DM']['arguments'] ) dm_args.update(self.global_args) self.dialogue_manager = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['DM'][ 'package'], self.configuration['AGENT_0']['DM'][ 'class'], dm_args ) # NLG Settings if 'NLG' in self.configuration['AGENT_0']: nlg_args = {} if 'package' in self.configuration['AGENT_0']['NLG'] and \ 'class' in self.configuration['AGENT_0']['NLG']: if 'arguments' in \ self.configuration['AGENT_0']['NLG']: nlg_args = \ self.configuration['AGENT_0']['NLG']['arguments'] nlg_args.update(self.global_args) self.nlg = \ ConversationalGenericAgent.load_module( self.configuration['AGENT_0']['NLG'][ 'package'], self.configuration['AGENT_0']['NLG'][ 'class'], nlg_args ) if self.nlg: self.USE_NLG = True # True if at least one module is training self.IS_TRAINING = self.nlu and self.nlu.training or \ self.dialogue_manager and self.dialogue_manager.training or \ self.nlg and self.nlg.training def __del__(self): """ Do some house-keeping and save the models. :return: nothing """ if self.recorder and self.SAVE_LOG: self.recorder.save() if self.nlu: self.nlu.save() if self.dialogue_manager: self.dialogue_manager.save() if self.nlg: self.nlg.save() self.curr_state = None self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None def initialize(self): """ Initializes the conversational agent based on settings in the configuration file. :return: Nothing """ self.dialogue_episode = 0 self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 if self.nlu: self.nlu.initialize({}) self.dialogue_manager.initialize({}) if self.nlg: self.nlg.initialize({}) self.curr_state = None self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None def start_dialogue(self, args=None): """ Perform initial dialogue turn. :param args: optional args :return: """ self.dialogue_turn = 0 sys_utterance = '' if self.USE_USR_SIMULATOR: self.user_simulator.initialize(self.user_simulator_args) print('DEBUG > usr goal:') print(self.user_simulator.goal) self.dialogue_manager.restart({}) if not self.USER_HAS_INITIATIVE: # sys_response = self.dialogue_manager.respond() sys_response = [DialogueAct('welcomemsg', [])] if self.USE_NLG: sys_utterance = self.nlg.generate_output( {'dacts': sys_response} ) print('SYSTEM > %s ' % sys_utterance) if self.USE_SPEECH: try: tts = gTTS(sys_utterance) tts.save('sys_output.mp3') os.system('afplay sys_output.mp3') except Exception as e: print( 'WARNING: gTTS encountered an error: {0}. ' 'Falling back to sys TTS.'.format(e) ) os.system('say ' + sys_utterance) else: print( 'SYSTEM > %s ' % '; '. join([str(sr) for sr in sys_response]) ) if self.USE_USR_SIMULATOR: usim_input = sys_response if self.USER_SIMULATOR_NLU and self.USE_NLG: usim_input = self.user_simulator.nlu.process_input( sys_utterance ) self.user_simulator.receive_input(usim_input) rew, success, task_success = self.reward_func.calculate( self.dialogue_manager.get_state(), sys_response, self.user_simulator.goal ) else: rew, success, task_success = 0, None, None self.recorder.record( deepcopy(self.dialogue_manager.get_state()), self.dialogue_manager.get_state(), sys_response, rew, success, task_success, output_utterance=sys_utterance ) self.dialogue_turn += 1 self.prev_state = None # Re-initialize these for good measure self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None self.continue_dialogue() def continue_dialogue(self): """ Perform next dialogue turn. :return: nothing """ usr_utterance = '' sys_utterance = '' if self.USE_USR_SIMULATOR: usr_input = self.user_simulator.respond() # TODO: THIS FIRST IF WILL BE HANDLED BY ConversationalAgentGeneric # -- SHOULD NOT LIVE HERE if isinstance(self.user_simulator, DTLUserSimulator): print('USER (nlg) > %s \n' % usr_input) usr_input = self.nlu.process_input( usr_input, self.dialogue_manager.get_state() ) elif self.USER_SIMULATOR_NLG: print('USER > %s \n' % usr_input) if self.nlu: usr_input = self.nlu.process_input(usr_input) # Otherwise it will just print the user's nlg but use the # simulator's output DActs to proceed. else: print('USER (DACT) > %s \n' % '; '.join( [str(ui) for ui in usr_input])) else: if self.USE_SPEECH: # Listen for input from the microphone with speech_rec.Microphone() as source: print('(listening...)') audio = self.asr.listen(source, phrase_time_limit=3) try: # This uses the default key usr_utterance = self.asr.recognize_google(audio) print("Google ASR: " + usr_utterance) except speech_rec.UnknownValueError: print("Google ASR did not understand you") except speech_rec.RequestError as e: print("Google ASR request error: {0}".format(e)) else: usr_utterance = input('USER > ') # Process the user's utterance if self.nlu: usr_input = self.nlu.process_input( usr_utterance, self.dialogue_manager.get_state() ) else: raise EnvironmentError( 'agent: No nlu defined for ' 'text-based interaction!' ) self.dialogue_manager.receive_input(usr_input) # Keep track of prev_state, for the DialogueEpisodeRecorder # Store here because this is the state that the dialogue manager # will use to make a decision. self.curr_state = deepcopy(self.dialogue_manager.get_state()) if self.dialogue_turn < self.MAX_TURNS: sys_response = self.dialogue_manager.generate_output() else: # Force dialogue stop sys_response = [DialogueAct('bye', [])] if self.USE_NLG: sys_utterance = self.nlg.generate_output({'dacts': sys_response}) print('SYSTEM > %s ' % sys_utterance) if self.USE_SPEECH: try: tts = gTTS(text=sys_utterance, lang='en') tts.save('sys_output.mp3') os.system('afplay sys_output.mp3') except: print('WARNING: gTTS encountered an error. ' 'Falling back to sys TTS.') os.system('say ' + sys_utterance) else: print('SYSTEM > %s ' % '; '.join([str(sr) for sr in sys_response])) if self.USE_USR_SIMULATOR: usim_input = sys_response if self.USER_SIMULATOR_NLU and self.USE_NLG: usim_input = \ self.user_simulator.nlu.process_input(sys_utterance) self.user_simulator.receive_input(usim_input) rew, success, task_success = \ self.reward_func.calculate( self.dialogue_manager.get_state(), sys_response, self.user_simulator.goal ) else: rew, success, task_success = 0, None, None if self.prev_state: self.recorder.record( self.prev_state, self.curr_state, self.prev_action, self.prev_reward, self.prev_success, input_utterance=self.prev_usr_utterance, output_utterance=self.prev_sys_utterance, task_success=self.prev_task_success ) self.dialogue_turn += 1 self.prev_state = deepcopy(self.curr_state) self.prev_action = deepcopy(sys_response) self.prev_usr_utterance = deepcopy(usr_utterance) self.prev_sys_utterance = deepcopy(sys_utterance) self.prev_reward = rew self.prev_success = success self.prev_task_success = task_success def end_dialogue(self): """ Perform final dialogue turn. Train and save models if applicable. :return: nothing """ # Record final state self.recorder.record( self.curr_state, self.curr_state, self.prev_action, self.prev_reward, self.prev_success, input_utterance=self.prev_usr_utterance, output_utterance=self.prev_sys_utterance, task_success=self.prev_task_success, force_terminate=True ) self.dialogue_episode += 1 if self.IS_TRAINING: if self.dialogue_episode % self.train_interval == 0 and \ len(self.recorder.dialogues) >= self.minibatch_length: for epoch in range(self.train_epochs): print('Training epoch {0} of {1}'.format( (epoch+1), self.train_epochs) ) # Sample minibatch minibatch = random.sample( self.recorder.dialogues, self.minibatch_length ) if self.nlu and self.nlu.training: self.nlu.train(minibatch) if self.dialogue_manager.is_training(): self.dialogue_manager.train(minibatch) if self.nlg and self.nlg.training: self.nlg.train(minibatch) # Keep track of dialogue statistics self.cumulative_rewards += \ self.recorder.dialogues[-1][-1]['cumulative_reward'] print('CUMULATIVE REWARD: {0}'. format(self.recorder.dialogues[-1][-1]['cumulative_reward'])) if self.dialogue_turn > 0: self.total_dialogue_turns += self.dialogue_turn if self.dialogue_episode % self.SAVE_INTERVAL == 0: if self.nlu: self.nlu.save() if self.dialogue_manager: self.dialogue_manager.save() if self.nlg: self.nlg.save() # Count successful dialogues if self.recorder.dialogues[-1][-1]['success']: print('SUCCESS (Subjective)!') self.num_successful_dialogues += \ int(self.recorder.dialogues[-1][-1]['success']) else: print('FAILURE (Subjective).') if self.recorder.dialogues[-1][-1]['task_success']: self.num_task_success += \ int(self.recorder.dialogues[-1][-1]['task_success']) print('OBJECTIVE TASK SUCCESS: {0}'. format(self.recorder.dialogues[-1][-1]['task_success'])) def terminated(self): """ Check if this agent is at a terminal state. :return: True or False """ return self.dialogue_manager.at_terminal_state() or \ self.dialogue_turn > self.MAX_TURNS
def __init__(self, configuration, agent_id): """ Initialize the internal structures of this agent. :param configuration: a dictionary representing the configuration file :param agent_id: an integer, this agent's id """ super(ConversationalMultiAgent, self).__init__() self.agent_id = agent_id # Flag to alternate training between roles self.train_system = True # dialogue statistics self.dialogue_episode = 1 # Note that since this is a multi-agent setting, dialogue_turn refers # to this agent's turns only self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 self.total_dialogue_turns = 0 self.minibatch_length = 200 self.train_interval = 50 self.train_epochs = 3 self.global_args = {} # Alternate training between the agents self.train_alternate_training = True self.train_switch_trainable_agents_every = self.train_interval self.configuration = configuration # True values here would imply some default modules self.USE_NLU = False self.USE_NLG = False self.USE_SPEECH = False self.USER_HAS_INITIATIVE = True self.SAVE_LOG = True self.SAVE_INTERVAL = 10000 # The dialogue will terminate after MAX_TURNS (this agent will issue # a bye() dialogue act. self.MAX_TURNS = 10 self.ontology = None self.database = None self.domain = None self.dialogue_manager = None self.user_model = None self.nlu = None self.nlg = None self.agent_role = None self.agent_goal = None self.goal_generator = None self.goals_path = None self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None self.user_model = UserModel() # The size of the experience pool is a hyperparameter. # Do not have an experience window larger than the current batch, # as past experience may not be relevant since both agents learn. self.recorder = DialogueEpisodeRecorder(size=20000) # TODO: Get reward type from the config self.reward_func = SlotFillingReward() if self.configuration: ag_id_str = 'AGENT_' + str(self.agent_id) # Error checks for options the config must have if not self.configuration['GENERAL']: raise ValueError('Cannot run Plato without GENERAL settings!') elif not self.configuration['GENERAL']['interaction_mode']: raise ValueError('Cannot run Plato without an interaction ' 'mode!') elif not self.configuration['DIALOGUE']: raise ValueError('Cannot run Plato without DIALOGUE settings!') elif not self.configuration[ag_id_str]: raise ValueError('Cannot run Plato without at least ' 'one agent!') # General settings if 'GENERAL' in self.configuration and \ self.configuration['GENERAL']: if 'global_arguments' in self.configuration['GENERAL']: self.global_args = \ self.configuration['GENERAL']['global_arguments'] if 'experience_logs' in self.configuration['GENERAL']: dialogues_path = None if 'path' in \ self.configuration['GENERAL']['experience_logs']: dialogues_path = \ self.configuration['GENERAL'][ 'experience_logs']['path'] if 'load' in \ self.configuration['GENERAL'][ 'experience_logs'] and \ bool( self.configuration[ 'GENERAL' ]['experience_logs']['load'] ): if dialogues_path and os.path.isfile(dialogues_path): self.recorder.load(dialogues_path) else: raise FileNotFoundError( 'dialogue Log file %s not found (did you ' 'provide one?)' % dialogues_path ) if 'save' in \ self.configuration['GENERAL']['experience_logs']: self.recorder.set_path(dialogues_path) self.SAVE_LOG = bool( self.configuration['GENERAL'][ 'experience_logs']['save'] ) # Retrieve agent role if 'role' in self.configuration[ag_id_str]: self.agent_role = self.configuration[ag_id_str][ 'role'] else: raise ValueError( 'agent: No role assigned for ' 'agent {0} in ' 'config!'.format(self.agent_id) ) # Dialogue settings if 'DIALOGUE' in self.configuration and \ self.configuration['DIALOGUE']: if 'initiative' in self.configuration['DIALOGUE']: self.USER_HAS_INITIATIVE = bool( self.configuration['DIALOGUE']['initiative'] == 'user' ) if 'domain' in self.configuration['DIALOGUE']: self.domain = self.configuration['DIALOGUE']['domain'] elif 'domain' in self.global_args: self.domain = self.global_args['domain'] ontology_path = None if 'ontology_path' in self.configuration['DIALOGUE']: ontology_path = \ self.configuration['DIALOGUE']['ontology_path'] elif 'ontology' in self.global_args: ontology_path = self.global_args['ontology'] if ontology_path and os.path.isfile(ontology_path): self.ontology = ontology.Ontology(ontology_path) else: raise FileNotFoundError( 'domain file %s not found' % ontology_path ) db_path = None if 'db_path' in self.configuration['DIALOGUE']: db_path = self.configuration['DIALOGUE']['db_path'] elif 'database' in self.global_args: db_path = self.global_args['database'] if db_path and os.path.isfile(db_path): if db_path[-3:] == '.db': self.database = database.SQLDataBase(db_path) else: self.database = database.DataBase(db_path) else: raise FileNotFoundError( 'Database file %s not found' % db_path ) if 'goals_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['goals_path'] ): self.goals_path = \ self.configuration['DIALOGUE']['goals_path'] else: raise FileNotFoundError( 'Goals file %s not ' 'found' % self.configuration[ 'DIALOGUE' ]['goals_path'] ) # Agent Settings # Retrieve agent role if 'role' in self.configuration[ag_id_str]: self.agent_role = self.configuration[ag_id_str][ 'role'] else: raise ValueError( 'agent: No role assigned for agent {0} in ' 'config!'.format(self.agent_id) ) if self.agent_role == 'user': if self.ontology and self.database: self.goal_generator = GoalGenerator({ 'ontology': self.ontology, 'database': self.database }) else: raise ValueError( 'Conversational Multi Agent (user): Cannot generate ' 'goal without ontology and database.' ) # Retrieve agent parameters if 'max_turns' in self.configuration[ag_id_str]: self.MAX_TURNS = self.configuration[ag_id_str][ 'max_turns'] if 'train_interval' in self.configuration[ag_id_str]: self.train_interval = \ self.configuration[ag_id_str]['train_interval'] if 'train_minibatch' in self.configuration[ag_id_str]: self.minibatch_length = \ self.configuration[ag_id_str]['train_minibatch'] if 'train_epochs' in self.configuration[ag_id_str]: self.train_epochs = \ self.configuration[ag_id_str]['train_epochs'] if 'save_interval' in self.configuration[ag_id_str]: self.SAVE_INTERVAL = \ self.configuration[ag_id_str]['save_interval'] # NLU Settings if 'NLU' in self.configuration[ag_id_str]: nlu_args = {} if 'package' in self.configuration[ag_id_str]['NLU'] and \ 'class' in self.configuration[ag_id_str]['NLU']: if 'arguments' in \ self.configuration[ag_id_str]['NLU']: nlu_args = \ self.configuration[ag_id_str]['NLU'][ 'arguments'] nlu_args.update(self.global_args) self.nlu = \ ConversationalGenericAgent.load_module( self.configuration[ag_id_str]['NLU'][ 'package'], self.configuration[ag_id_str]['NLU'][ 'class'], nlu_args ) # DM Settings if 'DM' in self.configuration[ag_id_str]: dm_args = dict( zip( ['settings', 'ontology', 'database', 'domain', 'agent_id', 'agent_role'], [self.configuration, self.ontology, self.database, self.domain, self.agent_id, self.agent_role ] ) ) if 'package' in self.configuration[ag_id_str]['DM'] and \ 'class' in self.configuration[ag_id_str]['DM']: if 'arguments' in \ self.configuration[ag_id_str]['DM']: dm_args.update( self.configuration[ag_id_str]['DM'][ 'arguments'] ) dm_args.update(self.global_args) self.dialogue_manager = \ ConversationalGenericAgent.load_module( self.configuration[ag_id_str]['DM'][ 'package'], self.configuration[ag_id_str]['DM'][ 'class'], dm_args ) # NLG Settings if 'NLG' in self.configuration[ag_id_str]: nlg_args = {} if 'package' in self.configuration[ag_id_str]['NLG'] and \ 'class' in self.configuration[ag_id_str]['NLG']: if 'arguments' in \ self.configuration[ag_id_str]['NLG']: nlg_args = \ self.configuration[ag_id_str]['NLG'][ 'arguments'] nlg_args.update(self.global_args) self.nlg = \ ConversationalGenericAgent.load_module( self.configuration[ag_id_str]['NLG'][ 'package'], self.configuration[ag_id_str]['NLG'][ 'class'], nlg_args ) if self.nlg: self.USE_NLG = True # True if at least one module is training self.IS_TRAINING = self.nlu and self.nlu.training or \ self.dialogue_manager and self.dialogue_manager.training or \ self.nlg and self.nlg.training
class ConversationalMultiAgent(ConversationalAgent): """ Essentially the dialogue system. Will be able to interact with: - Simulated Users via: - dialogue Acts - Text - Human Users via: - Text - Speech - Online crowd? - parser """ def __init__(self, configuration, agent_id): """ Initialize the internal structures of this agent. :param configuration: a dictionary representing the configuration file :param agent_id: an integer, this agent's id """ super(ConversationalMultiAgent, self).__init__() self.agent_id = agent_id # Flag to alternate training between roles self.train_system = True # dialogue statistics self.dialogue_episode = 1 # Note that since this is a multi-agent setting, dialogue_turn refers # to this agent's turns only self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 self.total_dialogue_turns = 0 self.minibatch_length = 200 self.train_interval = 50 self.train_epochs = 3 self.global_args = {} # Alternate training between the agents self.train_alternate_training = True self.train_switch_trainable_agents_every = self.train_interval self.configuration = configuration # True values here would imply some default modules self.USE_NLU = False self.USE_NLG = False self.USE_SPEECH = False self.USER_HAS_INITIATIVE = True self.SAVE_LOG = True self.SAVE_INTERVAL = 10000 # The dialogue will terminate after MAX_TURNS (this agent will issue # a bye() dialogue act. self.MAX_TURNS = 10 self.ontology = None self.database = None self.domain = None self.dialogue_manager = None self.user_model = None self.nlu = None self.nlg = None self.agent_role = None self.agent_goal = None self.goal_generator = None self.goals_path = None self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None self.user_model = UserModel() # The size of the experience pool is a hyperparameter. # Do not have an experience window larger than the current batch, # as past experience may not be relevant since both agents learn. self.recorder = DialogueEpisodeRecorder(size=20000) # TODO: Get reward type from the config self.reward_func = SlotFillingReward() if self.configuration: ag_id_str = 'AGENT_' + str(self.agent_id) # Error checks for options the config must have if not self.configuration['GENERAL']: raise ValueError('Cannot run Plato without GENERAL settings!') elif not self.configuration['GENERAL']['interaction_mode']: raise ValueError('Cannot run Plato without an interaction ' 'mode!') elif not self.configuration['DIALOGUE']: raise ValueError('Cannot run Plato without DIALOGUE settings!') elif not self.configuration[ag_id_str]: raise ValueError('Cannot run Plato without at least ' 'one agent!') # General settings if 'GENERAL' in self.configuration and \ self.configuration['GENERAL']: if 'global_arguments' in self.configuration['GENERAL']: self.global_args = \ self.configuration['GENERAL']['global_arguments'] if 'experience_logs' in self.configuration['GENERAL']: dialogues_path = None if 'path' in \ self.configuration['GENERAL']['experience_logs']: dialogues_path = \ self.configuration['GENERAL'][ 'experience_logs']['path'] if 'load' in \ self.configuration['GENERAL'][ 'experience_logs'] and \ bool( self.configuration[ 'GENERAL' ]['experience_logs']['load'] ): if dialogues_path and os.path.isfile(dialogues_path): self.recorder.load(dialogues_path) else: raise FileNotFoundError( 'dialogue Log file %s not found (did you ' 'provide one?)' % dialogues_path ) if 'save' in \ self.configuration['GENERAL']['experience_logs']: self.recorder.set_path(dialogues_path) self.SAVE_LOG = bool( self.configuration['GENERAL'][ 'experience_logs']['save'] ) # Retrieve agent role if 'role' in self.configuration[ag_id_str]: self.agent_role = self.configuration[ag_id_str][ 'role'] else: raise ValueError( 'agent: No role assigned for ' 'agent {0} in ' 'config!'.format(self.agent_id) ) # Dialogue settings if 'DIALOGUE' in self.configuration and \ self.configuration['DIALOGUE']: if 'initiative' in self.configuration['DIALOGUE']: self.USER_HAS_INITIATIVE = bool( self.configuration['DIALOGUE']['initiative'] == 'user' ) if 'domain' in self.configuration['DIALOGUE']: self.domain = self.configuration['DIALOGUE']['domain'] elif 'domain' in self.global_args: self.domain = self.global_args['domain'] ontology_path = None if 'ontology_path' in self.configuration['DIALOGUE']: ontology_path = \ self.configuration['DIALOGUE']['ontology_path'] elif 'ontology' in self.global_args: ontology_path = self.global_args['ontology'] if ontology_path and os.path.isfile(ontology_path): self.ontology = ontology.Ontology(ontology_path) else: raise FileNotFoundError( 'domain file %s not found' % ontology_path ) db_path = None if 'db_path' in self.configuration['DIALOGUE']: db_path = self.configuration['DIALOGUE']['db_path'] elif 'database' in self.global_args: db_path = self.global_args['database'] if db_path and os.path.isfile(db_path): if db_path[-3:] == '.db': self.database = database.SQLDataBase(db_path) else: self.database = database.DataBase(db_path) else: raise FileNotFoundError( 'Database file %s not found' % db_path ) if 'goals_path' in self.configuration['DIALOGUE']: if os.path.isfile( self.configuration['DIALOGUE']['goals_path'] ): self.goals_path = \ self.configuration['DIALOGUE']['goals_path'] else: raise FileNotFoundError( 'Goals file %s not ' 'found' % self.configuration[ 'DIALOGUE' ]['goals_path'] ) # Agent Settings # Retrieve agent role if 'role' in self.configuration[ag_id_str]: self.agent_role = self.configuration[ag_id_str][ 'role'] else: raise ValueError( 'agent: No role assigned for agent {0} in ' 'config!'.format(self.agent_id) ) if self.agent_role == 'user': if self.ontology and self.database: self.goal_generator = GoalGenerator({ 'ontology': self.ontology, 'database': self.database }) else: raise ValueError( 'Conversational Multi Agent (user): Cannot generate ' 'goal without ontology and database.' ) # Retrieve agent parameters if 'max_turns' in self.configuration[ag_id_str]: self.MAX_TURNS = self.configuration[ag_id_str][ 'max_turns'] if 'train_interval' in self.configuration[ag_id_str]: self.train_interval = \ self.configuration[ag_id_str]['train_interval'] if 'train_minibatch' in self.configuration[ag_id_str]: self.minibatch_length = \ self.configuration[ag_id_str]['train_minibatch'] if 'train_epochs' in self.configuration[ag_id_str]: self.train_epochs = \ self.configuration[ag_id_str]['train_epochs'] if 'save_interval' in self.configuration[ag_id_str]: self.SAVE_INTERVAL = \ self.configuration[ag_id_str]['save_interval'] # NLU Settings if 'NLU' in self.configuration[ag_id_str]: nlu_args = {} if 'package' in self.configuration[ag_id_str]['NLU'] and \ 'class' in self.configuration[ag_id_str]['NLU']: if 'arguments' in \ self.configuration[ag_id_str]['NLU']: nlu_args = \ self.configuration[ag_id_str]['NLU'][ 'arguments'] nlu_args.update(self.global_args) self.nlu = \ ConversationalGenericAgent.load_module( self.configuration[ag_id_str]['NLU'][ 'package'], self.configuration[ag_id_str]['NLU'][ 'class'], nlu_args ) # DM Settings if 'DM' in self.configuration[ag_id_str]: dm_args = dict( zip( ['settings', 'ontology', 'database', 'domain', 'agent_id', 'agent_role'], [self.configuration, self.ontology, self.database, self.domain, self.agent_id, self.agent_role ] ) ) if 'package' in self.configuration[ag_id_str]['DM'] and \ 'class' in self.configuration[ag_id_str]['DM']: if 'arguments' in \ self.configuration[ag_id_str]['DM']: dm_args.update( self.configuration[ag_id_str]['DM'][ 'arguments'] ) dm_args.update(self.global_args) self.dialogue_manager = \ ConversationalGenericAgent.load_module( self.configuration[ag_id_str]['DM'][ 'package'], self.configuration[ag_id_str]['DM'][ 'class'], dm_args ) # NLG Settings if 'NLG' in self.configuration[ag_id_str]: nlg_args = {} if 'package' in self.configuration[ag_id_str]['NLG'] and \ 'class' in self.configuration[ag_id_str]['NLG']: if 'arguments' in \ self.configuration[ag_id_str]['NLG']: nlg_args = \ self.configuration[ag_id_str]['NLG'][ 'arguments'] nlg_args.update(self.global_args) self.nlg = \ ConversationalGenericAgent.load_module( self.configuration[ag_id_str]['NLG'][ 'package'], self.configuration[ag_id_str]['NLG'][ 'class'], nlg_args ) if self.nlg: self.USE_NLG = True # True if at least one module is training self.IS_TRAINING = self.nlu and self.nlu.training or \ self.dialogue_manager and self.dialogue_manager.training or \ self.nlg and self.nlg.training def __del__(self): """ Do some house-keeping and save the models. :return: nothing """ if self.recorder and self.SAVE_LOG: self.recorder.save() if self.nlu: self.nlu.save() if self.dialogue_manager: self.dialogue_manager.save() if self.nlg: self.nlg.save() self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None def initialize(self): """ Initializes the conversational agent based on settings in the configuration file. :return: Nothing """ self.dialogue_episode = 0 self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 if self.nlu: self.nlu.initialize({}) self.dialogue_manager.initialize({}) if self.nlg: self.nlg.initialize({}) self.prev_state = None self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None self.prev_task_success = None def start_dialogue(self, args=None): """ Perform initial dialogue turn. :param args: optional arguments :return: """ self.dialogue_turn = 0 if self.agent_role == 'user': self.agent_goal = self.goal_generator.generate() self.dialogue_manager.update_goal(self.agent_goal) print('DEBUG > usr goal:') for c in self.agent_goal.constraints: print(f'\t\tConstr({self.agent_goal.constraints[c].slot}=' f'{self.agent_goal.constraints[c].value})') print('\t\t-------------') for r in self.agent_goal.requests: print(f'\t\tReq({self.agent_goal.requests[r].slot})') print('\n') elif args and 'goal' in args: # No deep copy here so that all agents see the same goal. self.agent_goal = args['goal'] else: raise ValueError( 'ConversationalMultiAgent - no goal provided ' 'for agent {0}!'.format(self.agent_role) ) self.dialogue_manager.restart({'goal': self.agent_goal}) response = [DialogueAct('welcomemsg', [])] response_utterance = '' # The above forces the DM's initial utterance to be the welcomemsg act. # The code below can be used to push an empty list of DialogueActs to # the DM and get a response from the model. # self.dialogue_manager.receive_input([]) # response = self.dialogue_manager.respond() if self.agent_role == 'system': response = [DialogueAct('welcomemsg', [])] if self.USE_NLG: response_utterance = self.nlg.generate_output( { 'dacts': response, 'system': self.agent_role == 'system', 'last_sys_utterance': '' } ) print( '{0} > {1}'.format( self.agent_role.upper(), response_utterance ) ) if self.USE_SPEECH: tts = gTTS(text=response_utterance, lang='en') tts.save('sys_output.mp3') os.system('afplay sys_output.mp3') else: print( '{0} > {1}'.format( self.agent_role.upper(), '; '.join([str(sr) for sr in response]) ) ) # TODO: Generate output depending on initiative - i.e. # have users also start the dialogue self.prev_state = None # Re-initialize these for good measure self.curr_state = None self.prev_usr_utterance = None self.prev_sys_utterance = None self.prev_action = None self.prev_reward = None self.prev_success = None return {'input_utterance': None, 'output_raw': response_utterance, 'output_dacts': response, 'goal': self.agent_goal } def continue_dialogue(self, args): """ Perform next dialogue turn. :return: this agent's output """ if 'other_input_raw' not in args: raise ValueError( 'ConversationalMultiAgent called without raw input!' ) other_input_raw = args['other_input_raw'] other_input_dacts = None if 'other_input_dacts' in args: other_input_dacts = args['other_input_dacts'] goal = None if 'goal' in args: goal = args['goal'] if goal: self.agent_goal = goal sys_utterance = '' other_input_nlu = deepcopy(other_input_raw) if self.nlu and isinstance(other_input_raw, str): # Process the other agent's utterance other_input_nlu = self.nlu.process_input( other_input_raw, self.dialogue_manager.get_state() ) elif other_input_dacts: # If no utterance provided, use the dacts other_input_nlu = other_input_dacts self.dialogue_manager.receive_input(other_input_nlu) # Keep track of prev_state, for the DialogueEpisodeRecorder # Store here because this is the state that the dialogue # manager will use to make a decision. self.curr_state = deepcopy(self.dialogue_manager.get_state()) # Update goal's ground truth if self.agent_role == 'system': self.agent_goal.ground_truth = deepcopy( self.curr_state.item_in_focus ) if self.dialogue_turn < self.MAX_TURNS: response = self.dialogue_manager.generate_output() self.agent_goal = self.dialogue_manager.DSTracker.DState.user_goal else: # Force dialogue stop response = [DialogueAct('bye', [])] rew, success, task_success = self.reward_func.calculate( self.dialogue_manager.get_state(), response, goal=self.agent_goal, agent_role=self.agent_role ) if self.USE_NLG: sys_utterance = self.nlg.generate_output( { 'dacts': response, 'system': self.agent_role == 'system', 'last_sys_utterance': other_input_raw } ) + ' ' print('{0} > {1}'.format(self.agent_role.upper(), sys_utterance)) if self.USE_SPEECH: tts = gTTS(text=sys_utterance, lang='en') tts.save('sys_output.mp3') os.system('afplay sys_output.mp3') else: print( '{0} > {1} \n'.format( self.agent_role.upper(), '; '.join([str(sr) for sr in response]) ) ) if self.prev_state: self.recorder.record( self.prev_state, self.curr_state, self.prev_action, self.prev_reward, self.prev_success, input_utterance=self.prev_usr_utterance, output_utterance=self.prev_sys_utterance, task_success=self.prev_task_success ) self.dialogue_turn += 1 self.prev_state = deepcopy(self.curr_state) self.prev_usr_utterance = deepcopy(other_input_raw) self.prev_sys_utterance = deepcopy(sys_utterance) self.prev_action = deepcopy(response) self.prev_reward = rew self.prev_success = success self.prev_task_success = task_success return {'input_utterance': None, 'output_raw': sys_utterance, 'output_dacts': response, 'goal': self.agent_goal } def end_dialogue(self): """ Perform final dialogue turn. Train and ave models if applicable. :return: """ if self.dialogue_episode % \ self.train_switch_trainable_agents_every == 0: self.train_system = not self.train_system # Record final state if self.curr_state: if not self.curr_state.is_terminal_state: self.curr_state.is_terminal_state = True self.prev_reward, self.prev_success, self.prev_task_success = \ self.reward_func.calculate( self.curr_state, [DialogueAct('bye', [])], goal=self.agent_goal, agent_role=self.agent_role ) else: print( 'Warning! Conversational Multi Agent attempted to end the' 'dialogue with no state. This dialogue will NOT be saved.') return self.recorder.record( self.curr_state, self.curr_state, self.prev_action, self.prev_reward, self.prev_success, input_utterance=self.prev_usr_utterance, output_utterance=self.prev_sys_utterance, task_success=self.prev_task_success, role=self.agent_role, force_terminate=True ) self.dialogue_episode += 1 if self.IS_TRAINING: if not self.train_alternate_training or \ (self.train_system and self.agent_role == 'system' or not self.train_system and self.agent_role == 'user'): if self.dialogue_episode % self.train_interval == 0 and \ len(self.recorder.dialogues) >= self.minibatch_length: for epoch in range(self.train_epochs): print( '{0}: Training epoch {1} of {2}'.format( self.agent_role, (epoch+1), self.train_epochs ) ) # Sample minibatch minibatch = random.sample( self.recorder.dialogues, self.minibatch_length ) if self.nlu: self.nlu.train(minibatch) self.dialogue_manager.train(minibatch) if self.nlg: self.nlg.train(minibatch) self.cumulative_rewards += \ self.recorder.dialogues[-1][-1]['cumulative_reward'] if self.dialogue_turn > 0: self.total_dialogue_turns += self.dialogue_turn if self.dialogue_episode % self.SAVE_INTERVAL == 0: if self.nlu: self.nlu.save() if self.dialogue_manager: self.dialogue_manager.save() if self.nlg: self.nlg.save() # Count successful dialogues if self.recorder.dialogues[-1][-1]['success']: print( '{0} SUCCESS! (reward: {1})'.format( self.agent_role, sum([t['reward'] for t in self.recorder.dialogues[-1]]) ) ) self.num_successful_dialogues += \ int(self.recorder.dialogues[-1][-1]['success']) else: print( '{0} FAILURE. (reward: {1})'.format( self.agent_role, sum([t['reward'] for t in self.recorder.dialogues[-1]]) ) ) if self.recorder.dialogues[-1][-1]['task_success']: self.num_task_success += int( self.recorder.dialogues[-1][-1]['task_success'] ) def terminated(self): """ Check if this agent is at a terminal state. :return: True or False """ # Hard coded response to bye to enforce dialogue_policy according to # which if any agent issues a 'bye' then the dialogue # terminates. Otherwise in multi-agent settings it is very hard to # learn the association and learn to terminate # the dialogue. if self.dialogue_manager.get_state().user_acts: for act in self.dialogue_manager.get_state().user_acts: if act.intent == 'bye': return True return self.dialogue_manager.at_terminal_state() def get_state(self): """ Get this agent's state :return: a DialogueState """ return self.dialogue_manager.get_state() def get_goal(self): """ Get this agent's goal :return: a Goal """ return self.agent_goal def set_goal(self, goal): """ Set or update this agent's goal :param goal: a Goal :return: nothing """ # Note: reason for non-deep copy is that if this agent changes the goal # these changes are propagated to e.g. the reward function, and # the reward calculation is up to date. self.agent_goal = goal
def __init__(self, configuration, agent_id): """ Initialize the internal structures of this agent. :param configuration: a dictionary representing the configuration file :param agent_id: an integer, this agent's id """ self.agent_id = agent_id # Dialogue statistics self.dialogue_episode = 0 self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 self.total_dialogue_turns = 0 self.minibatch_length = 250 self.train_interval = 50 self.train_epochs = 10 self.configuration = configuration self.recorder = DialogueEpisodeRecorder() self.SAVE_LOG = True self.SAVE_INTERVAL = 1000 self.MAX_TURNS = 15 self.INTERACTION_MODE = 'simulation' self.USE_GUI = False # This indicates which module controls the state so that we can query # it for dialogue termination (e.g. at end_dialogue) self.STATEFUL_MODULE = -1 self.reward_func = SlotFillingGoalAdvancementReward() self.ConversationalModules = [] self.prev_m_out = ConversationalFrame({}) self.goal_generator = None self.agent_goal = None self.agent_role = '' ag_id_str = 'AGENT_' + str(agent_id) if self.configuration: if 'GENERAL' not in self.configuration: raise ValueError('No GENERAL section in config!') if 'AGENT_' + str(agent_id) not in self.configuration: raise ValueError(f'NO AGENT_{agent_id} section in config!') if 'role' in self.configuration[ag_id_str]: self.agent_role = self.configuration[ag_id_str]['role'] # Retrieve agent parameters if 'max_turns' in self.configuration[ag_id_str]: self.MAX_TURNS = self.configuration[ag_id_str]['max_turns'] if 'train_interval' in self.configuration[ag_id_str]: self.train_interval = \ self.configuration[ag_id_str]['train_interval'] if 'train_minibatch' in self.configuration[ag_id_str]: self.minibatch_length = \ self.configuration[ag_id_str]['train_minibatch'] if 'train_epochs' in self.configuration[ag_id_str]: self.train_epochs = \ self.configuration[ag_id_str]['train_epochs'] if 'save_interval' in self.configuration[ag_id_str]: self.SAVE_INTERVAL = \ self.configuration[ag_id_str]['save_interval'] if 'interaction_mode' in self.configuration['GENERAL']: self.INTERACTION_MODE = \ self.configuration['GENERAL']['interaction_mode'] if 'use_gui' in self.configuration['GENERAL']: self.USE_GUI = self.configuration['GENERAL']['use_gui'] if 'experience_logs' in self.configuration['GENERAL']: dialogues_path = None if 'path' in self.configuration['GENERAL']['experience_logs']: dialogues_path = \ self.configuration['GENERAL'][ 'experience_logs']['path'] if 'load' in self.configuration['GENERAL']['experience_logs'] \ and bool(self.configuration['GENERAL'] ['experience_logs']['load'] ): if dialogues_path and os.path.isfile(dialogues_path): self.recorder.load(dialogues_path) else: raise FileNotFoundError( 'dialogue Log file %s not found (did you ' 'provide one?)' % dialogues_path) if 'save' in self.configuration['GENERAL']['experience_logs']: self.recorder.set_path(dialogues_path) self.SAVE_LOG = bool(self.configuration['GENERAL'] ['experience_logs']['save']) self.NModules = 0 if 'modules' in self.configuration[ag_id_str]: self.NModules = int(self.configuration[ag_id_str]['modules']) if 'stateful_module' in self.configuration[ag_id_str]: self.STATEFUL_MODULE = int( self.configuration[ag_id_str]['stateful_module']) # Note: Since we pass settings as a default argument, any # module can access the global args. However, we # add it here too for ease of use. self.global_arguments = {'settings': deepcopy(self.configuration)} if 'global_arguments' in self.configuration['GENERAL']: self.global_arguments.update( self.configuration['GENERAL']['global_arguments']) # Load the goal generator, if any if 'GOAL_GENERATOR' in self.configuration[ag_id_str]: if 'package' not in \ self.configuration[ag_id_str]['GOAL_GENERATOR']: raise ValueError(f'No package path provided for ' f'goal generator!') elif 'class' not in \ self.configuration[ag_id_str]['GOAL_GENERATOR']: raise ValueError(f'No class name provided for ' f'goal generator!') else: self.goal_generator = self.load_module( self.configuration[ag_id_str]['GOAL_GENERATOR'] ['package'], self.configuration[ag_id_str] ['GOAL_GENERATOR']['class'], self.global_arguments) # Load the modules for m in range(self.NModules): if 'MODULE_'+str(m) not in \ self.configuration[ag_id_str]: raise ValueError(f'No MODULE_{m} section in config!') if 'parallel_modules' in self.configuration[ag_id_str][ 'MODULE_' + str(m)]: n_parallel_modules = self.configuration[ag_id_str][ 'MODULE_' + str(m)]['parallel_modules'] parallel_modules = [] for pm in range(n_parallel_modules): if 'package' not in self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]: raise ValueError( f'No arguments provided for parallel module ' f'{pm} of module {m}!') package = self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]['package'] if 'class' not in self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]: raise ValueError( f'No arguments provided for parallel module ' f'{pm} of module {m}!') klass = self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]['class'] # Append global arguments # (add configuration by default) args = deepcopy(self.global_arguments) if 'arguments' in \ self.configuration[ ag_id_str ]['MODULE_' + str(m)][ 'PARALLEL_MODULE_' + str(pm)]: args.update(self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]['arguments']) parallel_modules.append( self.load_module(package, klass, args)) self.ConversationalModules.append(parallel_modules) else: if 'package' not in self.configuration[ag_id_str]['MODULE_' + str(m)]: raise ValueError(f'No arguments provided for module ' f'{m}!') package = self.configuration[ag_id_str]['MODULE_' + str(m)]['package'] if 'class' not in self.configuration[ag_id_str]['MODULE_' + str(m)]: raise ValueError(f'No arguments provided for module ' f'{m}!') klass = self.configuration[ag_id_str]['MODULE_' + str(m)]['class'] # Append global arguments (add configuration by default) args = deepcopy(self.global_arguments) if 'arguments' in \ self.configuration[ ag_id_str ]['MODULE_' + str(m)]: args.update(self.configuration[ 'AGENT_' + str(agent_id)]['MODULE_' + str(m)]['arguments']) self.ConversationalModules.append( self.load_module(package, klass, args)) else: raise AttributeError('ConversationalGenericAgent: ' 'No settings (config) provided!') # TODO: Parse config modules I/O and raise error if # any inconsistencies found # Initialize automatic speech recognizer, if necessary self.asr = None if self.INTERACTION_MODE == 'speech' and not self.USE_GUI: self.asr = speech_rec.Recognizer()
class ConversationalGenericAgent(ConversationalAgent): """ The ConversationalGenericAgent receives a list of modules in its configuration file, that are chained together serially - i.e. the input to the agent is passed to the first module, the first module's output is passed as input to the second module and so on. Modules are wrapped using ConversationalModules. The input and output passed between modules is wrapped into ConversationalFrames. """ def __init__(self, configuration, agent_id): """ Initialize the internal structures of this agent. :param configuration: a dictionary representing the configuration file :param agent_id: an integer, this agent's id """ self.agent_id = agent_id # Dialogue statistics self.dialogue_episode = 0 self.dialogue_turn = 0 self.num_successful_dialogues = 0 self.num_task_success = 0 self.cumulative_rewards = 0 self.total_dialogue_turns = 0 self.minibatch_length = 250 self.train_interval = 50 self.train_epochs = 10 self.configuration = configuration self.recorder = DialogueEpisodeRecorder() self.SAVE_LOG = True self.SAVE_INTERVAL = 1000 self.MAX_TURNS = 15 self.INTERACTION_MODE = 'simulation' self.USE_GUI = False # This indicates which module controls the state so that we can query # it for dialogue termination (e.g. at end_dialogue) self.STATEFUL_MODULE = -1 self.reward_func = SlotFillingGoalAdvancementReward() self.ConversationalModules = [] self.prev_m_out = ConversationalFrame({}) self.goal_generator = None self.agent_goal = None self.agent_role = '' ag_id_str = 'AGENT_' + str(agent_id) if self.configuration: if 'GENERAL' not in self.configuration: raise ValueError('No GENERAL section in config!') if 'AGENT_' + str(agent_id) not in self.configuration: raise ValueError(f'NO AGENT_{agent_id} section in config!') if 'role' in self.configuration[ag_id_str]: self.agent_role = self.configuration[ag_id_str]['role'] # Retrieve agent parameters if 'max_turns' in self.configuration[ag_id_str]: self.MAX_TURNS = self.configuration[ag_id_str]['max_turns'] if 'train_interval' in self.configuration[ag_id_str]: self.train_interval = \ self.configuration[ag_id_str]['train_interval'] if 'train_minibatch' in self.configuration[ag_id_str]: self.minibatch_length = \ self.configuration[ag_id_str]['train_minibatch'] if 'train_epochs' in self.configuration[ag_id_str]: self.train_epochs = \ self.configuration[ag_id_str]['train_epochs'] if 'save_interval' in self.configuration[ag_id_str]: self.SAVE_INTERVAL = \ self.configuration[ag_id_str]['save_interval'] if 'interaction_mode' in self.configuration['GENERAL']: self.INTERACTION_MODE = \ self.configuration['GENERAL']['interaction_mode'] if 'use_gui' in self.configuration['GENERAL']: self.USE_GUI = self.configuration['GENERAL']['use_gui'] if 'experience_logs' in self.configuration['GENERAL']: dialogues_path = None if 'path' in self.configuration['GENERAL']['experience_logs']: dialogues_path = \ self.configuration['GENERAL'][ 'experience_logs']['path'] if 'load' in self.configuration['GENERAL']['experience_logs'] \ and bool(self.configuration['GENERAL'] ['experience_logs']['load'] ): if dialogues_path and os.path.isfile(dialogues_path): self.recorder.load(dialogues_path) else: raise FileNotFoundError( 'dialogue Log file %s not found (did you ' 'provide one?)' % dialogues_path) if 'save' in self.configuration['GENERAL']['experience_logs']: self.recorder.set_path(dialogues_path) self.SAVE_LOG = bool(self.configuration['GENERAL'] ['experience_logs']['save']) self.NModules = 0 if 'modules' in self.configuration[ag_id_str]: self.NModules = int(self.configuration[ag_id_str]['modules']) if 'stateful_module' in self.configuration[ag_id_str]: self.STATEFUL_MODULE = int( self.configuration[ag_id_str]['stateful_module']) # Note: Since we pass settings as a default argument, any # module can access the global args. However, we # add it here too for ease of use. self.global_arguments = {'settings': deepcopy(self.configuration)} if 'global_arguments' in self.configuration['GENERAL']: self.global_arguments.update( self.configuration['GENERAL']['global_arguments']) # Load the goal generator, if any if 'GOAL_GENERATOR' in self.configuration[ag_id_str]: if 'package' not in \ self.configuration[ag_id_str]['GOAL_GENERATOR']: raise ValueError(f'No package path provided for ' f'goal generator!') elif 'class' not in \ self.configuration[ag_id_str]['GOAL_GENERATOR']: raise ValueError(f'No class name provided for ' f'goal generator!') else: self.goal_generator = self.load_module( self.configuration[ag_id_str]['GOAL_GENERATOR'] ['package'], self.configuration[ag_id_str] ['GOAL_GENERATOR']['class'], self.global_arguments) # Load the modules for m in range(self.NModules): if 'MODULE_'+str(m) not in \ self.configuration[ag_id_str]: raise ValueError(f'No MODULE_{m} section in config!') if 'parallel_modules' in self.configuration[ag_id_str][ 'MODULE_' + str(m)]: n_parallel_modules = self.configuration[ag_id_str][ 'MODULE_' + str(m)]['parallel_modules'] parallel_modules = [] for pm in range(n_parallel_modules): if 'package' not in self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]: raise ValueError( f'No arguments provided for parallel module ' f'{pm} of module {m}!') package = self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]['package'] if 'class' not in self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]: raise ValueError( f'No arguments provided for parallel module ' f'{pm} of module {m}!') klass = self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]['class'] # Append global arguments # (add configuration by default) args = deepcopy(self.global_arguments) if 'arguments' in \ self.configuration[ ag_id_str ]['MODULE_' + str(m)][ 'PARALLEL_MODULE_' + str(pm)]: args.update(self.configuration[ag_id_str][ 'MODULE_' + str(m)]['PARALLEL_MODULE_' + str(pm)]['arguments']) parallel_modules.append( self.load_module(package, klass, args)) self.ConversationalModules.append(parallel_modules) else: if 'package' not in self.configuration[ag_id_str]['MODULE_' + str(m)]: raise ValueError(f'No arguments provided for module ' f'{m}!') package = self.configuration[ag_id_str]['MODULE_' + str(m)]['package'] if 'class' not in self.configuration[ag_id_str]['MODULE_' + str(m)]: raise ValueError(f'No arguments provided for module ' f'{m}!') klass = self.configuration[ag_id_str]['MODULE_' + str(m)]['class'] # Append global arguments (add configuration by default) args = deepcopy(self.global_arguments) if 'arguments' in \ self.configuration[ ag_id_str ]['MODULE_' + str(m)]: args.update(self.configuration[ 'AGENT_' + str(agent_id)]['MODULE_' + str(m)]['arguments']) self.ConversationalModules.append( self.load_module(package, klass, args)) else: raise AttributeError('ConversationalGenericAgent: ' 'No settings (config) provided!') # TODO: Parse config modules I/O and raise error if # any inconsistencies found # Initialize automatic speech recognizer, if necessary self.asr = None if self.INTERACTION_MODE == 'speech' and not self.USE_GUI: self.asr = speech_rec.Recognizer() def __del__(self): """ Do some house-keeping, save the models. :return: nothing """ if self.recorder and self.SAVE_LOG: self.recorder.save() for m in self.ConversationalModules: if isinstance(m, list): for sm in m: sm.save() else: m.save() # Dynamically load classes @staticmethod def load_module(package_path, class_name, args): """ Dynamically load the specified class. :param package_path: Path to the package to load :param class_name: Name of the class within the package :param args: arguments to pass when creating the object :return: the instantiated class object """ module = __import__(package_path, fromlist=[class_name]) klass = getattr(module, class_name) return klass(args) def initialize(self): """ Initializes the conversational agent based on settings in the configuration file. :return: Nothing """ self.dialogue_episode = 0 self.dialogue_turn = 0 self.cumulative_rewards = 0 self.agent_goal = None # For each module for m in self.ConversationalModules: if isinstance(m, list): for sm in m: sm.initialize({}) else: # Load and initialize m.initialize({}) def start_dialogue(self, args=None): """ Reset or initialize internal structures at the beginning of the dialogue. May issue first utterance if this agent has the initiative. :param args: :return: """ self.initialize() self.dialogue_turn = 0 if args and 'goal' in args: self.agent_goal = deepcopy(args['goal']) elif self.goal_generator: self.agent_goal = self.goal_generator.generate() print(f'GOAL:\n=====\n{self.agent_goal}') # TODO: Get initial trigger from config if self.INTERACTION_MODE == 'dialogue_acts': self.prev_m_out = \ ConversationalFrame([DialogueAct('hello')]) else: self.prev_m_out = ConversationalFrame('hello') self.continue_dialogue(args) return { 'input_utterance': None, 'output_raw': self.prev_m_out.content, 'output_dacts': '', 'goal': self.agent_goal } def continue_dialogue(self, args=None): """ Perform one dialogue turn :param args: input to this agent :return: output of this agent """ utterance = None if self.INTERACTION_MODE == 'text' and not self.USE_GUI: utterance = input('USER > ') self.prev_m_out = ConversationalFrame(utterance) elif self.INTERACTION_MODE == 'speech' and not self.USE_GUI: # Listen for input from the microphone with speech_rec.Microphone() as source: print('(listening...)') audio = self.asr.listen(source, phrase_time_limit=3) try: # This uses the default key utterance = self.asr.recognize_google(audio) print("Google ASR: " + utterance) self.prev_m_out = ConversationalFrame(utterance) except speech_rec.UnknownValueError: print("Google ASR did not understand you") except speech_rec.RequestError as e: print("Google ASR request error: {0}".format(e)) elif args and 'input' in args: self.prev_m_out = ConversationalFrame(args['input']) for m in self.ConversationalModules: # If executing parallel sub-modules if isinstance(m, list): idx = 0 prev_m_out = deepcopy(self.prev_m_out) self.prev_m_out.content = {} for sm in m: # WARNING! Module compatibility cannot be guaranteed here! sm.generic_receive_input(prev_m_out) sm_out = sm.generic_generate_output(prev_m_out) if not isinstance(sm_out, ConversationalFrame): sm_out = ConversationalFrame(sm_out) self.prev_m_out.content['sm' + str(idx)] = sm_out.content idx += 1 else: # WARNING! Module compatibility cannot be guaranteed here! m.generic_receive_input(self.prev_m_out) self.prev_m_out = m.generic_generate_output(self.prev_m_out) # Make sure prev_m_out is a Conversational Frame if not isinstance(self.prev_m_out, ConversationalFrame): self.prev_m_out = ConversationalFrame(self.prev_m_out) # DEBUG: if isinstance(self.prev_m_out.content, str): print(f'(DEBUG) {self.agent_role}> ' f'{str(self.prev_m_out.content)}') self.dialogue_turn += 1 # In text or speech based interactions, return the input utterance as # it may be used for statistics or to show it to a GUI. return { 'input_utterance': utterance, 'output_raw': self.prev_m_out.content, 'output_dacts': '', 'goal': self.agent_goal } def end_dialogue(self): """ Perform final dialogue turn. Save models if applicable. :return: """ if self.dialogue_episode % self.train_interval == 0: for m in self.ConversationalModules: if isinstance(m, list): for sm in m: sm.train(self.recorder.dialogues) else: m.train(self.recorder.dialogues) if self.dialogue_episode % self.SAVE_INTERVAL == 0: for m in self.ConversationalModules: if isinstance(m, list): for sm in m: sm.save() else: m.save() # Keep track of dialogue statistics self.dialogue_episode += 1 if self.dialogue_turn > 0: self.total_dialogue_turns += self.dialogue_turn # Count successful dialogues _, _, obj_succ = self.reward_func.calculate( self.get_state(), [], # TODO: In case of single agents, we actually need the user's goal goal=self.agent_goal, agent_role=self.agent_role) self.num_successful_dialogues += 1 if obj_succ else 0 def terminated(self): """ Check if this agent is at a terminal state. :return: True or False """ return self.ConversationalModules[ self.STATEFUL_MODULE ].at_terminal_state() or \ self.dialogue_turn > self.MAX_TURNS def set_goal(self, goal): """ Set or update this agent's goal. :param goal: a Goal :return: nothing """ self.agent_goal = goal def get_goal(self): """ Get this agent's goal. :return: a Goal """ return self.agent_goal def get_state(self): return self.ConversationalModules[self.STATEFUL_MODULE].get_state()