code: def parley(self):
      act = None  # Adding this for linter errors.
      logging.verbose(
          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"""
      logging.verbose(
          f"{self.__class__.__name__}:{self.tag}: About to act with task turn idx: {self.task_turn_idx}"
      )
  
      if not self.chat_done:
          act = self.bot.act()
          human_turn = act.get("human_turn", False)
          # Bot decided it is human turn.
  
          if human_turn:
              act = self.agent.act(timeout=self.max_resp_time)
              self.chat_done = self.has_final_rating(act)
              Compatibility.backward_compatible_force_set(
                  act, "id", self.personas[0]["name"]
              )
  
              act = Message(
                  Compatibility.maybe_fix_act(act)
              ).json_safe_payload()
          utterance_data = {
              "agent_idx": 0 if human_turn else 1,
              # Get rid of annotations HTML if it's the bot response
              "text": act["text"].split("<br>")[0],
              "id": act.get("id", "NULL_ID"),
          }
          self.dialog.append(utterance_data)
  
          if human_turn:
              self.bot.observe(validate(act))
          else:
              act["needs_rating"] = True
              self.agent.observe(validate(act))
  
              # The new act replaces the old one
              act = self.agent.act(timeout=self.max_resp_time)
              act.force_set("text", "THIS IS A RATING ACTION")
              p = act["task_data"].get("problem_data_for_prior_message")
              if p is not None:
                  turn_idx = -1
                  # Attach the problem data to the last utterance (just generated by bot).
                  self.__add_problem_data_to_utterance(
                      p, turn_idx=turn_idx
                  )
  
              self.chat_done = self.has_final_rating(act)
  
          self.task_turn_idx += 1
  
      if self.chat_done:
          self.dialog[-1]["final_rating"] = act["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)
          logging.info(
              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 != ""
          ):
              logging.warning(
                  f"**NOTE** Acceptability violations detected: {acceptability_violations}"
              )
              # Grant the failed qualification
              self.agent.mephisto_agent.get_worker().grant_qualification(
                  self.block_qualification, 1
              )
  
imports: import json
  import os
  import numpy
  import random
  import time
  from parlai.core.message import Message
  from parlai.core.worlds import validate
  import parlai.utils.logging
  from parlai.crowdsourcing.tasks.model_chat.utils import Compatibility
  from parlai.crowdsourcing.tasks.model_chat.worlds import get_bot_worker, ModelChatWorld, ModelChatOnboardWorld
label_desc: Use image node_id 4 for calling the MultipartyChatWorld obj's underlying member method code with example usage: obj.parley() without return types