示例#1
0
 def test_encoding_decoding_of_acts_for_user(self):
     intents = self.qp.dstc2_acts_usr
     for intent in intents:
         da = DialogueAct(intent=intent)
         da_as_int = self.qp.encode_action([da], system=False)
         da_decoded = self.qp.decode_action(da_as_int, system=False)[0]
         self.assertEqual(intent, da_decoded.intent)
示例#2
0
def build_inform(d_state, new_sys_acts, sys_act):
    slots = []
    if sys_act.params:
        # use the slots addressed by the inform act (slots selected by the policy)
        slots = [x.slot for x in sys_act.params]
    else:
        # use the requested slots, if any available
        if d_state.requested_slots:
            slots = [x for x in d_state.requested_slots]
    if not slots:
        # if we still have no slot(s) use one from the filled slots
        slots = [random.choice(list(d_state.slots_filled.keys()))]

    def get_value(slot):
        if not d_state.item_in_focus or (slot not in d_state.item_in_focus
                                         or not d_state.item_in_focus[slot]):
            value = "no info"
        else:
            value = d_state.item_in_focus[slot]
        return value

    act_items = [
        DialogueActItem(slot, Operator.EQ, get_value(slot)) for slot in slots
    ]
    new_sys_acts.append(DialogueAct("inform", act_items))
示例#3
0
 def decode_action(self, step: AgentStep) -> DialogueAct:
     intent, slots = self.action_enc.decode(*step.action)
     slots = self._filter_slots(intent, slots)
     return DialogueAct(
         intent,
         params=[DialogueActItem(slot, Operator.EQ, "") for slot in slots],
     )
示例#4
0
def create_random_dialog_act(domain:Domain,is_system=True):
    acts = []
    if is_system:
        inform_slots = domain.requestable_slots
        request_slots = domain.system_requestable_slots
    else:
        inform_slots = domain.system_requestable_slots
        request_slots = domain.requestable_slots

    intent_p = random.choice(domain.acts_params)
    if intent_p is not None:
        if intent_p == 'inform':
            slots = pick_some(inform_slots,1,1)
        elif intent_p == 'request':
            slots = pick_some(request_slots,1,1)
        elif intent_p == 'expl-conf':
            slots = pick_some(request_slots,1,1)
        else:
            assert False
        act = DialogueAct(intent_p,params=[DialogueActItem(slot,Operator.EQ,None) for slot in slots])
        acts.append(act)

    # if is_system:
    #     intens_w = pick_some(domain.dstc2_acts_sys,0,3)
    # else:
    #     intens_w = pick_some(domain.dstc2_acts_usr,0,3)
    # acts.extend([DialogueAct(i) for i in intens_w])
    return acts
示例#5
0
    def receive_input(self, system_acts):
        """
        Process input received and do some housekeeping.

        :param system_acts: list containing dialogue acts from the system
        :return: nothing
        """

        if self.prev_sys_acts and self.prev_sys_acts == system_acts:
            self.curr_patience -= 1
        else:
            self.curr_patience = self.patience
            self.prev_sys_acts = deepcopy(system_acts)

        self.input_system_acts = deepcopy(system_acts)

        # Check for goal satisfaction
        # Update user goal (in ABUS the state is factored into the goal and
        # the agenda)
        for system_act in system_acts:
            if system_act.intent == 'offer':
                self.offer_made = True

                # Reset past requests
                # TODO: Is this reasonable?
                self.goal.actual_requests = {}

                for item in self.goal.requests:
                    self.goal.requests[item].value = ''

        # Gather all inform or offer params into one dialogue act
        inform_dact = DialogueAct('inform', [])
        for system_act in system_acts:
            if system_act.intent in ['inform', 'offer']:
                inform_dact.params += deepcopy(system_act.params)

        # Check for constraint satisfaction
        if self.offer_made:
            # Check that the venue provided meets the constraints
            meets_constraints = all(
                [i.value == self.goal.constraints[i.slot].value
                 for i in inform_dact.params
                 if i.slot in self.goal.constraints])

            # If it meets the constraints, update the requests
            if meets_constraints:
                for item in inform_dact.params:
                    if item.slot in self.goal.actual_requests:
                        self.goal.actual_requests[item.slot].value = item.value

                        if item.slot in self.goal.requests:
                            self.goal.requests[item.slot].value = item.value

                # Use the true requests for asserting goal is met
                self.goal_met = True
                for slot in self.goal.requests:
                    if not self.goal.requests[slot].value:
                        self.goal_met = False
                        break
示例#6
0
 def test_encoding_decoding_of_acts_for_system(self):
     intents = self.qp.dstc2_acts_sys
     for intent in intents:
         da = DialogueAct(intent=intent)
         da_as_int = self.qp.encode_action([da], system=True)
         self.assertNotEqual(-1, da_as_int)
         da_decoded = self.qp.decode_action(da_as_int, system=True)[0]
         self.assertEqual(intent, da_decoded.intent)
示例#7
0
def build_offer_from_inform(d_state, new_sys_acts, slots):

    new_sys_acts.append(
        DialogueAct(
            "offer",
            [
                DialogueActItem("name", Operator.EQ,
                                d_state.item_in_focus["name"])
            ],
        ))

    new_sys_acts.append(
        DialogueAct(
            "inform",
            [
                DialogueActItem(slot, Operator.EQ, d_state.item_in_focus[slot])
                for slot in slots if slot is not "name"
            ],
        ))
