Example #1
0
    def _run_initial_turn(self) -> None:
        """
        Show the image to the human and bot, and show the bot's response to the human.
        """

        system_id = 'SYSTEM'
        system_agent_idx = None

        # Show the image to the human
        image_act_for_human = {
            'episode_done': False,
            'id': system_id,
            'text':
            f"""Welcome! You'll now have a conversation with your partner.

<-- FIRST, YOUR PARTNER WILL SAY SOMETHING ABOUT THIS IMAGE TO YOUR LEFT.

Be sure to talk about this image a little bit before discussing other things!
""",
            'task_data': {
                'image_src': self.image_src
            },
            'agent_idx': system_agent_idx,
        }
        self.agent.observe(validate(image_act_for_human))

        # Show the image to the bot
        image_act = {
            **self.image_act,
            'episode_done': False,
            'id': system_id,
            'agent_idx': system_agent_idx,
        }
        self.bot.observe(validate(image_act))
        del image_act['image']
        # Don't save the image features to disk

        # Have the bot respond
        bot_first_act_raw = self.bot.act()
        bot_first_act_raw = Message(
            Compatibility.maybe_fix_act(
                bot_first_act_raw)).json_safe_payload()
        bot_first_act_raw['id'] = self.bot.agent_id
        self.agent.observe(validate(bot_first_act_raw))
        bot_first_act = {
            'episode_done': False,
            'id': bot_first_act_raw['id'],
            'text': bot_first_act_raw['text'],
            'agent_idx': 1,
        }

        # Record lines of dialogue
        self.dialog.append(image_act)
        self.dialog.append(bot_first_act)
Example #2
0
    def _run_initial_turn(self) -> None:
        """
        Run the initial turn for both the human and the bot.

        Optionally show the bot its persona. If we are in BST conversation mode, show 2
        previous BST utterances to both the human and the bot; if we are in Meena-like
        conversation mode, show "Hi!" to the human and the bot and let the bot respond
        accordingly.
        """

        control_msg = {"episode_done": False}

        time.sleep(2)
        coordinator_first_msg = {
            'episode_done': False,
            'id': 'Coordinator',
            'text':
            'Please chitchat with another worker for 6 turns as if you were catching up since last time you two spoke.',
            'fake_start': True,
            'agent_idx': 2,
            'task_data': self.task_data,
        }
        self.agent.observe(validate(coordinator_first_msg))
        time.sleep(2)
        human_first_msg = {
            'episode_done': False,
            'id': self.agent.id,
            'text': self.context_for_bot_prompt,
            'fake_start': True,
            'agent_idx': 0,
            'task_data': self.task_data,
        }
        for k, v in control_msg.items():
            human_first_msg[k] = v

        # self.dialog.append(human_first_msg)
        # self.agent.observe(validate(human_first_msg))
        print(human_first_msg)
        self.bot.observe(validate(human_first_msg))

        first_bot_act = self.bot.act()
        first_bot_act = Compatibility.maybe_fix_act(first_bot_act)
        first_bot_act['id'] = 'THEY'
        self.agent.observe(validate(first_bot_act))

        bot_utterance_data = {
            'agent_idx': 1,
            'text': first_bot_act['text'],
            'id': 'THEY',
        }
        self.dialog.append(bot_utterance_data)
