Пример #1
0
    def next_action(self, dialogue_state):
        """
        Generate a response given which conditions are met by the current
        dialogue state.

        :param dialogue_state:
        :return:
        """
        # Check for terminal state
        if dialogue_state.is_terminal_state:
            return [DialogueAct('bye', [DialogueActItem('', Operator.EQ, '')])]

        # Select intent
        intent = random.choice(self.intents)

        dact = DialogueAct(intent, [])

        # Select slot
        if intent in ['inform', 'request']:

            if intent == 'inform':
                # The Dialogue Manager will fill the slot's value
                slot = random.choice(self.ontology.ontology['requestable'])
            else:
                slot = \
                    random.choice(self.ontology.ontology['system_requestable'])

            dact.params = [DialogueActItem(slot, Operator.EQ, '')]

        return [dact]
Пример #2
0
    def process_input(self, utterance, dialogue_state=None):
        """
        Process the utterance and see if any intent pattern matches.

        :param utterance: a string, the utterance to be recognised
        :param dialogue_state: the current dialogue state, if available
        :return: a list of recognised dialogue acts
        """
        dacts = []
        dact = DialogueAct('UNK', [])

        # TODO: Remove this once nlg is updated
        utterance = utterance.replace('<PAD>', '')

        if not utterance:
            return [dact]

        last_sys_act = \
            dialogue_state.last_sys_acts[0] \
            if dialogue_state and dialogue_state.last_sys_acts else None

        utterance = utterance.rstrip().lower()
        utterance = utterance.translate(self.punctuation_remover)

        # Replace synonyms
        utterance = utterance.replace('location', 'area')
        utterance = utterance.replace('part of town', 'area')
        utterance = utterance.replace('center', 'centre')
        utterance = utterance.replace('cheaply', 'cheap')
        utterance = utterance.replace('moderately', 'moderate')
        utterance = utterance.replace('expensively', 'expensive')
        utterance = utterance.replace('address', 'addr')
        utterance = utterance.replace('telephone', 'phone')
        utterance = utterance.replace('postal code', 'postcode')
        utterance = utterance.replace('post code', 'postcode')
        utterance = utterance.replace('zip code', 'postcode')
        utterance = utterance.replace('price range', 'pricerange')

        # First check if the user doesn't care
        if last_sys_act and last_sys_act.intent in ['request', 'expl-conf']:
            for p in self.dontcare_pattern:
                # Look for exact matches here only (i.e. user just says
                # 'i don't care')
                if p == utterance:
                    dact.intent = 'inform'
                    dact.params.append(
                        DialogueActItem(
                            last_sys_act.params[0].slot,
                            Operator.EQ,
                            'dontcare'))

                    return [dact]

        # Look for slot keyword and corresponding value
        words = utterance.split(' ')

        for p in self.ack_pattern:
            if p == utterance:
                dact.intent = 'ack'
                break

        for p in self.deny_pattern:
            if p == utterance:
                dact.intent = 'deny'
                break

        for p in self.affirm_pattern:
            if p == utterance:
                dact.intent = 'affirm'
                break

        # Check for dialogue ending
        for p in self.bye_pattern:
            match = re.search(r'\b{0}\b'.format(p), utterance)
            if match:
                dact.intent = 'bye'
                break

        # Search for 'welcome' first because it may contain 'hello'
        if dact.intent == 'UNK':
            for p in self.welcome_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'welcomemsg'
                    break

        if dact.intent == 'UNK':
            for p in self.hi_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'hello'
                    break

        if dact.intent == 'UNK':
            for p in self.reqalts_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'reqalts'
                    break

        if dact.intent == 'UNK':
            for p in self.reqmore_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'reqmore'
                    break

        if dact.intent == 'UNK':
            for p in self.repeat_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'repeat'
                    break

        if dact.intent == 'UNK':
            for p in self.restart_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'restart'
                    break

        if dact.intent == 'UNK':
            for p in self.thankyou_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'thankyou'
                    break

        if dact.intent == 'UNK':
            for p in self.request_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'request'
                    break

        if dact.intent == 'UNK':
            for p in self.select_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'select'
                    break

        if dact.intent == 'UNK':
            for p in self.confirm_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'confirm'
                    break

        if dact.intent == 'UNK':
            for p in self.expl_conf_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'expl-conf'
                    break

        if dact.intent == 'UNK':
            for p in self.cant_help_pattern:
                match = re.search(r'\b{0}\b'.format(p), utterance)
                if match:
                    dact.intent = 'canthelp'
                    dact.params = []
                    return [dact]

        if dact.intent == 'UNK':
            dact.intent = 'inform'

            # Check if there is no information about the slot
            if 'no info' in utterance:
                # Search for a slot name in the utterance
                for slot in self.ontology.ontology['requestable']:
                    if slot in utterance:
                        dact.params.append(
                            DialogueActItem(slot, Operator.EQ, 'no info'))
                        return [dact]

                # Else try to grab slot name from the other agent's
                # previous act
                if last_sys_act and \
                        last_sys_act.intent in ['request', 'expl-conf']:
                    dact.params.append(
                        DialogueActItem(
                            last_sys_act.params[0].slot,
                            Operator.EQ,
                            'dontcare'))

                    return [dact]

                # Else do nothing, and see if anything matches below

        if dact.intent in ['inform', 'request']:
            for word in words:
                # Check for requests. Requests for informable slots are
                # captured below
                if word in self.requestable_only_slots:
                    if dact.intent == 'request':
                        dact.params.append(
                            DialogueActItem(word, Operator.EQ, ''))
                        break

                    elif word != 'name':
                        if 'is' not in utterance and 'its' not in utterance:
                            dact.intent = 'request'
                            dact.params.append(
                                DialogueActItem(word, Operator.EQ, ''))
                            break

                        # For any other kind of intent, we have no way of
                        # determining the slot's value, since such
                        # information is not in the ontology.

                # Check for informs (most intensive)
                if word in self.ontology.ontology['informable']:

                    # If a request intent has already been recognized,
                    # do not search for slot values
                    if dact.intent == 'request':
                        dact.params.append(
                            DialogueActItem(word, Operator.EQ, ''))
                        break

                    found = False

                    for p in self.ontology.ontology['informable'][word]:
                        match = re.search(r'\b{0}\b'.format(p), utterance)
                        if match:
                            if word == 'name':
                                dact.intent = 'offer'
                            else:
                                dact.intent = 'inform'

                            dact.params.append(
                                DialogueActItem(word, Operator.EQ, p))
                            found = True
                            break

                    if not found:
                        # Search for dontcare (e.g. I want any area)
                        for p in self.dontcare_pattern:
                            match = re.search(r'\b{0}\b'.format(p), utterance)
                            if match:
                                dact.intent = 'inform'
                                dact.params.append(
                                    DialogueActItem(
                                        word,
                                        Operator.EQ,
                                        'dontcare'))

                                return [dact]

                        dact.intent = 'request'
                        dact.params.append(
                            DialogueActItem(word, Operator.EQ, ''))

        # If nothing was recognised, do an even more brute-force search
        if dact.intent in ['UNK', 'inform'] and not dact.params:
            slot_vals = self.ontology.ontology['informable']
            if self.slot_values:
                slot_vals = self.slot_values

            for slot in slot_vals:
                for value in slot_vals[slot]:
                    if value and \
                            value.lower().translate(self.punctuation_remover) \
                            in utterance:
                        if slot == 'name':
                            dact.intent = 'offer'

                        di = DialogueActItem(slot, Operator.EQ, value)

                        if di not in dact.params:
                            dact.params.append(di)

        # Check if something has been missed (e.g. utterance is dont care and
        # there's no previous sys act)
        if dact.intent == 'inform':
            # Check to see that all slots have an identified value
            if dact.params:
                for dact_item in dact.params:
                    if not dact_item.slot or not dact_item.value:
                        dact.params.remove(dact_item)

                        if not dact.params:
                            dact.intent = 'UNK'
                            break

                    # Else, break up the inform into several acts
                    elif dact_item.slot == 'name':
                        dacts.append(DialogueAct('offer', [dact_item]))
                    else:
                        dacts.append(DialogueAct('inform', [dact_item]))
            else:
                # Try to see if the utterance refers to a slot in the
                # requestable ones (e.g. 'I prefer any phone')
                for slot in self.ontology.ontology['requestable']:
                    if slot in utterance:
                        # We can only handle 'dontcare' kind of values here,
                        # as we do not know values of req. slots.
                        for p in self.dontcare_pattern:
                            match = re.search(r'\b{0}\b'.format(p), utterance)
                            if match:
                                dact.params = \
                                    [DialogueActItem(
                                        slot,
                                        Operator.EQ,
                                        'dontcare')]

                dact.intent = 'UNK'

        else:
            dacts.append(dact)

        return dacts
    def next_action(self, state):
        """
        Consult the dialogue policy and produce the agent's response

        :param state: the current dialogue state
        :return: a list of actions, representing the agent's response
        """
        sys_acts = []

        state_enc = self.encode_state(state)

        if state not in self.policy:
            # TODO: Reactive dialogue policy. Fix this properly.
            state_enc = ''
            if state.user_acts:
                for sa in state.user_acts:
                    state_enc = sa.intent

                    # This is due to the DM rules and to be fair to the other
                    # policies
                    if sa.intent == 'offer':
                        state_enc += '_name'

                    elif sa.params:
                        state_enc += '_' + sa.params[0].slot

                    state_enc += ';'

                state_enc = state_enc[:-1]

        if state_enc in self.policy:
            sys_actions = list(self.policy[state_enc]['dacts'].keys())
            probs = [self.policy[state_enc]['dacts'][i] for i in sys_actions]

            sys_act_slots = \
                deepcopy(random.choices(
                    sys_actions, weights=probs)[0]).split(';')

            for sys_act_slot in sys_act_slots:
                if not sys_act_slot:
                    # Skip empty sys_act_slot strings (e.g. case where
                    # there is ; at the end: inform_food;inform_area;)
                    continue

                sys_act = DialogueAct('UNK')
                sys_act_slot_parts = sys_act_slot.split('_')

                sys_act.intent = sys_act_slot_parts[0]

                if len(sys_act_slot_parts) > 1:
                    sys_act.params = \
                        [DialogueActItem(
                            sys_act_slot_parts[1],
                            Operator.EQ, '')]

                if sys_act.intent == 'offer':
                    sys_act.params = []

                elif sys_act.intent == 'canthelp.exception':
                    sys_act.intent = 'canthelp'

                sys_acts.append(sys_act)

        else:
            print(f'Warning! {self.agent_role} Calculated dialogue policy: '
                  f'state not found, selecting random action.')
            sys_act = DialogueAct('UNK')

            if self.agent_role == 'system':
                sys_act.intent = \
                    random.choice(['welcomemsg', 'inform', 'request'])
            elif self.agent_role == 'user':
                sys_act.intent = random.choice(['hello', 'inform', 'request'])
            else:
                sys_act.intent = random.choice(['bye', 'inform', 'request'])

            sys_acts.append(sys_act)

        return sys_acts