示例#8
0
 def test_encoding_decoding_of_request_for_user(self):
     slots = self.qp.requestable_slots
     for s in slots:
         da = DialogueAct(intent='request',
                          params=[DialogueActItem(s, Operator.EQ, '')])
         da_as_int = self.qp.encode_action([da], system=False)
         self.assertNotEqual(-1, da_as_int)
         da_decoded = self.qp.decode_action(da_as_int, system=False)[0]
         self.assertEqual('request', da_decoded.intent)
         slot_decoded = da_decoded.params[0].slot
         self.assertEqual(s, slot_decoded, 'Error in slot en- or decoding.')
示例#9
0
    def respond(self):
        """
        Creates the response of the simulated user.

        :return: List of DialogueActs as a response
        """

        if self.curr_patience == 0:
            if self.nlg:
                return self.nlg.generate_output(
                    {'dacts': [DialogueAct('bye', [])], 'system': False})
            else:
                return [DialogueAct('bye', [])]

        # Sample the number of acts to pop.
        # pop_distributions defines the weighted probability for the number of items to be pushed
        # the index in the list reflects the number, the value at this position the weight:
        # e.g. pop_distribution = [3.0, 2.0, 1.0] means, that in mean, in 3 of 6 choices one item will be popped,
        # in 2 of 6 choices two items, and in 1 of 6 choices three items.

        acts = []

        # Use pop_distribution to determine the number of items (i.e. dialog acts) to be popped.
        # However, we can pop only as many items as on the agenda.
        pops = self._get_number_of_pop_items()

        for pop in range(pops):
            act = self.error_model.semantic_noise(self.agenda.pop())

            # Keep track of actual requests made. These are used in reward and
            # success calculation
            if act.intent == 'request' and act.params:
                self.goal.actual_requests[act.params[0].slot] = act.params[0]

            acts.append(act)

        if self.nlg:
            acts = self.nlg.generate_output({'dacts': acts, 'system': False})

        return acts
    def respond(self):
        """
        Creates the response of the simulated user.

        :return: List of DialogueActs as a response
        """

        if self.curr_patience == 0:
            if self.nlg:
                return self.nlg.generate_output({
                    'dacts': [DialogueAct('bye', [])],
                    'system':
                    False
                })
            else:
                return [DialogueAct('bye', [])]

        # Sample the number of acts to pop.
        acts = []
        pops = min(
            random.choices(range(1,
                                 len(self.pop_distribution) + 1),
                           weights=self.pop_distribution)[0],
            self.agenda.size())

        for pop in range(pops):
            act = self.error_model.semantic_noise(self.agenda.pop())

            # Keep track of actual requests made. These are used in reward and
            # success calculation
            if act.intent == 'request' and act.params:
                self.goal.actual_requests[act.params[0].slot] = act.params[0]

            acts.append(act)

        if self.nlg:
            acts = self.nlg.generate_output({'dacts': acts, 'system': False})

        return acts
示例#11
0
def build_offer(d_state, new_sys_acts, sys_act, sys_acts, sys_acts_copy):
    # TODO: What should happen if a policy selects offer, but there is no item in focus?
    if d_state.item_in_focus:
        # Remove the empty offer
        sys_acts_copy.remove(sys_act)

        new_sys_acts.append(
            DialogueAct(
                "offer",
                [
                    DialogueActItem("name", Operator.EQ,
                                    d_state.item_in_focus["name"])
                ],
            ))
        assert len(sys_acts) == 1
        # Only add these slots if no other acts were output
        # by the DM
        if len(sys_acts) == 1:
            for slot in d_state.slots_filled:
                if slot in d_state.item_in_focus:
                    if (slot not in ["id", "name"]
                            and slot not in d_state.requested_slots):
                        new_sys_acts.append(
                            DialogueAct(
                                "inform",
                                [
                                    DialogueActItem(
                                        slot, Operator.EQ,
                                        d_state.item_in_focus[slot])
                                ],
                            ))
                else:
                    new_sys_acts.append(
                        DialogueAct(
                            "inform",
                            [DialogueActItem(slot, Operator.EQ, "no info")]))
示例#12
0
    def consistency_check(self):
        """
        Perform some basic checks to ensure that items in the agenda are
        consistent - i.e. not duplicate, not
        contradicting with current goal, etc.

        :return: Nothing
        """

        # Remove all requests for slots that are filled in the goal
        if self.goal:
            for slot in self.goal.actual_requests:
                if self.goal.actual_requests[slot].value:
                    self.remove(
                        DialogueAct('request',
                                    [DialogueActItem(slot, Operator.EQ, '')]))
        else:
            print('Warning! Agenda consistency check called without goal. '
                  'Did you forget to initialize?')
示例#13
0
def build_explicit_confirm(d_state, new_sys_acts, sys_act, sys_acts_copy):
    slots = []
    if sys_act.params:
        # use the slots addressed by the expl-conf act (slots selected by the policy)
        slots = [x.slot for x in sys_act.params]
    if len(slots) == 0:
        # if no slot was selected by the policy, do randomly select one
        random_slot = random.choices(list(d_state.slots_filled.keys()))
        slots.extend(random_slot)

    def _build_expl_confirm_act_item(slot):
        value = d_state.slots_filled[slot] if d_state.slots_filled[
            slot] else "no info"
        item = DialogueActItem(slot, Operator.EQ, value)
        return item

    new_sys_acts.append(
        DialogueAct("expl-conf",
                    [_build_expl_confirm_act_item(slot) for slot in slots]))
    # Remove the empty expl-conf
    sys_acts_copy.remove(sys_act)
    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

        # TODO: Get initial trigger from config
        if self.INTERACTION_MODE in ['speech', 'text', 'simulation']:
            self.prev_m_out = ConversationalFrame({'utterance': 'hello'})
        else:
            self.prev_m_out = \
                ConversationalFrame([DialogueAct('hello')])

        self.continue_dialogue()

        return self.prev_m_out.content, '', self.agent_goal
