docstr: df: has exactly two columns, `role` and `content`. Rows contain >= 1 chats in long (stacked) format.
  For more details, check `FineTuning -> Data Format` in the Anyscale API reference.
docstr_tok: ["df",":","has","exactly","two","columns",",","`","role","`","and","`","content","`",".","Rows","contain",">","=","1","chats","in","long","(","stacked",")","format",".","For","more","details",",","check","`","FineTuning","-",">","Data","Format","`","in","the","Anyscale","API","reference","."]
code: def _prepare_ft_jsonl(
      self, df, temp_storage_path, temp_filename, _, test_size=0.2
  ):
      """
      df: has exactly two columns, `role` and `content`. Rows contain >= 1 chats in long (stacked) format.
      For more details, check `FineTuning -> Data Format` in the Anyscale API reference.
      """
  
      def _is_valid(chat):
          """Check if chat is valid according to Anyscale criteria."""
          roles = [m["role"] for m in chat]
          transitions = {
              None: ["system", "user"],
              "system": ["user"],
              "user": ["assistant"],
              "assistant": ["user"],
          }
  
          # check base condition
          if not ("user" in roles and "assistant" in roles):
              return False
  
          # check order is valid
          state = None
          for role in roles:
              if role not in transitions[state]:
                  return False
              else:
                  state = role
  
          # chat is valid, return
          return True
  
      # 1. aggregate each chat sequence into one row
      chats = []
      chat = []
      for i, row in df.iterrows():
          if row["role"] == "system" and len(chat) > 0:
              if _is_valid(chat):
                  chats.append({"messages": chat})
              chat = []
          event = {"role": row["role"], "content": row["content"]}
          chat.append(event)
  
      if _is_valid(chat):
          chats.append({"messages": chat})
  
      series = pd.Series(chats)
      if len(series) < 20 * 2:
          raise Exception(
              "Dataset is too small to finetune. Please include at least 40 samples (complete chats)."
          )
      val_size = max(
          20, int(len(series) * test_size)
      )  # at least 20 samples required by Anyscale
      train = series.iloc[:-val_size]
      val = series.iloc[-val_size:]
  
      # 2. write as jsonl
      file_names = {
          "train": f"{temp_filename}_prepared_train.jsonl",
          "val": f"{temp_filename}_prepared_valid.jsonl",
      }
      train.to_json(
          os.path.join(temp_storage_path, file_names["train"]),
          orient="records",
          lines=True,
      )
      val.to_json(
          os.path.join(temp_storage_path, file_names["val"]),
          orient="records",
          lines=True,
      )
  
      # 3. validate
      self._validate_jsonl(
          os.path.join(temp_storage_path, file_names["train"])
      )
      self._validate_jsonl(
          os.path.join(temp_storage_path, file_names["val"])
      )
      return file_names
  
imports: import os
  import openai
  import json
  import contextlib
  from typing import Optional, Dict
  import pandas
  from mindsdb.integrations.handlers.openai_handler.openai_handler import OpenAIHandler
  from mindsdb.integrations.handlers.openai_handler.constants import OPENAI_API_BASE
  from mindsdb.integrations.utilities.handler_utils import get_api_key
  from mindsdb.utilities import log
label_desc: Use image node_id 9 for calling the AnyscaleEndpointsHandler obj's underlying member method code with example usage: obj._prepare_ft_jsonl(df, temp_storage_path, temp_filename, _, test_size) and returns: file_names, True, False, False