Files
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

203 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""Run N simultaneous cb runs per task and report pass rates.
Usage:
python scripts/run_calibration.py --tasks "154d0750,1625e97a" [--attempts 5] [--max-steps 1000]
Output:
scripts/tasks/calibration_results.json
"""
import argparse
import json
import os
import re
import subprocess
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
TASKS_DIR = SCRIPT_DIR / "tasks"
CB = str(SCRIPT_DIR.parent / ".venv" / "bin" / "cb")
RUNS_DIR = Path.home() / ".local" / "share" / "cua-bench" / "runs"
OUTPUT_FILE = TASKS_DIR / "calibration_results.json"
MODELS = [
("claude", "anthropic/claude-opus-4-6"),
]
def load_env() -> None:
env_file = SCRIPT_DIR.parent / ".env"
if env_file.exists():
for line in env_file.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, _, v = line.partition("=")
os.environ[k.strip()] = v.strip()
def start_run(task_path: Path, model_id: str, max_steps: int) -> str:
"""Start one cb run dataset for a single task, return its run ID."""
result = subprocess.run(
[CB, "run", "dataset", str(task_path.parent),
"--agent", "cua-agent", "--model", model_id, "--max-steps", str(max_steps),
"--max-parallel", "5", "--task-filter", task_path.name],
capture_output=True, text=True, cwd=str(SCRIPT_DIR.parent),
)
clean = re.sub(r"\x1b\[[0-9;]*m", "", result.stdout)
for line in clean.splitlines():
if "Run ID:" in line:
return line.split("Run ID:")[-1].strip().split()[0]
raise RuntimeError(f"No run ID in output:\n{result.stdout}\n{result.stderr}")
def kill_containers() -> None:
"""Stop and remove any lingering cua-* Docker containers."""
result = subprocess.run(
["docker", "ps", "-q", "--filter", "name=cua-"],
capture_output=True, text=True,
)
ids = result.stdout.strip().split() if result.stdout.strip() else []
if ids:
subprocess.run(["docker", "stop", "--time", "10"] + ids,
capture_output=True)
subprocess.run(["docker", "rm", "-f"] + ids,
capture_output=True)
def wait_for_runs(run_ids: list[str]) -> None:
"""Block until all run IDs reach a terminal state, then kill any lingering containers."""
pending = set(run_ids)
print(f" Waiting for {len(pending)} runs...", end="", flush=True)
while pending:
result = subprocess.run(
[CB, "run", "list"], capture_output=True, text=True, cwd=str(SCRIPT_DIR.parent),
)
for run_id in list(pending):
for line in result.stdout.splitlines():
if run_id[:8] in line and any(s in line for s in ("completed", "failed", "error")):
pending.discard(run_id)
if pending:
print(".", end="", flush=True)
time.sleep(20)
print(" done.")
def extract_score(run_id: str, task_id: str) -> float | None:
"""Extract evaluation score from a run's log file."""
log = RUNS_DIR / run_id / f"{task_id}_v0" / "run.log"
if not log.exists():
return None
try:
for line in log.read_text(errors="replace").splitlines():
if "Evaluation result:" in line:
return float(line.split("Evaluation result:")[-1].strip())
except Exception:
pass
return None
def is_server_error(run_id: str, task_id: str) -> bool:
"""Return True if the run failed due to a remote 500/server error."""
log = RUNS_DIR / run_id / f"{task_id}_v0" / "run.log"
if not log.exists():
return False
text = log.read_text(errors="replace")
return "500 Internal Server Error" in text or "InternalServerError" in text
def print_summary(results: dict, task_ids: list[str], output: Path) -> None:
"""Print running summary table and flush results to disk."""
print("\n=== Summary so far ===")
print(f"{'Task':<12} {'Claude':>10} {'OpenAI':>10}")
print("-" * 34)
for task_id in task_ids:
row = results[task_id]
rates = {}
for model_name, _ in MODELS:
scores = row.get(model_name, [])
rates[model_name] = sum(1 for s in scores if s > 0) / len(scores) if scores is not None and len(scores) > 0 else None
claude = f"{rates['claude']:.0%}" if rates["claude"] is not None else "pending"
openai = f"{rates['openai']:.0%}" if rates["openai"] is not None else "pending"
print(f"{task_id:<12} {claude:>10} {openai:>10}")
output.write_text(json.dumps(results, indent=2))
print(f"(results written to {output})\n")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--tasks", required=True, help="Comma-separated task IDs")
parser.add_argument("--attempts", type=int, default=5, help="Simultaneous runs per model")
parser.add_argument("--max-steps", type=int, default=1000)
parser.add_argument("--output", type=Path, default=OUTPUT_FILE)
args = parser.parse_args()
load_env()
task_ids = [t.strip() for t in args.tasks.split(",")]
print(f"Tasks: {task_ids}")
# results[task_id][model_name] = [score, ...]
results: dict[str, dict[str, list[float]]] = {
tid: {m: [] for m, _ in MODELS} for tid in task_ids
}
for task_id in task_ids:
task_path = TASKS_DIR / task_id
if not task_path.exists():
print(f"WARNING: {task_path} not found, skipping")
continue
print(f"\n=== Task: {task_id} ===")
# Launch all models simultaneously
model_run_ids: dict[str, list[str]] = {model_name: [] for model_name, _ in MODELS}
for model_name, model_id in MODELS:
print(f" [{model_name}] Launching {args.attempts} simultaneous runs...")
for i in range(args.attempts):
try:
run_id = start_run(task_path, model_id, args.max_steps)
print(f" [{model_name}] run {i+1}: {run_id}")
model_run_ids[model_name].append(run_id)
except Exception as e:
print(f" [{model_name}] run {i+1}: FAILED to start — {e}")
# Wait for all runs across all models
all_run_ids = [rid for rids in model_run_ids.values() for rid in rids]
wait_for_runs(all_run_ids)
# Retry any OpenAI runs that failed with a server 500 error
retry_ids = []
for model_name, model_id in MODELS:
if model_name != "openai":
continue
for i, run_id in enumerate(model_run_ids[model_name]):
if is_server_error(run_id, task_id):
print(f" [openai] {run_id}: 500 server error — retrying...")
try:
new_id = start_run(task_path, model_id, args.max_steps)
print(f" [openai] retry run: {new_id}")
model_run_ids[model_name][i] = new_id
retry_ids.append(new_id)
except Exception as e:
print(f" [openai] retry FAILED to start — {e}")
if retry_ids:
wait_for_runs(retry_ids)
# Extract scores per model
for model_name, _ in MODELS:
scores = []
for run_id in model_run_ids[model_name]:
score = extract_score(run_id, task_id)
scores.append(score if score is not None else 0.0)
print(f" [{model_name}] {run_id}: {score}")
results[task_id][model_name] = scores
pass_rate = sum(1 for s in scores if s > 0) / len(scores) if scores else 0.0
print(f" [{model_name}] pass rate: {pass_rate:.0%} ({sum(1 for s in scores if s > 0)}/{len(scores)})")
print_summary(results, task_ids, args.output)
if __name__ == "__main__":
main()