docstr: 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.
docstr_tok: ["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","."]
code: 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}"
      )
  
imports: import random
  import parlai.utils.logging
  from typing import Any, Dict, Optional
  from parlai.core.agents import create_agent_from_shared
  from parlai.core.message import Message
  from parlai.core.worlds import validate
  from parlai.crowdsourcing.tasks.model_chat.constants import ONBOARD_SUCCESS
  from parlai.crowdsourcing.tasks.model_chat.utils import Compatibility
  from parlai.crowdsourcing.tasks.model_chat.worlds import BaseModelChatWorld, ModelChatWorld, ModelChatOnboardWorld
  from parlai.crowdsourcing.utils.acceptability import AcceptabilityChecker
  from parlai.crowdsourcing.utils.mturk import get_mturk_id_from_mephisto_wrapper
  from parlai.crowdsourcing.tasks.pairwise_per_turn_eval.bot_agent import PerTurnEvalTurkLikeAgent
label_desc: Use image node_id 3 for calling the PerTurnEvalWorld obj's underlying member method code with example usage: obj.parley() without return types