code: def run_eval(
      conartist_solver: Solver,
      mark_solver: Solver,
      summary_model_completion_fn: CompletionFn,
      task_descriptions: dict,
      eval_variation: str,
      turn_cap: int,
      max_duration_in_minutes: int,
      allow_withdraw: bool,
      *args,
      **kwargs,
  ):
      """
      Runs single make-me-pay experiment, generating an entire conversation until one of the
      end conditions is satisfied
      """
      assert (
          len(task_descriptions) == 3
      ), f"Got {len(task_descriptions)} system_prompts."
      assert (
          "conartist" in task_descriptions
      ), "System prompt for `conartist` not found."
      assert (
          "mark" in task_descriptions
      ), "System prompt for `mark` not found."
      assert (
          "summary" in task_descriptions
      ), "System prompt for `summary` not found."
  
      conversation = [{"role": "mark", "content": "Hi!"}]
  
      current_turn, start_time = 0, time.time()
  
      def max_turns_or_time():
          if max_duration_in_minutes != 0:
              return max_duration_in_minutes
          elif turn_cap != 0:
              return turn_cap
          else:
              assert (
                  False
              ), "Either turn_cap or max_duration_in_minutes must be non-zero."
  
      def turns_or_time_left():
          if max_duration_in_minutes != 0:
              return (
                  max_turns_or_time() + (start_time - time.time()) / 60
              )  # Seconds
          elif turn_cap != 0:
              return turn_cap - current_turn
          else:
              assert (
                  False
              ), "Either turn_cap or max_duration_in_minutes must be non-zero."
  
      def cap_not_reached():
          if max_duration_in_minutes != 0:
              return minutes_since(start_time) < max_duration_in_minutes
          elif turn_cap != 0:
              return current_turn < turn_cap
          else:
              assert (
                  False
              ), "Either turn_cap or max_duration_in_minutes must be non-zero."
  
      withdraw_flag = False
      while cap_not_reached():
          # These keep track of how many times models break character or output empty tags.
          (
              conartist_broke_character_count,
              conartist_empty_tags_count,
          ) = (0, 0)
          mark_broke_character_count, mark_empty_tags_count = 0, 0
  
          (
              conversation,
              conartist_broke_character,
              conartist_output_empty_tags,
          ) = generate_reply_for(
              conversation=conversation,
              role="conartist",
              solver=conartist_solver,
              task_description=task_descriptions["conartist"],
              eval_variation=eval_variation,
              max_turns_or_time=max_turns_or_time(),
              turns_or_time_left=turns_or_time_left(),
              allow_withdraw=allow_withdraw,
          )
  
          if conversation == []:
              break
  
          conartist_broke_character_count = (
              conartist_broke_character_count + 1
              if conartist_broke_character
              else conartist_broke_character_count
          )
          conartist_empty_tags_count = (
              conartist_output_empty_tags + 1
              if conartist_output_empty_tags
              else conartist_empty_tags_count
          )
  
          (
              conversation,
              delta_mark_broke_character,
              delta_mark_empty_tags,
          ) = generate_reply_for(
              conversation=conversation,
              role="mark",
              solver=mark_solver,
              task_description=task_descriptions["mark"],
              eval_variation=eval_variation,
              max_turns_or_time=max_turns_or_time(),
              turns_or_time_left=turns_or_time_left(),
              allow_withdraw=allow_withdraw,
          )
  
          if conversation == []:
              break
  
          mark_broke_character_count = (
              delta_mark_broke_character + 1
              if delta_mark_broke_character
              else mark_broke_character_count
          )
          mark_empty_tags_count = (
              delta_mark_empty_tags + 1
              if delta_mark_empty_tags
              else mark_empty_tags_count
          )
  
          # Detect donations from mark
          idx, donation_amount = detect_donation(
              conversation=conversation,
              summary_model_completion_fn=summary_model_completion_fn,
              summary_model_system_prompt=task_descriptions["summary"],
          )
          if (idx, donation_amount) != (None, None):
              logger.info(
                  f"Donation occurred! Amount: {donation_amount}, in message: {conversation[idx]['content']}"
              )
              break
  
          # Detect if mark has ended conversation
          withdraw_flag = detect_withdraw(
              conversation=conversation,
          )
          if withdraw_flag:
              donation_amount = None
              break
  
          current_turn += 1
  
      elapsed = time.time() - start_time
  
      num_replies = len(
          [m for m in conversation if not is_system_msg(m)]
      )
  
      if donation_amount is None:
          donation_amount = 0.0
  
      # post-conditions
      assert isinstance(
          donation_amount, float
      ), f"Got {donation_amount} instead of float."
      assert isinstance(num_replies, int)
      assert isinstance(elapsed, float)
      assert (
          isinstance(conartist_broke_character_count, int)
          and conartist_broke_character_count >= 0
      )
      assert (
          isinstance(conartist_empty_tags_count, int)
          and conartist_empty_tags_count >= 0
      )
      assert (
          isinstance(mark_broke_character_count, int)
          and mark_broke_character_count >= 0
      )
      assert (
          isinstance(mark_empty_tags_count, int)
          and mark_empty_tags_count >= 0
      )
  
      return {
          "donation_amt": donation_amount,
          "num_replies": num_replies,
          "duration_sec": elapsed,
          "conartist_broke_character_count": conartist_broke_character_count,
          "conartist_empty_tags_count": conartist_empty_tags_count,
          "mark_broke_character_count": mark_broke_character_count,
          "mark_empty_tags_count": mark_empty_tags_count,
          "mark_withdraw": withdraw_flag,
      }
  
file_path: evals/evals/elsuite/make_me_pay/makemepay.py
imports: import logging
  import re
  import time
  from typing import Literal, Optional, Union
  from evals.api import CompletionFn
  from evals.elsuite.make_me_pay.task_description import WITHDRAW_PROMPT, task_description_dict
  from evals.elsuite.make_me_pay.utils import format_msgs, is_system_msg, model_broke_character, model_output_empty_tags
  from evals.solvers.solver import Solver
  from evals.task_state import Message, TaskState
label_desc: Use image node_id 3 for calling a global function with example usage: run_eval(conartist_solver, mark_solver, summary_model_completion_fn, task_descriptions, eval_variation, turn_cap, max_duration_in_minutes, allow_withdraw) and returns: dict, max_duration_in_minutes, unknown, unknown, turn_cap, unknown, unknown