Example #3
0
    def _run_initial_turn(self) -> None:
        """
        Run the initial turn for both the human and the bot.

        Optionally show the bot its persona. If we are in BST conversation mode, show 2
        previous BST utterances to both the human and the bot; if we are in Meena-like
        conversation mode, show "Hi!" to the human and the bot and let the bot respond
        accordingly.
        """

        control_msg = {"episode_done": False}

        if self.opt['include_persona']:
            # The Bot agent
            # We add the personas and 1/3 of the time WoW topic as the
            # first utterance in the history.
            # Previously for BST task, we also had a big first utterance
            # that gave instructions. Removing that for this task.
            persona_strings = [s.strip() for s in self.personas[1]]
            persona_utterance = self._get_persona_utterance(
                persona_strings=persona_strings,
                context_dataset=self.context_info['context_dataset'],
                additional_context=self.context_info['additional_context'],
                is_bot=True,
            )
            message = control_msg.copy()
            message['text'] = persona_utterance
            # The bot seeing its persona does not count as a "turn"
            self.bot.observe(validate(message), increment_turn=False)

        if self.opt['conversation_start_mode'] == 'bst':
            print('[Displaying first utterances as per BST task.]')
            # Display the previous two utterances
            human_first_msg = {
                'episode_done': False,
                'id': self.agent.id,
                'text': self.context_info['person1_seed_utterance'],
                'fake_start': True,
                'agent_idx': 0,
            }
            for k, v in control_msg.items():
                human_first_msg[k] = v
            bot_first_msg = {
                'episode_done': False,
                'id': self.bot.id,
                'text': self.context_info['person2_seed_utterance'],
                'fake_start': True,
                'agent_idx': 1,
            }
            print(
                f'human_first_msg: {human_first_msg}, bot_first_msg: {bot_first_msg}'
            )

            self.dialog.append(human_first_msg)
            self.dialog.append(bot_first_msg)

            for observer in [self.agent, self.bot]:
                observer.observe(validate(human_first_msg))
                observer.observe(validate(bot_first_msg))

        elif self.opt['conversation_start_mode'] == 'hi':
            print('[Displaying "Hi!" only as per Meena task.]')
            human_first_msg = {
                'episode_done': False,
                'id': self.agent.id,
                'text': 'Hi!',
                'fake_start': True,
                'agent_idx': 0,
            }
            for k, v in control_msg.items():
                human_first_msg[k] = v

            self.dialog.append(human_first_msg)
            self.agent.observe(validate(human_first_msg))
            self.bot.observe(validate(human_first_msg))

            first_bot_act = self.bot.act()
            first_bot_act = Compatibility.maybe_fix_act(first_bot_act)

            self.agent.observe(validate(first_bot_act))

            bot_utterance_data = {
                'agent_idx': 1,
                'text': first_bot_act['text'],
                'id': first_bot_act['id'],
            }
            self.dialog.append(bot_utterance_data)

        else:
            raise ValueError(
                f"Conversation start mode {self.opt['conversation_start_mode']} "
                f"not recognized!")
