repomind-api / scripts /run_real_benchmark.py
SouravNath's picture
fix: pass base_commit to API so patches apply at correct SWE-bench commit
e7c4b2b
Raw
History Blame Contribute Delete
11.8 kB
"""
scripts/run_real_benchmark.py
─────────────────────────────
Automated SWE-bench evaluation using your live HuggingFace API.
Usage:
# Wake up the HF space first, then run
# Run 50 issues (recommended first run)
python scripts/run_real_benchmark.py --max 50
# Run specific number
python scripts/run_real_benchmark.py --max 20
# Resume interrupted run
python scripts/run_real_benchmark.py --max 50 --resume
Output:
results/real_benchmark/results.jsonl β€” one line per issue
results/real_benchmark/summary.json β€” final resolve rate
Requirements:
pip install websockets httpx
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from datetime import datetime
from pathlib import Path
import httpx
from datetime import timezone
import websockets.sync.client as wsclient
# ── Config ────────────────────────────────────────────────────────────────────
API_BASE = "https://souravnath-repomind-api.hf.space"
MAX_WAIT = 240 # seconds to wait per issue before timeout
DELAY_BTW = 5 # seconds between issues (avoid rate limits)
OUT_DIR = Path("results/real_benchmark")
# ── Helpers ───────────────────────────────────────────────────────────────────
def wake_up_space() -> bool:
"""Ping the API to wake up sleeping HF Space. Returns True if alive."""
print("Waking up HuggingFace Space (may take 30-60s)...", flush=True)
for attempt in range(3):
try:
r = httpx.get(f"{API_BASE}/api/metrics", timeout=60)
print(f" Space is awake! (status {r.status_code})")
return True
except Exception as e:
print(f" Attempt {attempt+1}/3 β€” waiting... ({e})")
time.sleep(15)
print(" Warning: Space may still be starting up, continuing anyway.")
return False
def submit_issue(repo: str, problem: str, base_commit: str = "", max_attempts: int = 3) -> str | None:
"""POST to /api/solve and return task_id."""
try:
r = httpx.post(
f"{API_BASE}/api/solve",
json={
"repo": repo,
"problem_statement": problem,
"base_commit": base_commit, # ← checkout exact SWE-bench commit
"max_attempts": max_attempts,
"top_k_files": 5,
},
timeout=90,
)
r.raise_for_status()
return r.json().get("task_id")
except Exception as e:
print(f" [submit error] {e}")
return None
def wait_for_result(task_id: str, timeout: int = MAX_WAIT) -> dict:
"""
Connect to WebSocket and collect events until 'done'.
Returns the final result dict.
"""
ws_url = f"wss://souravnath-repomind-api.hf.space/ws/{task_id}"
result = {
"task_id": task_id,
"resolved": False,
"patch": "",
"attempts": 0,
"tokens": 0,
"elapsed": 0,
"error": "",
"localised_files": [],
}
deadline = time.time() + timeout
try:
with wsclient.connect(ws_url, open_timeout=30) as ws:
while time.time() < deadline:
remaining = max(2.0, deadline - time.time())
try:
raw = ws.recv(timeout=remaining)
except TimeoutError:
break
except Exception:
break
try:
msg = json.loads(raw)
except json.JSONDecodeError:
continue
event = msg.get("event", "")
if event == "patch":
result["patch"] = msg.get("data", "")
elif event == "localised_files":
result["localised_files"] = msg.get("data", [])
elif event == "done":
data = msg.get("data", {})
result["resolved"] = data.get("resolved", False)
result["attempts"] = data.get("attempts", 0)
result["tokens"] = data.get("total_tokens", 0)
result["elapsed"] = data.get("elapsed_seconds", 0)
break
elif event == "log":
# Print live logs so user can see progress
log_msg = msg.get("data", {})
if isinstance(log_msg, dict):
print(f" β€Ί {log_msg.get('message', '')[:100]}")
except Exception as e:
result["error"] = str(e)[:200]
# ── Fallback: poll REST endpoint if WebSocket missed the done event ──────
if result["attempts"] == 0 and not result["error"]:
print(" (WebSocket incomplete β€” polling REST fallback...)")
poll_deadline = time.time() + timeout
while time.time() < poll_deadline:
time.sleep(8)
try:
r = httpx.get(f"{API_BASE}/api/task/{result['task_id']}", timeout=15)
if r.status_code == 200:
d = r.json()
status = d.get("status", "")
if status in ("done", "error"):
result["resolved"] = d.get("resolved", False)
result["attempts"] = d.get("attempts", 0)
result["tokens"] = d.get("total_tokens", 0)
result["elapsed"] = d.get("elapsed_seconds", 0)
result["patch"] = d.get("patch", "")
if status == "error":
result["error"] = d.get("error", "api_error")
break
print(f" polling... status={status}")
except Exception as pe:
print(f" poll error: {pe}")
return result
def load_swebench_issues(max_issues: int) -> list[dict]:
"""Load SWE-bench Lite test split from local cache."""
sys.path.insert(0, str(Path(__file__).parent.parent))
from swe_bench.loader import SWEBenchLoader
loader = SWEBenchLoader()
instances = loader.load("test")
return instances[:max_issues]
def load_done_ids(results_file: Path) -> set[str]:
"""Load already-completed instance IDs for resume."""
done = set()
if results_file.exists():
for line in results_file.read_text().splitlines():
try:
done.add(json.loads(line)["instance_id"])
except Exception:
pass
return done
def print_summary(results: list[dict]) -> None:
n = len(results)
resolved = sum(1 for r in results if r.get("resolved"))
errors = sum(1 for r in results if r.get("error"))
pct = 100 * resolved / max(n, 1)
print("\n" + "═" * 55)
print(f" BENCHMARK COMPLETE")
print("═" * 55)
print(f" Issues run: {n}")
print(f" Resolved: {resolved} ({pct:.1f}%)")
print(f" Errors/timeouts: {errors}")
print(f" Avg tokens: {sum(r.get('tokens',0) for r in results)//max(n,1):,}")
print("═" * 55)
if pct > 27.3:
print(f" πŸŽ‰ Beats Agentless SOTA (27.3%)!")
elif pct > 12.5:
print(f" βœ… Beats SWE-agent (12.5%)")
print()
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="Real SWE-bench eval via live HF API")
parser.add_argument("--max", type=int, default=50, help="Number of issues to run")
parser.add_argument("--resume", action="store_true", help="Skip already-done issues")
parser.add_argument("--timeout", type=int, default=MAX_WAIT, help="Seconds per issue")
args = parser.parse_args()
OUT_DIR.mkdir(parents=True, exist_ok=True)
results_file = OUT_DIR / "results.jsonl"
summary_file = OUT_DIR / "summary.json"
print(f" API: {API_BASE}")
print(f" Max issues: {args.max} | Timeout per issue: {args.timeout}s")
print(f" Output: {results_file}\n")
# Wake up HF Space before starting
wake_up_space()
print()
# Load issues
print("Loading SWE-bench Lite dataset...")
instances = load_swebench_issues(args.max)
print(f"Loaded {len(instances)} instances\n")
# Resume support
done_ids = load_done_ids(results_file) if args.resume else set()
if done_ids:
print(f"Resuming β€” skipping {len(done_ids)} already-done issues\n")
all_results = []
start_time = time.time()
with results_file.open("a" if args.resume else "w") as out_f:
for i, inst in enumerate(instances):
iid = inst["instance_id"]
repo = inst["repo"]
prob = inst["problem_statement"]
if iid in done_ids:
print(f"[{i+1}/{len(instances)}] SKIP {iid}")
continue
print(f"[{i+1}/{len(instances)}] {iid} ({repo})")
# Submit β€” pass base_commit so agent clones at the right version
base_commit = inst.get("base_commit", "")
task_id = submit_issue(repo, prob[:8000], base_commit=base_commit)
if not task_id:
rec = {"instance_id": iid, "repo": repo, "resolved": False,
"error": "submit_failed", "task_id": ""}
print(f" βœ— Submit failed\n")
else:
print(f" task_id: {task_id}")
rec = wait_for_result(task_id, timeout=args.timeout)
rec["instance_id"] = iid
rec["repo"] = repo
icon = "βœ…" if rec["resolved"] else "❌"
print(f" {icon} resolved={rec['resolved']} | "
f"attempts={rec['attempts']} | "
f"tokens={rec['tokens']:,} | "
f"time={rec.get('elapsed', 0):.1f}s")
if rec.get("error"):
print(f" ⚠ error: {rec['error'][:80]}")
print()
rec["timestamp"] = datetime.now(timezone.utc).isoformat()
all_results.append(rec)
out_f.write(json.dumps(rec) + "\n")
out_f.flush()
# Live running total
resolved_so_far = sum(1 for r in all_results if r.get("resolved"))
total_so_far = len(all_results)
print(f" Running total: {resolved_so_far}/{total_so_far} "
f"({100*resolved_so_far/max(total_so_far,1):.1f}% resolved)\n")
# Delay between issues
if i < len(instances) - 1:
time.sleep(DELAY_BTW)
# Final summary
print_summary(all_results)
# Save summary JSON
resolved = sum(1 for r in all_results if r.get("resolved"))
summary = {
"timestamp": datetime.utcnow().isoformat(),
"api": API_BASE,
"n_total": len(all_results),
"n_resolved": resolved,
"pct_resolved": round(100 * resolved / max(len(all_results), 1), 2),
"avg_tokens": sum(r.get("tokens",0) for r in all_results) // max(len(all_results),1),
"total_seconds":round(time.time() - start_time, 1),
"variant": "with_reflection",
"model": "gemini-2.5-flash",
}
summary_file.write_text(json.dumps(summary, indent=2))
print(f"Summary saved to: {summary_file}")
if __name__ == "__main__":
main()