Spaces:
Running
Running
| """ | |
| 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() | |