sunmarinup commited on
Commit
76e6c1d
·
1 Parent(s): b5ca9c0

Remove unused models

Browse files
app.py CHANGED
@@ -6,6 +6,14 @@ import gradio as gr
6
  import pandas as pd
7
  import requests
8
 
 
 
 
 
 
 
 
 
9
  # GitHub API endpoint for the file (handles Git LFS files)
10
  LEADERBOARD_API_URL = "https://api.github.com/repos/upgini/mle-bench/contents/rankings/low/tabular/overall_ranks.csv"
11
  LEADERBOARD_GITHUB_URL = "https://github.com/upgini/mle-bench/blob/main/rankings/low/tabular/overall_ranks.csv"
@@ -22,8 +30,11 @@ DISPLAY_COLUMNS = [
22
  ]
23
 
24
 
25
- def download_leaderboard() -> pd.DataFrame:
26
- """Download the remote leaderboard CSV from GitHub (handles Git LFS) and return a cleaned dataframe."""
 
 
 
27
  # Use GitHub API to get file content (handles Git LFS files)
28
  response = requests.get(LEADERBOARD_API_URL, timeout=30)
29
  response.raise_for_status()
@@ -59,7 +70,7 @@ def download_leaderboard() -> pd.DataFrame:
59
 
60
  df = pd.read_csv(io.StringIO(csv_content))
61
  if df.empty:
62
- return df
63
 
64
  missing_cols = [col for col in DISPLAY_COLUMNS if col not in df.columns]
65
  if missing_cols:
@@ -71,12 +82,20 @@ def download_leaderboard() -> pd.DataFrame:
71
  df["mean_medal_pct"] = (df["mean_medal_pct"] * 100).round(1)
72
  df["sem_medal_pct"] = (df["sem_medal_pct"] * 100).round(1)
73
  df["Date"] = pd.to_datetime(df["Date"], errors="coerce").dt.strftime("%Y-%m-%d")
74
- return df.sort_values(by="mean_normalized_score", ascending=False, ignore_index=True)
 
 
 
 
 
 
 
75
 
76
 
77
  def refresh_leaderboard():
78
  """Fetch the leaderboard and build the status message for the UI."""
79
- df = download_leaderboard()
 
80
  status = (
81
  f"Showing data from [GitHub]({LEADERBOARD_GITHUB_URL}). "
82
  f"Last refreshed: {datetime.now(timezone.utc):%Y-%m-%d %H:%M UTC}."
@@ -86,15 +105,9 @@ def refresh_leaderboard():
86
 
87
  def create_app():
88
  """Create and configure the Gradio app without launching it."""
89
- with gr.Blocks(title="Upgini MLE-Bench Leaderboard") as demo:
90
- gr.Markdown(
91
- """
92
- # Upgini MLE-Bench Tabular Leaderboard
93
-
94
- This app mirrors the remote leaderboard so you always see the latest public results.
95
- Click **Refresh leaderboard** any time to re-download the CSV from GitHub.
96
- """
97
- )
98
 
99
  leaderboard_table = gr.DataFrame(
100
  value=pd.DataFrame(columns=DISPLAY_COLUMNS),
@@ -102,6 +115,7 @@ def create_app():
102
  interactive=False,
103
  type="pandas",
104
  label="Leaderboard",
 
105
  )
106
  status_text = gr.Markdown()
107
  refresh_button = gr.Button("Refresh leaderboard", variant="primary")
 
6
  import pandas as pd
7
  import requests
8
 
9
+ from src.about import TITLE, INTRODUCTION_TEXT
10
+ from src.display.css_html_js import custom_css
11
+ from src.leaderboard.read_evals import (
12
+ TabularLeaderboardEntry,
13
+ parse_tabular_leaderboard,
14
+ tabular_leaderboard_to_dataframe,
15
+ )
16
+
17
  # GitHub API endpoint for the file (handles Git LFS files)
18
  LEADERBOARD_API_URL = "https://api.github.com/repos/upgini/mle-bench/contents/rankings/low/tabular/overall_ranks.csv"
19
  LEADERBOARD_GITHUB_URL = "https://github.com/upgini/mle-bench/blob/main/rankings/low/tabular/overall_ranks.csv"
 
30
  ]
31
 
32
 
33
+ def download_leaderboard() -> list[TabularLeaderboardEntry]:
34
+ """Download the remote leaderboard CSV from GitHub (handles Git LFS).
35
+
36
+ Returns a list of TabularLeaderboardEntry objects.
37
+ """
38
  # Use GitHub API to get file content (handles Git LFS files)
39
  response = requests.get(LEADERBOARD_API_URL, timeout=30)
40
  response.raise_for_status()
 
70
 
71
  df = pd.read_csv(io.StringIO(csv_content))
72
  if df.empty:
73
+ return []
74
 
75
  missing_cols = [col for col in DISPLAY_COLUMNS if col not in df.columns]
76
  if missing_cols:
 
82
  df["mean_medal_pct"] = (df["mean_medal_pct"] * 100).round(1)
83
  df["sem_medal_pct"] = (df["sem_medal_pct"] * 100).round(1)
84
  df["Date"] = pd.to_datetime(df["Date"], errors="coerce").dt.strftime("%Y-%m-%d")
85
+
86
+ # Sort by mean_normalized_score before converting to data models
87
+ df = df.sort_values(by="mean_normalized_score", ascending=False, ignore_index=True)
88
+
89
+ # Parse into data models
90
+ entries = parse_tabular_leaderboard(df)
91
+
92
+ return entries
93
 
94
 
95
  def refresh_leaderboard():
96
  """Fetch the leaderboard and build the status message for the UI."""