Example #4
0
    def parley(self):
        print(
            f'{self.__class__.__name__}:{self.tag}: is at turn {self.task_turn_idx}, with {self.num_turns} pairs of turns needed...'
        )

        if self.task_turn_idx == 0:
            self._run_initial_turn()
            self.task_turn_idx += 1
            return
        """Otherwise, we proceed accordingly"""
        print(
            f'{self.__class__.__name__}:{self.tag}: About to act with task turn idx: {self.task_turn_idx}'
        )
        acts = [None, None]
        for idx, agent in enumerate([self.agent, self.bot]):
            if not self.chat_done:
                acts[idx] = agent.act(timeout=self.max_resp_time)
                acts[idx] = Compatibility.maybe_fix_act(acts[idx])
                if 'metrics' in acts[idx]:
                    del acts[idx]['metrics']
                    # Metrics can't be saved to JSON and are not needed here
                print(
                    f'Got act for agent idx {idx}, act was: {acts[idx]} and self.task_turn_idx: {self.task_turn_idx}.'
                )

            if acts[idx].get('task_data', {}).get('final_rating') is not None:

                self.chat_done = True
                # agent ends chat after exceeding minimum number of turns

                if self.task_turn_idx > self.num_turns:
                    # Human has just responded. Any problem data received now will be
                    # regarding the bot's prior utterance
                    p = acts[idx]['task_data'].get(
                        'problem_data_for_prior_message')
                    if p is not None:
                        turn_idx = -1
                        # Attach the problem data to the last utterance, since the human
                        # hasn't said anything since then
                        self.__add_problem_data_to_utterance(p,
                                                             turn_idx=turn_idx)

                # Save the final chat data
                time_string = time.strftime('%Y%m%d_%H%M%S')
                chat_data_folder = self.opt['chat_data_folder']
                os.makedirs(chat_data_folder, exist_ok=True)
                chat_data_path = os.path.join(
                    chat_data_folder,
                    f'{time_string}_{np.random.randint(0, 1000)}_{self.task_type}.json',
                )
                final_chat_data = self.get_final_chat_data()
                self.agent.mephisto_agent.state.messages.append(
                    {'final_chat_data': final_chat_data})
                # Append the chat data directly to the agent state's message list in
                # order to prevent the worker from seeing a new text response in the UI
                with open(chat_data_path, 'w+') as f_json:
                    data_str = json.dumps(final_chat_data)
                    f_json.write(data_str)
                print(f'{self.__class__.__name__}:{self.tag}: Data saved at '
                      f'{chat_data_path} for model: {self.bot.worker_id}.')

                # Soft-block the worker if there were acceptability violations
                acceptability_violations = final_chat_data[
                    'acceptability_violations'][0]
                if (acceptability_violations is not None
                        and acceptability_violations != ''):
                    print(
                        f'**NOTE** Acceptability violations detected: {acceptability_violations}'
                    )
                    # Grant the failed qualification
                    self.agent.mephisto_agent.get_worker().grant_qualification(
                        self.block_qualification, 1)

                return

            else:
                utterance_data = {
                    'agent_idx': idx,
                    # Get rid of annotations HTML if it's the bot response
                    'text': acts[idx]['text'].split('<br>')[0],
                    'id': acts[idx]['id'] if 'id' in acts[idx] else
                    'NULL_ID',  # Person1 or Polyencoder
                }
                self.dialog.append(utterance_data)
                if idx == 0:
                    # Human has just responded. Any problem data received now will be
                    # regarding the bot's prior utterance
                    p = acts[idx]['task_data'].get(
                        'problem_data_for_prior_message')
                    if p is not None:
                        turn_idx = -2
                        # Attach the problem data to the second-to-last utterance, since
                        # the last utterance is what the human just said
                        self.__add_problem_data_to_utterance(p,
                                                             turn_idx=turn_idx)

                self._postprocess_acts(acts=acts, agent_idx=idx)
                for other_agent in [self.agent, self.bot]:
                    if other_agent != agent:
                        other_agent.observe(validate(acts[idx]))

                print(
                    f'[agent {idx}] self.task_turn_idx: {self.task_turn_idx}, self.dialog is: {self.dialog}'
                )
                self.task_turn_idx += 1