示例#15
0
    def initialize(self, goal, us_has_initiative=False):
        """
        Initialize the Agenda at the beginning of each dialogue

        :param goal: the new goal for the current dialogue
        :param us_has_initiative: if the simulator has the initiative at the
                                  first turn
        :return: nothing
        """

        self.goal = goal

        self.clear()

        # Generate candidate actions
        dacts = []

        # If there are sub-goals
        # Iterate from last to first because the acts will be popped in
        # reverse order.
        for i in range(len(self.goal.subgoals) - 1, -1, -1):
            sg = self.goal.subgoals[i]

            # Acknowledge completion of subgoal
            dacts.append(DialogueAct('ack_subgoal', []))

            for constr in sg.constraints.values():
                dacts.append(DialogueAct('inform', [constr]))

        for req in goal.requests.values():
            dacts.append((DialogueAct('request', [req])))

        for constr in goal.constraints.values():
            dacts.append(DialogueAct('inform', [constr]))

        # Push actions into the agenda
        self.push(DialogueAct('bye', []))

        for da in dacts:
            self.push(da, force=True)

        if us_has_initiative:
            self.push(DialogueAct('hello', []))
    def receive_input_handcrafted(self, system_acts):
        """
        Handle the input according to probabilistic rules

        :param system_acts: a list with the system's dialogue acts
        :return: Nothing
        """

        # TODO: Revise these rules wrt other operators (i.e. not only EQ)

        if self.prev_system_acts and self.prev_system_acts == system_acts:
            self.curr_patience -= 1
        else:
            self.curr_patience = self.patience

        self.prev_system_acts = deepcopy(system_acts)

        for system_act in system_acts:
            # Update user goal (in ABUS the state is factored into the goal
            # and the agenda)
            if system_act.intent == 'bye' or self.dialogue_turn > 15:
                self.agenda.clear()
                self.agenda.push(DialogueAct('bye', []))

            elif system_act.intent in ['inform', 'offer']:
                # Check that the venue provided meets the constraints
                meets_constraints = True
                for item in system_act.params:
                    if item.slot in self.goal.constraints and \
                            self.goal.constraints[item.slot].value != \
                            'dontcare':
                        # Remove the inform from the agenda, assuming the
                        # value provided is correct. If it is not, the
                        # act will be pushed again and will be on top of the
                        # agenda (this way we avoid adding / removing
                        # twice.
                        dact = \
                            DialogueAct(
                                'inform',
                                [DialogueActItem(
                                    deepcopy(item.slot),
                                    deepcopy(
                                        self.goal.constraints[item.slot].op),
                                    deepcopy(
                                        self.goal.constraints[
                                            item.slot].value))])

                        # Remove and push to make sure the act is on top -
                        # if it already exists
                        self.agenda.remove(dact)

                        if item.value != \
                                self.goal.constraints[item.slot].value:
                            meets_constraints = False

                            # For each violated constraint add an inform
                            # TODO: Make this a deny-inform or change
                            # operator to NE

                            self.agenda.push(dact)

                # If it meets the constraints, update the requests
                if meets_constraints:
                    for item in system_act.params:
                        if item.slot in self.goal.actual_requests:
                            self.goal.actual_requests[item.slot].value = \
                                item.value

                            # Mark the value only if the slot has been
                            # requested and is in the requests
                            if item.slot in self.goal.requests:
                                self.goal.requests[item.slot].value = \
                                    item.value

                            # Remove any requests from the agenda that ask
                            # for that slot
                            # TODO: Revise this for all operators
                            self.agenda.remove(
                                DialogueAct('request', [
                                    DialogueActItem(item.slot, Operator.EQ, '')
                                ]))

                # When the system makes a new offer, replace all requests in
                # the agenda
                if system_act.intent == 'offer':
                    for r in self.goal.requests:
                        req = deepcopy(self.goal.requests[r])
                        req_dact = DialogueAct('request', [req])

                        # The agenda will replace the old act first
                        self.agenda.push(req_dact)

            # Push appropriate acts into the agenda
            elif system_act.intent == 'request':
                if system_act.params:
                    for item in system_act.params:
                        if item.slot in self.goal.constraints:
                            self.agenda.push(
                                DialogueAct('inform', [
                                    DialogueActItem(
                                        deepcopy(item.slot),
                                        deepcopy(self.goal.constraints[
                                            item.slot].op),
                                        deepcopy(self.goal.constraints[
                                            item.slot].value))
                                ]))
                        else:
                            self.agenda.push(
                                DialogueAct('inform', [
                                    DialogueActItem(deepcopy(item.slot),
                                                    Operator.EQ, 'dontcare')
                                ]))
    def receive_input_policy(self, system_acts):
        """
        Handle the input according to a policy

        :param system_acts: a list with the system's dialogue acts
        :return: Nothing
        """

        if self.prev_system_acts and self.prev_system_acts == system_acts:
            self.curr_patience -= 1
        else:
            self.curr_patience = self.patience

        self.prev_system_acts = deepcopy(system_acts)

        for system_act in system_acts:
            # 'bye' doesn't seem to appear in the CamRest data
            if system_act.intent == 'bye' or self.curr_patience == 0 or \
                    self.dialogue_turn > 15:
                self.agenda.push(DialogueAct('bye', []))
                return

            sys_act_slot = 'inform' if system_act.intent == 'offer' else \
                system_act.intent

            if system_act.params and system_act.params[0].slot:
                sys_act_slot += '_' + system_act.params[0].slot

            # Attempt to recover
            if sys_act_slot not in self.policy:
                if sys_act_slot == 'inform_name':
                    sys_act_slot = 'offer_name'

            if sys_act_slot not in self.policy:
                if system_act.intent == 'inform' and system_act.params and \
                        system_act.params[0].slot in self.goal.constraints:
                    user_act_slots = ['inform_' + system_act.params[0].slot]
                else:
                    print('Warning! ABUS policy does not know what to do for'
                          ' %s' % sys_act_slot)
                    return
            else:
                dacts = list(self.policy[sys_act_slot]['dacts'].keys())
                probs = [self.policy[sys_act_slot]['dacts'][i] for i in dacts]

                user_act_slots = random.choices(dacts, weights=probs)

            for user_act_slot in user_act_slots:
                intent, slot = user_act_slot.split('_')

                if slot == 'this' and system_act.params and \
                        system_act.params[0].slot:
                    slot = system_act.params[0].slot

                value = ''
                if intent == 'inform':
                    if slot in self.goal.constraints:
                        value = self.goal.constraints[slot].value
                    else:
                        value = 'dontcare'

                dact = \
                    DialogueAct(intent,
                                [DialogueActItem(slot, Operator.EQ, value)])

                self.agenda.remove(dact)
                self.agenda.push(dact)
    def next_action(self, state):
        """
        Consult the 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 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 DialoguePolicy: '
                  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
    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, '')])]

        # Check if the user has made any requests
        elif dialogue_state.requested_slot:
            if dialogue_state.item_in_focus and \
                    dialogue_state.system_made_offer:
                requested_slot = dialogue_state.requested_slot

                # Reset request as we attempt to address it
                dialogue_state.requested_slot = ''

                value = 'not available'
                if requested_slot in dialogue_state.item_in_focus and \
                        dialogue_state.item_in_focus[requested_slot]:
                    value = dialogue_state.item_in_focus[requested_slot]

                return \
                    [DialogueAct(
                        'inform',
                        [DialogueActItem(requested_slot, Operator.EQ, value)])]

            # Else, if no item is in focus or no offer has been made,
            # ignore the user's request

        # Try to fill slots
        requestable_slots = \
            deepcopy(self.ontology.ontology['system_requestable'])

        if not hasattr(dialogue_state, 'requestable_slot_entropies') or \
                not dialogue_state.requestable_slot_entropies:
            slot = random.choice(requestable_slots)

            while dialogue_state.slots_filled[slot] and \
                    len(requestable_slots) > 1:
                requestable_slots.remove(slot)
                slot = random.choice(requestable_slots)

        else:
            slot = ''
            slots = \
                [k for k, v in
                 dialogue_state.requestable_slot_entropies.items()
                 if v == max(
                    dialogue_state.requestable_slot_entropies.values())
                 and v > 0 and k in requestable_slots]

            if slots:
                slot = random.choice(slots)

                while dialogue_state.slots_filled[slot] \
                        and dialogue_state.requestable_slot_entropies[
                    slot] > 0 \
                        and len(requestable_slots) > 1:
                    requestable_slots.remove(slot)
                    slots = \
                        [k for k, v in
                         dialogue_state.requestable_slot_entropies.items()
                         if v == max(
                            dialogue_state.requestable_slot_entropies.values())
                         and k in requestable_slots]

                    if slots:
                        slot = random.choice(slots)
                    else:
                        break

        if slot and not dialogue_state.slots_filled[slot]:
            return [DialogueAct(
                'request',
                [DialogueActItem(slot, Operator.EQ, '')])]

        elif dialogue_state.item_in_focus:
            name = dialogue_state.item_in_focus['name'] \
                if 'name' in dialogue_state.item_in_focus \
                else 'unknown'

            dacts = [DialogueAct(
                'offer',
                [DialogueActItem('name', Operator.EQ, name)])]

            for slot in dialogue_state.slots_filled:
                if slot != 'requested' and dialogue_state.slots_filled[slot]:
                    if slot in dialogue_state.item_in_focus:
                        if slot not in ['id', 'name']:
                            dacts.append(
                                DialogueAct(
                                    'inform',
                                    [DialogueActItem(
                                        slot,
                                        Operator.EQ,
                                        dialogue_state.item_in_focus[slot])]))
                    else:
                        dacts.append(DialogueAct(
                            'inform',
                            [DialogueActItem(
                                slot,
                                Operator.EQ,
                                'no info')]))

            return dacts
        else:
            # Fallback action - cannot help!
            # Note: We can have this check (no item in focus) at the beginning,
            # but this would assume that the system
            # queried a database before coming in here.
            return [DialogueAct('canthelp', [])]
示例#20
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, '')])]

        # Check if the user denies and the system has sent an expl-conf in the previous turn
        elif dialogue_state.user_denied_last_sys_acts and \
                dialogue_state.last_sys_acts and \
                'expl-conf' in [x.intent for x in dialogue_state.last_sys_acts]:
            # If the user denies an explicit confirmation, request the slots to be confirmed again
            act: DialogueAct
            request_act = DialogueAct('request', [])
            for act in dialogue_state.last_sys_acts:
                # search the explicit confirmation act in the system acts from the previous turn
                item: DialogueActItem
                if act.intent == 'expl-conf' and act.params:
                    for item in act.params:
                        new_item = DialogueActItem(slot=item.slot,
                                                   op=Operator.EQ,
                                                   value=None)
                        request_act.params.append(new_item)

            return [request_act]

        # Check if the user has made any requests
        elif len(dialogue_state.requested_slots) > 0:
            if dialogue_state.item_in_focus and \
                    dialogue_state.system_made_offer:
                requested_slots = dialogue_state.requested_slots

                # Reset request as we attempt to address it
                dialogue_state.requested_slots = []

                items = []
                for rs in requested_slots:
                    value = 'not available'
                    if rs in dialogue_state.item_in_focus and \
                            dialogue_state.item_in_focus[rs]:
                        value = dialogue_state.item_in_focus[rs]

                    items.append(DialogueActItem(rs, Operator.EQ, value))

                return \
                    [DialogueAct(
                        'inform',
                        items)]

            # Else, if no item is in focus or no offer has been made,
            # ignore the user's request

        # Try to fill slots
        requestable_slots = \
            deepcopy(self.ontology.ontology['system_requestable'])

        if not hasattr(dialogue_state, 'requestable_slot_entropies') or \
                not dialogue_state.requestable_slot_entropies:
            slot = random.choice(requestable_slots)

            while dialogue_state.slots_filled[slot] and \
                    len(requestable_slots) > 1:
                requestable_slots.remove(slot)
                slot = random.choice(requestable_slots)

        else:
            slot = ''
            slots = \
                [k for k, v in
                 dialogue_state.requestable_slot_entropies.items()
                 if v == max(
                    dialogue_state.requestable_slot_entropies.values())
                 and v > 0 and k in requestable_slots]

            if slots:
                slot = random.choice(slots)

                while dialogue_state.slots_filled[slot] \
                        and dialogue_state.requestable_slot_entropies[
                    slot] > 0 \
                        and len(requestable_slots) > 1:
                    requestable_slots.remove(slot)
                    slots = \
                        [k for k, v in
                         dialogue_state.requestable_slot_entropies.items()
                         if v == max(
                            dialogue_state.requestable_slot_entropies.values())
                         and k in requestable_slots]

                    if slots:
                        slot = random.choice(slots)
                    else:
                        break

        if slot and not dialogue_state.slots_filled[slot]:
            return [
                DialogueAct('request',
                            [DialogueActItem(slot, Operator.EQ, '')])
            ]

        elif dialogue_state.item_in_focus:
            name = dialogue_state.item_in_focus['name'] \
                if 'name' in dialogue_state.item_in_focus \
                else 'unknown'

            dacts = [
                DialogueAct('offer',
                            [DialogueActItem('name', Operator.EQ, name)])
            ]

            for slot in dialogue_state.slots_filled:
                if slot != 'requested' and dialogue_state.slots_filled[slot]:
                    if slot in dialogue_state.item_in_focus:
                        if slot not in ['id', 'name']:
                            dacts.append(
                                DialogueAct('inform', [
                                    DialogueActItem(
                                        slot, Operator.EQ,
                                        dialogue_state.item_in_focus[slot])
                                ]))
                    else:
                        dacts.append(
                            DialogueAct('inform', [
                                DialogueActItem(slot, Operator.EQ, 'no info')
                            ]))

            return dacts

        # if not all filled slot confirmed, try confirmation
        elif not all([v for k, v in dialogue_state.slots_confirmed.items()]):
            # get unconfirmed slots
            unconfirmed_slots = [
                k for k, v in dialogue_state.slots_confirmed.items() if not v
            ]

            # match match unconfirmed slots with filled slots, as we ask only for confirmation of already filled slots
            confirmation_candidates = []
            for us in unconfirmed_slots:
                if dialogue_state.slots_filled[us]:
                    confirmation_candidates.append(us)

            first_unconfirmed_slot = confirmation_candidates[0]

            return [
                DialogueAct('expl-conf', [
                    DialogueActItem(
                        first_unconfirmed_slot, Operator.EQ,
                        dialogue_state.slots_filled[first_unconfirmed_slot])
                ])
            ]

        else:
            # Fallback action - cannot help!
            # Note: We can have this check (no item in focus) at the beginning,
            # but this would assume that the system
            # queried a database before coming in here.
            return [DialogueAct('canthelp', [])]
    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 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_dact = None
        if 'other_input_dact' in args:
            other_input_dact = args['other_input_dact']

        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_dact:
            # If no utterance provided, use the dacts
            other_input_nlu = other_input_dact

        # print(
        #     '{0} Recognised Input: {1}'.format(
        #         self.agent_role.upper(),
        #         '; '.join([str(ui) for ui in other_input_nlu])
        #     )
        # )

        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
            # print('{0}: terminating dialogue due to too many turns'.
            #       format(self.agent_role))
            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=other_input_raw,
                                 output_utterance=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 sys_utterance, response, 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 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
                )

        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)

        if self.dialogue_manager.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+1) % 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)
                        self.dialogue_manager.train(minibatch)

        self.dialogue_episode += 1
        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 % 10000 == 0:
            self.dialogue_manager.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 start_dialogue(self, goal=None):
        """
        Perform initial dialogue turn.

        :param goal: an optional Goal
        :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 goal:
            # No deep copy here so that all agents see the same goal.
            self.agent_goal = 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 response_utterance, response, self.agent_goal
    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' % usr_input[0])

        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(
                    'ConversationalAgent: No NLU defined for '
                    'text-based interaction!')

        # DEBUG print
        # print(
        #     '\nSYSTEM NLU > %s ' % '; '.join([str(ui) for ui in usr_input])
        # )

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

        # print('\nDEBUG> '+str(self.dialogue_manager.get_state()) + '\n')

        if self.dialogue_turn < self.MAX_TURNS:
            sys_response = self.dialogue_manager.generate_output()

        else:
            # Force dialogue stop
            # print(
            #     '{0}: terminating dialogue due to too '
            #     'many turns'.format(self.agent_role)
            # )
            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)

                print('USER NLU '
                      '> %s ' % '; '.join([str(ui) for ui in usim_input]))

            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=usr_utterance,
                                 output_utterance=sys_utterance)

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

        if self.agent_role == 'user':
            self.agent_goal = self.goal_generator.generate()
            self.dialogue_manager.restart({'goal': self.agent_goal})

        else:
            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()