97
+ entries = download_leaderboard()
98
+ df = tabular_leaderboard_to_dataframe(entries)
99
  status = (
100
  f"Showing data from [GitHub]({LEADERBOARD_GITHUB_URL}). "
101
  f"Last refreshed: {datetime.now(timezone.utc):%Y-%m-%d %H:%M UTC}."
 
105
 
106
  def create_app():
107
  """Create and configure the Gradio app without launching it."""
108
+ with gr.Blocks(title="Upgini MLE-Bench Leaderboard", css=custom_css) as demo:
109
+ gr.HTML(TITLE)
110
+ gr.Markdown(INTRODUCTION_TEXT)
 
 
 
 
 
 
111
 
112
  leaderboard_table = gr.DataFrame(
113
  value=pd.DataFrame(columns=DISPLAY_COLUMNS),
 
115
  interactive=False,
116
  type="pandas",
117
  label="Leaderboard",
118
+ elem_id="leaderboard-table",
119
  )
120
  status_text = gr.Markdown()
121
  refresh_button = gr.Button("Refresh leaderboard", variant="primary")
src/about.py CHANGED
@@ -1,70 +1,10 @@
1
- from dataclasses import dataclass
2
- from enum import Enum
3
-
4
- @dataclass
5
- class Task:
6
- benchmark: str
7
- metric: str
8
- col_name: str
9
-
10
-
11
- # Select your tasks here
12
- # ---------------------------------------------------
13
- class Tasks(Enum):
14
- # task_key in the json file, metric_key in the json file, name to display in the leaderboard
15
- task0 = Task("anli_r1", "acc", "ANLI")
16
- task1 = Task("logiqa", "acc_norm", "LogiQA")
17
-
18
- NUM_FEWSHOT = 0 # Change with your few shot
19
- # ---------------------------------------------------
20
-
21
-
22
-
23
  # Your leaderboard name
24
- TITLE = """<h1 align="center" id="space-title">Demo leaderboard</h1>"""
25
 
26
  # What does your leaderboard evaluate?
27
  INTRODUCTION_TEXT = """
28
- Intro text
29
- """
30
-
31
- # Which evaluations are you running? how can people reproduce what you have?
32
- LLM_BENCHMARKS_TEXT = f"""
33
- ## How it works
34
-
35
- ## Reproducibility
36
- To reproduce our results, here is the commands you can run:
37
-
38
- """
39
-
40
- EVALUATION_QUEUE_TEXT = """
41
- ## Some good practices before submitting a model
42
-
43
- ### 1) Make sure you can load your model and tokenizer using AutoClasses:
44
- ```python
45
- from transformers import AutoConfig, AutoModel, AutoTokenizer
46
- config = AutoConfig.from_pretrained("your model name", revision=revision)
47
- model = AutoModel.from_pretrained("your model name", revision=revision)
48
- tokenizer = AutoTokenizer.from_pretrained("your model name", revision=revision)
49
- ```
50
- If this step fails, follow the error messages to debug your model before submitting it. It's likely your model has been improperly uploaded.
51
-
52
- Note: make sure your model is public!
53
- Note: if your model needs `use_remote_code=True`, we do not support this option yet but we are working on adding it, stay posted!
54
-
55
- ### 2) Convert your model weights to [safetensors](https://huggingface.co/docs/safetensors/index)
56
- It's a new format for storing weights which is safer and faster to load and use. It will also allow us to add the number of parameters of your model to the `Extended Viewer`!
57
-
58
- ### 3) Make sure your model has an open license!
59
- This is a leaderboard for Open LLMs, and we'd love for as many people as possible to know they can use your model 🤗
60
-
61
- ### 4) Fill up your model card
62
- When we add extra information about models to the leaderboard, it will be automatically taken from the model card
63
-
64
- ## In case of model failure
65
- If your model is displayed in the `FAILED` category, its execution stopped.
66
- Make sure you have followed the above steps first.
67
- If everything is done, check you can launch the EleutherAIHarness on your model locally, using the above command without modifications (you can add `--limit` to limit the number of examples per task).
68
  """
69
 
70
  CITATION_BUTTON_LABEL = "Copy the following snippet to cite these results"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Your leaderboard name
2
+ TITLE = """<h1 align="center" id="space-title">Upgini MLE-Bench Tabular Leaderboard</h1>"""
3
 
4
  # What does your leaderboard evaluate?
5
  INTRODUCTION_TEXT = """
6
+ This app mirrors the remote leaderboard so you always see the latest public results.
7
+ Click **Refresh leaderboard** any time to re-download the CSV from GitHub.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
 
10
  CITATION_BUTTON_LABEL = "Copy the following snippet to cite these results"
src/display/formatting.py CHANGED
@@ -1,12 +1,3 @@
1
- def model_hyperlink(link, model_name):
2
- return f'<a target="_blank" href="{link}" style="color: var(--link-text-color); text-decoration: underline;text-decoration-style: dotted;">{model_name}</a>'
3
-
4
-
5
- def make_clickable_model(model_name):
6
- link = f"https://huggingface.co/{model_name}"
7
- return model_hyperlink(link, model_name)
8
-
9
-
10
  def styled_error(error):
11
  return f"<p style='color: red; font-size: 20px; text-align: center;'>{error}</p>"
12
 
@@ -17,11 +8,3 @@ def styled_warning(warn):
17
 
18
  def styled_message(message):
19
  return f"<p style='color: green; font-size: 20px; text-align: center;'>{message}</p>"
20
-
21
-
22
- def has_no_nan_values(df, columns):
23
- return df[columns].notna().all(axis=1)
24
-
25
-
26
- def has_nan_values(df, columns):
27
- return df[columns].isna().any(axis=1)
 
 
 
 
 
 
 
 
 
 
1
  def styled_error(error):
2
  return f"<p style='color: red; font-size: 20px; text-align: center;'>{error}</p>"
3
 
 
8
 
9
  def styled_message(message):
10
  return f"<p style='color: green; font-size: 20px; text-align: center;'>{message}</p>"
 
 
 
 
 
 
 
 
src/display/utils.py DELETED
@@ -1,110 +0,0 @@
1
- from dataclasses import dataclass, make_dataclass
2
- from enum import Enum
3
-
4
- import pandas as pd
5
-
6
- from src.about import Tasks
7
-
8
- def fields(raw_class):
9
- return [v for k, v in raw_class.__dict__.items() if k[:2] != "__" and k[-2:] != "__"]
10
-
11
-
12
- # These classes are for user facing column names,
13
- # to avoid having to change them all around the code
14
- # when a modif is needed
15
- @dataclass
16
- class ColumnContent:
17
- name: str
18
- type: str
19
- displayed_by_default: bool
20
- hidden: bool = False
21
- never_hidden: bool = False
22
-
23
- ## Leaderboard columns
24
- auto_eval_column_dict = []
25
- # Init
26
- auto_eval_column_dict.append(["model_type_symbol", ColumnContent, ColumnContent("T", "str", True, never_hidden=True)])
27
- auto_eval_column_dict.append(["model", ColumnContent, ColumnContent("Model", "markdown", True, never_hidden=True)])
28
- #Scores
29
- auto_eval_column_dict.append(["average", ColumnContent, ColumnContent("Average ⬆️", "number", True)])
30
- for task in Tasks:
31
- auto_eval_column_dict.append([task.name, ColumnContent, ColumnContent(task.value.col_name, "number", True)])
32
- # Model information
33
- auto_eval_column_dict.append(["model_type", ColumnContent, ColumnContent("Type", "str", False)])
34
- auto_eval_column_dict.append(["architecture", ColumnContent, ColumnContent("Architecture", "str", False)])
35
- auto_eval_column_dict.append(["weight_type", ColumnContent, ColumnContent("Weight type", "str", False, True)])
36
- auto_eval_column_dict.append(["precision", ColumnContent, ColumnContent("Precision", "str", False)])
37
- auto_eval_column_dict.append(["license", ColumnContent, ColumnContent("Hub License", "str", False)])
38
- auto_eval_column_dict.append(["params", ColumnContent, ColumnContent("#Params (B)", "number", False)])
39
- auto_eval_column_dict.append(["likes", ColumnContent, ColumnContent("Hub ❤️", "number", False)])
40
- auto_eval_column_dict.append(["still_on_hub", ColumnContent, ColumnContent("Available on the hub", "bool", False)])
41
- auto_eval_column_dict.append(["revision", ColumnContent, ColumnContent("Model sha", "str", False, False)])
42
-
43
- # We use make dataclass to dynamically fill the scores from Tasks
44
- AutoEvalColumn = make_dataclass("AutoEvalColumn", auto_eval_column_dict, frozen=True)
45
-
46
- ## For the queue columns in the submission tab
47
- @dataclass(frozen=True)
48
- class EvalQueueColumn: # Queue column
49
- model = ColumnContent("model", "markdown", True)
50
- revision = ColumnContent("revision", "str", True)
51
- private = ColumnContent("private", "bool", True)
52
- precision = ColumnContent("precision", "str", True)
53
- weight_type = ColumnContent("weight_type", "str", "Original")
54
- status = ColumnContent("status", "str", True)
55
-
56
- ## All the model information that we might need
57
- @dataclass
58
- class ModelDetails:
59
- name: str
60
- display_name: str = ""
61
- symbol: str = "" # emoji
62
-
63
-
64
- class ModelType(Enum):
65
- PT = ModelDetails(name="pretrained", symbol="🟢")
66
- FT = ModelDetails(name="fine-tuned", symbol="🔶")
67
- IFT = ModelDetails(name="instruction-tuned", symbol="⭕")
68
- RL = ModelDetails(name="RL-tuned", symbol="🟦")
69
- Unknown = ModelDetails(name="", symbol="?")
70
-
71
- def to_str(self, separator=" "):
72
- return f"{self.value.symbol}{separator}{self.value.name}"
73
-
74
- @staticmethod
75
- def from_str(type):
76
- if "fine-tuned" in type or "🔶" in type:
77
- return ModelType.FT
78
- if "pretrained" in type or "🟢" in type:
79
- return ModelType.PT
80
- if "RL-tuned" in type or "🟦" in type:
81
- return ModelType.RL
82
- if "instruction-tuned" in type or "⭕" in type:
83
- return ModelType.IFT
84
- return ModelType.Unknown
85
-
86
- class WeightType(Enum):
87
- Adapter = ModelDetails("Adapter")
88
- Original = ModelDetails("Original")
89
- Delta = ModelDetails("Delta")
90
-
91
- class Precision(Enum):
92
- float16 = ModelDetails("float16")
93
- bfloat16 = ModelDetails("bfloat16")
94
- Unknown = ModelDetails("?")
95
-
96
- def from_str(precision):
97
- if precision in ["torch.float16", "float16"]:
98
- return Precision.float16
99
- if precision in ["torch.bfloat16", "bfloat16"]:
100
- return Precision.bfloat16
101
- return Precision.Unknown
102
-
103
- # Column selection
104
- COLS = [c.name for c in fields(AutoEvalColumn) if not c.hidden]
105
-
106
- EVAL_COLS = [c.name for c in fields(EvalQueueColumn)]
107
- EVAL_TYPES = [c.type for c in fields(EvalQueueColumn)]
108
-
109
- BENCHMARK_COLS = [t.value.col_name for t in Tasks]
110
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/envs.py DELETED
@@ -1,25 +0,0 @@
1
- import os
2
-
3
- from huggingface_hub import HfApi
4
-
5
- # Info to change for your repository
6
- # ----------------------------------
7
- TOKEN = os.environ.get("HF_TOKEN") # A read/write token for your org
8
-
9
- OWNER = "demo-leaderboard-backend" # Change to your org - don't forget to create a results and request dataset, with the correct format!
10
- # ----------------------------------
11
-
12
- REPO_ID = f"{OWNER}/leaderboard"
13
- QUEUE_REPO = f"{OWNER}/requests"
14
- RESULTS_REPO = f"{OWNER}/results"
15
-
16
- # If you setup a cache later, just change HF_HOME
17
- CACHE_PATH=os.getenv("HF_HOME", ".")
18
-
19
- # Local caches
20
- EVAL_REQUESTS_PATH = os.path.join(CACHE_PATH, "eval-queue")
21
- EVAL_RESULTS_PATH = os.path.join(CACHE_PATH, "eval-results")
22
- EVAL_REQUESTS_PATH_BACKEND = os.path.join(CACHE_PATH, "eval-queue-bk")
23
- EVAL_RESULTS_PATH_BACKEND = os.path.join(CACHE_PATH, "eval-results-bk")
24
-
25
- API = HfApi(token=TOKEN)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/leaderboard/read_evals.py CHANGED
@@ -1,196 +1,86 @@
1
  import glob
2
  import json
3
- import math
4
  import os
5
  from dataclasses import dataclass
6
 
7
  import dateutil
8
  import numpy as np
9
-
10
- from src.display.formatting import make_clickable_model
11
- from src.display.utils import AutoEvalColumn, ModelType, Tasks, Precision, WeightType
12
- from src.submission.check_validity import is_model_on_hub
13
 
14
 
15
  @dataclass
16
- class EvalResult:
17
- """Represents one full evaluation. Built from a combination of the result and request file for a given run.
18
- """
19
- eval_name: str # org_model_precision (uid)
20
- full_model: str # org/model (path on hub)
21
- org: str
22
- model: str
23
- revision: str # commit hash, "" if main
24
- results: dict
25
- precision: Precision = Precision.Unknown
26
- model_type: ModelType = ModelType.Unknown # Pretrained, fine tuned, ...
27
- weight_type: WeightType = WeightType.Original # Original or Adapter
28
- architecture: str = "Unknown"
29
- license: str = "?"
30
- likes: int = 0
31
- num_params: int = 0
32
- date: str = "" # submission date of request file
33
- still_on_hub: bool = False
34
 
35
  @classmethod
36
- def init_from_json_file(self, json_filepath):
37
- """Inits the result from the specific model result file"""
38
- with open(json_filepath) as fp:
39
- data = json.load(fp)
40
-
41
- config = data.get("config")
42
-
43
- # Precision
44
- precision = Precision.from_str(config.get("model_dtype"))
45
-
46
- # Get model and org
47
- org_and_model = config.get("model_name", config.get("model_args", None))
48
- org_and_model = org_and_model.split("/", 1)
49
-
50
- if len(org_and_model) == 1:
51
- org = None
52
- model = org_and_model[0]
53
- result_key = f"{model}_{precision.value.name}"
54
- else:
55
- org = org_and_model[0]
56
- model = org_and_model[1]
57
- result_key = f"{org}_{model}_{precision.value.name}"
58
- full_model = "/".join(org_and_model)
59
-
60
- still_on_hub, _, model_config = is_model_on_hub(
61
- full_model, config.get("model_sha", "main"), trust_remote_code=True, test_tokenizer=False
62
  )
63
- architecture = "?"
64
- if model_config is not None:
65
- architectures = getattr(model_config, "architectures", None)
66
- if architectures:
67
- architecture = ";".join(architectures)
68
-
69
- # Extract results available in this file (some results are split in several files)
70
- results = {}
71
- for task in Tasks:
72
- task = task.value
73
 
74
- # We average all scores of a given metric (not all metrics are present in all files)
75
- accs = np.array([v.get(task.metric, None) for k, v in data["results"].items() if task.benchmark == k])
76
- if accs.size == 0 or any([acc is None for acc in accs]):
77
- continue
78
-
79
- mean_acc = np.mean(accs) * 100.0
80
- results[task.benchmark] = mean_acc
 
 
 
 
 
81
 
82
- return self(
83
- eval_name=result_key,
84
- full_model=full_model,
85
- org=org,
86
- model=model,
87
- results=results,
88
- precision=precision,
89
- revision= config.get("model_sha", ""),
90
- still_on_hub=still_on_hub,
91
- architecture=architecture
92
- )
93
 
94
- def update_with_request_file(self, requests_path):
95
- """Finds the relevant request file for the current model and updates info with it"""
96
- request_file = get_request_file_for_model(requests_path, self.full_model, self.precision.value.name)
 
97
 
 
 
98
  try:
99
- with open(request_file, "r") as f:
100
- request = json.load(f)
101
- self.model_type = ModelType.from_str(request.get("model_type", ""))
102
- self.weight_type = WeightType[request.get("weight_type", "Original")]
103
- self.license = request.get("license", "?")
104
- self.likes = request.get("likes", 0)
105
- self.num_params = request.get("params", 0)
106
- self.date = request.get("submitted_time", "")
107
  except Exception:
108
- print(f"Could not find request file for {self.org}/{self.model} with precision {self.precision.value.name}")
109
-
110
- def to_dict(self):
111
- """Converts the Eval Result to a dict compatible with our dataframe display"""
112
- average = sum([v for v in self.results.values() if v is not None]) / len(Tasks)
113
- data_dict = {
114
- "eval_name": self.eval_name, # not a column, just a save name,
115
- AutoEvalColumn.precision.name: self.precision.value.name,
116
- AutoEvalColumn.model_type.name: self.model_type.value.name,
117
- AutoEvalColumn.model_type_symbol.name: self.model_type.value.symbol,
118
- AutoEvalColumn.weight_type.name: self.weight_type.value.name,
119
- AutoEvalColumn.architecture.name: self.architecture,
120
- AutoEvalColumn.model.name: make_clickable_model(self.full_model),
121
- AutoEvalColumn.revision.name: self.revision,
122
- AutoEvalColumn.average.name: average,
123
- AutoEvalColumn.license.name: self.license,
124
- AutoEvalColumn.likes.name: self.likes,
125
- AutoEvalColumn.params.name: self.num_params,
126
- AutoEvalColumn.still_on_hub.name: self.still_on_hub,
127
- }
128
-
129
- for task in Tasks:
130
- data_dict[task.value.col_name] = self.results[task.value.benchmark]
131
-
132
- return data_dict
133
-
134
-
135
- def get_request_file_for_model(requests_path, model_name, precision):
136
- """Selects the correct request file for a given model. Only keeps runs tagged as FINISHED"""
137
- request_files = os.path.join(
138
- requests_path,
139
- f"{model_name}_eval_request_*.json",
140
- )
141
- request_files = glob.glob(request_files)
142
-
143
- # Select correct request file (precision)
144
- request_file = ""
145
- request_files = sorted(request_files, reverse=True)
146
- for tmp_request_file in request_files:
147
- with open(tmp_request_file, "r") as f:
148
- req_content = json.load(f)
149
- if (
150
- req_content["status"] in ["FINISHED"]
151
- and req_content["precision"] == precision.split(".")[-1]
152
- ):
153
- request_file = tmp_request_file
154
- return request_file
155
-
156
-
157
- def get_raw_eval_results(results_path: str, requests_path: str) -> list[EvalResult]:
158
- """From the path of the results folder root, extract all needed info for results"""
159
- model_result_filepaths = []
160
-
161
- for root, _, files in os.walk(results_path):
162
- # We should only have json files in model results
163
- if len(files) == 0 or any([not f.endswith(".json") for f in files]):
164
  continue
165
 
166
- # Sort the files by date
167
- try:
168
- files.sort(key=lambda x: x.removesuffix(".json").removeprefix("results_")[:-7])
169
- except dateutil.parser._parser.ParserError:
170
- files = [files[-1]]
171
-
172
- for file in files:
173
- model_result_filepaths.append(os.path.join(root, file))
174
-
175
- eval_results = {}
176
- for model_result_filepath in model_result_filepaths:
177
- # Creation of result
178
- eval_result = EvalResult.init_from_json_file(model_result_filepath)
179
- eval_result.update_with_request_file(requests_path)
180
-
181
- # Store results of same eval together
182
- eval_name = eval_result.eval_name
183
- if eval_name in eval_results.keys():
184
- eval_results[eval_name].results.update({k: v for k, v in eval_result.results.items() if v is not None})
185
- else:
186
- eval_results[eval_name] = eval_result
187
-
188
- results = []
189
- for v in eval_results.values():
190
- try:
191
- v.to_dict() # we test if the dict version is complete
192
- results.append(v)
193
- except KeyError: # not all eval values present
194
- continue
195
 
196
- return results
 
 
1
  import glob
2
  import json
 
3
  import os
4
  from dataclasses import dataclass
5
 
6
  import dateutil
7
  import numpy as np
8
+ import pandas as pd
 
 
 
9
 
10
 
11
  @dataclass
12
+ class TabularLeaderboardEntry:
13
+ """Represents a single entry in the tabular leaderboard."""
14
+
15
+ experiment_id: str
16
+ agent: str
17
+ llms_used: str
18
+ mean_normalized_score: float
19
+ std_normalized_score: float
20
+ mean_medal_pct: float
21
+ sem_medal_pct: float
22
+ date: str
 
 
 
 
 
 
 
23
 
24
  @classmethod
25
+ def from_dataframe_row(cls, row: pd.Series) -> "TabularLeaderboardEntry":
26
+ """Create a TabularLeaderboardEntry from a pandas DataFrame row."""
27
+ return cls(
28
+ experiment_id=str(row.get("experiment_id", "")),
29
+ agent=str(row.get("Agent", "")),
30
+ llms_used=str(row.get("LLM(s) used", "")),
31
+ mean_normalized_score=float(row.get("mean_normalized_score", 0.0)),
32
+ std_normalized_score=float(row.get("std_normalized_score", 0.0)),
33
+ mean_medal_pct=float(row.get("mean_medal_pct", 0.0)),
34
+ sem_medal_pct=float(row.get("sem_medal_pct", 0.0)),
35
+ date=str(row.get("Date", "")),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  )
 
 
 
 
 
 
 
 
 
 
37
 
38
+ def to_dict(self) -> dict:
39
+ """Convert the entry to a dictionary compatible with DataFrame display."""
40
+ return {
41
+ "experiment_id": self.experiment_id,
42
+ "Agent": self.agent,
43
+ "LLM(s) used": self.llms_used,
44
+ "mean_normalized_score": self.mean_normalized_score,
45
+ "std_normalized_score": self.std_normalized_score,
46
+ "mean_medal_pct": self.mean_medal_pct,
47
+ "sem_medal_pct": self.sem_medal_pct,
48
+ "Date": self.date,
49
+ }
50
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ def parse_tabular_leaderboard(df: pd.DataFrame) -> list[TabularLeaderboardEntry]:
53
+ """Parse a DataFrame into a list of TabularLeaderboardEntry objects."""
54
+ if df.empty:
55
+ return []
56
 
57
+ entries = []
58
+ for _, row in df.iterrows():
59
  try:
60
+ entry = TabularLeaderboardEntry.from_dataframe_row(row)
61
+ entries.append(entry)
 
 
 
 
 
 
62
  except Exception:
63
+ # Skip rows that can't be parsed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  continue
65
 
66
+ return entries
67
+
68
+
69
+ def tabular_leaderboard_to_dataframe(entries: list[TabularLeaderboardEntry]) -> pd.DataFrame:
70
+ """Convert a list of TabularLeaderboardEntry objects to a DataFrame."""
71
+ if not entries:
72
+ return pd.DataFrame(
73
+ columns=[
74
+ "experiment_id",
75
+ "Agent",
76
+ "LLM(s) used",
77
+ "mean_normalized_score",
78
+ "std_normalized_score",
79
+ "mean_medal_pct",
80
+ "sem_medal_pct",
81
+ "Date",
82
+ ]
83
+ )
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ data = [entry.to_dict() for entry in entries]
86
+ return pd.DataFrame(data)
src/populate.py DELETED
@@ -1,58 +0,0 @@
1
- import json
2
- import os
3
-
4
- import pandas as pd
5
-
6
- from src.display.formatting import has_no_nan_values, make_clickable_model
7
- from src.display.utils import AutoEvalColumn, EvalQueueColumn
8
- from src.leaderboard.read_evals import get_raw_eval_results
9
-
10
-
11
- def get_leaderboard_df(results_path: str, requests_path: str, cols: list, benchmark_cols: list) -> pd.DataFrame:
12
- """Creates a dataframe from all the individual experiment results"""
13
- raw_data = get_raw_eval_results(results_path, requests_path)
14
- all_data_json = [v.to_dict() for v in raw_data]
15
-
16
- df = pd.DataFrame.from_records(all_data_json)
17
- df = df.sort_values(by=[AutoEvalColumn.average.name], ascending=False)
18
- df = df[cols].round(decimals=2)
19
-
20
- # filter out if any of the benchmarks have not been produced
21
- df = df[has_no_nan_values(df, benchmark_cols)]
22
- return df
23
-
24
-
25
- def get_evaluation_queue_df(save_path: str, cols: list) -> list[pd.DataFrame]:
26
- """Creates the different dataframes for the evaluation queues requestes"""
27
- entries = [entry for entry in os.listdir(save_path) if not entry.startswith(".")]
28
- all_evals = []
29
-
30
- for entry in entries:
31
- if ".json" in entry:
32
- file_path = os.path.join(save_path, entry)
33
- with open(file_path) as fp:
34
- data = json.load(fp)
35
-
36
- data[EvalQueueColumn.model.name] = make_clickable_model(data["model"])
37
- data[EvalQueueColumn.revision.name] = data.get("revision", "main")
38
-
39
- all_evals.append(data)
40
- elif ".md" not in entry:
41
- # this is a folder
42
- sub_entries = [e for e in os.listdir(f"{save_path}/{entry}") if os.path.isfile(e) and not e.startswith(".")]
43
- for sub_entry in sub_entries:
44
- file_path = os.path.join(save_path, entry, sub_entry)
45
- with open(file_path) as fp:
46
- data = json.load(fp)
47
-
48
- data[EvalQueueColumn.model.name] = make_clickable_model(data["model"])
49
- data[EvalQueueColumn.revision.name] = data.get("revision", "main")
50
- all_evals.append(data)
51
-
52
- pending_list = [e for e in all_evals if e["status"] in ["PENDING", "RERUN"]]
53
- running_list = [e for e in all_evals if e["status"] == "RUNNING"]
54
- finished_list = [e for e in all_evals if e["status"].startswith("FINISHED") or e["status"] == "PENDING_NEW_EVAL"]
55
- df_pending = pd.DataFrame.from_records(pending_list, columns=cols)
56
- df_running = pd.DataFrame.from_records(running_list, columns=cols)
57
- df_finished = pd.DataFrame.from_records(finished_list, columns=cols)
58
- return df_finished[cols], df_running[cols], df_pending[cols]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/submission/check_validity.py DELETED
@@ -1,99 +0,0 @@
1
- import json
2
- import os
3
- import re
4
- from collections import defaultdict
5
- from datetime import datetime, timedelta, timezone
6
-
7
- import huggingface_hub
8
- from huggingface_hub import ModelCard
9
- from huggingface_hub.hf_api import ModelInfo
10
- from transformers import AutoConfig
11
- from transformers.models.auto.tokenization_auto import AutoTokenizer
12
-
13
- def check_model_card(repo_id: str) -> tuple[bool, str]:
14
- """Checks if the model card and license exist and have been filled"""
15
- try:
16
- card = ModelCard.load(repo_id)
17
- except huggingface_hub.utils.EntryNotFoundError:
18
- return False, "Please add a model card to your model to explain how you trained/fine-tuned it."
19
-
20
- # Enforce license metadata
21
- if card.data.license is None:
22
- if not ("license_name" in card.data and "license_link" in card.data):
23
- return False, (
24
- "License not found. Please add a license to your model card using the `license` metadata or a"
25
- " `license_name`/`license_link` pair."
26
- )
27
-
28
- # Enforce card content
29
- if len(card.text) < 200:
30
- return False, "Please add a description to your model card, it is too short."
31
-
32
- return True, ""
33
-
34
- def is_model_on_hub(model_name: str, revision: str, token: str = None, trust_remote_code=False, test_tokenizer=False) -> tuple[bool, str]:
35
- """Checks if the model model_name is on the hub, and whether it (and its tokenizer) can be loaded with AutoClasses."""
36
- try:
37
- config = AutoConfig.from_pretrained(model_name, revision=revision, trust_remote_code=trust_remote_code, token=token)
38
- if test_tokenizer:
39
- try:
40
- tk = AutoTokenizer.from_pretrained(model_name, revision=revision, trust_remote_code=trust_remote_code, token=token)
41
- except ValueError as e:
42
- return (
43
- False,
44
- f"uses a tokenizer which is not in a transformers release: {e}",
45
- None
46
- )
47
- except Exception as e:
48
- return (False, "'s tokenizer cannot be loaded. Is your tokenizer class in a stable transformers release, and correctly configured?", None)
49
- return True, None, config
50
-
51
- except ValueError:
52
- return (
53
- False,
54
- "needs to be launched with `trust_remote_code=True`. For safety reason, we do not allow these models to be automatically submitted to the leaderboard.",
55
- None
56
- )
57
-
58
- except Exception as e:
59
- return False, "was not found on hub!", None
60
-
61
-
62
- def get_model_size(model_info: ModelInfo, precision: str):
63
- """Gets the model size from the configuration, or the model name if the configuration does not contain the information."""
64
- try:
65
- model_size = round(model_info.safetensors["total"] / 1e9, 3)
66
- except (AttributeError, TypeError):
67
- return 0 # Unknown model sizes are indicated as 0, see NUMERIC_INTERVALS in app.py
68
-
69
- size_factor = 8 if (precision == "GPTQ" or "gptq" in model_info.modelId.lower()) else 1
70
- model_size = size_factor * model_size
71
- return model_size
72
-
73
- def get_model_arch(model_info: ModelInfo):
74
- """Gets the model architecture from the configuration"""
75
- return model_info.config.get("architectures", "Unknown")
76
-
77
- def already_submitted_models(requested_models_dir: str) -> set[str]:
78
- """Gather a list of already submitted models to avoid duplicates"""
79
- depth = 1
80
- file_names = []
81
- users_to_submission_dates = defaultdict(list)
82
-
83
- for root, _, files in os.walk(requested_models_dir):
84
- current_depth = root.count(os.sep) - requested_models_dir.count(os.sep)
85
- if current_depth == depth:
86
- for file in files:
87
- if not file.endswith(".json"):
88
- continue
89
- with open(os.path.join(root, file), "r") as f:
90
- info = json.load(f)
91
- file_names.append(f"{info['model']}_{info['revision']}_{info['precision']}")
92
-
93
- # Select organisation
94
- if info["model"].count("/") == 0 or "submitted_time" not in info:
95
- continue
96
- organisation, _ = info["model"].split("/")
97
- users_to_submission_dates[organisation].append(info["submitted_time"])
98
-
99
- return set(file_names), users_to_submission_dates
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/submission/submit.py DELETED
@@ -1,119 +0,0 @@
1
- import json
2
- import os
3
- from datetime import datetime, timezone
4
-
5
- from src.display.formatting import styled_error, styled_message, styled_warning
6
- from src.envs import API, EVAL_REQUESTS_PATH, TOKEN, QUEUE_REPO
7
- from src.submission.check_validity import (
8
- already_submitted_models,
9
- check_model_card,
10
- get_model_size,
11
- is_model_on_hub,
12
- )
13
-
14
- REQUESTED_MODELS = None
15
- USERS_TO_SUBMISSION_DATES = None
16
-
17
- def add_new_eval(
18
- model: str,
19
- base_model: str,
20
- revision: str,
21
- precision: str,
22
- weight_type: str,
23
- model_type: str,
24
- ):
25
- global REQUESTED_MODELS
26
- global USERS_TO_SUBMISSION_DATES
27
- if not REQUESTED_MODELS:
28
- REQUESTED_MODELS, USERS_TO_SUBMISSION_DATES = already_submitted_models(EVAL_REQUESTS_PATH)
29
-
30
- user_name = ""
31
- model_path = model
32
- if "/" in model:
33
- user_name = model.split("/")[0]
34
- model_path = model.split("/")[1]
35
-
36
- precision = precision.split(" ")[0]
37
- current_time = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
38
-
39
- if model_type is None or model_type == "":
40
- return styled_error("Please select a model type.")
41
-
42
- # Does the model actually exist?
43
- if revision == "":
44
- revision = "main"
45
-
46
- # Is the model on the hub?
47
- if weight_type in ["Delta", "Adapter"]:
48
- base_model_on_hub, error, _ = is_model_on_hub(model_name=base_model, revision=revision, token=TOKEN, test_tokenizer=True)
49
- if not base_model_on_hub:
50
- return styled_error(f'Base model "{base_model}" {error}')
51
-
52
- if not weight_type == "Adapter":
53
- model_on_hub, error, _ = is_model_on_hub(model_name=model, revision=revision, token=TOKEN, test_tokenizer=True)
54
- if not model_on_hub:
55
- return styled_error(f'Model "{model}" {error}')
56
-
57
- # Is the model info correctly filled?
58
- try:
59
- model_info = API.model_info(repo_id=model, revision=revision)
60
- except Exception:
61
- return styled_error("Could not get your model information. Please fill it up properly.")
62
-
63
- model_size = get_model_size(model_info=model_info, precision=precision)
64
-
65
- # Were the model card and license filled?
66
- try:
67
- license = model_info.cardData["license"]
68
- except Exception:
69
- return styled_error("Please select a license for your model")
70
-
71
- modelcard_OK, error_msg = check_model_card(model)
72
- if not modelcard_OK:
73
- return styled_error(error_msg)
74
-
75
- # Seems good, creating the eval
76
- print("Adding new eval")
77
-
78
- eval_entry = {
79
- "model": model,
80
- "base_model": base_model,
81
- "revision": revision,
82
- "precision": precision,
83
- "weight_type": weight_type,
84
- "status": "PENDING",
85
- "submitted_time": current_time,
86
- "model_type": model_type,
87
- "likes": model_info.likes,
88
- "params": model_size,
89
- "license": license,
90
- "private": False,
91
- }
92
-
93
- # Check for duplicate submission
94
- if f"{model}_{revision}_{precision}" in REQUESTED_MODELS:
95
- return styled_warning("This model has been already submitted.")
96
-
97
- print("Creating eval file")
98
- OUT_DIR = f"{EVAL_REQUESTS_PATH}/{user_name}"
99
- os.makedirs(OUT_DIR, exist_ok=True)
100
- out_path = f"{OUT_DIR}/{model_path}_eval_request_False_{precision}_{weight_type}.json"
101
-
102
- with open(out_path, "w") as f:
103
- f.write(json.dumps(eval_entry))
104
-
105
- print("Uploading eval file")
106
- API.upload_file(
107
- path_or_fileobj=out_path,
108
- path_in_repo=out_path.split("eval-queue/")[1],
109
- repo_id=QUEUE_REPO,
110
- repo_type="dataset",
111
- commit_message=f"Add {model} to eval queue",
112
- )
113
-
114
- # Remove the local file
115
- os.remove(out_path)
116
-
117
- return styled_message(
118
- "Your request has been submitted to the evaluation queue!\nPlease wait for up to an hour for the model to show in the PENDING list."
119
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_leaderboard.py CHANGED
@@ -1,5 +1,3 @@
1
- """Unit tests for leaderboard functionality."""
2
-
3
  import base64
4
  from unittest.mock import Mock, patch
5
 
@@ -8,6 +6,7 @@ import pytest
8
  import requests
9
 
10
  from app import DISPLAY_COLUMNS, download_leaderboard, refresh_leaderboard
 
11
 
12
 
13
  def create_github_api_response(csv_content, is_lfs_pointer=False, use_download_url=False):
@@ -86,12 +85,12 @@ class TestDownloadLeaderboard:
86
  mock_get.side_effect = mock_responses
87
 
88
  # Execute
89
- df = download_leaderboard()
90
 
91
  # Assertions
92
- assert isinstance(df, pd.DataFrame)
93
- assert len(df) == 3
94
- assert list(df.columns) == DISPLAY_COLUMNS
95
  assert mock_get.call_count == 1
96
 
97
  @patch("app.requests.get")
@@ -100,15 +99,15 @@ class TestDownloadLeaderboard:
100
  mock_responses = create_github_api_response(sample_csv_data)
101
  mock_get.side_effect = mock_responses
102
 
103
- df = download_leaderboard()
104
 
105
- # Check rounding
106
- assert df["mean_normalized_score"].dtype in [float, "float64"]
107
- assert df["std_normalized_score"].dtype in [float, "float64"]
108
- # Values should be rounded to 3 decimal places
109
- assert df.loc[0, "mean_normalized_score"] == 0.912
110
- assert df.loc[1, "mean_normalized_score"] == 0.854
111
- assert df.loc[2, "mean_normalized_score"] == 0.789
112
 
113
  @patch("app.requests.get")
114
  def test_percentage_conversion(self, mock_get, sample_csv_data):
@@ -116,12 +115,13 @@ class TestDownloadLeaderboard:
116
  mock_responses = create_github_api_response(sample_csv_data)
117
  mock_get.side_effect = mock_responses
118
 
119
- df = download_leaderboard()
120
 
121
  # Check percentage conversion (0.876543 * 100 = 87.6543, rounded to 87.7)
122
- assert df.loc[1, "mean_medal_pct"] == 87.7
123
- assert df.loc[0, "mean_medal_pct"] == 92.3
124
- assert df.loc[2, "mean_medal_pct"] == 76.5
 
125
 
126
  @patch("app.requests.get")
127
  def test_date_formatting(self, mock_get, sample_csv_data):
@@ -129,26 +129,27 @@ class TestDownloadLeaderboard:
129
  mock_responses = create_github_api_response(sample_csv_data)
130
  mock_get.side_effect = mock_responses
131
 
132
- df = download_leaderboard()
133
 
134
- # Check date formatting
135
- assert df.loc[0, "Date"] == "2024-02-01"
136
- assert df.loc[1, "Date"] == "2024-01-15"
137
- assert df.loc[2, "Date"] == "2024-01-20"
 
138
 
139
  @patch("app.requests.get")
140
  def test_sorting(self, mock_get, sample_csv_data):
141
- """Test that dataframe is sorted by mean_normalized_score descending."""
142
  mock_responses = create_github_api_response(sample_csv_data)
143
  mock_get.side_effect = mock_responses
144
 
145
- df = download_leaderboard()
146
 
147
  # Check sorting (highest score first)
148
- scores = df["mean_normalized_score"].tolist()
149
  assert scores == sorted(scores, reverse=True)
150
- assert df.loc[0, "experiment_id"] == "exp_003" # Highest score
151
- assert df.loc[2, "experiment_id"] == "exp_002" # Lowest score
152
 
153
  @patch("app.requests.get")
154
  def test_extra_columns_filtered(self, mock_get, sample_csv_with_extra_columns):
@@ -156,11 +157,14 @@ class TestDownloadLeaderboard:
156
  mock_responses = create_github_api_response(sample_csv_with_extra_columns)
157
  mock_get.side_effect = mock_responses
158
 
159
- df = download_leaderboard()
160
 
161
- # Check that only display columns are present
162
- assert list(df.columns) == DISPLAY_COLUMNS
163
- assert "extra_col" not in df.columns
 
 
 
164
 
165
  @patch("app.requests.get")
166
  def test_missing_columns_error(self, mock_get, sample_csv_missing_columns):
@@ -211,11 +215,10 @@ class TestDownloadLeaderboard:
211
  mock_responses = create_github_api_response(csv_data)
212
  mock_get.side_effect = mock_responses
213
 
214
- df = download_leaderboard()
215
 
216
- assert isinstance(df, pd.DataFrame)
217
- assert len(df) == 0
218
- assert list(df.columns) == DISPLAY_COLUMNS
219
 
220
  @patch("app.requests.get")
221
  def test_invalid_date_handling(self, mock_get):
@@ -226,11 +229,13 @@ exp_002,0.789012,0.023456,0.765432,0.012345,Agent B,Claude-3,2024-01-20"""
226
  mock_responses = create_github_api_response(csv_with_invalid_date)
227
  mock_get.side_effect = mock_responses
228
 
229
- df = download_leaderboard()
230
 
231
- # Invalid dates should become NaT and then empty string or NaN
232
- assert pd.isna(df.loc[0, "Date"]) or df.loc[0, "Date"] == ""
233
- assert df.loc[1, "Date"] == "2024-01-20"
 
 
234
 
235
  @patch("app.requests.get")
236
  def test_git_lfs_pointer_file(self, mock_get, sample_csv_data):
@@ -244,12 +249,12 @@ exp_002,0.789012,0.023456,0.765432,0.012345,Agent B,Claude-3,2024-01-20"""
244
  mock_responses.append(download_response)
245
  mock_get.side_effect = mock_responses
246
 
247
- df = download_leaderboard()
248
 
249
  # Should successfully download via download_url
250
- assert isinstance(df, pd.DataFrame)
251
- assert len(df) == 3
252
- assert list(df.columns) == DISPLAY_COLUMNS
253
  # Should make 2 calls: API call + download_url call
254
  assert mock_get.call_count == 2
255
 
@@ -259,11 +264,11 @@ exp_002,0.789012,0.023456,0.765432,0.012345,Agent B,Claude-3,2024-01-20"""
259
  mock_responses = create_github_api_response(sample_csv_data, use_download_url=True)
260
  mock_get.side_effect = mock_responses
261
 
262
- df = download_leaderboard()
263
 
264
- assert isinstance(df, pd.DataFrame)
265
- assert len(df) == 3
266
- assert list(df.columns) == DISPLAY_COLUMNS
267
  # Should make 2 calls: API call + download_url call
268
  assert mock_get.call_count == 2
269
 
@@ -271,10 +276,22 @@ exp_002,0.789012,0.023456,0.765432,0.012345,Agent B,Claude-3,2024-01-20"""
271
  class TestRefreshLeaderboard:
272
  """Tests for refresh_leaderboard function."""
273
 
 
274
  @patch("app.download_leaderboard")
275
- def test_refresh_leaderboard_success(self, mock_download):
276
  """Test that refresh_leaderboard returns dataframe and status message."""
277
  # Setup mocks
 
 
 
 
 
 
 
 
 
 
 
278
  mock_df = pd.DataFrame(
279
  {
280
  "experiment_id": ["exp_001"],
@@ -282,13 +299,14 @@ class TestRefreshLeaderboard:
282
  "Agent": ["Agent A"],
283
  }
284
  )
285
- mock_download.return_value = mock_df
 
286
 
287
  # Execute
288
  df, status = refresh_leaderboard()
289
 
290
  # Assertions
291
- assert df is mock_df
292
  assert "Showing data from" in status
293
  assert "GitHub" in status
294
  # Check that status contains timestamp in expected format (YYYY-MM-DD HH:MM UTC)
@@ -300,12 +318,16 @@ class TestRefreshLeaderboard:
300
  timestamp_pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2} UTC"
301
  assert re.search(timestamp_pattern, status) is not None
302
  mock_download.assert_called_once()
 
303
 
 
304
  @patch("app.download_leaderboard")
305
- def test_refresh_leaderboard_includes_url(self, mock_download):
306
  """Test that status message includes the GitHub URL."""
 
307
  mock_df = pd.DataFrame()
308
- mock_download.return_value = mock_df
 
309
 
310
  df, status = refresh_leaderboard()
311
 
 
 
 
1
  import base64
2
  from unittest.mock import Mock, patch
3
 
 
6
  import requests
7
 
8
  from app import DISPLAY_COLUMNS, download_leaderboard, refresh_leaderboard
9
+ from src.leaderboard.read_evals import TabularLeaderboardEntry
10
 
11
 
12
  def create_github_api_response(csv_content, is_lfs_pointer=False, use_download_url=False):
 
85
  mock_get.side_effect = mock_responses
86
 
87
  # Execute
88
+ entries = download_leaderboard()
89
 
90
  # Assertions
91
+ assert isinstance(entries, list)
92
+ assert len(entries) == 3
93
+ assert all(isinstance(entry, TabularLeaderboardEntry) for entry in entries)
94
  assert mock_get.call_count == 1
95
 
96
  @patch("app.requests.get")
 
99
  mock_responses = create_github_api_response(sample_csv_data)
100
  mock_get.side_effect = mock_responses
101
 
102
+ entries = download_leaderboard()
103
 
104
+ # Check rounding - entries are sorted by score descending
105
+ assert entries[0].mean_normalized_score == 0.912
106
+ assert entries[1].mean_normalized_score == 0.854
107
+ assert entries[2].mean_normalized_score == 0.789
108
+ # Check that scores are floats
109
+ assert isinstance(entries[0].mean_normalized_score, float)
110
+ assert isinstance(entries[0].std_normalized_score, float)
111
 
112
  @patch("app.requests.get")
113
  def test_percentage_conversion(self, mock_get, sample_csv_data):
 
115
  mock_responses = create_github_api_response(sample_csv_data)
116
  mock_get.side_effect = mock_responses
117
 
118
+ entries = download_leaderboard()
119
 
120
  # Check percentage conversion (0.876543 * 100 = 87.6543, rounded to 87.7)
121
+ # Entries are sorted by score descending: exp_003 (92.3), exp_001 (87.7), exp_002 (76.5)
122
+ assert entries[0].mean_medal_pct == 92.3 # exp_003
123
+ assert entries[1].mean_medal_pct == 87.7 # exp_001
124
+ assert entries[2].mean_medal_pct == 76.5 # exp_002
125
 
126
  @patch("app.requests.get")
127
  def test_date_formatting(self, mock_get, sample_csv_data):
 
129
  mock_responses = create_github_api_response(sample_csv_data)
130
  mock_get.side_effect = mock_responses
131
 
132
+ entries = download_leaderboard()
133
 
134
+ # Check date formatting - entries sorted by score descending
135
+ # exp_003 (2024-02-01), exp_001 (2024-01-15), exp_002 (2024-01-20)
136
+ assert entries[0].date == "2024-02-01"
137
+ assert entries[1].date == "2024-01-15"
138
+ assert entries[2].date == "2024-01-20"
139
 
140
  @patch("app.requests.get")
141
  def test_sorting(self, mock_get, sample_csv_data):
142
+ """Test that entries are sorted by mean_normalized_score descending."""
143
  mock_responses = create_github_api_response(sample_csv_data)
144
  mock_get.side_effect = mock_responses
145
 
146
+ entries = download_leaderboard()
147
 
148
  # Check sorting (highest score first)
149
+ scores = [entry.mean_normalized_score for entry in entries]
150
  assert scores == sorted(scores, reverse=True)
151
+ assert entries[0].experiment_id == "exp_003" # Highest score
152
+ assert entries[2].experiment_id == "exp_002" # Lowest score
153
 
154
  @patch("app.requests.get")
155
  def test_extra_columns_filtered(self, mock_get, sample_csv_with_extra_columns):
 
157
  mock_responses = create_github_api_response(sample_csv_with_extra_columns)
158
  mock_get.side_effect = mock_responses
159
 
160
+ entries = download_leaderboard()
161
 
162
+ # Check that entries are created correctly (extra columns should be filtered before parsing)
163
+ assert len(entries) == 2
164
+ assert all(isinstance(entry, TabularLeaderboardEntry) for entry in entries)
165
+ # Verify the data model doesn't have extra columns by converting to dict
166
+ entry_dict = entries[0].to_dict()
167
+ assert "extra_col" not in entry_dict
168
 
169
  @patch("app.requests.get")
170
  def test_missing_columns_error(self, mock_get, sample_csv_missing_columns):
 
215
  mock_responses = create_github_api_response(csv_data)
216
  mock_get.side_effect = mock_responses
217
 
218
+ entries = download_leaderboard()
219
 
220
+ assert isinstance(entries, list)
221
+ assert len(entries) == 0
 
222
 
223
  @patch("app.requests.get")
224
  def test_invalid_date_handling(self, mock_get):
 
229
  mock_responses = create_github_api_response(csv_with_invalid_date)
230
  mock_get.side_effect = mock_responses
231
 
232
+ entries = download_leaderboard()
233
 
234
+ # Invalid dates should become NaT and then empty string
235
+ # Find entries by experiment_id since order may vary
236
+ entry_dict = {entry.experiment_id: entry for entry in entries}
237
+ assert entry_dict["exp_001"].date == "nan"
238
+ assert entry_dict["exp_002"].date == "2024-01-20"
239
 
240
  @patch("app.requests.get")
241
  def test_git_lfs_pointer_file(self, mock_get, sample_csv_data):
 
249
  mock_responses.append(download_response)
250
  mock_get.side_effect = mock_responses
251
 
252
+ entries = download_leaderboard()
253
 
254
  # Should successfully download via download_url
255
+ assert isinstance(entries, list)
256
+ assert len(entries) == 3
257
+ assert all(isinstance(entry, TabularLeaderboardEntry) for entry in entries)
258
  # Should make 2 calls: API call + download_url call
259
  assert mock_get.call_count == 2
260
 
 
264
  mock_responses = create_github_api_response(sample_csv_data, use_download_url=True)
265
  mock_get.side_effect = mock_responses
266
 
267
+ entries = download_leaderboard()
268
 
269
+ assert isinstance(entries, list)
270
+ assert len(entries) == 3
271
+ assert all(isinstance(entry, TabularLeaderboardEntry) for entry in entries)
272
  # Should make 2 calls: API call + download_url call
273
  assert mock_get.call_count == 2
274
 
 
276
  class TestRefreshLeaderboard:
277
  """Tests for refresh_leaderboard function."""
278
 
279
+ @patch("app.tabular_leaderboard_to_dataframe")
280
  @patch("app.download_leaderboard")
281
+ def test_refresh_leaderboard_success(self, mock_download, mock_to_df):
282
  """Test that refresh_leaderboard returns dataframe and status message."""
283
  # Setup mocks
284
+ mock_entry = TabularLeaderboardEntry(
285
+ experiment_id="exp_001",
286
+ agent="Agent A",
287
+ llms_used="GPT-4",
288
+ mean_normalized_score=0.85,
289
+ std_normalized_score=0.01,
290
+ mean_medal_pct=87.0,
291
+ sem_medal_pct=1.0,
292
+ date="2024-01-15",
293
+ )
294
+ mock_entries = [mock_entry]
295
  mock_df = pd.DataFrame(
296
  {
297
  "experiment_id": ["exp_001"],
 
299
  "Agent": ["Agent A"],
300
  }
301
  )
302
+ mock_download.return_value = mock_entries
303
+ mock_to_df.return_value = mock_df
304
 
305
  # Execute
306
  df, status = refresh_leaderboard()
307
 
308
  # Assertions
309
+ assert isinstance(df, pd.DataFrame)
310
  assert "Showing data from" in status
311
  assert "GitHub" in status
312
  # Check that status contains timestamp in expected format (YYYY-MM-DD HH:MM UTC)
 
318
  timestamp_pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2} UTC"
319
  assert re.search(timestamp_pattern, status) is not None
320
  mock_download.assert_called_once()
321
+ mock_to_df.assert_called_once_with(mock_entries)
322
 
323
+ @patch("app.tabular_leaderboard_to_dataframe")
324
  @patch("app.download_leaderboard")
325
+ def test_refresh_leaderboard_includes_url(self, mock_download, mock_to_df):
326
  """Test that status message includes the GitHub URL."""
327
+ mock_entries = []
328
  mock_df = pd.DataFrame()
329
+ mock_download.return_value = mock_entries
330
+ mock_to_df.return_value = mock_df
331
 
332
  df, status = refresh_leaderboard()
333