项目文件夹

文件
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

289 行
10 KiB
Python
可执行文件

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/usr/bin/env python3
"""Calibrate KiCad task difficulty by running each task N times per model.
Usage:
python scripts/calibrate_tasks.py [--attempts N] [--parallel P] [--tasks-dir DIR]
Outputs:
scripts/tasks/calibration.json — per-task pass rates and difficulty tiers
"""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
import time
from pathlib import Path
# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------
SCRIPT_DIR = Path(__file__).parent
TASKS_DIR = SCRIPT_DIR / "tasks"
OUTPUT_FILE = TASKS_DIR / "calibration.json"
MODELS = [
("claude", "anthropic/claude-opus-4-6"),
("openai", "openai/computer-use-preview"),
]
DEFAULT_ATTEMPTS = 5
DEFAULT_PARALLEL = 6
DEFAULT_MAX_STEPS = 150
# Difficulty tiers (from TBench spec, based on worst-model accuracy)
# Frontier: best model ≤ 20%
# Advanced Plus: worst model ≤ 20% (and not Frontier)
# Advanced: 20% < worst ≤ 60%
# Core: 60% < worst ≤ 80%
# Easy: worst > 80%
def _tier(claude_rate: float, openai_rate: float) -> str:
best = max(claude_rate, openai_rate)
worst = min(claude_rate, openai_rate)
if best <= 0.20:
return "frontier"
if worst <= 0.20:
return "advanced_plus"
if worst <= 0.60:
return "advanced"
if worst <= 0.80:
return "core"
return "easy"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _load_env() -> dict[str, str]:
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("=")
key, val = k.strip(), v.strip()
os.environ[key] = val # export into current process too
return os.environ.copy()
def _cb() -> str:
return str(SCRIPT_DIR.parent / ".venv" / "bin" / "cb")
def _run_dataset(model_name: str, model_id: str, parallel: int,
max_steps: int, tasks_dir: Path,
task_ids: list[str], attempts: int) -> str:
"""Build a temp dir with `attempts` copies of each task, run as one dataset."""
import tempfile, shutil
tmp = Path(tempfile.mkdtemp(prefix="cb_calib_"))
try:
for task_id in task_ids:
for i in range(attempts):
dst = tmp / f"{task_id}_{i}"
shutil.copytree(tasks_dir / task_id, dst)
cmd = [
_cb(), "run", "dataset", str(tmp),
"--agent", "cua-agent",
"--model", model_id,
"--max-parallel", str(parallel),
"--max-steps", str(max_steps),
]
print(f" [{model_name}] Starting {len(task_ids)} tasks × {attempts} attempts "
f"(parallel={parallel}): {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True, cwd=str(SCRIPT_DIR.parent))
for line in result.stdout.splitlines():
if "Run ID:" in line:
return line.split("Run ID:")[-1].strip().split()[0]
raise RuntimeError(f"Could not parse run ID:\n{result.stdout}\n{result.stderr}")
finally:
shutil.rmtree(tmp, ignore_errors=True)
def _wait_for_run(run_id: str, poll_interval: int = 30) -> None:
"""Poll cb run list until all sessions for this run are in a terminal state."""
cb = _cb()
print(f" Waiting for run {run_id}...", end="", flush=True)
while True:
result = subprocess.run(
[cb, "run", "list"],
capture_output=True, text=True,
cwd=str(SCRIPT_DIR.parent),
)
lines = [l for l in result.stdout.splitlines() if run_id[:8] in l]
if lines:
terminal = [l for l in lines if any(
s in l for s in ("completed", "failed", "error", "done", "0.", "1.")
)]
if len(terminal) == len(lines):
print(" done.")
return
print(".", end="", flush=True)
time.sleep(poll_interval)
def _get_run_output_dir(run_id: str) -> Path | None:
"""Find the output directory for a run from cb run info."""
cb = _cb()
result = subprocess.run(
[cb, "run", "info", run_id],
capture_output=True, text=True,
cwd=str(SCRIPT_DIR.parent),
)
for line in result.stdout.splitlines():
if "Output:" in line or "output" in line.lower():
parts = line.split(":", 1)
if len(parts) == 2:
p = Path(parts[1].strip())
if p.exists():
return p
# Fallback: search default location
default = Path.home() / ".local" / "share" / "cua-bench" / "runs" / run_id
if default.exists():
return default
return None
def _extract_scores(run_output_dir: Path) -> dict[str, float]:
"""Extract per-task scores from a run output directory.
Returns {task_id: score} where score is 0.0–1.0.
"""
scores: dict[str, float] = {}
if not run_output_dir or not run_output_dir.exists():
return scores
try:
from datasets import load_from_disk
except ImportError:
# Fallback: parse run.log for "Evaluation result:"
for log in run_output_dir.rglob("run.log"):
task_id = log.parent.name.split("_")[0]
for line in log.read_text().splitlines():
if "Evaluation result:" in line:
try:
scores[task_id] = float(line.split(":")[-1].strip())
except ValueError:
pass
return scores
for trace_dir in run_output_dir.rglob("task_*_trace"):
session_dir = trace_dir.parent
task_id = session_dir.name.split("_")[0]
try:
ds = load_from_disk(str(trace_dir))
for row in ds:
if row.get("event_name") == "evaluate":
data = json.loads(row["data_json"])
scores[task_id] = float(data.get("result", 0.0))
break
except Exception:
# Fallback to run.log
log = session_dir / "run.log"
if log.exists():
for line in log.read_text().splitlines():
if "Evaluation result:" in line:
try:
scores[task_id] = float(line.split(":")[-1].strip())
except ValueError:
pass
return scores
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description="Calibrate KiCad task difficulty")
parser.add_argument("--attempts", type=int, default=DEFAULT_ATTEMPTS,
help=f"Number of attempts per model (default: {DEFAULT_ATTEMPTS})")
parser.add_argument("--parallel", type=int, default=DEFAULT_PARALLEL,
help=f"Max parallel tasks per run (default: {DEFAULT_PARALLEL})")
parser.add_argument("--max-steps", type=int, default=DEFAULT_MAX_STEPS,
help=f"Max steps per task (default: {DEFAULT_MAX_STEPS})")
parser.add_argument("--tasks-dir", type=Path, default=TASKS_DIR)
parser.add_argument("--output", type=Path, default=OUTPUT_FILE)
parser.add_argument("--task-filter", type=str, default=None,
help="Comma-separated task IDs to run (e.g. '55f2eefb,d1c655da')")
args = parser.parse_args()
# Load .env into os.environ so cb and any subprocesses pick it up
_load_env()
# Discover task IDs (optionally filtered)
all_task_ids = sorted(
p.name for p in args.tasks_dir.iterdir()
if p.is_dir() and (p / "main.py").exists()
)
if args.task_filter:
patterns = [p.strip() for p in args.task_filter.split(",")]
task_ids = [t for t in all_task_ids if any(t.startswith(p) or t == p for p in patterns)]
else:
task_ids = all_task_ids
print(f"Found {len(task_ids)} tasks: {task_ids}")
# Accumulate scores: {task_id: {model_name: [score, ...]}}
all_scores: dict[str, dict[str, list[float]]] = {
tid: {m: [] for m, _ in MODELS} for tid in task_ids
}
for model_name, model_id in MODELS:
print(f"\n=== Model: {model_name} ({model_id}) ===")
for attempt in range(args.attempts):
run_id = _run_dataset(
model_name, model_id, attempt,
args.parallel, args.max_steps,
args.tasks_dir, task_ids,
)
_wait_for_run(run_id)
out_dir = _get_run_output_dir(run_id)
scores = _extract_scores(out_dir)
print(f" Attempt {attempt+1} scores: {scores}")
for task_id in task_ids:
score = scores.get(task_id, 0.0)
all_scores[task_id][model_name].append(score)
# Compute pass rates and tiers
results: dict[str, dict] = {}
for task_id in task_ids:
task_scores = all_scores[task_id]
rates = {
m: (sum(task_scores[m]) / len(task_scores[m]) if task_scores[m] else 0.0)
for m, _ in MODELS
}
tier = _tier(rates["claude"], rates["openai"])
results[task_id] = {
"pass_rates": rates,
"attempts": args.attempts,
"difficulty": tier,
"raw_scores": task_scores,
}
print(f" {task_id}: claude={rates['claude']:.2f} openai={rates['openai']:.2f}{tier}")
# Summary
tier_counts: dict[str, int] = {}
for r in results.values():
tier_counts[r["difficulty"]] = tier_counts.get(r["difficulty"], 0) + 1
print(f"\nDifficulty distribution: {tier_counts}")
# Write manifest
manifest = {
"version": "1.0",
"models": {m: mid for m, mid in MODELS},
"attempts_per_model": args.attempts,
"tasks": results,
}
args.output.write_text(json.dumps(manifest, indent=2))
print(f"\nWrote {args.output}")
if __name__ == "__main__":
main()