示例#27
0
    def decode_action(self, action_enc, system=True):
        """
        Decode the action, given the role. Note that does not have to match
        the agent's role, as the agent may be decoding another agent's action
        (e.g. a system decoding the previous user act).

        :param action_enc: action encoding to be decoded
        :param system: whether the role whose action we are decoding is a
                       'system'
        :return: the decoded action
        """

        if system:
            if action_enc < len(self.dstc2_acts_sys):
                return [DialogueAct(self.dstc2_acts_sys[action_enc], [])]

            if action_enc < len(self.dstc2_acts_sys) + \
                    len(self.system_requestable_slots):
                return [
                    DialogueAct('request', [
                        DialogueActItem(
                            self.system_requestable_slots[
                                action_enc - len(self.dstc2_acts_sys)],
                            Operator.EQ, '')
                    ])
                ]

            if action_enc < len(self.dstc2_acts_sys) + \
                    len(self.system_requestable_slots) + \
                    len(self.requestable_slots):
                index = \
                    action_enc - len(self.dstc2_acts_sys) - \
                    len(self.system_requestable_slots)
                return [
                    DialogueAct('inform', [
                        DialogueActItem(self.requestable_slots[index],
                                        Operator.EQ, '')
                    ])
                ]

        else:
            if action_enc < len(self.dstc2_acts_usr):
                return [DialogueAct(self.dstc2_acts_usr[action_enc], [])]

            if action_enc < len(self.dstc2_acts_usr) + \
                    len(self.requestable_slots):
                return [
                    DialogueAct('request', [
                        DialogueActItem(
                            self.requestable_slots[action_enc -
                                                   len(self.dstc2_acts_usr)],
                            Operator.EQ, '')
                    ])
                ]

            if action_enc < len(self.dstc2_acts_usr) + \
                    2 * len(self.requestable_slots):
                return [
                    DialogueAct('inform', [
                        DialogueActItem(
                            self.requestable_slots[
                                action_enc - len(self.dstc2_acts_usr) -
                                len(self.requestable_slots)], Operator.EQ, '')
                    ])
                ]
    def process_input(self, utterance, dialogue_state=None):
        """
        Query the Ludwig model with the given utterance to obtain predictions
        for IOB tags and intent. Then parse the results and package them into
        a list of Dialogue Acts.

        :param utterance: a string, the utterance to be recognised
        :param dialogue_state: the current dialogue state, if available
        :return: a list of dialogue acts containing the recognised intents
        """
        # Pre-process utterance
        utterance = utterance.rstrip().lower()
        utterance = utterance.translate(self.punctuation_remover)

        # Warning: Make sure the same tokenizer that was used to train the
        # model is used during prediction
        result = \
            self.model.predict(
                pd.DataFrame(
                    data={'transcript': [utterance]}), return_type=dict)

        dacts = []

        # Only keep the first act for now
        last_sys_act = dialogue_state.last_sys_acts[0] \
            if dialogue_state and dialogue_state.last_sys_acts else None

        utterance_parts = utterance.split(' ')
        iob_tags = [tag for tag in result['iob']['predictions'][0]]

        for intent in result['intent']['predictions'][0]:
            intent_parts = intent.split('_')
            intent = intent_parts[0]

            if intent == 'request' and len(intent_parts) > 1:
                dacts.append(
                    DialogueAct(
                        intent,
                        [DialogueActItem(intent_parts[1], Operator.EQ, '')]))

            elif intent == 'dontcare' and len(intent_parts) > 1:
                if intent_parts[1] == 'this':
                    if dialogue_state and last_sys_act and last_sys_act.params:
                        dacts.append(
                            DialogueAct('inform', [
                                DialogueActItem(last_sys_act.params[0].slot,
                                                Operator.EQ, 'dontcare')
                            ]))

                else:
                    dacts.append(
                        DialogueAct('inform', [
                            DialogueActItem(intent_parts[1], Operator.EQ,
                                            'dontcare')
                        ]))

            # Exclude acts that take slot-value arguments
            # (will be handled below)
            elif intent not in ['inform', 'confirm', 'offer']:
                dacts.append(DialogueAct(intent, []))

        # If no act was recognised
        if not dacts:
            # Search for tags
            intent = ''
            slot = ''
            value = ''

            # iob_tags is a fixed length but the utterance may be shorter.
            for t in range(len(utterance_parts)):
                # If however we encounter input longer than the IOB tags,
                # we can't handle it.
                if t >= len(iob_tags):
                    print('Warning! CamRestNLU cannot handle such a long '
                          'sequence. Returning partial result.')
                    break

                if iob_tags[t][0] == 'B':
                    # Case where we have B-slot1 I-slot1 I-slot1 B-slot2 ...
                    if value:
                        # Correct offer / inform mis-prediction
                        if slot == 'name':
                            dacts.append(
                                DialogueAct('offer', [
                                    DialogueActItem(slot, Operator.EQ, value)
                                ]))
                        else:
                            dacts.append(
                                DialogueAct('inform', [
                                    DialogueActItem(slot, Operator.EQ, value)
                                ]))

                    else:
                        tag_parts = iob_tags[t].split('-')
                        intent = tag_parts[1]
                        slot = tag_parts[2]
                        value = utterance_parts[t]

                elif iob_tags[t][0] == 'I':
                    # Case where NLU doesn't work perfectly well and the first
                    # tag is I-... instead of B-...
                    if not value or not slot or not intent:
                        tag_parts = iob_tags[t].split('-')
                        intent = tag_parts[1]
                        slot = tag_parts[2]
                        value = utterance_parts[t]

                    value += ' ' + utterance_parts[t]

                elif iob_tags[t] == 'O' and value:
                    if slot == 'name':
                        dacts.append(
                            DialogueAct(
                                'offer',
                                [DialogueActItem(slot, Operator.EQ, value)]))
                    else:
                        dacts.append(
                            DialogueAct(
                                'inform',
                                [DialogueActItem(slot, Operator.EQ, value)]))

                    # Reset intent, slot, and value
                    intent = ''
                    slot = ''
                    value = ''

            # Special case for when a tag is at the end of the utterance
            if value and intent:
                # Save the recognised slot value
                dacts.append(
                    DialogueAct(intent,
                                [DialogueActItem(slot, Operator.EQ, value)]))

        # If still no act is recognised
        if not dacts:
            print('WARNING! CamRestNLU did not understand slots or values for '
                  'utterance: {0}\n'.format(utterance))
            # Print recognized IOB tags
            # print('IOB: {0}\n'.format(iob_tags))

        return dacts
