Skip to content

Commit 41e80a5

Browse files
committed
Add OpenAI sweep tooling and analysis helpers
- add benchmark runners, watcher, and eval pipeline scripts for OpenAI sweeps - add generated configs, reporting utilities, and OpenAI feedback notes - preserve model aliases in analysis and fix RoboCode tie handling
1 parent a66d63e commit 41e80a5

58 files changed

Lines changed: 4512 additions & 27 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codeclash/analysis/metrics/elo.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from tqdm import tqdm
1515

1616
from codeclash.analysis.significance import calculate_p_value
17-
from codeclash.analysis.viz.utils import ASSETS_DIR, FONT_BOLD, MODEL_TO_DISPLAY_NAME
17+
from codeclash.analysis.viz.utils import ASSETS_DIR, FONT_BOLD, MODEL_TO_DISPLAY_NAME, model_display_name
1818
from codeclash.constants import LOCAL_LOG_DIR, RESULT_TIE
1919
from codeclash.utils.log import add_file_handler, get_logger
2020

@@ -75,9 +75,6 @@ def __init__(
7575
lambda: defaultdict(list)
7676
)
7777

78-
def _get_unique_model_name(self, model: str) -> str:
79-
return model.rpartition("/")[2]
80-
8178
def _get_sorted_pair(self, p1: str, p2: str) -> tuple[str, str]:
8279
return tuple(sorted([p1, p2]))
8380

@@ -154,8 +151,6 @@ def _process_tournament(self, metadata_path: Path) -> None:
154151
return
155152

156153
player_names = [p["name"] for p in players]
157-
models = [p["config"]["model"]["model_name"].strip("@") for p in players]
158-
159154
# Aggregate scores for each round
160155
p1_round_scores = []
161156
p2_round_scores = []
@@ -199,7 +194,7 @@ def _process_tournament(self, metadata_path: Path) -> None:
199194
p2_score = sum(p2_round_scores)
200195

201196
# Convert to unique names and sorted pair when updating matrix
202-
unique_names = [self._get_unique_model_name(m) for m in models]
197+
unique_names = player_names
203198
sorted_pair = self._get_sorted_pair(unique_names[0], unique_names[1])
204199

205200
if unique_names[0] == sorted_pair[0]:
@@ -550,7 +545,7 @@ def create_elo_plots(self, output_dir: Path) -> None:
550545
player_order = [all_players[i] for i in all_indices]
551546

552547
# Translate to display names
553-
display_names = [MODEL_TO_DISPLAY_NAME.get(p, p) for p in player_order]
548+
display_names = [model_display_name(p) for p in player_order]
554549

