dsh-eval-guard 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -13
- package/assets/commit0/instance.json +30 -0
- package/assets/commit0/workspace.zip +0 -0
- package/assets/featurebench/hidden/test_regression.py +690 -0
- package/assets/featurebench/instance.json +16 -0
- package/assets/featurebench/workspace.zip +0 -0
- package/assets/swe-bench-pro/hidden/test.patch +36 -0
- package/assets/swe-bench-pro/instance.json +10 -0
- package/assets/swe-bench-pro/workspace.zip +0 -0
- package/eval/README.md +18 -0
- package/eval/arena/arena/README.md +21 -0
- package/eval/arena/arena/bt.py +104 -0
- package/eval/arena/arena/clock.py +15 -0
- package/eval/arena/arena/core.py +265 -0
- package/eval/arena/arena/run.cmd +24 -0
- package/eval/arena/arena/run.sh +11 -0
- package/eval/arena/arena/schedule.py +79 -0
- package/eval/arena/arena/tournament.py +183 -0
- package/eval/arena/bomberman/bots/README.md +16 -0
- package/eval/arena/bomberman/bots/deepseek-v4.1-flash.cpp +447 -0
- package/eval/arena/bomberman/bots/doubao-seed-2.1.cpp +542 -0
- package/eval/arena/bomberman/bots/glm-5.3.cpp +406 -0
- package/eval/arena/bomberman/bots/gpt-6-astra.cpp +768 -0
- package/eval/arena/bomberman/bots/gpt-6-sol.cpp +444 -0
- package/eval/arena/bomberman/bots/kimi-k3.cpp +432 -0
- package/eval/arena/bomberman/bots/mimo-v2.6-pro.cpp +529 -0
- package/eval/arena/bomberman/bots/opus-5.5.cpp +548 -0
- package/eval/arena/bomberman/bots/qwen-3.8-max.cpp +836 -0
- package/eval/arena/bomberman/tournament/README.md +11 -0
- package/eval/arena/bomberman/tournament/bt.py +56 -0
- package/eval/arena/bomberman/tournament/evaluate.py +199 -0
- package/eval/arena/bomberman/tournament/run.py +79 -0
- package/eval/arena/bomberman/tournament/schedule-2to10.json.gz +0 -0
- package/eval/arena/bomberman/workspace/MANIFEST.json +13 -0
- package/eval/arena/bomberman/workspace/README.md +29 -0
- package/eval/arena/bomberman/workspace/RULES.md +100 -0
- package/eval/arena/bomberman/workspace/baseline.cpp +240 -0
- package/eval/arena/bomberman/workspace/clock.py +25 -0
- package/eval/arena/bomberman/workspace/engine/arena.py +198 -0
- package/eval/arena/bomberman/workspace/engine/build.py +35 -0
- package/eval/arena/bomberman/workspace/engine/engine.py +256 -0
- package/eval/arena/bomberman/workspace/engine/verify_replay.py +24 -0
- package/eval/arena/bomberman/workspace/run.cmd +28 -0
- package/eval/arena/bomberman/workspace/run.sh +16 -0
- package/eval/arena/bomberman/workspace/viewer/index.html +42 -0
- package/eval/arena/ctf/bots/README.md +18 -0
- package/eval/arena/ctf/bots/deepseek-v4.1-flash.cpp +601 -0
- package/eval/arena/ctf/bots/doubao-seed-2.1-turbo.cpp +320 -0
- package/eval/arena/ctf/bots/fable-5.1.cpp +634 -0
- package/eval/arena/ctf/bots/glm-5.3.cpp +689 -0
- package/eval/arena/ctf/bots/gpt-6-astra.cpp +657 -0
- package/eval/arena/ctf/bots/gpt-6.1-sol.cpp +725 -0
- package/eval/arena/ctf/bots/kimi-k3.cpp +667 -0
- package/eval/arena/ctf/bots/mimo-v2.6-pro.cpp +533 -0
- package/eval/arena/ctf/bots/opus-5.5.cpp +645 -0
- package/eval/arena/ctf/bots/qwen-3.8-max.cpp +726 -0
- package/eval/arena/ctf/bots/sonnet-5.5.cpp +569 -0
- package/eval/arena/ctf/tournament/README.md +15 -0
- package/eval/arena/ctf/tournament/formal-2to8.json +31 -0
- package/eval/arena/ctf/tournament/formal-9to15.json +31 -0
- package/eval/arena/ctf/workspace/README.md +41 -0
- package/eval/arena/ctf/workspace/RULES.md +119 -0
- package/eval/arena/ctf/workspace/arena.py +186 -0
- package/eval/arena/ctf/workspace/baseline.cpp +257 -0
- package/eval/arena/ctf/workspace/clock.py +15 -0
- package/eval/arena/ctf/workspace/core.py +265 -0
- package/eval/arena/ctf/workspace/engine.py +389 -0
- package/eval/arena/ctf/workspace/run.cmd +24 -0
- package/eval/arena/ctf/workspace/run.sh +11 -0
- package/eval/arena/ctf/workspace/viewer.html +71 -0
- package/eval/arena/land/bots/README.md +17 -0
- package/eval/arena/land/bots/deepseek-4.1f@workbuddy#1/bot.cpp +608 -0
- package/eval/arena/land/bots/glm-5.3@workbuddy#1/bot.cpp +521 -0
- package/eval/arena/land/bots/gpt-6-astra@codex#1/bot.cpp +373 -0
- package/eval/arena/land/bots/gpt-6-sol@codex#1/bot.cpp +341 -0
- package/eval/arena/land/bots/kimi-k3@workbuddy#1/bot.cpp +492 -0
- package/eval/arena/land/bots/mimo-2.6-pro@zcode#1/bot.cpp +652 -0
- package/eval/arena/land/bots/mystery-model@zcode#1/bot.cpp +466 -0
- package/eval/arena/land/bots/opus-5.5@claude-code#1/bot.cpp +630 -0
- package/eval/arena/land/bots/sonnet-5@claude-code#1/bot.cpp +619 -0
- package/eval/arena/land/tournament/make_replay_page.py +17 -0
- package/eval/arena/land/tournament/rate.py +290 -0
- package/eval/arena/land/tournament/round_robin.py +172 -0
- package/eval/arena/land/tournament/sparring.cpp +356 -0
- package/eval/arena/land/workspace/RULES.md +114 -0
- package/eval/arena/land/workspace/TASK.md +46 -0
- package/eval/arena/land/workspace/anchors/.build/greedy_f2e6d2dea5ca +0 -0
- package/eval/arena/land/workspace/anchors/greedy.cpp +194 -0
- package/eval/arena/land/workspace/anchors/random.cpp +80 -0
- package/eval/arena/land/workspace/clock.py +64 -0
- package/eval/arena/land/workspace/engine/build.py +33 -0
- package/eval/arena/land/workspace/engine/game.py +273 -0
- package/eval/arena/land/workspace/engine/referee.py +308 -0
- package/eval/arena/land/workspace/starter/.build/bot_be32b45b4699 +0 -0
- package/eval/arena/land/workspace/starter/bot.cpp +82 -0
- package/eval/arena/land/workspace/viewer/viewer.html +210 -0
- package/eval/arena/tank/bots/README.md +24 -0
- package/eval/arena/tank/bots/half1/deepseek-v4.1-flash.cpp +367 -0
- package/eval/arena/tank/bots/half1/doubao-seed-evolving.cpp +402 -0
- package/eval/arena/tank/bots/half1/gemini-4-argon.cpp +1132 -0
- package/eval/arena/tank/bots/half1/glm-5.3.cpp +551 -0
- package/eval/arena/tank/bots/half1/gpt-6-astra.cpp +233 -0
- package/eval/arena/tank/bots/half1/gpt-6.1-sol-ultrafast.cpp +350 -0
- package/eval/arena/tank/bots/half1/gpt-6.1-sol.cpp +78 -0
- package/eval/arena/tank/bots/half1/grok-4.7.cpp +1099 -0
- package/eval/arena/tank/bots/half1/haiku-5.5.cpp +210 -0
- package/eval/arena/tank/bots/half1/kimi-k3.cpp +353 -0
- package/eval/arena/tank/bots/half1/mimo-v2.6-pro.cpp +336 -0
- package/eval/arena/tank/bots/half1/muse-wonder-full.cpp +560 -0
- package/eval/arena/tank/bots/half1/opus-5.5.cpp +435 -0
- package/eval/arena/tank/bots/half1/qwen-3.8-max.cpp +537 -0
- package/eval/arena/tank/bots/half2/deepseek-v4.1-flash.cpp +425 -0
- package/eval/arena/tank/bots/half2/doubao-seed-evolving.cpp +308 -0
- package/eval/arena/tank/bots/half2/gemini-4-argon.cpp +1390 -0
- package/eval/arena/tank/bots/half2/glm-5.3.cpp +651 -0
- package/eval/arena/tank/bots/half2/gpt-6-astra.cpp +241 -0
- package/eval/arena/tank/bots/half2/gpt-6.1-sol-ultrafast.cpp +356 -0
- package/eval/arena/tank/bots/half2/gpt-6.1-sol.cpp +87 -0
- package/eval/arena/tank/bots/half2/grok-4.7.cpp +1205 -0
- package/eval/arena/tank/bots/half2/haiku-5.5.cpp +210 -0
- package/eval/arena/tank/bots/half2/kimi-k3.cpp +487 -0
- package/eval/arena/tank/bots/half2/mimo-v2.6-pro.cpp +336 -0
- package/eval/arena/tank/bots/half2/muse-wonder-full.cpp +560 -0
- package/eval/arena/tank/bots/half2/opus-5.5.cpp +476 -0
- package/eval/arena/tank/bots/half2/qwen-3.8-max.cpp +392 -0
- package/eval/arena/tank/tournament/README.md +24 -0
- package/eval/arena/tank/tournament/bt.py +218 -0
- package/eval/arena/tank/tournament/final.json +202 -0
- package/eval/arena/tank/tournament/halftime.json +146 -0
- package/eval/arena/tank/tournament/progress.json +911 -0
- package/eval/arena/tank/tournament/schedule.py +100 -0
- package/eval/arena/tank/tournament/sparring.cpp +928 -0
- package/eval/arena/tank/tournament/tournament.py +1426 -0
- package/eval/arena/tank/workspace/README.md +169 -0
- package/eval/arena/tank/workspace/RULES.md +443 -0
- package/eval/arena/tank/workspace/arena.py +862 -0
- package/eval/arena/tank/workspace/baseline.cpp +259 -0
- package/eval/arena/tank/workspace/clock.py +103 -0
- package/eval/arena/tank/workspace/core.py +712 -0
- package/eval/arena/tank/workspace/engine.py +573 -0
- package/eval/arena/tank/workspace/history/.lock +0 -0
- package/eval/arena/tank/workspace/history/20261017-193610-08715287f91f-bot.cpp +109 -0
- package/eval/arena/tank/workspace/history/20261017-193619-f00b2d5635fa-gpt-6-astra.cpp +241 -0
- package/eval/arena/tank/workspace/history/20261017-193643-63319e0a2b1a-gpt-6_1-sol-ultrafast.cpp +356 -0
- package/eval/arena/tank/workspace/history/20261017-193647-41c1341d0a8a-gemini-4-argon.cpp +1390 -0
- package/eval/arena/tank/workspace/history/20261017-193652-e34c0f45539a-gpt-6_1-sol.cpp +87 -0
- package/eval/arena/tank/workspace/history/20261017-193654-c026348ce96e-opus-5_5.cpp +476 -0
- package/eval/arena/tank/workspace/history/20261017-193655-4a21366e94d1-muse-wonder-full.cpp +560 -0
- package/eval/arena/tank/workspace/history/20261017-193657-dbc4e9969c52-mimo-v2_6-pro.cpp +336 -0
- package/eval/arena/tank/workspace/history/20261017-193703-723cd036eb31-grok-4_7.cpp +1205 -0
- package/eval/arena/tank/workspace/history/20261017-193707-1ebbb227be68-deepseek-v4_1-flash.cpp +425 -0
- package/eval/arena/tank/workspace/history/20261017-193709-aeb0e7202ad5-doubao-seed-evolving.cpp +308 -0
- package/eval/arena/tank/workspace/history/20261017-193710-b21ebeb9bc76-kimi-k3.cpp +487 -0
- package/eval/arena/tank/workspace/history/20261017-193712-54cd86531905-haiku-5_5.cpp +210 -0
- package/eval/arena/tank/workspace/history/20261017-193714-efdb8e35c0bf-qwen-3_8-max.cpp +392 -0
- package/eval/arena/tank/workspace/history/20261017-193715-681423c23fdb-glm-5_3.cpp +651 -0
- package/eval/arena/tank/workspace/history/20261017-193717-1a8b3c75181d-sparring.cpp +928 -0
- package/eval/arena/tank/workspace/history/20261017-193720-1e0803a541e8-baseline.cpp +259 -0
- package/eval/arena/tank/workspace/history/log.jsonl +103 -0
- package/eval/arena/tank/workspace/judge/cycles.hpp +53 -0
- package/eval/arena/tank/workspace/judge/fast.cpp +337 -0
- package/eval/arena/tank/workspace/judge/referee.cpp +125 -0
- package/eval/arena/tank/workspace/judge/speed.cpp +99 -0
- package/eval/arena/tank/workspace/run.cmd +31 -0
- package/eval/arena/tank/workspace/run.sh +23 -0
- package/eval/arena/tank/workspace/tank.hpp +1762 -0
- package/eval/arena/tank/workspace/tank_main.cpp +215 -0
- package/eval/arena/tank/workspace/viewer.html +1150 -0
- package/eval/arena/warehouse/workspace/SPEC.md +130 -0
- package/eval/arena/warehouse/workspace/TASK.md +71 -0
- package/eval/arena/warehouse/workspace/baseline/common.h +88 -0
- package/eval/arena/warehouse/workspace/baseline/main.cpp +27 -0
- package/eval/arena/warehouse/workspace/baseline/old_stuff.cpp +87 -0
- package/eval/arena/warehouse/workspace/baseline/sim.cpp +427 -0
- package/eval/arena/warehouse/workspace/baseline/util.cpp +220 -0
- package/eval/arena/warehouse/workspace/clock.py +42 -0
- package/eval/arena/warehouse/workspace/src/common.h +88 -0
- package/eval/arena/warehouse/workspace/src/main.cpp +27 -0
- package/eval/arena/warehouse/workspace/src/old_stuff.cpp +87 -0
- package/eval/arena/warehouse/workspace/src/sim.cpp +427 -0
- package/eval/arena/warehouse/workspace/src/util.cpp +220 -0
- package/eval/arena/warehouse/workspace/tools/bench.py +119 -0
- package/eval/arena/warehouse/workspace/tools/check_input.py +161 -0
- package/eval/arena/warehouse/workspace/tools/gen.py +132 -0
- package/eval/arena/warehouse/workspace/tools/measure.py +107 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.in +1420 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.out +9162 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.in +1465 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.out +3439 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_flood.in +2757 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_flood.out +3126 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_small.in +731 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_small.out +2578 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_wide.in +528 -0
- package/eval/arena/warehouse/workspace/workloads/public/pub_wide.out +2026 -0
- package/eval/bomberman.py +20 -0
- package/eval/commit0.py +21 -0
- package/eval/common.py +61 -0
- package/eval/ctf.py +20 -0
- package/eval/featurebench.py +25 -0
- package/eval/land.py +20 -0
- package/eval/legacy/bomberman-prices.json +4 -0
- package/eval/legacy/bomberman.py +161 -0
- package/eval/legacy/commit0.py +91 -0
- package/eval/legacy/ctf-prices.json +4 -0
- package/eval/legacy/ctf.py +178 -0
- package/eval/legacy/featurebench.py +103 -0
- package/eval/legacy/land-prices.json +4 -0
- package/eval/legacy/land.py +177 -0
- package/eval/legacy/swe_bench_pro.py +116 -0
- package/eval/legacy/tank-prices.json +3 -0
- package/eval/legacy/tank.py +408 -0
- package/eval/legacy/warehouse-prices.json +4 -0
- package/eval/legacy/warehouse.py +365 -0
- package/eval/swe-bench-pro.py +25 -0
- package/eval/tank.py +20 -0
- package/eval/warehouse.py +20 -0
- package/lib/client/index.js +376 -63
- package/lib/client/index.js.map +1 -1
- package/lib/index.js +684 -27
- package/package.json +2 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Evaluate a FeatureBench single-feature submission.
|
|
3
|
+
|
|
4
|
+
Submission layout:
|
|
5
|
+
submissions/<name>/workspace/ (copy of the agent workspace, including .venv)
|
|
6
|
+
|
|
7
|
+
The hidden feature test file is copied from the plugin asset; scoring runs the
|
|
8
|
+
instance's fail-to-pass and pass-to-pass files. Missing run/ statistics are skipped.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
ROOT = Path(__file__).resolve().parent
|
|
22
|
+
ASSET = ROOT.parents[1] / "assets" / "featurebench"
|
|
23
|
+
PASSED = re.compile(r"(\d+) passed")
|
|
24
|
+
FAILED = re.compile(r"(\d+) failed")
|
|
25
|
+
ERRORS = re.compile(r"(\d+) error")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_json(path: Path):
|
|
29
|
+
try:
|
|
30
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def find_workspace(directory: Path) -> Path | None:
|
|
36
|
+
for candidate in (directory / "workspace", directory):
|
|
37
|
+
if (candidate / "seaborn").is_dir() and (candidate / "tests").is_dir():
|
|
38
|
+
return candidate
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def install_hidden_test(workspace: Path) -> None:
|
|
43
|
+
source = ASSET / "hidden" / "test_regression.py"
|
|
44
|
+
if source.is_file():
|
|
45
|
+
shutil.copy2(source, workspace / "tests" / "test_regression.py")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
|
|
49
|
+
venv = workspace / ".venv" / "bin" / "python"
|
|
50
|
+
python = str(venv) if venv.is_file() else "python3"
|
|
51
|
+
tests = list(instance.get("FAIL_TO_PASS", [])) + list(instance.get("PASS_TO_PASS", []))
|
|
52
|
+
env = {k: v for k, v in __import__("os").environ.items() if k not in {"PYTHONPATH", "PYTHONHOME"}}
|
|
53
|
+
completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no"], cwd=workspace,
|
|
54
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800, env=env)
|
|
55
|
+
output = (completed.stdout or "") + "\n" + (completed.stderr or "")
|
|
56
|
+
passed = int(PASSED.search(output).group(1)) if PASSED.search(output) else 0
|
|
57
|
+
failed = int(FAILED.search(output).group(1)) if FAILED.search(output) else 0
|
|
58
|
+
errors = int(ERRORS.search(output).group(1)) if ERRORS.search(output) else 0
|
|
59
|
+
if completed.returncode != 0 and passed == 0 and failed == 0 and errors == 0:
|
|
60
|
+
errors = 1
|
|
61
|
+
return passed, failed, errors, output[-3000:]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main() -> int:
|
|
65
|
+
parser = argparse.ArgumentParser(description="featurebench-eval")
|
|
66
|
+
parser.add_argument("--submissions", default=str(ROOT / "submissions"))
|
|
67
|
+
parser.add_argument("--out", default="")
|
|
68
|
+
args = parser.parse_args()
|
|
69
|
+
instance = load_json(ASSET / "instance.json") or {}
|
|
70
|
+
root = Path(args.submissions).resolve()
|
|
71
|
+
directories = [p for p in sorted(root.iterdir()) if p.is_dir()] if root.is_dir() else []
|
|
72
|
+
if not directories:
|
|
73
|
+
print("no submissions under", root, file=sys.stderr)
|
|
74
|
+
return 2
|
|
75
|
+
out = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
|
|
76
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
rows = []
|
|
78
|
+
for directory in directories:
|
|
79
|
+
workspace = find_workspace(directory)
|
|
80
|
+
if workspace is None:
|
|
81
|
+
rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0})
|
|
82
|
+
continue
|
|
83
|
+
try:
|
|
84
|
+
install_hidden_test(workspace)
|
|
85
|
+
passed, failed, errors, detail = run_tests(workspace, instance)
|
|
86
|
+
total = passed + failed + errors
|
|
87
|
+
score = 100.0 * passed / total if total else 0.0
|
|
88
|
+
rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail})
|
|
89
|
+
except Exception as error:
|
|
90
|
+
rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "detail": str(error)})
|
|
91
|
+
rows.sort(key=lambda row: (-row["score"], row["name"]))
|
|
92
|
+
payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "benchmark": "featurebench", "instance": instance.get("instance_id"), "results": rows}
|
|
93
|
+
(out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
94
|
+
lines = ["# FeatureBench · 单 feature 排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 测试通过率", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
|
|
95
|
+
for rank, row in enumerate(rows, 1):
|
|
96
|
+
lines.append("| %d | %s | %.1f | %d | %d | %d | %s |" % (rank, row["name"], row["score"], row["passed"], row["failed"], row["errors"], row["status"]))
|
|
97
|
+
(out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
98
|
+
print("wrote", out / "summary.md")
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Independent evaluation for land (territory) bot submissions.
|
|
3
|
+
|
|
4
|
+
Reads submissions/<name>/{bot.cpp,run/,cost.json}, then runs the official
|
|
5
|
+
round_robin.py + rate.py over the submissions and archive players. A missing
|
|
6
|
+
run/ directory is fine: only the bot is evaluated.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
ROOT = Path(__file__).resolve().parent
|
|
20
|
+
DEFAULT_TASK = ROOT.parent / "meow-ai-arena" / "land"
|
|
21
|
+
TIERS = {"quick": (2, 0), "full": (6, 10), "formal": (12, 20)}
|
|
22
|
+
STATIC_PATTERNS = [
|
|
23
|
+
(re.compile(r"#\s*include\s*<(fstream|filesystem|unistd\.h|windows\.h|winsock2?\.h|sys/|netinet/|arpa/|dlfcn\.h)>"), "可疑头文件"),
|
|
24
|
+
(re.compile(r"\b(fopen|freopen|ifstream|ofstream|filesystem::)\b"), "文件 IO"),
|
|
25
|
+
(re.compile(r"\b(system|popen|execv?p?e?|fork|posix_spawn|CreateProcess)\b"), "启动进程"),
|
|
26
|
+
(re.compile(r"\b(socket|connect|bind|listen|accept|sendto|recvfrom|getaddrinfo)\b"), "网络调用"),
|
|
27
|
+
(re.compile(r"\b(std::thread|pthread_create|_beginthread)\b"), "线程"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_json(path: Path):
|
|
32
|
+
try:
|
|
33
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
34
|
+
except Exception:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def static_scan(source: Path) -> list:
|
|
39
|
+
findings = []
|
|
40
|
+
for number, line in enumerate(source.read_text(encoding="utf-8", errors="replace").splitlines(), 1):
|
|
41
|
+
for pattern, label in STATIC_PATTERNS:
|
|
42
|
+
if pattern.search(line):
|
|
43
|
+
findings.append({"line": number, "kind": label, "text": line.strip()[:160]})
|
|
44
|
+
return findings
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def money(directory: Path, run: dict, usage: dict, cost: dict, prices: dict):
|
|
48
|
+
if isinstance(cost, dict):
|
|
49
|
+
try:
|
|
50
|
+
return float(cost.get("total", 0) or 0), "manual", str(cost.get("currency", "USD"))
|
|
51
|
+
except (TypeError, ValueError):
|
|
52
|
+
pass
|
|
53
|
+
if not usage:
|
|
54
|
+
return 0.0, "missing", "USD"
|
|
55
|
+
provider = str(run.get("provider") or "")
|
|
56
|
+
model = str(run.get("model") or "")
|
|
57
|
+
table = prices.get(provider + "/" + model) or prices.get(model) or {}
|
|
58
|
+
if not table:
|
|
59
|
+
return 0.0, "missing", "USD"
|
|
60
|
+
def rate(name: str) -> float:
|
|
61
|
+
return float(table.get(name, 0) or 0) / 1_000_000.0
|
|
62
|
+
total = float(usage.get("inputTokens", 0) or 0) * rate("input")
|
|
63
|
+
total += float(usage.get("outputTokens", 0) or 0) * rate("output")
|
|
64
|
+
total += float(usage.get("cacheReadTokens", 0) or 0) * rate("cacheRead")
|
|
65
|
+
total += float(usage.get("cacheWriteTokens", 0) or 0) * rate("cacheWrite")
|
|
66
|
+
return total, "tokens", "USD"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main() -> int:
|
|
70
|
+
parser = argparse.ArgumentParser(description="land-eval")
|
|
71
|
+
parser.add_argument("--tier", choices=sorted(TIERS), default="quick")
|
|
72
|
+
parser.add_argument("--submissions", default=str(ROOT / "submissions"))
|
|
73
|
+
parser.add_argument("--task", default=str(DEFAULT_TASK))
|
|
74
|
+
parser.add_argument("--with-history", action="store_true")
|
|
75
|
+
parser.add_argument("--out", default="")
|
|
76
|
+
parser.add_argument("--jobs", type=int, default=2)
|
|
77
|
+
args = parser.parse_args()
|
|
78
|
+
|
|
79
|
+
task = Path(args.task).resolve()
|
|
80
|
+
workspace = task / "workspace"
|
|
81
|
+
if not (workspace / "engine" / "referee.py").is_file():
|
|
82
|
+
print("land task not found:", task, file=sys.stderr)
|
|
83
|
+
return 2
|
|
84
|
+
submissions = Path(args.submissions).resolve()
|
|
85
|
+
dirs = [p for p in sorted(submissions.iterdir()) if p.is_dir() and (p / "bot.cpp").is_file()] if submissions.is_dir() else []
|
|
86
|
+
if not dirs:
|
|
87
|
+
print("no submissions found under", submissions, file=sys.stderr)
|
|
88
|
+
return 2
|
|
89
|
+
|
|
90
|
+
out = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
|
|
91
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
entries = out / "entries"
|
|
93
|
+
entries.mkdir(exist_ok=True)
|
|
94
|
+
names = {}
|
|
95
|
+
for directory in dirs:
|
|
96
|
+
name = re.sub(r"[^A-Za-z0-9_.-]+", "-", directory.name).strip("-") or "submission"
|
|
97
|
+
target = entries / name
|
|
98
|
+
target.mkdir(exist_ok=True)
|
|
99
|
+
shutil.copy2(directory / "bot.cpp", target / "bot.cpp")
|
|
100
|
+
names[name] = directory
|
|
101
|
+
if args.with_history:
|
|
102
|
+
for bot in sorted((task / "bots").glob("*/bot.cpp")):
|
|
103
|
+
target = entries / ("history-" + bot.parent.name.replace("/", "-"))
|
|
104
|
+
target.mkdir(exist_ok=True)
|
|
105
|
+
shutil.copy2(bot, target / "bot.cpp")
|
|
106
|
+
|
|
107
|
+
maps, melee = TIERS[args.tier]
|
|
108
|
+
results_jsonl = out / "results.jsonl"
|
|
109
|
+
cmd = [sys.executable, "-X", "utf8", str(task / "tournament" / "round_robin.py"),
|
|
110
|
+
"--entries", str(entries), "--out", str(results_jsonl), "--maps", str(maps),
|
|
111
|
+
"--melee-rounds", str(melee), "--jobs", str(max(1, args.jobs))]
|
|
112
|
+
if melee == 0:
|
|
113
|
+
cmd += ["--only", "duel"]
|
|
114
|
+
print("tier=%s maps=%d melee=%d entries=%d" % (args.tier, maps, melee, len(list(entries.iterdir()))), flush=True)
|
|
115
|
+
completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
116
|
+
if completed.returncode != 0:
|
|
117
|
+
print(completed.stdout[-4000:], completed.stderr[-4000:], file=sys.stderr)
|
|
118
|
+
return 2
|
|
119
|
+
ratings_prefix = out / "ratings"
|
|
120
|
+
rate = subprocess.run([sys.executable, "-X", "utf8", str(task / "tournament" / "rate.py"), str(results_jsonl),
|
|
121
|
+
"--entries", str(entries), "--out", str(ratings_prefix)], capture_output=True, text=True,
|
|
122
|
+
encoding="utf-8", errors="replace")
|
|
123
|
+
if rate.returncode != 0:
|
|
124
|
+
print(rate.stdout[-4000:], rate.stderr[-4000:], file=sys.stderr)
|
|
125
|
+
return 2
|
|
126
|
+
ratings = load_json(Path(str(ratings_prefix) + ".json")) or {}
|
|
127
|
+
models = ratings.get("models", {})
|
|
128
|
+
prices = load_json(ROOT / "prices.json") or {}
|
|
129
|
+
|
|
130
|
+
rows = []
|
|
131
|
+
all_names = list(names) + [name for name in models if name not in names]
|
|
132
|
+
for name in all_names:
|
|
133
|
+
directory = names.get(name)
|
|
134
|
+
if directory is not None:
|
|
135
|
+
run_dir = directory / "run"
|
|
136
|
+
run = load_json(run_dir / "run.json") or {}
|
|
137
|
+
usage = load_json(run_dir / "usage.json") or {}
|
|
138
|
+
cost = load_json(directory / "cost.json") or load_json(run_dir / "cost.json")
|
|
139
|
+
amount, source, currency = money(directory, run, usage, cost, prices)
|
|
140
|
+
violations = 0
|
|
141
|
+
if (run_dir / "violations.jsonl").is_file():
|
|
142
|
+
violations = len([line for line in (run_dir / "violations.jsonl").read_text(encoding="utf-8").splitlines() if line.strip()])
|
|
143
|
+
scan = static_scan(directory / "bot.cpp")
|
|
144
|
+
kind = "submission"
|
|
145
|
+
else:
|
|
146
|
+
run = {}
|
|
147
|
+
amount, source, currency = 0.0, "missing", "USD"
|
|
148
|
+
violations = 0
|
|
149
|
+
candidate = entries / name / "bot.cpp"
|
|
150
|
+
scan = static_scan(candidate) if candidate.is_file() else []
|
|
151
|
+
kind = "baseline" if name.startswith("anchor-") else "history"
|
|
152
|
+
model = models.get(name, {})
|
|
153
|
+
rows.append({
|
|
154
|
+
"name": name,
|
|
155
|
+
"kind": kind,
|
|
156
|
+
"score": round(float(model.get("score", 0) or 0), 2),
|
|
157
|
+
"theta": round(float(model.get("modes", {}).get("duel", {}).get("theta", 0) or 0), 3),
|
|
158
|
+
"cost": round(amount, 4), "costSource": source, "currency": currency,
|
|
159
|
+
"violations": violations,
|
|
160
|
+
"static": scan,
|
|
161
|
+
"run": run,
|
|
162
|
+
})
|
|
163
|
+
rows.sort(key=lambda row: (-row["score"], -row["theta"], row["name"]))
|
|
164
|
+
payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "tier": args.tier, "maps": maps, "melee": melee, "results": rows}
|
|
165
|
+
(out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
166
|
+
lines = ["# land-eval 结果", "", "- 时间:%s" % payload["generatedAt"], "- 档位:%s(单挑 %d 张图,混战 %d 轮)" % (args.tier, maps, melee), "", "## 排行榜", "", "| 名次 | 名称 | 总分 | 单挑 theta | 成本 | 价格来源 | 违规 | 静态扫描 |", "|---:|---|---:|---:|---:|---|---:|---|"]
|
|
167
|
+
for rank, row in enumerate(rows, 1):
|
|
168
|
+
scan = "未发现" if not row["static"] else "%d 项" % len(row["static"])
|
|
169
|
+
lines.append("| %d | %s | %.1f | %.2f | %.4f %s | %s | %d | %s |" % (rank, row["name"], row["score"], row["theta"], row["cost"], row["currency"], row["costSource"], row["violations"], scan))
|
|
170
|
+
lines += ["", "## 说明", "", "- 使用圈地官方 round_robin.py + rate.py;锚点 greedy/random/sparring 自动加入。", "- 没有 run/ 时仍评测 bot,只是成本/用量显示 missing。", "- 静态扫描只报告,不取消资格。"]
|
|
171
|
+
(out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
172
|
+
print("wrote", out / "summary.md")
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Evaluate a SWE-Bench Pro single-task submission.
|
|
3
|
+
|
|
4
|
+
Submission layout:
|
|
5
|
+
submissions/<name>/workspace/ (copy of the agent workspace, including .venv)
|
|
6
|
+
|
|
7
|
+
The hidden test patch is applied from the plugin asset; scoring runs the
|
|
8
|
+
instance's fail-to-pass and pass-to-pass tests. Missing run/ statistics are skipped.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
ROOT = Path(__file__).resolve().parent
|
|
21
|
+
ASSET = ROOT.parents[1] / "assets" / "swe-bench-pro"
|
|
22
|
+
PASSED = re.compile(r"(\d+) passed")
|
|
23
|
+
FAILED = re.compile(r"(\d+) failed")
|
|
24
|
+
ERRORS = re.compile(r"(\d+) error")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_json(path: Path):
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
30
|
+
except Exception:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def as_list(value) -> list:
|
|
35
|
+
if isinstance(value, str):
|
|
36
|
+
try:
|
|
37
|
+
parsed = json.loads(value)
|
|
38
|
+
except Exception:
|
|
39
|
+
return [value]
|
|
40
|
+
return parsed if isinstance(parsed, list) else [value]
|
|
41
|
+
return list(value or [])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def find_workspace(directory: Path) -> Path | None:
|
|
45
|
+
for candidate in (directory / "workspace", directory):
|
|
46
|
+
if (candidate / "test").is_dir() and (candidate / "lib" / "ansible").is_dir():
|
|
47
|
+
return candidate
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def apply_hidden_tests(workspace: Path) -> None:
|
|
52
|
+
patch = ASSET / "hidden" / "test.patch"
|
|
53
|
+
if not patch.is_file():
|
|
54
|
+
return
|
|
55
|
+
subprocess.run(["patch", "-p1", "--forward", "-i", str(patch)], cwd=workspace,
|
|
56
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
|
|
60
|
+
venv = workspace / ".venv" / "bin" / "python"
|
|
61
|
+
python = str(venv) if venv.is_file() else "python3"
|
|
62
|
+
tests = as_list(instance.get("fail_to_pass")) + as_list(instance.get("pass_to_pass"))
|
|
63
|
+
if not tests:
|
|
64
|
+
tests = as_list(instance.get("selected_test_files_to_run"))
|
|
65
|
+
env = {k: v for k, v in __import__("os").environ.items() if k not in {"PYTHONPATH", "PYTHONHOME"}}
|
|
66
|
+
completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no"], cwd=workspace,
|
|
67
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800, env=env)
|
|
68
|
+
output = (completed.stdout or "") + "\n" + (completed.stderr or "")
|
|
69
|
+
passed = int(PASSED.search(output).group(1)) if PASSED.search(output) else 0
|
|
70
|
+
failed = int(FAILED.search(output).group(1)) if FAILED.search(output) else 0
|
|
71
|
+
errors = int(ERRORS.search(output).group(1)) if ERRORS.search(output) else 0
|
|
72
|
+
if completed.returncode != 0 and passed == 0 and failed == 0 and errors == 0:
|
|
73
|
+
errors = 1
|
|
74
|
+
return passed, failed, errors, output[-3000:]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main() -> int:
|
|
78
|
+
parser = argparse.ArgumentParser(description="swe-bench-pro-eval")
|
|
79
|
+
parser.add_argument("--submissions", default=str(ROOT / "submissions"))
|
|
80
|
+
parser.add_argument("--out", default="")
|
|
81
|
+
args = parser.parse_args()
|
|
82
|
+
instance = load_json(ASSET / "instance.json") or {}
|
|
83
|
+
root = Path(args.submissions).resolve()
|
|
84
|
+
directories = [p for p in sorted(root.iterdir()) if p.is_dir()] if root.is_dir() else []
|
|
85
|
+
if not directories:
|
|
86
|
+
print("no submissions under", root, file=sys.stderr)
|
|
87
|
+
return 2
|
|
88
|
+
out = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
|
|
89
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
rows = []
|
|
91
|
+
for directory in directories:
|
|
92
|
+
workspace = find_workspace(directory)
|
|
93
|
+
if workspace is None:
|
|
94
|
+
rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0})
|
|
95
|
+
continue
|
|
96
|
+
try:
|
|
97
|
+
apply_hidden_tests(workspace)
|
|
98
|
+
passed, failed, errors, detail = run_tests(workspace, instance)
|
|
99
|
+
total = passed + failed + errors
|
|
100
|
+
score = 100.0 * passed / total if total else 0.0
|
|
101
|
+
rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail})
|
|
102
|
+
except Exception as error:
|
|
103
|
+
rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "detail": str(error)})
|
|
104
|
+
rows.sort(key=lambda row: (-row["score"], row["name"]))
|
|
105
|
+
payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "benchmark": "swe-bench-pro", "instance": instance.get("instance_id"), "results": rows}
|
|
106
|
+
(out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
107
|
+
lines = ["# SWE-Bench Pro · 单任务排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 测试通过率", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
|
|
108
|
+
for rank, row in enumerate(rows, 1):
|
|
109
|
+
lines.append("| %d | %s | %.1f | %d | %d | %d | %s |" % (rank, row["name"], row["score"], row["passed"], row["failed"], row["errors"], row["status"]))
|
|
110
|
+
(out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
111
|
+
print("wrote", out / "summary.md")
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
raise SystemExit(main())
|