Example #5
0
    def parley(self):
        """
        The main function that controls the logic of the task. Uses self.task_turn_idx
        to control the sequence of the conversation.

        Specifically, when self.task_turn_idx is even, we know that the bots just gave
        their potential responses, and that it is the human's turn to choose one of the
        responses and give a justification value.

        When self.task_turn_idx is odd, we know that the human just chose one of the
        bots' responses, and now needs to respond to that response.

        self.task_turn_idx is initially 0, and during _run_initial_turn() the UI is
        redrawn to have the human select between the bots' responses. Then,
        self.task_turn_idx is incremented to 1.

        During self.agent.observe(), the UI is redrawn for the following human input,
        and during self.agent.act(), the code awaits the human input.
        """

        logging.info(
            f'{self.__class__.__name__}:{self.tag}: is at task_turn_idx '
            f'{self.task_turn_idx}, with {self.num_turns} pairs of turns needed...'
        )

        if self.task_turn_idx == 0:
            self._run_initial_turn()
            self.task_turn_idx += 1
            return

        logging.info(
            f'{self.__class__.__name__}:{self.tag}: About to act with task turn idx: '
            f'{self.task_turn_idx}')

        # At this point, we know that the human now needs to respond to the bot's
        # response that the human just chose

        # We retrieve information regarding the human's choice and justification using
        # self.agent.act()

        human_choose_bot_response_act = self.agent.act(
            timeout=self.max_resp_time)
        human_choose_bot_response_act = Message(
            Compatibility.maybe_fix_act(
                human_choose_bot_response_act)).json_safe_payload()

        logging.info(
            f'Got act for human, act was: {human_choose_bot_response_act} and '
            f'self.task_turn_idx: {self.task_turn_idx}.')

        accepted_bot_response = human_choose_bot_response_act['task_data'][
            'accepted_bot_response']
        accepted_bot_id = human_choose_bot_response_act['task_data'][
            'accepted_bot_id']
        accepted_bot_justification_value = human_choose_bot_response_act[
            'task_data']['justification_value']

        not_accepted_bot_response = human_choose_bot_response_act['task_data'][
            'not_accepted_bot_response']
        not_accepted_bot_id = human_choose_bot_response_act['task_data'][
            'not_accepted_bot_id']

        # We have both bots observe the accepted bot's response so that the conversation
        # history stays the same

        self.bots[0].observe(accepted_bot_response)
        self.bots[1].observe(accepted_bot_response)

        task_data = {}

        accepted_bot_utterance_data = {
            'text': accepted_bot_response['text'].split('<br>')[0],
            'id': accepted_bot_id,
        }
        not_accepted_bot_utterance_data = {
            'text': not_accepted_bot_response['text'].split('<br>')[0],
            'id': not_accepted_bot_id,
        }
        bot_utterance_data = {
            'agent_idx': 1,
            'accepted_bot_data': accepted_bot_utterance_data,
            'not_accepted_bot_data': not_accepted_bot_utterance_data,
            'human_choice': accepted_bot_id,
            'human_justification': accepted_bot_justification_value,
        }
        self.dialog.append(bot_utterance_data)

        self._postprocess_acts(acts=None, agent_idx=0)

        # All logic and processing for this step has now been done, so we do
        # self.agent.observe() to send the accepted response back to the frontend to
        # display and update task turn index, as well as await for the next action,
        # which is the human typing their response

        task_data['task_turn_idx'] = self.task_turn_idx
        # The UI will ask the human to respond to the chosen bot response
        self.agent.observe({
            'text': accepted_bot_response['text'],
            'task_data': task_data
        })

        # Make self.task_turn_idx even now
        self.task_turn_idx += 1

        # Check for whether 6 pairs of turns has been done, since the last message of a
        # conversation should always be the bot's response

        if (human_choose_bot_response_act is not None
                and human_choose_bot_response_act.get(
                    'task_data', {}).get('finished') is not None):
            self.chat_done = True
            # agent ends chat after exceeding minimum number of turns

            # Bot has just responded. Any problem data received now will be
            # regarding this bot's utterance

            # Get the final chat data
            self.final_chat_data = self.get_final_chat_data()

            # Soft-block the worker if there were acceptability violations
            acceptability_violations = self.final_chat_data[
                'acceptability_violations'][0]
            if acceptability_violations is not None and acceptability_violations != '':
                logging.info(f'**NOTE** Acceptability violations detected: '
                             f'{acceptability_violations}')
                # Grant the failed qualification
                self.agent.mephisto_agent.get_worker().grant_qualification(
                    self.block_qualification, 1)

            return

        logging.info(
            f'[human agent] self.task_turn_idx: {self.task_turn_idx}, self.dialog is: '
            f'{self.dialog}')

        logging.info(
            f'Got act for human, act was: {human_choose_bot_response_act} and '
            f'self.task_turn_idx: {self.task_turn_idx}.')

        # At this point, we know that the human now needs to respond to the bot's
        # response that the human just chose

        # We retrieve information regarding the human's response using self.agent.act()

        human_response_act = self.agent.act(timeout=self.max_resp_time)

        # Again, we have both bots observe the human response so that the conversation
        # history stays the same
        self.bots[0].observe(validate(human_response_act))
        self.bots[1].observe(validate(human_response_act))

        # Check that the models' conversation histories are the same
        bot_1_history = self.bots[0].model_agent.history.history_strings
        bot_2_history = self.bots[1].model_agent.history.history_strings

        assert (
            bot_1_history == bot_2_history
        ), f"The two bots' conversation histories are different.\nBot 1 history: {bot_1_history}\nBot 2 history: {bot_2_history}"

        # After the bots have observed the human response, it's time for them to produce
        # their response to the human using self.bots.act()

        bot_1_response = self.bots[0].act()
        bot_1_response = Compatibility.maybe_fix_act(bot_1_response)

        bot_2_response = self.bots[1].act()
        bot_2_response = Compatibility.maybe_fix_act(bot_2_response)

        # We display the result to the frontend randomly so there is no selection bias.
        # Also, we attach our result to task_data to send arbitrary data to the frontend

        if random.random() > 0.5:
            task_data = {
                'top_bot_data': {
                    'top_bot_id': self.bots[0].worker_id,
                    'top_bot_response': bot_1_response,
                },
                'bottom_bot_data': {
                    'bottom_bot_id': self.bots[1].worker_id,
                    'bottom_bot_response': bot_2_response,
                },
                'task_turn_idx': self.task_turn_idx,
            }
        else:
            task_data = {
                'top_bot_data': {
                    'top_bot_id': self.bots[1].worker_id,
                    'top_bot_response': bot_2_response,
                },
                'bottom_bot_data': {
                    'bottom_bot_id': self.bots[0].worker_id,
                    'bottom_bot_response': bot_1_response,
                },
                'task_turn_idx': self.task_turn_idx,
            }

        human_utterance_data = {
            'agent_idx':
            0,
            # Get rid of annotations HTML if it's the bot response
            'text':
            human_response_act['text'].split('<br>')[0],
            'id':
            human_response_act['id'] if 'id' in human_response_act else
            'NULL_ID',  # Person1 or Polyencoder
        }

        self.dialog.append(human_utterance_data)

        # Human has just responded. Any problem data received now will be regarding the
        # bot's prior utterance
        p = human_response_act['task_data'].get(
            'problem_data_for_prior_message')
        if p is not None:
            turn_idx = -2
            # Attach the problem data to the second-to-last utterance, since the last
            # utterance is what the human just said
            self.__add_problem_data_to_utterance(p, turn_idx=turn_idx)

        self._postprocess_acts(acts=None, agent_idx=0)

        task_data['task_turn_idx'] = self.task_turn_idx

        # All logic and processing for this step has now been done, so we do
        # self.agent.observe() to send the two bots' responses back to the frontend to
        # display and update task turn index, as well as await for the next action,
        # which is the human choosing from the two responses and providing a
        # justification value

        # The UI will ask the human to choose between two bot responses and give a
        # justification
        logging.info(f'*** self.task_turn_idx: {self.task_turn_idx} ***')
        self.agent.observe({'text': '', 'task_data': task_data})

        # Make self.task_turn_idx odd now
        self.task_turn_idx += 1

        logging.info(
            f'[bot agent] self.task_turn_idx: {self.task_turn_idx}, self.dialog is: '
            f'{self.dialog}')