555550
# Create mapping from player to y-position
556551
player_to_pos = {p: i for i, p in enumerate(player_order)}
@@ -698,7 +693,7 @@ def create_validation_plots(self, output_dir: Path, regularization: float = 0.01
698693

699694
ax.set_xlabel("BT Strength", fontproperties=FONT_BOLD, fontsize=12)
700695
ax.set_ylabel("Negative Log-Likelihood", fontproperties=FONT_BOLD, fontsize=12)
701-
display_name = MODEL_TO_DISPLAY_NAME.get(player, player)
696+
display_name = model_display_name(player)
702697
ax.set_title(display_name, fontproperties=FONT_BOLD, fontsize=14)
703698
legend = ax.legend(prop=FONT_BOLD, fontsize=10, loc="upper right")
704699
legend.set_frame_on(False)
@@ -777,7 +772,7 @@ def _create_rank_matrix_plot(
777772
rank_matrix = (rank_matrix / self.n_bootstrap) * 100
778773

779774
# Translate player names to display names
780-
display_names = [MODEL_TO_DISPLAY_NAME.get(p, p) for p in players]
775+
display_names = [model_display_name(p) for p in players]
781776

782777
fig, ax = plt.subplots(figsize=(6, 6))
783778
im = ax.imshow(rank_matrix, cmap="YlOrRd", aspect="auto", vmin=0, vmax=100)
@@ -826,7 +821,7 @@ def _create_elo_violin_plot(
826821
elo_data = [elo_samples[p] for p in players]
827822

828823
# Translate player names to display names
829-
display_names = [MODEL_TO_DISPLAY_NAME.get(p, p) for p in players]
824+
display_names = [model_display_name(p) for p in players]
830825

831826
fig, ax = plt.subplots(figsize=(6, 6))
832827

@@ -1095,7 +1090,7 @@ def _plot_results(self, results_by_max_round: dict[int, dict[str, dict[str, floa
10951090
elos_list.append(results_by_max_round[max_round][game_name][player])
10961091

10971092
if max_rounds_list:
1098-
display_name = MODEL_TO_DISPLAY_NAME.get(player, player)
1093+
display_name = model_display_name(player)
10991094
ax.plot(max_rounds_list, elos_list, marker="o", label=display_name, linewidth=2, markersize=6)
11001095

11011096
ax.set_xlabel("Max Round", fontproperties=FONT_BOLD, fontsize=14)
@@ -1212,7 +1207,7 @@ def _plot_results(self, results_by_round: dict[int, dict[str, dict[str, float]]]
12121207
elos_list.append(results_by_round[round_num][game_name][player])
12131208

12141209
if rounds_list:
1215-
display_name = MODEL_TO_DISPLAY_NAME.get(player, player)
1210+
display_name = model_display_name(player)
12161211
ax.plot(rounds_list, elos_list, marker="o", label=display_name, linewidth=2, markersize=6)
12171212

12181213
ax.set_xlabel("Round", fontproperties=FONT_BOLD, fontsize=14)
@@ -1348,7 +1343,7 @@ def write_latex_table(results: dict[str, dict], output_dir: Path) -> None:
13481343
lines.append(r"\midrule")
13491344

13501345
for player, all_elo in sorted_players:
1351-
display_name = MODEL_TO_DISPLAY_NAME.get(player, player)
1346+
display_name = model_display_name(player)
13521347
row_parts = [display_name.replace("_", r"\_")]
13531348

13541349
for game_name in games_in_table:
@@ -1407,7 +1402,7 @@ def write_website_results(results: dict[str, dict], output_dir: Path) -> None:
14071402
# Create leaderboard entries
14081403
board = []
14091404
for rank, (player, elo) in enumerate(sorted_players):
1410-
entry = {"rank": rank + 1, "model": MODEL_TO_DISPLAY_NAME.get(player, player), "elo": int(round(elo))}
1405+
entry = {"rank": rank + 1, "model": model_display_name(player), "elo": int(round(elo))}
14111406
# Add confidence interval if available
14121407
if elo_std is not None:
14131408
player_idx = players.index(player)
@@ -1506,7 +1501,7 @@ def write_latex_table_plain(results: dict[str, dict], output_dir: Path) -> None:
15061501
lines.append(r"\midrule")
15071502

15081503
for player, all_elo in sorted_players:
1509-
display_name = MODEL_TO_DISPLAY_NAME.get(player, player)
1504+
display_name = model_display_name(player)
15101505
row_parts = [display_name.replace("_", r"\_")]
15111506

15121507
for game_name in games_in_table:

codeclash/analysis/metrics/win_rate.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ def main(log_dir: Path):
3131
model_profiles = {}
3232
for game_log_folder in tqdm([x.parent for x in log_dir.rglob("metadata.json")]):
3333
game_id = game_log_folder.name.split(".")[1]
34-
player_ids = [x.name for x in (game_log_folder / "players").iterdir() if x.is_dir()]
3534
metadata = json.load(open(game_log_folder / "metadata.json"))
3635
try:
37-
player_to_model = {
38-
x["name"]: x["config"]["model"]["model_name"].strip("@").split("/")[-1]
39-
for x in metadata["config"]["players"]
40-
}
36+
player_ids = [x["name"] for x in metadata["config"]["players"]]
37+
player_to_model = {x["name"]: x["name"] for x in metadata["config"]["players"]}
4138
except KeyError:
4239
continue
43-
num_rounds = len(metadata["round_stats"])
40+
round_stats = metadata.get("round_stats")
41+
if not isinstance(round_stats, dict) or not round_stats:
42+
continue
43+
num_rounds = len(round_stats)
4444

4545
# Only count each unique model once per game
4646
unique_models = {player_to_model[player] for player in player_ids}
@@ -55,7 +55,7 @@ def main(log_dir: Path):
5555
player_id=player_id, model_name=model_name, game_id=game_id, count=num_rounds
5656
)
5757

58-
for round, details in metadata["round_stats"].items():
58+
for round, details in round_stats.items():
5959
if round == "0":
6060
# Skip initial round
6161
continue

codeclash/analysis/viz/heatmap_win_rates.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def main(log_dir: Path, unit: str = "rounds", output_file: Path = ASSETS_DIR / "
5959

6060
# Build matrix
6161
models = sorted({m for pair in results.keys() for m in pair})
62-
clean_names = [MODEL_TO_DISPLAY_NAME[m.split("/")[-1]] for m in models]
62+
clean_names = [MODEL_TO_DISPLAY_NAME.get(m.split("/")[-1], m.split("/")[-1]) for m in models]
6363
n = len(models)
6464

6565
matrix = np.full((n, n), np.nan)
@@ -73,7 +73,8 @@ def main(log_dir: Path, unit: str = "rounds", output_file: Path = ASSETS_DIR / "
7373
total_wins = sum(results[(m1, m2)][0] for m2 in models if m1 != m2)
7474
total_matches = sum(results[(m1, m2)][1] for m2 in models if m1 != m2)
7575
avg_win_rate = total_wins / total_matches if total_matches > 0 else 0
76-
print(f"{MODEL_TO_DISPLAY_NAME[m1.split('/')[-1]]}: {avg_win_rate:.2%} win rate over {total_matches} matches")
76+
label = MODEL_TO_DISPLAY_NAME.get(m1.split("/")[-1], m1.split("/")[-1])
77+
print(f"{label}: {avg_win_rate:.2%} win rate over {total_matches} matches")
7778

7879
# Plot
7980
FONT_BOLD.set_size(18)

codeclash/analysis/viz/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@
2121
"o3": "o3",
2222
}
2323

24+
25+
def model_display_name(model: str) -> str:
26+
label = MODEL_TO_DISPLAY_NAME.get(model, model)
27+
tier_labels = {
28+
"-default": " (Default)",
29+
"-low": " (Low)",
30+
"-medium": " (Medium)",
31+
"-high": " (High)",
32+
}
33+
for suffix, pretty in tier_labels.items():
34+
if model.endswith(suffix):
35+
base = model[: -len(suffix)]
36+
base_label = MODEL_TO_DISPLAY_NAME.get(base, base)
37+
return f"{base_label}{pretty}"
38+
return label
39+
2440
MODEL_TO_COLOR = {
2541
"anthropic/claude-sonnet-4-20250514": "#FFD449",
2642
"anthropic/claude-sonnet-4-5-20250929": "#F75C03",

codeclash/arenas/huskybench/HuskyBench.Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ RUN git clone https://github.com/CodeClash-ai/HuskyBench.git /workspace \
1616
&& git remote set-url origin https://github.com/CodeClash-ai/HuskyBench.git
1717
WORKDIR /workspace
1818

19-
RUN pip install -r engine/requirements.txt
19+
RUN pip install --no-cache-dir Cython setuptools wheel \
20+
&& pip install --no-cache-dir -r engine/requirements.txt
2021
RUN mkdir -p /workspace/engine/output

codeclash/arenas/robocode/robocode.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from codeclash.agents.player import Player
1212
from codeclash.arenas.arena import CodeArena, RoundStats
13+
from codeclash.constants import RESULT_TIE
1314
from codeclash.utils.environment import create_file_in_container
1415

1516
RC_FILE = Path("MyTank.java")
@@ -140,7 +141,13 @@ def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
140141
player = match.group(2).rsplit(".", 1)[0]
141142
scores[player] += int(match.group(3))
142143

143-
stats.winner = max(scores, key=scores.get)
144+
if not scores:
145+
stats.winner = RESULT_TIE
146+
return
147+
148+
max_score = max(scores.values())
149+
leaders = [player for player, score in scores.items() if score == max_score]
150+
stats.winner = RESULT_TIE if len(leaders) > 1 else leaders[0]
144151
stats.scores = scores
145152
for player, score in scores.items():
146153
stats.player_stats[player].score = score

0 commit comments

Comments
 (0)