예제 #1
0
    def generate_output(self, args=None):
        """
        Generate output, used by the Generic Agent.

        :param args:
        :return:
        """
        if not args:
            print('WARNING! nlu.generate_output called without args!')
            return ConversationalFrame([])

        if isinstance(args, ConversationalFrame):
            args = args.content

        if 'args' in args:
            args = {'utterance': args['args']}

        if 'utterance' not in args:
            print('WARNING! nlu.generate_output called without utterance!')
            return ConversationalFrame([])

        dialogue_state = None
        if 'dialogue_state' in args:
            dialogue_state = args['dialogue_state']

        return self.process_input(args['utterance'], dialogue_state)
예제 #2
0
    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
        }
예제 #3
0
    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()
예제 #4
0
    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
        }