Example #6
0
    def _run_initial_turn(self) -> None:
        """
        Run the initial turn for both the human and the bot.

        Optionally show the bot its persona. If we are in Meena-like conversation mode
        show "Hi!" to the human and the bot and let the bot respond accordingly.

        Check parley() function for more information on the main logic.
        """
        control_msg = {"episode_done": False}

        if self.opt['include_persona']:
            # The Bot agent
            # We add the personas and 1/3 of the time WoW topic as the
            # first utterance in the history.
            # Previously for BST task, we also had a big first utterance
            # that gave instructions. Removing that for this task.
            persona_strings = [s.strip() for s in self.personas[1]]
            persona_utterance = self._get_persona_utterance(
                persona_strings=persona_strings,
                context_dataset=self.context_info['context_dataset'],
                additional_context=self.context_info['additional_context'],
                is_bot=True,
            )
            message = control_msg.copy()
            message['text'] = persona_utterance
            # The bot seeing its persona does not count as a "turn"
            self.bots[0].observe(validate(message), increment_turn=False)
            self.bots[1].observe(validate(message), increment_turn=False)

        if self.opt['conversation_start_mode'] == 'hi':
            logging.info('[Displaying "Hi!" only as per Meena task.]')
            if self.personas is not None:
                human_persona_strings = [s.strip() for s in self.personas[0]]
            else:
                human_persona_strings = ['', '']
            human_first_msg = {
                'episode_done': False,
                'id': self.agent.id,
                'text': 'Hi!',
                'fake_start': True,
                'agent_idx': 0,
                'task_data': {
                    'human_persona_string_1': human_persona_strings[0],
                    'human_persona_string_2': human_persona_strings[1],
                    'prompt_instruction': self.opt['task_question'],
                },
            }
            for k, v in control_msg.items():
                human_first_msg[k] = v

            # The first message is always "Hi", so we have both bots observe the message

            self.dialog.append(human_first_msg)
            self.agent.observe(validate(human_first_msg))
            self.bots[0].observe(validate(human_first_msg))
            self.bots[1].observe(validate(human_first_msg))

            bot_1_response = self.bots[0].act()
            bot_1_response = Compatibility.maybe_fix_act(bot_1_response)

            bot_2_response = self.bots[1].act()
            bot_2_response = Compatibility.maybe_fix_act(bot_2_response)

            if random.random() > 0.5:
                task_data = {
                    'top_bot_data': {
                        'top_bot_id': self.bots[0].worker_id,
                        'top_bot_response': bot_1_response,
                    },
                    'bottom_bot_data': {
                        'bottom_bot_id': self.bots[1].worker_id,
                        'bottom_bot_response': bot_2_response,
                    },
                    'task_turn_idx': self.task_turn_idx,
                }
            else:
                task_data = {
                    'top_bot_data': {
                        'top_bot_id': self.bots[1].worker_id,
                        'top_bot_response': bot_2_response,
                    },
                    'bottom_bot_data': {
                        'bottom_bot_id': self.bots[0].worker_id,
                        'bottom_bot_response': bot_1_response,
                    },
                    'task_turn_idx': self.task_turn_idx,
                }

            # Need an initial human's observe to observe the two choices from the bot
            self.agent.observe({'text': '', 'task_data': task_data})

        else:
            raise ValueError(
                f"Conversation start mode {self.opt['conversation_start_mode']} "
                f"not recognized!")
