Files
2026-07-13 12:49:17 +08:00

390 lines
15 KiB
Python
Executable File

import os
import sys
import traceback
from collections import Counter
from concurrent.futures import ProcessPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
from tqdm import tqdm
from trae_agent.utils.config import ModelConfig
from .sandbox import Sandbox
from .selector_agent import CandidatePatch, SelectorAgent
from .utils import clean_patch, get_trajectory_filename, save_patches, save_selection_success
def run_instance(
*,
instance,
candidate_log,
output_path,
max_retry,
num_candidate,
tools_path,
statistics_path,
group_size,
llm_config,
max_turn,
log_path,
patches_path,
majority_voting=True,
):
# candidate_log is a list of num_candidate candidate patches
# divide candidate_log into groups of group_size
groups = []
for i in range(0, num_candidate, group_size):
this_group = {
"instance_id": candidate_log["instance_id"],
"issue": candidate_log["issue"],
"patches": candidate_log["patches"][i : i + group_size],
"regressions": candidate_log["regressions"][i : i + group_size],
"success_id": candidate_log["success_id"][i : i + group_size],
}
groups.append(this_group)
for group_id, group in enumerate(groups):
run_instance_by_group(
instance=instance,
candidate_log=group,
output_path=output_path,
max_retry=max_retry,
num_candidate=len(group),
tools_path=tools_path,
statistics_path=statistics_path,
llm_config=llm_config,
max_turn=max_turn,
log_path=log_path,
patches_path=patches_path,
group_id=group_id,
num_groups=len(groups),
majority_voting=majority_voting,
)
def run_instance_by_group(
*,
instance,
candidate_log,
output_path,
max_retry,
num_candidate,
tools_path,
statistics_path,
llm_config,
max_turn,
log_path,
patches_path,
group_id,
num_groups,
majority_voting=True,
):
print(f"[Group {group_id}/{num_groups}] processing: {instance['instance_id']}")
sys.stdout.flush()
sys.stderr.flush()
# check if the group has already been processed: the statistics json file exists and is not empty
file_path = statistics_path + f"/group_{group_id}/{instance['instance_id']}.json"
if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
print(
f"[Group {group_id}/{num_groups}] for instance {instance['instance_id']} has already been processed. Skipping..."
)
sys.stdout.flush()
sys.stderr.flush()
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
return
# check if the group is all failed or all success. If so, skip this group
all_failed = True
all_success = True
for success_id in candidate_log["success_id"]:
if success_id == 1:
all_failed = False
if success_id != 1:
all_success = False
if all_failed or all_success:
print(
f"[Group ID {group_id} in {num_groups}] groups for instance {instance['instance_id']} {'all failed' if all_failed else 'all success'}. Skipping..."
)
sys.stdout.flush()
sys.stderr.flush()
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
save_patches(
instance_id=instance["instance_id"],
patches_path=patches_path,
patches=candidate_log["patches"][0],
group_id=group_id,
)
if all_failed:
save_selection_success(
instance_id=instance["instance_id"],
statistics_path=statistics_path,
patch_id=0,
is_success=0,
group_id=group_id,
is_all_failed=True,
is_all_success=False,
)
if all_success:
save_selection_success(
instance_id=instance["instance_id"],
statistics_path=statistics_path,
patch_id=0,
is_success=1,
group_id=group_id,
is_all_success=True,
is_all_failed=False,
)
return
log_dir_path = Path(output_path) / f"group_{group_id}"
log_dir_path.mkdir(parents=True, exist_ok=True)
log_file_path = log_dir_path / f"{instance['instance_id']}.log"
with open(log_file_path, "w") as log_file:
sys.stdout = log_file
sys.stderr = log_file
namespace = "swebench"
image_name = "sweb.eval.x86_64." + instance["instance_id"].replace("__", "_1776_")
tag = "latest"
try:
current_try = 0
while current_try < max_retry:
print("current_try:", current_try)
sys.stdout.flush()
sys.stderr.flush()
print("time: ", datetime.now().strftime("%Y%m%d%H%M%S"))
sys.stdout.flush()
sys.stderr.flush()
current_try += 1
sandbox = None
try:
candidate_list = []
for idx in range(len(candidate_log["patches"])):
if candidate_log["patches"][idx].strip() == "":
continue
cleaned_patch = clean_patch(candidate_log["patches"][idx])
is_success_regression = len(candidate_log["regressions"][idx]) == 0
candidate_list.append(
CandidatePatch(
idx,
candidate_log["patches"][idx],
cleaned_patch,
is_success_regression,
candidate_log["success_id"][idx],
)
)
# regression testing
candidate_list_regression = [
candidate for candidate in candidate_list if candidate.is_success_regression
]
if len(candidate_list_regression):
candidate_list = candidate_list_regression
print(f"[Retry No:{current_try}] regression testing done")
sys.stdout.flush()
sys.stderr.flush()
# patch deduplication
candidate_list_deduplication, cleaned_candidate_set = [], set()
for candidate in candidate_list:
if candidate.cleaned_patch not in cleaned_candidate_set:
cleaned_candidate_set.add(candidate.cleaned_patch)
candidate_list_deduplication.append(candidate)
candidate_list = candidate_list_deduplication
print(f"[Retry No:{current_try}] patch deduplication done")
sys.stdout.flush()
sys.stderr.flush()
# sandbox & tools
sandbox = Sandbox(namespace, image_name, tag, instance, tools_path)
sandbox.start_container()
project_path = sandbox.get_project_path()
print(f"[Retry No:{current_try}] sandbox & tools done")
sys.stdout.flush()
sys.stderr.flush()
# majority voting
if majority_voting:
final_id_list, final_patch_list = [], []
for idx in range(num_candidate):
select_agent = SelectorAgent(
llm_config=llm_config,
sandbox=sandbox,
project_path=project_path,
issue_description=instance["problem_statement"],
trajectory_file_name=get_trajectory_filename(
instance["instance_id"], log_path, group_id, idx
),
candidate_list=candidate_list,
max_turn=max_turn,
)
final_id, final_patch = select_agent.run()
final_id_list.append(final_id)
final_patch_list.append(final_patch)
if max(Counter(final_id_list).values()) > num_candidate / 2:
break
print(f"[Retry No:{current_try}] majority voting done")
sys.stdout.flush()
sys.stderr.flush()
counter = Counter(final_id_list)
max_count = max(counter.values())
most_common_ids = [
elem for elem, count in counter.items() if count == max_count
]
result = {}
for id_ in most_common_ids:
indexes = [i for i, val in enumerate(final_id_list) if val == id_]
result[id_] = indexes
final_id = most_common_ids[0]
final_patch = final_patch_list[result[final_id][0]]
print(f"[Retry No:{current_try}] final_id_list: {final_id_list}")
sys.stdout.flush()
sys.stderr.flush()
else:
select_agent = SelectorAgent(
llm_config=llm_config,
sandbox=sandbox,
project_path=project_path,
issue_description=instance["problem_statement"],
trajectory_file_name=get_trajectory_filename(
instance["instance_id"], log_path, group_id, 0
),
candidate_list=candidate_list,
max_turn=max_turn,
)
final_id, final_patch = select_agent.run()
save_patches(
instance_id=instance["instance_id"],
patches_path=patches_path,
patches=final_patch,
group_id=group_id,
)
is_success_patch = 0
for candidate in candidate_list:
if final_id == candidate.id:
is_success_patch = candidate.is_success_patch
save_selection_success(
instance_id=instance["instance_id"],
statistics_path=statistics_path,
patch_id=final_id,
is_success=is_success_patch,
group_id=group_id,
)
sandbox.stop_container()
break
except Exception as e:
print(f"Error occurred: {e}")
sys.stdout.flush()
sys.stderr.flush()
print("Detailed Error:\n", traceback.format_exc())
sys.stdout.flush()
sys.stderr.flush()
if sandbox is not None:
sandbox.stop_container()
finally:
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
print(f" finished: {instance['instance_id']}")
class SelectorEvaluation:
def __init__(
self,
llm_config: ModelConfig,
num_candidate: int,
max_retry: int,
max_turn: int,
log_path: str,
output_path: str,
patches_path: str,
instance_list: list,
candidate_dic: dict[str, dict],
tools_path: str,
statistics_path: str,
group_size: int,
majority_voting: bool = True,
):
self.llm_config = llm_config
self.num_candidate = num_candidate
self.max_retry = max_retry
self.log_path = log_path
self.output_path = output_path
self.patches_path = patches_path
self.instance_list = instance_list
self.candidate_dic = candidate_dic
self.max_turn = max_turn
self.tools_path = tools_path
self.statistics_path = statistics_path
self.group_size = group_size
self.majority_voting = majority_voting
def run_all(self, max_workers=None):
"""Run all instances concurrently using ThreadPoolExecutor.
Args:
max_workers: Maximum number of worker threads. If None, defaults to min(32, os.cpu_count() + 4)
"""
with ProcessPoolExecutor(max_workers=max_workers) as ex:
futures = {
ex.submit(
run_instance,
instance=instance,
candidate_log=self.candidate_dic[instance["instance_id"]],
output_path=self.output_path,
max_retry=self.max_retry,
num_candidate=self.num_candidate,
tools_path=self.tools_path,
statistics_path=self.statistics_path,
group_size=self.group_size,
llm_config=self.llm_config,
max_turn=self.max_turn,
log_path=self.log_path,
patches_path=self.patches_path,
majority_voting=self.majority_voting,
): instance["instance_id"]
for instance in self.instance_list
}
with tqdm(total=len(futures), ascii=True, desc="Processing instances") as pbar:
for fut in as_completed(futures):
iid = futures[fut]
try:
result_iid = fut.result()
pbar.set_postfix({"completed": result_iid})
except Exception:
result_iid = iid
print(traceback.format_exc())
sys.stdout.flush()
sys.stderr.flush()
finally:
pbar.update(1)
def run_one(self, instance_id):
for idx in range(len(self.instance_list)):
if instance_id == self.instance_list[idx]["instance_id"]:
run_instance(
instance=self.instance_list[idx],
candidate_log=self.candidate_dic[instance_id],
output_path=self.output_path,
max_retry=self.max_retry,
num_candidate=self.num_candidate,
tools_path=self.tools_path,
statistics_path=self.statistics_path,
group_size=self.group_size,
llm_config=self.llm_config,
max_turn=self.max_turn,
log_path=self.log_path,
patches_path=self.patches_path,
majority_voting=self.majority_voting,
)