Skip to content

Commit c9a9103

Browse files
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents f105e2a + 5c75f78 commit c9a9103

14 files changed

Lines changed: 639 additions & 71 deletions

aws/setup/batch/environment.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
"type": "EC2",
1414
"allocationStrategy": "BEST_FIT_PROGRESSIVE",
1515
"minvCpus": 0,
16-
"maxvCpus": 200,
17-
"desiredvCpus": 0,
16+
"maxvCpus": 1000,
17+
"desiredvCpus": 400,
1818
"instanceTypes": [
19-
"m5.xlarge"
19+
"m5.4xlarge"
2020
],
2121
"subnets": [
2222
"subnet-02f03254ff2613e05",

aws/setup/batch/job_definition.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
"ulimits": [],
1818
"resourceRequirements": [
1919
{
20-
"value": "4",
20+
"value": "16",
2121
"type": "VCPU"
2222
},
2323
{
24-
"value": "14000",
24+
"value": "60000",
2525
"type": "MEMORY"
2626
}
2727
],

aws/setup/docker/build_and_push_aws.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ if [ -z "${GITHUB_TOKEN:-}" ]; then
5555
exit 1
5656
fi
5757

58+
# Verify AWS account identity
59+
echo -e "${YELLOW}Checking AWS account identity...${NC}"
60+
CURRENT_ACCOUNT=$(aws sts get-caller-identity --query Account --output text 2>/dev/null || echo "unknown")
61+
EXPECTED_ACCOUNT=$(echo $ECR_REGISTRY | cut -d'.' -f1)
62+
if [ "$CURRENT_ACCOUNT" != "$EXPECTED_ACCOUNT" ]; then
63+
echo -e "${RED}ERROR: Your current AWS account ($CURRENT_ACCOUNT) does NOT match the target ECR account ($EXPECTED_ACCOUNT)!${NC}"
64+
exit 1
65+
fi
66+
echo -e "${GREEN}✓ AWS account matches target ECR account${NC}"
67+
5868
# Build the Docker image
5969
echo -e "${YELLOW}Building Docker image...${NC}"
6070
FULL_IMAGE_NAME="$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

codeclash/agents/player.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def __init__(
4747
"agent_stats": {}, # mapping round -> agent stats
4848
}
4949

50-
if "branch_init" in config:
51-
self.logger.info(f"Initializing codebase from branch {config['branch_init']}")
52-
assert_zero_exit_code(self.environment.execute(f"git checkout {config['branch_init']}"), logger=self.logger)
50+
if branch := config.get("branch_init"):
51+
self.logger.info(f"Checking out branch {branch}")
52+
assert_zero_exit_code(self.environment.execute(f"git checkout {branch}"), logger=self.logger)
5353

5454
if self.push:
5555
self.logger.info("Will push agent gameplay as branch to remote repository after each round")
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
from pathlib import Path
2+
3+
import matplotlib.pyplot as plt
4+
import pandas as pd
5+
from matplotlib.ticker import AutoMinorLocator
6+
7+
from codeclash.analysis.viz.utils import FONT_BOLD, MODEL_TO_DISPLAY_NAME
8+
9+
# Load data
10+
df = pd.read_parquet(Path(__file__).parent / "aggregated_results.parquet")
11+
12+
# Map model names early to merge models that map to the same display name
13+
df["model_name"] = df["model_name"].map(lambda x: MODEL_TO_DISPLAY_NAME.get(x, x) if isinstance(x, str) else x)
14+
15+
# Define action category mappings
16+
action_category_mapping = {
17+
"Navigate/search": ["search", "navigate"],
18+
"Read source": ["read.source.new", "read.source.old"],
19+
"Read docs/logs": ["read.docs.new", "read.docs.old", "read.logs.new", "read.logs.old"],
20+
"Modify main": ["write.source.main.create", "write.source.main.modify_old", "write.source.main.modify_new"],
21+
"Write docs": ["write.docs.create", "write.docs.modify_new", "write.docs.modify_old"],
22+
"Write analysis/tests": [
23+
"write.source.analysis.create",
24+
"write.source.tests.create",
25+
"write.source.analysis.modify_new",
26+
"write.source.tests.modify_new",
27+
"write.source.analysis.modify_old",
28+
"write.source.tests.modify_old",
29+
],
30+
"Execute unittests": ["execute.unittest.in_mem", "execute.unittest.new", "execute.unittest.old"],
31+
"Execute analysis": ["execute.analysis.new", "execute.analysis.old", "execute.analysis.in_mem"],
32+
"Execute game": [
33+
"execute.game.setup.new",
34+
"execute.game.setup.old",
35+
"execute.game.new",
36+
"execute.game.old",
37+
"execute.game.setup.in_mem",
38+
"execute.game.in_mem",
39+
],
40+
}
41+
42+
action_category_mapping2 = {
43+
"Read": action_category_mapping["Navigate/search"]
44+
+ action_category_mapping["Read source"]
45+
+ action_category_mapping["Read docs/logs"],
46+
"Modify main": action_category_mapping["Modify main"],
47+
"Unittests": action_category_mapping["Execute unittests"]
48+
+ [
49+
"write.source.tests.create",
50+
"write.source.tests.modify_new",
51+
"write.source.tests.modify_old",
52+
],
53+
"Analysis": action_category_mapping["Execute analysis"]
54+
+ [
55+
"write.source.analysis.create",
56+
"write.source.analysis.modify_new",
57+
"write.source.analysis.modify_old",
58+
],
59+
"Simulations": action_category_mapping["Execute game"]
60+
+ [
61+
"execute.game.setup.new",
62+
"execute.game.setup.old",
63+
"execute.game.new",
64+
"execute.game.old",
65+
"execute.game.setup.in_mem",
66+
"execute.game.in_mem",
67+
],
68+
}
69+
70+
prefix = "c_"
71+
models = sorted([m for m in df["model_name"].unique() if isinstance(m, str)], reverse=True)
72+
73+
# Get all mapped actions for MISC calculation
74+
all_mapped_actions = set()
75+
for actions in action_category_mapping2.values():
76+
all_mapped_actions.update(actions)
77+
78+
# Collect data for each model
79+
category_order = list(action_category_mapping2.keys()) + ["MISC"]
80+
early_data = []
81+
late_data = []
82+
83+
for model in models:
84+
# Early rounds (≤7)
85+
early_model_df = df[(df["model_name"] == model) & (df["round_number"] <= 7)]
86+
early_category_values = []
87+
88+
for category in category_order[:-1]:
89+
total = 0
90+
for action in action_category_mapping2[category]:
91+
col = f"{prefix}{action}"
92+
if col in df.columns:
93+
total += early_model_df[col].mean()
94+
early_category_values.append(total)
95+
96+
# Get MISC value
97+
misc_total = 0
98+
for col in df.columns:
99+
if col.startswith(prefix):
100+
action = col.removeprefix(prefix)
101+
if action not in all_mapped_actions:
102+
misc_total += early_model_df[col].mean()
103+
early_category_values.append(misc_total)
104+
early_data.append(early_category_values)
105+
106+
# Late rounds (≥8)
107+
late_model_df = df[(df["model_name"] == model) & (df["round_number"] >= 8)]
108+
late_category_values = []
109+
110+
for category in category_order[:-1]:
111+
total = 0
112+
for action in action_category_mapping2[category]:
113+
col = f"{prefix}{action}"
114+
if col in df.columns:
115+
total += late_model_df[col].mean()
116+
late_category_values.append(total)
117+
118+
# Get MISC value
119+
misc_total = 0
120+
for col in df.columns:
121+
if col.startswith(prefix):
122+
action = col.removeprefix(prefix)
123+
if action not in all_mapped_actions:
124+
misc_total += late_model_df[col].mean()
125+
late_category_values.append(misc_total)
126+
late_data.append(late_category_values)
127+
128+
# Create horizontal stacked bar chart
129+
fig, ax = plt.subplots(figsize=(12, 8))
130+
131+
# Create y positions: two bars per model (touching), with gaps between models
132+
gap = 0.3
133+
y_positions = []
134+
model_positions = []
135+
for i, model in enumerate(models):
136+
base = i * (2 + gap)
137+
y_positions.extend([base, base + 1])
138+
model_positions.append(base + 0.5)
139+
140+
# Define colors for each category
141+
colors = plt.cm.tab10(range(len(category_order)))
142+
143+
# Plot late rounds (bottom bar) and early rounds (top bar) for each model
144+
all_data = []
145+
for early, late in zip(early_data, late_data):
146+
all_data.extend([late, early])
147+
148+
# Starting position for each stack
149+
left = [0] * len(y_positions)
150+
151+
for cat_idx, category in enumerate(category_order):
152+
values = [all_data[pos_idx][cat_idx] for pos_idx in range(len(y_positions))]
153+
ax.barh(y_positions, values, left=left, label=category, alpha=0.8, color=colors[cat_idx], height=1.0)
154+
155+
# Add value labels for significant values
156+
for i, (y_pos, val) in enumerate(zip(y_positions, values)):
157+
x_pos = left[i] + val / 2
158+
if val >= 1.0:
159+
ax.text(
160+
x_pos,
161+
y_pos,
162+
f"{val:.1f}",
163+
fontsize=11,
164+
ha="center",
165+
va="center",
166+
color="white",
167+
fontweight="bold",
168+
fontproperties=FONT_BOLD,
169+
)
170+
elif val >= 0.5:
171+
ax.text(
172+
x_pos,
173+
y_pos,
174+
f"{val:.1f}",
175+
fontsize=10,
176+
ha="center",
177+
va="center",
178+
color="white",
179+
fontweight="bold",
180+
fontproperties=FONT_BOLD,
181+
)
182+
183+
left = [left[i] + values[i] for i in range(len(y_positions))]
184+
185+
# Add total bar length numbers at the end of each bar
186+
for y_pos, total in zip(y_positions, left):
187+
ax.text(
188+
total + 0.5,
189+
y_pos,
190+
f"{total:.1f}",
191+
fontsize=12,
192+
ha="left",
193+
va="center",
194+
color="black",
195+
fontweight="bold",
196+
fontproperties=FONT_BOLD,
197+
)
198+
199+
# Add round labels on the left side of the plot
200+
for i, (y_late, y_early) in enumerate(zip(y_positions[::2], y_positions[1::2])):
201+
ax.text(-0.2, y_late, "round ≥8", fontsize=11, ha="right", va="center", color="gray", fontproperties=FONT_BOLD)
202+
ax.text(-0.2, y_early, "round ≤7", fontsize=11, ha="right", va="center", color="gray", fontproperties=FONT_BOLD)
203+
204+
legend_font = FONT_BOLD.copy()
205+
legend_font.set_size(15)
206+
ax.legend(loc="upper center", bbox_to_anchor=(0.5, 1.05), frameon=False, prop=legend_font, ncol=6)
207+
ax.set_yticks(model_positions)
208+
ax.set_yticklabels(models, fontsize=14, fontproperties=FONT_BOLD)
209+
ax.set_xlabel("Mean Action Count", fontsize=15, fontproperties=FONT_BOLD)
210+
211+
# Add minor ticks to x-axis
212+
ax.xaxis.set_minor_locator(AutoMinorLocator())
213+
ax.tick_params(axis="x", which="minor", length=3)
214+
ax.tick_params(axis="x", which="major", length=6)
215+
216+
# Remove top and right spines
217+
ax.spines["top"].set_visible(False)
218+
ax.spines["right"].set_visible(False)
219+
ax.spines["left"].set_visible(True)
220+
221+
# Set tick label fonts
222+
for label in ax.get_xticklabels():
223+
label.set_fontproperties(FONT_BOLD)
224+
label.set_fontsize(14)
225+
for label in ax.get_yticklabels():
226+
label.set_fontproperties(FONT_BOLD)
227+
228+
plt.tight_layout()
229+
230+
# Save to PDF
231+
output_path = Path(__file__).parent / "action_categories_by_model.pdf"
232+
plt.savefig(output_path, format="pdf", bbox_inches="tight")
233+
print(f"Plot saved to {output_path}")

codeclash/analysis/llm_as_judge/aggregate_results.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from codeclash.analysis.llm_as_judge.categorize_actions import _all_categories as ACTION_CATEGORIES
1515
from codeclash.analysis.llm_as_judge.hallucination import claim_categories as CLAIM_CATEGORIES
1616
from codeclash.analysis.llm_as_judge.hallucination import source_categories as SOURCE_CATEGORIES
17-
from codeclash.analysis.llm_as_judge.utils import Instance
17+
from codeclash.analysis.llm_as_judge.utils import Instance, InstanceBatch
1818
from codeclash.utils.log import get_logger
1919

2020
logger = get_logger("AggregateResults", emoji="📊")
@@ -36,9 +36,13 @@ def __init__(self):
3636
self.action_categories_data_id = f"action_categories_v{ACTION_CATEGORIES_VERSION}"
3737
self.hallucination_data_id = f"hallucination_v{HALLUCINATION_VERSION}"
3838

39-
def aggregate_results_to_dataframe(self, input_dir: Path) -> pd.DataFrame:
39+
def aggregate_results_to_dataframe(self, input_dir: Path, *, instance_ids: set[str] | None = None) -> pd.DataFrame:
4040
"""Aggregate all llm_as_judge.json results from the input directory into a DataFrame.
4141
42+
Args:
43+
input_dir: Directory containing llm_as_judge.json files
44+
instance_ids: If provided, only include results for these instance IDs
45+
4246
Returns:
4347
DataFrame with flattened structure containing all evaluation data merged by instance_id
4448
"""
@@ -47,6 +51,8 @@ def aggregate_results_to_dataframe(self, input_dir: Path) -> pd.DataFrame:
4751
llm_judge_files = list(input_dir.rglob("llm_as_judge.json"))
4852

4953
logger.info(f"Found {len(llm_judge_files)} llm_as_judge.json files")
54+
if instance_ids is not None:
55+
logger.info(f"Filtering to {len(instance_ids)} specific instances")
5056

5157
for file_path in llm_judge_files:
5258
logger.debug(f"Processing {file_path}")
@@ -70,6 +76,10 @@ def aggregate_results_to_dataframe(self, input_dir: Path) -> pd.DataFrame:
7076
continue
7177

7278
for instance_id, instance_data in instances.items():
79+
# Skip if we're filtering and this instance isn't in the filter set
80+
if instance_ids is not None and instance_id not in instance_ids:
81+
continue
82+
7383
# Initialize instance data if not seen before
7484
if instance_id not in instance_data_dict:
7585
instance_data_dict[instance_id] = self._initialize_instance_row(instance_data)
@@ -82,6 +92,15 @@ def aggregate_results_to_dataframe(self, input_dir: Path) -> pd.DataFrame:
8292
except Exception as e:
8393
logger.error(f"Error processing {file_path}: {e}", exc_info=True)
8494

95+
# Check if all expected instances were found
96+
if instance_ids is not None:
97+
found_instance_ids = set(instance_data_dict.keys())
98+
missing_instance_ids = instance_ids - found_instance_ids
99+
if missing_instance_ids:
100+
raise ValueError(
101+
f"Could not find {len(missing_instance_ids)} instances in llm_as_judge.json files: {sorted(missing_instance_ids)}"
102+
)
103+
85104
# Convert to list of rows for DataFrame
86105
rows = list(instance_data_dict.values())
87106
df = pd.DataFrame(rows)
@@ -220,15 +239,29 @@ def main() -> None:
220239
parser.add_argument(
221240
"-o", "--output-file", type=Path, help="Path to the output Parquet file", default="aggregated_results.parquet"
222241
)
242+
parser.add_argument(
243+
"--instance-file",
244+
type=Path,
245+
help="Path to instances.json file (output of get_instances.py) to filter to specific instances",
246+
)
223247
args = parser.parse_args()
224248

225249
if not args.input_dir.exists():
226250
logger.error(f"Input directory does not exist: {args.input_dir}")
227251
return
228252

253+
# Load instance filter if provided
254+
instance_ids = None
255+
if args.instance_file is not None:
256+
if not args.instance_file.exists():
257+
raise FileNotFoundError(f"Instance file does not exist: {args.instance_file}")
258+
instance_batch = InstanceBatch.model_validate_json(args.instance_file.read_text())
259+
instance_ids = {instance.instance_id for instance in instance_batch.instances}
260+
logger.info(f"Loaded {len(instance_ids)} instances from {args.instance_file}")
261+
229262
logger.info(f"Aggregating results from {args.input_dir}")
230263
aggregator = ResultsAggregator()
231-
df = aggregator.aggregate_results_to_dataframe(args.input_dir)
264+
df = aggregator.aggregate_results_to_dataframe(args.input_dir, instance_ids=instance_ids)
232265

233266
df.to_parquet(args.output_file, compression="snappy", index=False)
234267
logger.info(f"Wrote aggregated results to {args.output_file}")

0 commit comments

Comments
 (0)