You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

process_logs.py 8.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """
  2. Credits: Hussein Mozannar
  3. """
  4. import os
  5. import re
  6. import json
  7. import glob
  8. import logging
  9. import pandas as pd
  10. logging.basicConfig(level=logging.INFO)
  11. def process_logs(logs_path, single_benchmark=False):
  12. """
  13. logs_path: str, path to the logs directory, containing subdirectories for each benchmark subset
  14. returns: pandas DataFrame with all the logs processed
  15. """
  16. # check if logs_path exists
  17. if not os.path.exists(logs_path):
  18. raise FileNotFoundError(
  19. f"Path {logs_path} does not exist, need to download logs, extract them into one common folder"
  20. )
  21. if single_benchmark:
  22. # subset should be a list with single folder which is the last part of the path
  23. subsets = [logs_path.split("/")[-1]]
  24. logs_path = "/".join(logs_path.split("/")[:-1])
  25. else:
  26. subsets = os.listdir(logs_path)
  27. results = []
  28. for subset in subsets:
  29. # check if folder is not empty
  30. if not os.listdir(os.path.join(logs_path, subset)) or subset == ".DS_Store" or subset == "__MACOSX":
  31. continue
  32. benchmark_name = subset.split("_")[0]
  33. instances = [
  34. f
  35. for f in os.listdir(os.path.join(logs_path, subset))
  36. if os.path.isdir(os.path.join(logs_path, subset, f))
  37. and os.path.exists(os.path.join(logs_path, subset, f, "0"))
  38. ]
  39. logging.info(f"Processing {subset} with {len(instances)} instances")
  40. for instance in instances:
  41. instance_dir_path = os.path.join(logs_path, subset, instance, "0")
  42. try:
  43. correct, expected_answer, final_answer = scorer(instance_dir_path, benchmark_name)
  44. except Exception as e:
  45. logging.error(f"Error processing {instance_dir_path}: {e}")
  46. continue
  47. messages = get_message_logs(instance_dir_path)
  48. results.append(
  49. {
  50. "benchmark": benchmark_name,
  51. "subset_benchmark": subset,
  52. "instance": instance,
  53. "task_information": get_task_information(instance_dir_path, benchmark_name),
  54. "expected_answer": expected_answer,
  55. "final_answer": final_answer,
  56. "correct": correct,
  57. "stalled": did_agent_stall(instance_dir_path),
  58. "num_messages": len(messages),
  59. "messages": messages,
  60. "progress_not_being_made": is_progress_not_being_made(instance_dir_path),
  61. }
  62. )
  63. df_logs = pd.DataFrame(results)
  64. return df_logs
  65. def normalize_answer(a):
  66. """
  67. Taken from custom_tabulate.py in the WebArena benchmark, given an answer, returns the normalized answer.
  68. Operations: lower case, trim, standardize comma separated values, replace multiple spaces with one space, remove trailing punctuation
  69. a: str, answer
  70. returns: str, normalized answer
  71. """
  72. norm_answer = ", ".join(a.strip().lower().split(","))
  73. norm_answer = re.sub(r"[\.\!\?]+$", "", re.sub(r"\s+", " ", norm_answer))
  74. return norm_answer
  75. def scorer(instance_dir, benchmark_name):
  76. """
  77. Returns results based on the benchmark name and the instance directory.
  78. benchmark_name: str, the name of the benchmark, either "gaia" or "webarena"
  79. instance_dir: str, path to the instance directory
  80. returns: tuple, (bool, str, str) or None, depending on the benchmark
  81. """
  82. if benchmark_name == "gaia" or benchmark_name == "assistant":
  83. # Read the expected answer
  84. expected_answer_file = os.path.join(instance_dir, "expected_answer.txt")
  85. if not os.path.isfile(expected_answer_file):
  86. return None
  87. with open(expected_answer_file, "rt") as fh:
  88. expected_answer = fh.read().strip()
  89. # Read the console log
  90. console_log_file = os.path.join(instance_dir, "console_log.txt")
  91. if not os.path.isfile(console_log_file):
  92. return None
  93. with open(console_log_file, "rt") as fh:
  94. console_log = fh.read()
  95. final_answer = None
  96. m = re.search(r"FINAL ANSWER:(.*?)\n", console_log, re.DOTALL)
  97. if m:
  98. final_answer = m.group(1).strip()
  99. if final_answer is None:
  100. return None
  101. not_normalized_final = final_answer
  102. n_ex = normalize_answer(expected_answer)
  103. n_final = normalize_answer(final_answer)
  104. return (n_ex != "" and n_ex == n_final), n_ex, not_normalized_final
  105. elif benchmark_name == "webarena":
  106. # Read the console log
  107. console_log_file = os.path.join(instance_dir, "console_log.txt")
  108. if not os.path.isfile(console_log_file):
  109. return None
  110. with open(console_log_file, "rt") as fh:
  111. console_log = fh.read()
  112. final_score = None
  113. m = re.search(r"FINAL SCORE:(.*?)\n", console_log, re.DOTALL)
  114. if m:
  115. final_score = m.group(1).strip()
  116. if final_score is None:
  117. return None
  118. else:
  119. return float(final_score) > 0, "", ""
  120. else:
  121. raise ValueError(f"Unsupported benchmark_name: {benchmark_name}")
  122. def get_number_of_chat_messages(chat_messages_dir):
  123. # Count the number of chat messages in the chat_messages_dir
  124. result = 0
  125. for file in glob.glob(f"{chat_messages_dir}/*_messages.json"):
  126. with open(file, "r") as f:
  127. content = json.load(f)
  128. for agent, messages in content.items():
  129. result += len(messages)
  130. return result
  131. def did_agent_stall(instance_dir):
  132. # Check if the agent stalled
  133. log_file_path = os.path.join(instance_dir, "log.jsonl")
  134. if not os.path.isfile(log_file_path):
  135. return None
  136. # Stalled.... Replanning...
  137. with open(log_file_path, "r") as f:
  138. for line in f:
  139. if "Stalled.... Replanning..." in line:
  140. return True
  141. return False
  142. def get_message_logs(instance_dir):
  143. # Read the log file and return the messages
  144. log_file_path = os.path.join(instance_dir, "log.jsonl")
  145. if not os.path.isfile(log_file_path):
  146. return None
  147. messages = []
  148. # for each line, convert to dict, check if it has a message and source key, and append to messages
  149. with open(log_file_path, "r") as f:
  150. for line in f:
  151. line_dict = json.loads(line)
  152. if "message" in line_dict and "source" in line_dict:
  153. messages.append(line_dict)
  154. return messages
  155. def get_task_information(instance_dir, benchmark_name):
  156. # Read the task information from the log file
  157. if benchmark_name == "gaia" or benchmark_name == "assistant":
  158. prompt_file = os.path.join(instance_dir, "prompt.txt")
  159. if not os.path.isfile(prompt_file):
  160. return None
  161. with open(prompt_file, "r") as f:
  162. return f.read().strip()
  163. elif benchmark_name == "webarena":
  164. task_prompt_file = os.path.join(instance_dir, "task_prompt.json")
  165. if not os.path.isfile(task_prompt_file):
  166. return None
  167. with open(task_prompt_file, "r") as f:
  168. return json.load(f)["intent"]
  169. else:
  170. raise ValueError(f"Unsupported benchmark_name: {benchmark_name}")
  171. def is_progress_not_being_made(instance_dir):
  172. # if at any point in the log, progress is not being made, return True
  173. pattern = r'"is_progress_being_made": \{\s+"reason": ".*?",\s+"answer": false\s+\}'
  174. log_file_path = os.path.join(instance_dir, "log.jsonl")
  175. if not os.path.isfile(log_file_path):
  176. return None
  177. with open(log_file_path, "r") as f:
  178. for line in f:
  179. line_dict = json.loads(line)
  180. if (
  181. "source" in line_dict
  182. and line_dict["source"] == "Orchestrator (thought)"
  183. and "Updated Ledger:" in line_dict["message"]
  184. and re.search(pattern, line_dict["message"])
  185. ):
  186. return True
  187. return False