Example #7
0
    def parley(self):
        print(
            f'{self.__class__.__name__}:{self.tag}: is at turn {self.task_turn_idx}, with {self.num_turns} pairs of turns needed...'
        )

        if self.task_turn_idx == 0:
            self._run_initial_turn()
            self.task_turn_idx += 1
            return
        """Otherwise, we proceed accordingly"""
        print(
            f'{self.__class__.__name__}:{self.tag}: About to act with task turn idx: {self.task_turn_idx}'
        )
        acts = [None, None]
        for idx, agent in enumerate([self.agent, self.bot]):
            if not self.chat_done:
                acts[idx] = agent.act(timeout=self.max_resp_time)
                if (agent == self.bot and hasattr(self.bot, 'agent_id')
                        and self.bot.agent_id):
                    # Set speaker name as self.bot_agent_id otherwise, at frontend bot name such as "TransformerGenerator" would appear
                    Compatibility.backward_compatible_force_set(
                        acts[idx], 'id', self.bot.agent_id)
                acts[idx] = Message(Compatibility.maybe_fix_act(
                    acts[idx])).json_safe_payload()
                print(
                    f'Got act for agent idx {idx}, act was: {acts[idx]} and self.task_turn_idx: {self.task_turn_idx}.'
                )

            if acts[idx].get('task_data', {}).get('final_rating') is not None:

                self.chat_done = True
                # agent ends chat after exceeding minimum number of turns

                # Human has just responded. Any problem data received now will be
                # regarding the bot's prior utterance
                turn_idx = -1
                # Attach the problem data and final rating to the last utterance, since
                # the human hasn't said anything since then
                p = acts[idx]['task_data'].get(
                    'problem_data_for_prior_message')
                if p is not None:
                    self.__add_problem_data_to_utterance(p, turn_idx=turn_idx)
                self.dialog[turn_idx]['final_rating'] = acts[idx]['task_data'][
                    'final_rating']

                # Save the final chat data
                date_folder = time.strftime('%Y_%m_%d')
                time_string = time.strftime('%Y%m%d_%H%M%S')
                chat_data_subfolder = os.path.join(
                    self.opt['chat_data_folder'], date_folder)
                os.makedirs(chat_data_subfolder, exist_ok=True)
                chat_data_path = os.path.join(
                    chat_data_subfolder,
                    f'{time_string}_{np.random.randint(0, 1000)}_{self.task_type}.json',
                )
                self.final_chat_data = self.get_final_chat_data()
                self.agent.mephisto_agent.state.messages.append({
                    'final_chat_data':
                    self.final_chat_data,
                    'data': {},
                    'packet_type':
                    None,
                    'timestamp':
                    None,
                })
                # Append the chat data directly to the agent state's message list in
                # order to prevent the worker from seeing a new text response in the UI.
                # Add some dummy keys for compatibility with all agent state messages
                # TODO: remove this when no longer saving data to disk manually
                with open(chat_data_path, 'w+') as f_json:
                    data_str = json.dumps(self.final_chat_data)
                    f_json.write(data_str)
                print(f'{self.__class__.__name__}:{self.tag}: Data saved at '
                      f'{chat_data_path} for model: {self.bot.worker_id}.')

                # Soft-block the worker if there were acceptability violations
                acceptability_violations = self.final_chat_data[
                    'acceptability_violations'][0]
                if (acceptability_violations is not None
                        and acceptability_violations != ''):
                    print(
                        f'**NOTE** Acceptability violations detected: {acceptability_violations}'
                    )
                    # Grant the failed qualification
                    self.agent.mephisto_agent.get_worker().grant_qualification(
                        self.block_qualification, 1)

                return

            else:
                utterance_data = {
                    'agent_idx': idx,
                    # Get rid of annotations HTML if it's the bot response
                    'text': acts[idx]['text'].split('<br>')[0],
                    'id':
                    acts[idx].get('id',
                                  'NULL_ID'),  # In case model doesn't set id
                }
                self.dialog.append(utterance_data)
                if idx == 0:
                    # Human has just responded. Any problem data received now will be
                    # regarding the bot's prior utterance
                    p = acts[idx]['task_data'].get(
                        'problem_data_for_prior_message')
                    if p is not None:
                        turn_idx = -2
                        # Attach the problem data to the second-to-last utterance, since
                        # the last utterance is what the human just said
                        self.__add_problem_data_to_utterance(p,
                                                             turn_idx=turn_idx)

                self._postprocess_acts(acts=acts, agent_idx=idx)
                for other_agent in [self.agent, self.bot]:
                    if other_agent != agent:
                        other_agent.observe(validate(acts[idx]))

                print(
                    f'[agent {idx}] self.task_turn_idx: {self.task_turn_idx}, self.dialog is: {self.dialog}'
                )
                self.task_turn_idx += 1