示例#29
0
    def decode_action(self, action_enc, system=True):
        """
        Decode the action, given the role. Note that does not have to match
        the agent's role, as the agent may be decoding another agent's action
        (e.g. a system decoding the previous user act).

        :param action_enc: action encoding to be decoded
        :param system: whether the role whose action we are decoding is a
                       'system'
        :return: the decoded action
        """

        if system:
            if action_enc < len(self.dstc2_acts_sys):
                return [DialogueAct(self.dstc2_acts_sys[action_enc], [])]

            if action_enc < len(self.dstc2_acts_sys) + \
                    len(self.system_requestable_slots):
                return [
                    DialogueAct('request', [
                        DialogueActItem(
                            self.system_requestable_slots[
                                action_enc - len(self.dstc2_acts_sys)],
                            Operator.EQ, '')
                    ])
                ]

            if action_enc < len(self.dstc2_acts_sys) + \
                    len(self.system_requestable_slots) + \
                    len(self.requestable_slots):
                index = \
                    action_enc - len(self.dstc2_acts_sys) - \
                    len(self.system_requestable_slots)
                return [
                    DialogueAct('inform', [
                        DialogueActItem(self.requestable_slots[index],
                                        Operator.EQ, '')
                    ])
                ]

        else:
            if action_enc < len(self.dstc2_acts_usr):
                return [DialogueAct(self.dstc2_acts_usr[action_enc], [])]

            if action_enc < len(self.dstc2_acts_usr) + \
                    len(self.requestable_slots):
                return [
                    DialogueAct('request', [
                        DialogueActItem(
                            self.requestable_slots[action_enc -
                                                   len(self.dstc2_acts_usr)],
                            Operator.EQ, '')
                    ])
                ]

            if action_enc < len(self.dstc2_acts_usr) + \
                    len(self.requestable_slots) + \
                    len(self.system_requestable_slots):
                return [
                    DialogueAct('inform', [
                        DialogueActItem(
                            self.system_requestable_slots[
                                action_enc - len(self.dstc2_acts_usr) -
                                len(self.requestable_slots)], Operator.EQ, '')
                    ])
                ]

        # Default fall-back action
        print('MinimaxQ DialoguePolicy ({0}) policy action decoder warning: '
              'Selecting repeat() action '
              '(index: {1})!'.format(self.agent_role, action_enc))
        return [DialogueAct('repeat', [])]
