Files
zk-data-agent/benchmarks/run.py
T
2026-04-06 03:42:44 +02:00

354 lines
11 KiB
Python

#!/usr/bin/env python3
"""
Local benchmark runner for claw-code-agent.
Runs the REAL agent binary against a suite of coding tasks and scores
pass/fail automatically. No Docker required.
Usage:
# Run all tasks
python3 -m benchmarks.run
# Run a single task
python3 -m benchmarks.run --task file-create-basic
# Run a category
python3 -m benchmarks.run --category bugfix
# Run a difficulty level
python3 -m benchmarks.run --difficulty easy
# List available tasks
python3 -m benchmarks.run --list
# Verbose output
python3 -m benchmarks.run --verbose
"""
from __future__ import annotations
import argparse
import json
import os
import shutil
import subprocess
import sys
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path
from benchmarks.tasks.definitions import TASKS, BenchmarkTask, get_task
from benchmarks.suites.base import make_temp_workspace
# ---------------------------------------------------------------------------
# Result tracking
# ---------------------------------------------------------------------------
@dataclass
class TaskResult:
task_id: str
category: str
difficulty: str
passed: bool
duration_sec: float
agent_exit_code: int
verify_exit_code: int
error: str = ""
# ---------------------------------------------------------------------------
# Task execution
# ---------------------------------------------------------------------------
def _run_shell(cmd: str, cwd: str, timeout: float = 30.0) -> tuple[int, str]:
"""Run a shell command, return (exit_code, combined_output)."""
try:
proc = subprocess.run(
cmd,
shell=True,
cwd=cwd,
capture_output=True,
text=True,
timeout=timeout,
)
output = (proc.stdout + proc.stderr).strip()
return proc.returncode, output
except subprocess.TimeoutExpired:
return 1, f"[TIMEOUT after {timeout}s]"
except Exception as exc:
return 1, str(exc)
def run_task(
task: BenchmarkTask,
*,
project_root: str,
agent_timeout: float = 300.0,
verbose: bool = False,
) -> TaskResult:
"""Run a single benchmark task end-to-end."""
# Create isolated temp workspace
workspace = make_temp_workspace("claw_bench", task.category, task.id)
if verbose:
print(f" workspace: {workspace}")
try:
# --- SETUP ---
if task.setup:
code, out = _run_shell(task.setup, cwd=workspace)
if code != 0:
return TaskResult(
task_id=task.id,
category=task.category,
difficulty=task.difficulty,
passed=False,
duration_sec=0.0,
agent_exit_code=-1,
verify_exit_code=-1,
error=f"Setup failed: {out}",
)
# --- RUN AGENT ---
agent_cmd = (
f"{sys.executable} -m src.main agent "
f"{_shell_quote(task.instruction)} "
f"--cwd {_shell_quote(workspace)} "
f"--allow-write "
f"--allow-shell"
)
if verbose:
print(f" agent cmd: {agent_cmd[:120]}...")
start = time.time()
agent_code, agent_out = _run_shell(
agent_cmd,
cwd=project_root,
timeout=agent_timeout,
)
duration = time.time() - start
if verbose:
print(f" agent exit={agent_code} duration={duration:.1f}s")
if agent_out:
# Print last few lines of agent output
lines = agent_out.split("\n")
for line in lines[-5:]:
print(f" > {line}")
# --- VERIFY ---
verify_code, verify_out = _run_shell(task.verify, cwd=workspace, timeout=30.0)
if verbose:
status = "PASS" if verify_code == 0 else "FAIL"
print(f" verify exit={verify_code} -> {status}")
if verify_code != 0 and verify_out:
print(f" verify output: {verify_out[:200]}")
return TaskResult(
task_id=task.id,
category=task.category,
difficulty=task.difficulty,
passed=(verify_code == 0),
duration_sec=duration,
agent_exit_code=agent_code,
verify_exit_code=verify_code,
error=verify_out if verify_code != 0 else "",
)
finally:
# Clean up workspace
shutil.rmtree(workspace, ignore_errors=True)
def _shell_quote(s: str) -> str:
"""Quote a string for shell use."""
import shlex
return shlex.quote(s)
# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------
def print_results(results: list[TaskResult]) -> None:
"""Print a formatted results table."""
total = len(results)
passed = sum(1 for r in results if r.passed)
failed = total - passed
print()
print("=" * 72)
print(" CLAW CODE AGENT — BENCHMARK RESULTS")
print("=" * 72)
print()
print(f" {'Task ID':<30} {'Category':<12} {'Diff':<8} {'Result':<8} {'Time':>6}")
print(f" {'' * 30} {'' * 12} {'' * 8} {'' * 8} {'' * 6}")
for r in results:
status = "PASS" if r.passed else "FAIL"
icon = "" if r.passed else ""
time_str = f"{r.duration_sec:.1f}s"
print(f"{icon} {r.task_id:<30} {r.category:<12} {r.difficulty:<8} {status:<8} {time_str:>6}")
print()
print("" * 72)
print(f" Total: {total} | Passed: {passed} | Failed: {failed} | Score: {passed}/{total} ({100*passed/total:.0f}%)")
print("" * 72)
# Breakdown by category
categories: dict[str, list[TaskResult]] = {}
for r in results:
categories.setdefault(r.category, []).append(r)
print()
print(" Category Breakdown:")
for cat, cat_results in sorted(categories.items()):
cat_passed = sum(1 for r in cat_results if r.passed)
cat_total = len(cat_results)
bar = "" * cat_passed + "" * (cat_total - cat_passed)
print(f" {cat:<14} {bar} {cat_passed}/{cat_total}")
# Breakdown by difficulty
difficulties: dict[str, list[TaskResult]] = {}
for r in results:
difficulties.setdefault(r.difficulty, []).append(r)
print()
print(" Difficulty Breakdown:")
for diff in ("easy", "medium", "hard"):
if diff in difficulties:
diff_results = difficulties[diff]
diff_passed = sum(1 for r in diff_results if r.passed)
diff_total = len(diff_results)
print(f" {diff:<14} {diff_passed}/{diff_total} ({100*diff_passed/diff_total:.0f}%)")
total_time = sum(r.duration_sec for r in results)
print()
print(f" Total time: {total_time:.1f}s")
print()
def save_results(results: list[TaskResult], output_path: str) -> None:
"""Save results to JSON."""
data = {
"benchmark": "claw-code-agent-local",
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S"),
"model": os.environ.get("OPENAI_MODEL", "unknown"),
"total": len(results),
"passed": sum(1 for r in results if r.passed),
"score_pct": round(100 * sum(1 for r in results if r.passed) / len(results), 1) if results else 0,
"results": [
{
"task_id": r.task_id,
"category": r.category,
"difficulty": r.difficulty,
"passed": r.passed,
"duration_sec": round(r.duration_sec, 2),
"agent_exit_code": r.agent_exit_code,
"verify_exit_code": r.verify_exit_code,
"error": r.error,
}
for r in results
],
}
with open(output_path, "w") as f:
json.dump(data, f, indent=2)
print(f" Results saved to {output_path}")
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description="Claw Code Agent local benchmark")
parser.add_argument("--task", help="Run a single task by ID")
parser.add_argument("--category", help="Run tasks in a category")
parser.add_argument("--difficulty", choices=["easy", "medium", "hard"], help="Run tasks by difficulty")
parser.add_argument("--list", action="store_true", help="List available tasks")
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
parser.add_argument("--timeout", type=float, default=300.0, help="Agent timeout per task in seconds (default: 300)")
parser.add_argument("--output", "-o", help="Save results to JSON file")
args = parser.parse_args()
if args.list:
print(f"\n {'ID':<30} {'Category':<12} {'Difficulty':<10}")
print(f" {'' * 30} {'' * 12} {'' * 10}")
for t in TASKS:
print(f" {t.id:<30} {t.category:<12} {t.difficulty:<10}")
print(f"\n Total: {len(TASKS)} tasks\n")
return
# Select tasks
tasks_to_run: list[BenchmarkTask] = []
if args.task:
t = get_task(args.task)
if t is None:
print(f"Unknown task: {args.task}")
print("Use --list to see available tasks")
sys.exit(1)
tasks_to_run = [t]
else:
tasks_to_run = list(TASKS)
if args.category:
tasks_to_run = [t for t in tasks_to_run if t.category == args.category]
if args.difficulty:
tasks_to_run = [t for t in tasks_to_run if t.difficulty == args.difficulty]
if not tasks_to_run:
print("No tasks matched the filters.")
sys.exit(1)
# Find project root
project_root = str(Path(__file__).resolve().parent.parent)
# Check environment
model = os.environ.get("OPENAI_MODEL", "not set")
base_url = os.environ.get("OPENAI_BASE_URL", "not set")
print()
print("=" * 72)
print(" CLAW CODE AGENT — LOCAL BENCHMARK")
print("=" * 72)
print(f" Model: {model}")
print(f" Base URL: {base_url}")
print(f" Tasks: {len(tasks_to_run)}")
print(f" Timeout: {args.timeout}s per task")
print("=" * 72)
print()
# Run tasks
results: list[TaskResult] = []
for i, task in enumerate(tasks_to_run, 1):
print(f"[{i}/{len(tasks_to_run)}] {task.id} ({task.category}, {task.difficulty})")
result = run_task(
task,
project_root=project_root,
agent_timeout=args.timeout,
verbose=args.verbose,
)
results.append(result)
status = "PASS ✅" if result.passed else "FAIL ❌"
print(f" -> {status} ({result.duration_sec:.1f}s)")
print()
# Report
print_results(results)
if args.output:
save_results(results, args.output)
if __name__ == "__main__":
main()