示例#30
0
    def generate_output(self, args=None):
        """
        Consult the current policy to generate a response.

        :return: List of DialogueAct representing the system's output.
        """

        d_state = self.DSTracker.get_state()

        sys_acts = self.policy.next_action(d_state)
        # Copy the sys_acts to be able to iterate over all sys_acts while also
        # replacing some acts
        sys_acts_copy = deepcopy(sys_acts)
        new_sys_acts = []

        # Safeguards to support policies that make decisions on intents only
        # (i.e. do not output slots or values)
        for sys_act in sys_acts:
            if sys_act.intent == 'canthelp' and not sys_act.params:
                slots = \
                    [
                        s for s in d_state.slots_filled if
                        d_state.slots_filled[s]
                    ]
                if slots:
                    slot = random.choice(slots)

                    # Remove the empty canthelp
                    sys_acts_copy.remove(sys_act)

                    new_sys_acts.append(
                        DialogueAct('canthelp', [
                            DialogueActItem(slot, Operator.EQ,
                                            d_state.slots_filled[slot])
                        ]))

                else:
                    print('DialogueManager Warning! No slot provided by '
                          'policy for canthelp and cannot find a reasonable '
                          'one!')

            if sys_act.intent == 'offer' and not sys_act.params:
                # Remove the empty offer
                sys_acts_copy.remove(sys_act)

                if d_state.item_in_focus:
                    new_sys_acts.append(
                        DialogueAct('offer', [
                            DialogueActItem('name', Operator.EQ,
                                            d_state.item_in_focus['name'])
                        ]))

                    # Only add these slots if no other acts were output
                    # by the DM
                    if len(sys_acts) == 1:
                        for slot in d_state.slots_filled:
                            if slot in d_state.item_in_focus:
                                if slot not in ['id', 'name'] and \
                                        slot != d_state.requested_slot:
                                    new_sys_acts.append(
                                        DialogueAct('inform', [
                                            DialogueActItem(
                                                slot, Operator.EQ,
                                                d_state.item_in_focus[slot])
                                        ]))
                            else:
                                new_sys_acts.append(
                                    DialogueAct('inform', [
                                        DialogueActItem(
                                            slot, Operator.EQ, 'no info')
                                    ]))

            elif sys_act.intent == 'inform':
                if self.agent_role == 'system':
                    if sys_act.params and sys_act.params[0].value:
                        continue

                    if sys_act.params:
                        slot = sys_act.params[0].slot
                    else:
                        slot = d_state.requested_slot

                    if not slot:
                        slot = random.choice(list(d_state.slots_filled.keys()))

                    if d_state.item_in_focus:
                        if slot not in d_state.item_in_focus or \
                                not d_state.item_in_focus[slot]:
                            new_sys_acts.append(
                                DialogueAct('inform', [
                                    DialogueActItem(slot, Operator.EQ,
                                                    'no info')
                                ]))
                        else:
                            if slot == 'name':
                                new_sys_acts.append(
                                    DialogueAct('offer', [
                                        DialogueActItem(
                                            slot, Operator.EQ,
                                            d_state.item_in_focus[slot])
                                    ]))
                            else:
                                new_sys_acts.append(
                                    DialogueAct('inform', [
                                        DialogueActItem(
                                            slot, Operator.EQ,
                                            d_state.item_in_focus[slot])
                                    ]))

                    else:
                        new_sys_acts.append(
                            DialogueAct('inform', [
                                DialogueActItem(slot, Operator.EQ, 'no info')
                            ]))

                elif self.agent_role == 'user':
                    if sys_act.params:
                        slot = sys_act.params[0].slot

                        # Do nothing if the slot is already filled
                        if sys_act.params[0].value:
                            continue

                    elif d_state.last_sys_acts and d_state.user_acts and \
                            d_state.user_acts[0].intent == 'request':
                        slot = d_state.user_acts[0].params[0].slot

                    else:
                        slot = \
                            random.choice(
                                list(d_state.user_goal.constraints.keys()))

                    # Populate the inform with a slot from the user goal
                    if d_state.user_goal:
                        # Look for the slot in the user goal
                        if slot in d_state.user_goal.constraints:
                            value = d_state.user_goal.constraints[slot].value
                        else:
                            value = 'dontcare'

                        new_sys_acts.append(
                            DialogueAct(
                                'inform',
                                [DialogueActItem(slot, Operator.EQ, value)]))

                # Remove the empty inform
                sys_acts_copy.remove(sys_act)

            elif sys_act.intent == 'request':
                # If the policy did not select a slot
                if not sys_act.params:
                    found = False

                    if self.agent_role == 'system':
                        # Select unfilled slot
                        for slot in d_state.slots_filled:
                            if not d_state.slots_filled[slot]:
                                found = True
                                new_sys_acts.append(
                                    DialogueAct('request', [
                                        DialogueActItem(slot, Operator.EQ, '')
                                    ]))
                                break

                    elif self.agent_role == 'user':
                        # Select request from goal
                        if d_state.user_goal:
                            for req in d_state.user_goal.requests:
                                if not d_state.user_goal.requests[req].value:
                                    found = True
                                    new_sys_acts.append(
                                        DialogueAct('request', [
                                            DialogueActItem(
                                                req, Operator.EQ, '')
                                        ]))
                                    break

                    if not found:
                        # All slots are filled
                        new_sys_acts.append(
                            DialogueAct('request', [
                                DialogueActItem(
                                    random.choice(
                                        list(
                                            d_state.slots_filled.keys())[:-1]),
                                    Operator.EQ, '')
                            ]))

                    # Remove the empty request
                    sys_acts_copy.remove(sys_act)

        # Append unique new sys acts
        for sa in new_sys_acts:
            if sa not in sys_acts_copy:
                sys_acts_copy.append(sa)

        self.DSTracker.update_state_sysact(sys_acts_copy)

        return sys_acts_copy