@tangle-network/agent-bench 0.1.0 → 0.3.6
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/CHANGELOG.md +23 -0
- package/HARNESS.md +302 -0
- package/README.md +39 -2
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +43 -0
- package/dist/adapters.js.map +1 -0
- package/dist/benchmarks/_harness.d.ts +125 -0
- package/dist/benchmarks/_harness.js +33 -0
- package/dist/benchmarks/_harness.js.map +1 -0
- package/dist/benchmarks/aec-bench.d.ts +27 -0
- package/dist/benchmarks/aec-bench.js +8 -0
- package/dist/benchmarks/aec-bench.js.map +1 -0
- package/dist/benchmarks/agentbench.d.ts +16 -0
- package/dist/benchmarks/agentbench.js +10 -0
- package/dist/benchmarks/agentbench.js.map +1 -0
- package/dist/benchmarks/appworld.d.ts +37 -0
- package/dist/benchmarks/appworld.js +14 -0
- package/dist/benchmarks/appworld.js.map +1 -0
- package/dist/benchmarks/bfcl.d.ts +18 -0
- package/dist/benchmarks/bfcl.js +10 -0
- package/dist/benchmarks/bfcl.js.map +1 -0
- package/dist/benchmarks/cad-design.d.ts +45 -0
- package/dist/benchmarks/cad-design.js +7 -0
- package/dist/benchmarks/cad-design.js.map +1 -0
- package/dist/benchmarks/cadbench.d.ts +19 -0
- package/dist/benchmarks/cadbench.js +8 -0
- package/dist/benchmarks/cadbench.js.map +1 -0
- package/dist/benchmarks/cadgenbench.d.ts +22 -0
- package/dist/benchmarks/cadgenbench.js +8 -0
- package/dist/benchmarks/cadgenbench.js.map +1 -0
- package/dist/benchmarks/commit0.d.ts +31 -0
- package/dist/benchmarks/commit0.js +10 -0
- package/dist/benchmarks/commit0.js.map +1 -0
- package/dist/benchmarks/crag.d.ts +14 -0
- package/dist/benchmarks/crag.js +9 -0
- package/dist/benchmarks/crag.js.map +1 -0
- package/dist/benchmarks/dabstep.d.ts +18 -0
- package/dist/benchmarks/dabstep.js +10 -0
- package/dist/benchmarks/dabstep.js.map +1 -0
- package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
- package/dist/benchmarks/enterpriseops-gym.js +10 -0
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
- package/dist/benchmarks/finresearchbench.d.ts +15 -0
- package/dist/benchmarks/finresearchbench.js +8 -0
- package/dist/benchmarks/finresearchbench.js.map +1 -0
- package/dist/benchmarks/finsearchcomp.d.ts +49 -0
- package/dist/benchmarks/finsearchcomp.js +7 -0
- package/dist/benchmarks/finsearchcomp.js.map +1 -0
- package/dist/benchmarks/frames.d.ts +59 -0
- package/dist/benchmarks/frames.js +13 -0
- package/dist/benchmarks/frames.js.map +1 -0
- package/dist/benchmarks/hotpotqa.d.ts +48 -0
- package/dist/benchmarks/hotpotqa.js +15 -0
- package/dist/benchmarks/hotpotqa.js.map +1 -0
- package/dist/benchmarks/humaneval.d.ts +53 -0
- package/dist/benchmarks/humaneval.js +15 -0
- package/dist/benchmarks/humaneval.js.map +1 -0
- package/dist/benchmarks/mind2web.d.ts +41 -0
- package/dist/benchmarks/mind2web.js +9 -0
- package/dist/benchmarks/mind2web.js.map +1 -0
- package/dist/benchmarks/nomiracl.d.ts +15 -0
- package/dist/benchmarks/nomiracl.js +9 -0
- package/dist/benchmarks/nomiracl.js.map +1 -0
- package/dist/benchmarks/open-rag-bench.d.ts +14 -0
- package/dist/benchmarks/open-rag-bench.js +9 -0
- package/dist/benchmarks/open-rag-bench.js.map +1 -0
- package/dist/benchmarks/programbench.d.ts +38 -0
- package/dist/benchmarks/programbench.js +10 -0
- package/dist/benchmarks/programbench.js.map +1 -0
- package/dist/benchmarks/rag-shared.d.ts +42 -0
- package/dist/benchmarks/rag-shared.js +39 -0
- package/dist/benchmarks/rag-shared.js.map +1 -0
- package/dist/benchmarks/ragbench.d.ts +16 -0
- package/dist/benchmarks/ragbench.js +9 -0
- package/dist/benchmarks/ragbench.js.map +1 -0
- package/dist/benchmarks/simpleqa.d.ts +64 -0
- package/dist/benchmarks/simpleqa.js +11 -0
- package/dist/benchmarks/simpleqa.js.map +1 -0
- package/dist/benchmarks/swe-bench.d.ts +56 -0
- package/dist/benchmarks/swe-bench.js +14 -0
- package/dist/benchmarks/swe-bench.js.map +1 -0
- package/dist/benchmarks/t2-ragbench.d.ts +14 -0
- package/dist/benchmarks/t2-ragbench.js +9 -0
- package/dist/benchmarks/t2-ragbench.js.map +1 -0
- package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
- package/dist/benchmarks/tau-bench-shared.js +10 -0
- package/dist/benchmarks/tau-bench-shared.js.map +1 -0
- package/dist/benchmarks/tau2-bench.d.ts +7 -0
- package/dist/benchmarks/tau2-bench.js +11 -0
- package/dist/benchmarks/tau2-bench.js.map +1 -0
- package/dist/benchmarks/tau3-banking.d.ts +15 -0
- package/dist/benchmarks/tau3-banking.js +9 -0
- package/dist/benchmarks/tau3-banking.js.map +1 -0
- package/dist/benchmarks/terminal-bench.d.ts +24 -0
- package/dist/benchmarks/terminal-bench.js +8 -0
- package/dist/benchmarks/terminal-bench.js.map +1 -0
- package/dist/benchmarks/toollm.d.ts +16 -0
- package/dist/benchmarks/toollm.js +10 -0
- package/dist/benchmarks/toollm.js.map +1 -0
- package/dist/benchmarks/trata-hedge.d.ts +32 -0
- package/dist/benchmarks/trata-hedge.js +7 -0
- package/dist/benchmarks/trata-hedge.js.map +1 -0
- package/dist/benchmarks/types.d.ts +107 -0
- package/dist/benchmarks/types.js +1 -0
- package/dist/benchmarks/types.js.map +1 -0
- package/dist/benchmarks/webarena-verified.d.ts +16 -0
- package/dist/benchmarks/webarena-verified.js +10 -0
- package/dist/benchmarks/webarena-verified.js.map +1 -0
- package/dist/chunk-2PVVP7GN.js +197 -0
- package/dist/chunk-2PVVP7GN.js.map +1 -0
- package/dist/chunk-2XU6OGEN.js +170 -0
- package/dist/chunk-2XU6OGEN.js.map +1 -0
- package/dist/chunk-53UPUNBZ.js +325 -0
- package/dist/chunk-53UPUNBZ.js.map +1 -0
- package/dist/chunk-5SBJCB6W.js +144 -0
- package/dist/chunk-5SBJCB6W.js.map +1 -0
- package/dist/chunk-7WSD27QQ.js +118 -0
- package/dist/chunk-7WSD27QQ.js.map +1 -0
- package/dist/chunk-C7T7WEK2.js +103 -0
- package/dist/chunk-C7T7WEK2.js.map +1 -0
- package/dist/chunk-CKUVRZ2T.js +251 -0
- package/dist/chunk-CKUVRZ2T.js.map +1 -0
- package/dist/chunk-HBSWHQNJ.js +30 -0
- package/dist/chunk-HBSWHQNJ.js.map +1 -0
- package/dist/chunk-HHXFIHXC.js +116 -0
- package/dist/chunk-HHXFIHXC.js.map +1 -0
- package/dist/chunk-IFAV6KEM.js +276 -0
- package/dist/chunk-IFAV6KEM.js.map +1 -0
- package/dist/chunk-INNOYXCP.js +387 -0
- package/dist/chunk-INNOYXCP.js.map +1 -0
- package/dist/chunk-J3KDJNX2.js +182 -0
- package/dist/chunk-J3KDJNX2.js.map +1 -0
- package/dist/chunk-JRWWGMK7.js +148 -0
- package/dist/chunk-JRWWGMK7.js.map +1 -0
- package/dist/chunk-JTHWEDEW.js +32 -0
- package/dist/chunk-JTHWEDEW.js.map +1 -0
- package/dist/chunk-KDIKRJGB.js +120 -0
- package/dist/chunk-KDIKRJGB.js.map +1 -0
- package/dist/chunk-LRRD7NAG.js +301 -0
- package/dist/chunk-LRRD7NAG.js.map +1 -0
- package/dist/chunk-ODT47UAY.js +221 -0
- package/dist/chunk-ODT47UAY.js.map +1 -0
- package/dist/chunk-PA2ZKHJC.js +230 -0
- package/dist/chunk-PA2ZKHJC.js.map +1 -0
- package/dist/chunk-PPYSEKFM.js +182 -0
- package/dist/chunk-PPYSEKFM.js.map +1 -0
- package/dist/chunk-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.js.map +1 -0
- package/dist/chunk-R36V2VP7.js +169 -0
- package/dist/chunk-R36V2VP7.js.map +1 -0
- package/dist/chunk-R67DFVLO.js +142 -0
- package/dist/chunk-R67DFVLO.js.map +1 -0
- package/dist/chunk-SEVJPLZC.js +260 -0
- package/dist/chunk-SEVJPLZC.js.map +1 -0
- package/dist/chunk-SYDW647C.js +318 -0
- package/dist/chunk-SYDW647C.js.map +1 -0
- package/dist/chunk-TBKU5XQI.js +228 -0
- package/dist/chunk-TBKU5XQI.js.map +1 -0
- package/dist/chunk-TSWPNOYM.js +147 -0
- package/dist/chunk-TSWPNOYM.js.map +1 -0
- package/dist/chunk-UAIOHCUK.js +27 -0
- package/dist/chunk-UAIOHCUK.js.map +1 -0
- package/dist/chunk-UPAMRDX4.js +233 -0
- package/dist/chunk-UPAMRDX4.js.map +1 -0
- package/dist/chunk-VQRS7VUC.js +342 -0
- package/dist/chunk-VQRS7VUC.js.map +1 -0
- package/dist/chunk-X3BTXCJ4.js +262 -0
- package/dist/chunk-X3BTXCJ4.js.map +1 -0
- package/dist/chunk-X5YKXC6V.js +211 -0
- package/dist/chunk-X5YKXC6V.js.map +1 -0
- package/dist/chunk-Y6O2OCUO.js +130 -0
- package/dist/chunk-Y6O2OCUO.js.map +1 -0
- package/dist/chunk-YCGY7UIZ.js +208 -0
- package/dist/chunk-YCGY7UIZ.js.map +1 -0
- package/dist/chunk-Z7ML6L77.js +162 -0
- package/dist/chunk-Z7ML6L77.js.map +1 -0
- package/dist/chunk-ZEWMTR5M.js +136 -0
- package/dist/chunk-ZEWMTR5M.js.map +1 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +1908 -0
- package/dist/index.js.map +1 -0
- package/fixtures/aec-bench.json +18 -0
- package/fixtures/agentbench-dbbench.json +22 -0
- package/fixtures/bfcl.json +45 -0
- package/fixtures/commit0.json +72 -0
- package/fixtures/crag.json +10 -0
- package/fixtures/dabstep.json +22 -0
- package/fixtures/enterpriseops-gym.json +103 -0
- package/fixtures/finresearchbench.json +21 -0
- package/fixtures/finsearchcomp.json +66 -0
- package/fixtures/frames.json +26 -0
- package/fixtures/hotpotqa.json +182 -0
- package/fixtures/nomiracl.json +26 -0
- package/fixtures/open-rag-bench.json +16 -0
- package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
- package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
- package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
- package/fixtures/pier-agent/no-model-task/task.toml +35 -0
- package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
- package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
- package/fixtures/programbench.json +17 -0
- package/fixtures/ragbench.json +21 -0
- package/fixtures/simpleqa.json +121 -0
- package/fixtures/t2-ragbench.json +13 -0
- package/fixtures/tau2-bench.json +16 -0
- package/fixtures/tau3-banking.json +16 -0
- package/fixtures/toollm.json +28 -0
- package/fixtures/webarena-verified.json +20 -0
- package/package.json +60 -20
- package/pier_agents/__init__.py +18 -0
- package/pier_agents/candidate_contract.py +755 -0
- package/pier_agents/process_boundary.py +321 -0
- package/pier_agents/tangle_candidate.py +907 -0
- package/pier_agents/workspace_boundary.py +368 -0
- package/scripts/appworld_driver.py +359 -0
- package/scripts/cadbench_prepare.py +22 -0
- package/scripts/cadgenbench_hard_parts.py +48 -0
- package/scripts/clbench_codebase_judge.py +73 -0
- package/scripts/commit0_judge.py +170 -0
- package/scripts/dabstep_judge.py +42 -0
- package/scripts/enterpriseops_gym_judge.py +281 -0
- package/scripts/programbench_judge.py +120 -0
- package/scripts/render-gate-chart.mjs +176 -0
- package/scripts/run-package-tests.mjs +56 -0
- package/scripts/terminate-pier-trial.mts +66 -0
- package/scripts/trata-hedge/README.md +56 -0
- package/scripts/trata-hedge/run.sh +60 -0
- package/scripts/trata-hedge/solve.py +83 -0
- package/scripts/verify-packed-consumer.mjs +235 -0
- package/scripts/verify-pier-agent.mts +715 -0
- package/scripts/verify-pier-pair.mts +74 -0
- package/scripts/verify-pier-recovery.mts +139 -0
- package/src/adapters.ts +26 -0
- package/src/benchmarks/_harness.test.mts +178 -0
- package/src/benchmarks/_harness.ts +239 -16
- package/src/benchmarks/agentbench.ts +163 -0
- package/src/benchmarks/appworld.test.mts +15 -9
- package/src/benchmarks/bfcl.ts +346 -0
- package/src/benchmarks/crag.ts +137 -0
- package/src/benchmarks/dabstep.test.mts +70 -0
- package/src/benchmarks/dabstep.ts +212 -0
- package/src/benchmarks/external-adapters.test.mts +150 -0
- package/src/benchmarks/finresearchbench.ts +269 -0
- package/src/benchmarks/humaneval.ts +20 -8
- package/src/benchmarks/nomiracl.ts +180 -0
- package/src/benchmarks/open-rag-bench.ts +153 -0
- package/src/benchmarks/rag-benchmarks.test.mts +138 -0
- package/src/benchmarks/rag-shared.ts +327 -0
- package/src/benchmarks/ragbench.ts +171 -0
- package/src/benchmarks/swe-bench.test.mts +61 -0
- package/src/benchmarks/swe-bench.ts +201 -19
- package/src/benchmarks/t2-ragbench.ts +166 -0
- package/src/benchmarks/tau-bench-shared.ts +214 -0
- package/src/benchmarks/tau2-bench.ts +30 -0
- package/src/benchmarks/tau3-banking.ts +29 -0
- package/src/benchmarks/terminal-bench.test.mts +33 -0
- package/src/benchmarks/terminal-bench.ts +23 -8
- package/src/benchmarks/toollm.ts +254 -0
- package/src/benchmarks/types.ts +42 -0
- package/src/benchmarks/webarena-verified.ts +200 -0
- package/src/commit0-prereqs.sh +0 -0
- package/src/coordination-mcp-container-reach.mts +181 -0
- package/src/decoder-live.mts +1 -1
- package/src/examples/README.md +103 -39
- package/src/examples/benchmark-matrix.mts +101 -0
- package/src/examples/lean-proof-gate.README.md +77 -0
- package/src/examples/lean-proof-gate.mts +162 -0
- package/src/examples/lean-verify.ts +95 -0
- package/src/examples/lean.Dockerfile +12 -0
- package/src/examples/math-demo.mts +9 -7
- package/src/examples/strategy-demo.mts +10 -12
- package/src/gate.ts +3 -2
- package/src/hev-eval.mts +69 -0
- package/src/hev-improve.mts +169 -0
- package/src/hev-structural.mts +688 -0
- package/src/index.ts +73 -0
- package/src/mbpp-structural.mts +662 -0
- package/src/pier-agent.test-fixtures.mts +19 -0
- package/src/pier-agent.test.mts +363 -0
- package/src/pier-agent.ts +657 -0
- package/src/pier-result-grader.mjs +30 -0
- package/src/pier-result-grader.test.mts +62 -0
- package/src/pier-result-grader.ts +108 -0
- package/src/pier-task-outcome.test.mts +117 -0
- package/src/pier-task-outcome.ts +240 -0
- package/src/pier-trial-controller.test.mts +412 -0
- package/src/pier-trial-controller.ts +858 -0
- package/src/pier-trial-supervisor.mjs +352 -0
- package/src/resolve-client.ts +25 -2
- package/src/run-benchmarks-cli.mts +72 -0
- package/src/run-benchmarks-report.ts +66 -0
- package/src/run-benchmarks.test.mts +231 -0
- package/src/run-benchmarks.ts +589 -0
- package/src/smoke-structural-rollout.mts +393 -0
- package/src/swe-bench-env.test.ts +207 -0
- package/src/swe-bench-env.ts +554 -0
- package/src/swe-jail.ts +293 -0
- package/src/swe-self-improve.mts +84 -0
- package/src/swe-structural-judge-policy.test.ts +117 -0
- package/src/swe-structural-judge-policy.ts +133 -0
- package/src/swe-structural-policy.test.ts +124 -0
- package/src/swe-structural-policy.ts +132 -0
- package/src/swe-structural-provenance.test.ts +93 -0
- package/src/swe-structural-provenance.ts +138 -0
- package/src/swe-structural.mts +1260 -0
- package/src/swe-temp.ts +14 -0
- package/src/tb-container-executor.mts +234 -0
- package/src/tb-container-executor.test.mts +99 -0
- package/src/tb-supervisor-sidecar.mts +222 -0
- package/src/trata-gepa.mts +1 -1
- package/steerers/eops-itsm-population.json +1 -0
- package/tb_agents/opencode_refine_agent.py +117 -0
- package/tb_agents/opencode_router_agent.py +406 -0
- package/tb_agents/opencode_supervisor_agent.py +239 -0
- package/tb_agents/script_agent.py +66 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# AppWorld engine driver. Two subcommands the TS adapter shells out to:
|
|
2
|
+
# load --split S [--limit N] [--ids a,b] -> {"tasks":[{task_id,instruction}]}
|
|
3
|
+
# evaluate --task-id T --split S (solution code on stdin)
|
|
4
|
+
# -> {"success":bool,"passes":int,"fails":int}
|
|
5
|
+
# evaluate runs the worker's solution in a fresh AppWorld(task_id=T) world, then
|
|
6
|
+
# AppWorld's OWN programmatic evaluator (world.evaluate().to_dict()) reports the
|
|
7
|
+
# verdict. JSON is emitted as the LAST stdout line. Fail loud: any engine error is
|
|
8
|
+
# printed as {"error": "..."} and exits nonzero — never a fabricated verdict.
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def fail(msg: str) -> None:
|
|
19
|
+
print(json.dumps({"error": msg}))
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_CODE_RE = re.compile(r"```(?:python|py)?\s*\n(.*?)```", re.DOTALL)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _extract_code(text: str) -> str:
|
|
27
|
+
blocks = _CODE_RE.findall(text or "")
|
|
28
|
+
return (blocks[-1] if blocks else "").strip()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _router_chat(base: str, key: str, model: str, messages: list, timeout: float = 180.0):
|
|
32
|
+
"""One router chat-completion with retry on transient/429/5xx. Returns
|
|
33
|
+
(content, input_tokens, output_tokens). Raises on exhausted retries."""
|
|
34
|
+
import httpx
|
|
35
|
+
|
|
36
|
+
url = base.rstrip("/") + "/chat/completions"
|
|
37
|
+
last = None
|
|
38
|
+
for attempt in range(4):
|
|
39
|
+
try:
|
|
40
|
+
r = httpx.post(
|
|
41
|
+
url,
|
|
42
|
+
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
|
43
|
+
json={"model": model, "messages": messages},
|
|
44
|
+
timeout=timeout,
|
|
45
|
+
)
|
|
46
|
+
if r.status_code in (429, 500, 502, 503, 504):
|
|
47
|
+
last = f"{r.status_code}: {r.text[:160]}"
|
|
48
|
+
time.sleep(2**attempt)
|
|
49
|
+
continue
|
|
50
|
+
r.raise_for_status()
|
|
51
|
+
d = r.json()
|
|
52
|
+
content = (d["choices"][0]["message"].get("content") or "")
|
|
53
|
+
usage = d.get("usage") or {}
|
|
54
|
+
return content, int(usage.get("prompt_tokens", 0) or 0), int(usage.get("completion_tokens", 0) or 0)
|
|
55
|
+
except Exception as e: # noqa: BLE001
|
|
56
|
+
last = str(e)
|
|
57
|
+
if attempt < 3:
|
|
58
|
+
time.sleep(2**attempt)
|
|
59
|
+
continue
|
|
60
|
+
raise RuntimeError(f"router_chat failed after retries: {last}")
|
|
61
|
+
raise RuntimeError(f"router_chat exhausted: {last}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _build_system(directive: str, world) -> str:
|
|
65
|
+
sup = world.task.supervisor
|
|
66
|
+
apps = list(getattr(world.task, "allowed_apps", []) or [])
|
|
67
|
+
descs = getattr(world.task, "app_descriptions", "")
|
|
68
|
+
desc_str = json.dumps(descs) if isinstance(descs, (dict, list)) else str(descs)
|
|
69
|
+
return (
|
|
70
|
+
f"You are an AI agent completing a digital task for your supervisor "
|
|
71
|
+
f"{getattr(sup, 'first_name', '')} {getattr(sup, 'last_name', '')} "
|
|
72
|
+
f"(email {getattr(sup, 'email', '')}, phone {getattr(sup, 'phone_number', '')}) "
|
|
73
|
+
"by WRITING PYTHON that calls app APIs (the apis.<app>.<function>(...) surface).\n\n"
|
|
74
|
+
f"Available apps: {', '.join(apps)}.\n"
|
|
75
|
+
f"App descriptions: {desc_str[:1500]}\n\n"
|
|
76
|
+
"How to work, one step per turn:\n"
|
|
77
|
+
"- Discover APIs with apis.api_docs.show_api_descriptions(app_name='<app>') and "
|
|
78
|
+
"apis.api_docs.show_api_doc(app_name='<app>', api_name='<api>') BEFORE calling them.\n"
|
|
79
|
+
"- Get the supervisor's app passwords with apis.supervisor.show_account_passwords(), then log in "
|
|
80
|
+
"to each app you use to obtain its access_token.\n"
|
|
81
|
+
"- Write ONE short Python code block per turn. After it runs you SEE its OUTPUT (or error "
|
|
82
|
+
"traceback) — use that to decide the next step. Print intermediate values you need.\n"
|
|
83
|
+
"- Iterate: inspect -> authenticate -> act -> verify. Do not guess API names or arguments.\n"
|
|
84
|
+
"- When the task is fully done call apis.supervisor.complete_task(answer=<answer>) (include the "
|
|
85
|
+
"answer if the task asks a question, otherwise apis.supervisor.complete_task()).\n"
|
|
86
|
+
"- Reply with EXACTLY ONE fenced ```python block per turn and nothing else.\n\n"
|
|
87
|
+
f"{directive}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def cmd_react(args) -> None:
|
|
92
|
+
"""Multi-turn REPL agent: the model writes a python block, the engine executes
|
|
93
|
+
it in the PERSISTENT world, the output is fed back, and it iterates until it
|
|
94
|
+
completes the task or hits max-turns. Then AppWorld's own evaluator scores it.
|
|
95
|
+
Config (directive, model, router creds, max_turns) arrives as JSON on stdin so
|
|
96
|
+
the candidate directive can be arbitrarily long. The directive is the optimized
|
|
97
|
+
surface; the loop + contract are fixed."""
|
|
98
|
+
cfg = {}
|
|
99
|
+
raw = sys.stdin.read()
|
|
100
|
+
if raw.strip():
|
|
101
|
+
try:
|
|
102
|
+
cfg = json.loads(raw)
|
|
103
|
+
except Exception as e: # noqa: BLE001
|
|
104
|
+
fail(f"react config JSON parse failed: {e}")
|
|
105
|
+
directive = str(cfg.get("directive", ""))
|
|
106
|
+
model = str(cfg.get("model", "gpt-4o"))
|
|
107
|
+
max_turns = int(cfg.get("max_turns", 8))
|
|
108
|
+
router_base = str(cfg.get("router_base", "https://router.tangle.tools/v1"))
|
|
109
|
+
router_key = str(cfg.get("router_key") or os.environ.get("TANGLE_API_KEY", ""))
|
|
110
|
+
if not router_key:
|
|
111
|
+
fail("react: router_key/TANGLE_API_KEY required")
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
from appworld import AppWorld
|
|
115
|
+
except Exception as e: # noqa: BLE001
|
|
116
|
+
fail(f"appworld import failed: {e}")
|
|
117
|
+
|
|
118
|
+
in_tok = 0
|
|
119
|
+
out_tok = 0
|
|
120
|
+
turns = 0
|
|
121
|
+
turns_log: list = []
|
|
122
|
+
try:
|
|
123
|
+
with AppWorld(
|
|
124
|
+
task_id=args.task_id,
|
|
125
|
+
experiment_name="bench-react",
|
|
126
|
+
raise_on_failure=False,
|
|
127
|
+
) as world:
|
|
128
|
+
messages = [
|
|
129
|
+
{"role": "system", "content": _build_system(directive, world)},
|
|
130
|
+
{"role": "user", "content": f"Task: {world.task.instruction}"},
|
|
131
|
+
]
|
|
132
|
+
for turn in range(max_turns):
|
|
133
|
+
turns = turn + 1
|
|
134
|
+
content, ui, uo = _router_chat(router_base, router_key, model, messages)
|
|
135
|
+
in_tok += ui
|
|
136
|
+
out_tok += uo
|
|
137
|
+
code = _extract_code(content)
|
|
138
|
+
messages.append({"role": "assistant", "content": content})
|
|
139
|
+
if not code:
|
|
140
|
+
messages.append({
|
|
141
|
+
"role": "user",
|
|
142
|
+
"content": "Reply with exactly one ```python block that makes progress, "
|
|
143
|
+
"or call apis.supervisor.complete_task().",
|
|
144
|
+
})
|
|
145
|
+
continue
|
|
146
|
+
output = world.execute(code)
|
|
147
|
+
turns_log.append({"code": code[:600], "output": str(output)[:600]})
|
|
148
|
+
messages.append({"role": "user", "content": "OUTPUT:\n" + str(output)[:4000]})
|
|
149
|
+
if world.task_completed():
|
|
150
|
+
break
|
|
151
|
+
evaluation = world.evaluate().to_dict()
|
|
152
|
+
except Exception as e: # noqa: BLE001
|
|
153
|
+
fail(f"react of {args.task_id} failed: {e}")
|
|
154
|
+
|
|
155
|
+
if "success" not in evaluation or "num_tests" not in evaluation:
|
|
156
|
+
fail(f"evaluation dict missing success/num_tests keys: {sorted(evaluation.keys())}")
|
|
157
|
+
passes = evaluation.get("passes", [])
|
|
158
|
+
failures = evaluation.get("failures", [])
|
|
159
|
+
n_pass = len(passes) if isinstance(passes, list) else int(passes or 0)
|
|
160
|
+
n_fail = len(failures) if isinstance(failures, list) else int(failures or 0)
|
|
161
|
+
print(
|
|
162
|
+
json.dumps(
|
|
163
|
+
{
|
|
164
|
+
"success": bool(evaluation["success"]),
|
|
165
|
+
"passes": n_pass,
|
|
166
|
+
"fails": n_fail,
|
|
167
|
+
"num_tests": int(evaluation["num_tests"]),
|
|
168
|
+
# Failed sub-test names — the evidence a trace analyst steers on.
|
|
169
|
+
"failure_names": [str(f)[:160] for f in failures][:8]
|
|
170
|
+
if isinstance(failures, list)
|
|
171
|
+
else [],
|
|
172
|
+
"turns": turns,
|
|
173
|
+
"input_tokens": in_tok,
|
|
174
|
+
"output_tokens": out_tok,
|
|
175
|
+
"transcript": "\n---\n".join(
|
|
176
|
+
f"CODE:\n{t['code']}\nOUTPUT:\n{t['output']}" for t in turns_log[-3:]
|
|
177
|
+
)[:1600],
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def cmd_session(args) -> None:
|
|
184
|
+
"""Dumb world shim: a persistent AppWorld session driven over stdin JSONL.
|
|
185
|
+
NO LLM calls here — the agent loop lives in the runtime (routerToolLoop);
|
|
186
|
+
this process only owns world state. One JSON object per line, both ways:
|
|
187
|
+
{"op":"execute","code":"..."} -> {"output":"...","task_completed":bool}
|
|
188
|
+
{"op":"evaluate"} -> the evaluate verdict JSON (+failure_names)
|
|
189
|
+
Emits {"ready":true,"instruction":...} on start; exits on stdin EOF."""
|
|
190
|
+
try:
|
|
191
|
+
from appworld import AppWorld
|
|
192
|
+
except Exception as e: # noqa: BLE001
|
|
193
|
+
fail(f"appworld import failed: {e}")
|
|
194
|
+
try:
|
|
195
|
+
with AppWorld(
|
|
196
|
+
task_id=args.task_id,
|
|
197
|
+
experiment_name="bench-session",
|
|
198
|
+
raise_on_failure=False,
|
|
199
|
+
) as world:
|
|
200
|
+
print(json.dumps({"ready": True, "instruction": world.task.instruction}), flush=True)
|
|
201
|
+
for line in sys.stdin:
|
|
202
|
+
line = line.strip()
|
|
203
|
+
if not line:
|
|
204
|
+
continue
|
|
205
|
+
try:
|
|
206
|
+
cmd = json.loads(line)
|
|
207
|
+
except Exception as e: # noqa: BLE001
|
|
208
|
+
print(json.dumps({"error": f"bad command JSON: {e}"}), flush=True)
|
|
209
|
+
continue
|
|
210
|
+
op = cmd.get("op")
|
|
211
|
+
if op == "execute":
|
|
212
|
+
# An exception here is the AGENT's outcome (bad code), not an
|
|
213
|
+
# infra fault — feed it back, keep the world alive.
|
|
214
|
+
try:
|
|
215
|
+
output = str(world.execute(str(cmd.get("code", ""))))
|
|
216
|
+
except Exception as e: # noqa: BLE001
|
|
217
|
+
output = f"EXECUTION ERROR: {e}"
|
|
218
|
+
print(
|
|
219
|
+
json.dumps(
|
|
220
|
+
{"output": output[:4000], "task_completed": bool(world.task_completed())}
|
|
221
|
+
),
|
|
222
|
+
flush=True,
|
|
223
|
+
)
|
|
224
|
+
elif op == "evaluate":
|
|
225
|
+
ev = world.evaluate().to_dict()
|
|
226
|
+
passes = ev.get("passes", [])
|
|
227
|
+
failures = ev.get("failures", [])
|
|
228
|
+
print(
|
|
229
|
+
json.dumps(
|
|
230
|
+
{
|
|
231
|
+
"success": bool(ev.get("success")),
|
|
232
|
+
"passes": len(passes) if isinstance(passes, list) else int(passes or 0),
|
|
233
|
+
"fails": len(failures) if isinstance(failures, list) else int(failures or 0),
|
|
234
|
+
"num_tests": int(ev.get("num_tests", 0)),
|
|
235
|
+
"failure_names": [str(f)[:160] for f in failures][:8]
|
|
236
|
+
if isinstance(failures, list)
|
|
237
|
+
else [],
|
|
238
|
+
}
|
|
239
|
+
),
|
|
240
|
+
flush=True,
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
print(json.dumps({"error": f"unknown op: {op}"}), flush=True)
|
|
244
|
+
except Exception as e: # noqa: BLE001
|
|
245
|
+
fail(f"session of {args.task_id} failed: {e}")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def cmd_load(args) -> None:
|
|
249
|
+
try:
|
|
250
|
+
from appworld import load_task_ids
|
|
251
|
+
except Exception as e: # noqa: BLE001
|
|
252
|
+
fail(f"appworld import failed: {e}")
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
ids = list(load_task_ids(args.split))
|
|
256
|
+
except Exception as e: # noqa: BLE001
|
|
257
|
+
fail(f"load_task_ids({args.split}) failed: {e}")
|
|
258
|
+
|
|
259
|
+
if args.ids:
|
|
260
|
+
want = set(args.ids.split(","))
|
|
261
|
+
ids = [i for i in ids if i in want]
|
|
262
|
+
elif args.limit is not None:
|
|
263
|
+
ids = ids[: args.limit]
|
|
264
|
+
|
|
265
|
+
from appworld import AppWorld
|
|
266
|
+
|
|
267
|
+
tasks = []
|
|
268
|
+
for task_id in ids:
|
|
269
|
+
# Open each world read-only just to read the instruction; close immediately.
|
|
270
|
+
try:
|
|
271
|
+
with AppWorld(task_id=task_id, experiment_name="bench-load") as world:
|
|
272
|
+
tasks.append({"task_id": task_id, "instruction": world.task.instruction})
|
|
273
|
+
except Exception as e: # noqa: BLE001
|
|
274
|
+
fail(f"opening task {task_id} failed: {e}")
|
|
275
|
+
print(json.dumps({"tasks": tasks}))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def cmd_evaluate(args) -> None:
|
|
279
|
+
code = sys.stdin.read()
|
|
280
|
+
try:
|
|
281
|
+
from appworld import AppWorld
|
|
282
|
+
except Exception as e: # noqa: BLE001
|
|
283
|
+
fail(f"appworld import failed: {e}")
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
with AppWorld(
|
|
287
|
+
task_id=args.task_id,
|
|
288
|
+
experiment_name="bench-eval",
|
|
289
|
+
# An API error should surface to the agent's output, not crash the world.
|
|
290
|
+
raise_on_failure=False,
|
|
291
|
+
) as world:
|
|
292
|
+
if code.strip():
|
|
293
|
+
world.execute(code)
|
|
294
|
+
evaluation = world.evaluate().to_dict()
|
|
295
|
+
except Exception as e: # noqa: BLE001
|
|
296
|
+
fail(f"evaluate of {args.task_id} failed: {e}")
|
|
297
|
+
|
|
298
|
+
# TestTracker.to_dict() carries `success` (binary task-goal-completion),
|
|
299
|
+
# `num_tests` (the authoritative per-requirement total), and the
|
|
300
|
+
# `passes`/`failures` lists. `failures` (not `fails`) is the real key — read it
|
|
301
|
+
# directly and never substitute a default count.
|
|
302
|
+
if "success" not in evaluation or "num_tests" not in evaluation:
|
|
303
|
+
fail(f"evaluation dict missing success/num_tests keys: {sorted(evaluation.keys())}")
|
|
304
|
+
success = bool(evaluation["success"])
|
|
305
|
+
passes = evaluation.get("passes", [])
|
|
306
|
+
failures = evaluation.get("failures", [])
|
|
307
|
+
n_pass = len(passes) if isinstance(passes, list) else int(passes or 0)
|
|
308
|
+
n_fail = len(failures) if isinstance(failures, list) else int(failures or 0)
|
|
309
|
+
print(
|
|
310
|
+
json.dumps(
|
|
311
|
+
{
|
|
312
|
+
"success": success,
|
|
313
|
+
"passes": n_pass,
|
|
314
|
+
"fails": n_fail,
|
|
315
|
+
"num_tests": int(evaluation["num_tests"]),
|
|
316
|
+
# The failed sub-test names are the diagnosable evidence a trace
|
|
317
|
+
# analyst steers on — bounded so the JSON line stays small.
|
|
318
|
+
"failure_names": [str(f)[:160] for f in failures][:8]
|
|
319
|
+
if isinstance(failures, list)
|
|
320
|
+
else [],
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def main() -> None:
|
|
327
|
+
ap = argparse.ArgumentParser(description="appworld engine driver")
|
|
328
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
329
|
+
|
|
330
|
+
p_load = sub.add_parser("load")
|
|
331
|
+
p_load.add_argument("--split", required=True)
|
|
332
|
+
p_load.add_argument("--limit", type=int, default=None)
|
|
333
|
+
p_load.add_argument("--ids", default=None)
|
|
334
|
+
|
|
335
|
+
p_eval = sub.add_parser("evaluate")
|
|
336
|
+
p_eval.add_argument("--task-id", required=True)
|
|
337
|
+
p_eval.add_argument("--split", required=True)
|
|
338
|
+
|
|
339
|
+
p_react = sub.add_parser("react")
|
|
340
|
+
p_react.add_argument("--task-id", required=True)
|
|
341
|
+
p_react.add_argument("--split", required=True)
|
|
342
|
+
|
|
343
|
+
p_session = sub.add_parser("session")
|
|
344
|
+
p_session.add_argument("--task-id", required=True)
|
|
345
|
+
p_session.add_argument("--split", required=True)
|
|
346
|
+
|
|
347
|
+
args = ap.parse_args()
|
|
348
|
+
if args.cmd == "load":
|
|
349
|
+
cmd_load(args)
|
|
350
|
+
elif args.cmd == "evaluate":
|
|
351
|
+
cmd_evaluate(args)
|
|
352
|
+
elif args.cmd == "react":
|
|
353
|
+
cmd_react(args)
|
|
354
|
+
elif args.cmd == "session":
|
|
355
|
+
cmd_session(args)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == "__main__":
|
|
359
|
+
main()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch FreedomIntelligence/CADBench + flatten its nested `criteria` into a flat
|
|
3
|
+
bullet list, writing the cleaned JSONL the CADBench adapter reads via CADBENCH_PATH.
|
|
4
|
+
Usage: python3 scripts/cadbench_prepare.py [out.jsonl] (clones the HF dataset to /tmp)"""
|
|
5
|
+
import json, subprocess, sys, os
|
|
6
|
+
OUT = sys.argv[1] if len(sys.argv) > 1 else '/tmp/cadbench_clean.jsonl'
|
|
7
|
+
SRC = '/tmp/cadbench-ds'
|
|
8
|
+
if not os.path.exists(SRC):
|
|
9
|
+
subprocess.run(['git', 'clone', '--depth', '1', 'https://huggingface.co/datasets/FreedomIntelligence/CADBench', SRC], check=True)
|
|
10
|
+
def flatten(x, out):
|
|
11
|
+
if isinstance(x, dict):
|
|
12
|
+
for v in x.values(): flatten(v, out)
|
|
13
|
+
elif isinstance(x, list):
|
|
14
|
+
for v in x: flatten(v, out)
|
|
15
|
+
elif isinstance(x, str): out.append(x)
|
|
16
|
+
rows = [json.loads(l) for l in open(f'{SRC}/CADBench.jsonl', encoding='utf-8-sig') if l.strip()]
|
|
17
|
+
clean = []
|
|
18
|
+
for r in rows:
|
|
19
|
+
b = []; flatten(r['criteria'], b)
|
|
20
|
+
clean.append({'id': r['id'], 'name': r.get('name', ''), 'instruction': r['instruction'], 'type': r.get('type', ''), 'criteria': b})
|
|
21
|
+
open(OUT, 'w').write('\n'.join(json.dumps(c) for c in clean))
|
|
22
|
+
print(f'wrote {len(clean)} tasks -> {OUT}')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from build123d import *
|
|
2
|
+
import os, json
|
|
3
|
+
OUT = "/tmp/cgb-hard"; os.makedirs(OUT, exist_ok=True)
|
|
4
|
+
tasks = []
|
|
5
|
+
def save(pid, part, desc):
|
|
6
|
+
p = f"{OUT}/{pid}.step"; export_step(part, p)
|
|
7
|
+
tasks.append({"id": pid, "prompt": desc, "gtStep": p}); print(pid, "OK", round(part.volume,1))
|
|
8
|
+
|
|
9
|
+
# 1. L-bracket: base plate + vertical leg + 4 holes through base
|
|
10
|
+
base = Pos(0,0,4) * Box(60,40,8)
|
|
11
|
+
leg = Pos(-26,0,30) * Box(8,40,52)
|
|
12
|
+
lb = base + leg
|
|
13
|
+
for (x,y) in [(-10,-10),(-10,10),(20,-10),(20,10)]:
|
|
14
|
+
lb -= Pos(x,y,4) * Cylinder(3,12)
|
|
15
|
+
save("l-bracket", lb, "An L-shaped steel mounting bracket. A base plate 60 (X) by 40 (Y) by 8 (Z) units sits flat with its bottom at z=0, centered on X and Y. A vertical leg 8 (X) by 40 (Y) by 52 (Z) units rises from the base's -X edge, forming a right angle (the whole part is ~60 wide, 40 deep, 60 tall). Four vertical through-holes of radius 3 pass through the base plate at X,Y offsets (-10,-10), (-10,10), (20,-10), (20,10) from center.")
|
|
16
|
+
|
|
17
|
+
# 2. Washer: flat annulus
|
|
18
|
+
w = Cylinder(20,4) - Cylinder(10,4)
|
|
19
|
+
save("washer", w, "A flat circular washer: an annular disk of outer radius 20, inner radius 10, and thickness 4 units, centered at the origin.")
|
|
20
|
+
|
|
21
|
+
# 3. Flanged pipe: pipe + flange disk at base + 4 bolt holes in flange
|
|
22
|
+
pipe = Cylinder(12,50, align=(Align.CENTER,Align.CENTER,Align.MIN)) - Cylinder(8,50, align=(Align.CENTER,Align.CENTER,Align.MIN))
|
|
23
|
+
flange = Cylinder(24,6, align=(Align.CENTER,Align.CENTER,Align.MIN)) - Cylinder(8,6, align=(Align.CENTER,Align.CENTER,Align.MIN))
|
|
24
|
+
fp = pipe + flange
|
|
25
|
+
import math
|
|
26
|
+
for i in range(4):
|
|
27
|
+
a = math.radians(45 + i*90); fp -= Pos(18*math.cos(a),18*math.sin(a),3) * Cylinder(2.5,8)
|
|
28
|
+
save("flanged-pipe", fp, "A flanged pipe. A hollow cylindrical pipe of outer radius 12, inner radius 8 (bore), height 50, standing with its base at z=0. At the base is a circular flange disk of radius 24 and thickness 6, sharing the same central bore (radius 8). Four bolt holes of radius 2.5 pass vertically through the flange on a circle of radius 18 from the axis, at 45, 135, 225, 315 degrees.")
|
|
29
|
+
|
|
30
|
+
# 4. Stepped shaft: 3 stacked cylinders, decreasing radius
|
|
31
|
+
s = Cylinder(15,20, align=(Align.CENTER,Align.CENTER,Align.MIN))
|
|
32
|
+
s += Pos(0,0,20) * Cylinder(10,20, align=(Align.CENTER,Align.CENTER,Align.MIN))
|
|
33
|
+
s += Pos(0,0,40) * Cylinder(6,20, align=(Align.CENTER,Align.CENTER,Align.MIN))
|
|
34
|
+
save("stepped-shaft", s, "A stepped cylindrical shaft, coaxial along Z, base at z=0: a bottom section radius 15 height 20, a middle section radius 10 height 20, and a top section radius 6 height 20 (total height 60).")
|
|
35
|
+
|
|
36
|
+
# 5. Mounting plate: plate + 4 corner holes + center hole
|
|
37
|
+
mp = Box(80,60,6)
|
|
38
|
+
for (x,y) in [(-32,-22),(-32,22),(32,-22),(32,22)]:
|
|
39
|
+
mp -= Pos(x,y,0) * Cylinder(3,10)
|
|
40
|
+
mp -= Cylinder(8,10)
|
|
41
|
+
save("mounting-plate", mp, "A rectangular mounting plate 80 (X) by 60 (Y) by 6 (Z) units, centered at the origin. A central through-hole of radius 8, plus four through-holes of radius 3 at the corners, offset (+/-32, +/-22) from center.")
|
|
42
|
+
|
|
43
|
+
# 6. Hex standoff: hexagonal prism with axial hole
|
|
44
|
+
hexp = extrude(RegularPolygon(radius=10, side_count=6), 30) - Cylinder(5,30, align=(Align.CENTER,Align.CENTER,Align.MIN))
|
|
45
|
+
save("hex-standoff", hexp, "A hexagonal standoff: a regular hexagonal prism (circumradius 10, i.e. ~17.3 across flats... actually 20 across corners) extruded 30 units tall from z=0, with a coaxial cylindrical through-hole of radius 5 along its full height.")
|
|
46
|
+
|
|
47
|
+
json.dump(tasks, open(f"{OUT}/tasks.json","w"), indent=2)
|
|
48
|
+
print("WROTE", len(tasks), "tasks")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Deployable-checker bridge for CL-Bench (Continual) Codebase Adaptation.
|
|
2
|
+
|
|
3
|
+
CL-Bench's `codebase_adaptation` is the ONE domain whose scorer is a deployable
|
|
4
|
+
checker (not an oracle): it applies the instance's provided `test_patch` and runs
|
|
5
|
+
the project's pytest suite inside the instance's Docker image, keying off the exit
|
|
6
|
+
code — exactly the SWE-bench / commit0 regime. This bridge exposes that scorer as a
|
|
7
|
+
standalone (instance_id, patch) -> {success,status} call so our TypeScript gate can
|
|
8
|
+
rank K candidate patches by a verifier the agent could legitimately run itself.
|
|
9
|
+
|
|
10
|
+
Run it with CL-Bench's OWN venv + repo root on the path (its `src.tasks...` package):
|
|
11
|
+
|
|
12
|
+
<clbench>/.venv/bin/python clbench_codebase_judge.py \
|
|
13
|
+
--dataset <clbench>/data/codebase_adaptation/final-dataset.jsonl \
|
|
14
|
+
--instance-id jazzband__tablib-534 --patch-file /tmp/candidate.patch
|
|
15
|
+
# invoked with cwd=<clbench> so `import src.tasks...` resolves
|
|
16
|
+
|
|
17
|
+
Prints one JSON line: {"instance_id","success","status","error"}. Fail loud — a
|
|
18
|
+
Docker/import failure exits non-zero with the message on stderr, never a silent 0.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
from src.tasks.codebase_adaptation.evaluator import evaluate_submission
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_instance(dataset_path: str, instance_id: str) -> dict:
|
|
31
|
+
with open(dataset_path, encoding="utf-8") as f:
|
|
32
|
+
for line in f:
|
|
33
|
+
line = line.strip()
|
|
34
|
+
if not line:
|
|
35
|
+
continue
|
|
36
|
+
row = json.loads(line)
|
|
37
|
+
if row.get("instance_id") == instance_id:
|
|
38
|
+
return row
|
|
39
|
+
raise SystemExit(f"instance_id not found in {dataset_path}: {instance_id}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main() -> None:
|
|
43
|
+
ap = argparse.ArgumentParser(description="CL-Bench codebase_adaptation deployable judge")
|
|
44
|
+
ap.add_argument("--dataset", required=True, help="path to final-dataset.jsonl")
|
|
45
|
+
ap.add_argument("--instance-id", required=True)
|
|
46
|
+
ap.add_argument("--patch-file", required=True, help="file holding the candidate unified git diff")
|
|
47
|
+
args = ap.parse_args()
|
|
48
|
+
|
|
49
|
+
instance = load_instance(args.dataset, args.instance_id)
|
|
50
|
+
with open(args.patch_file, encoding="utf-8") as f:
|
|
51
|
+
patch = f.read()
|
|
52
|
+
|
|
53
|
+
# evaluate_submission spins the instance's Docker image, applies test_patch + the
|
|
54
|
+
# candidate, runs pytest, and reports success on a clean exit. A genuine infra
|
|
55
|
+
# failure raises — let it propagate (non-zero exit) so the caller never reads a
|
|
56
|
+
# transport fault as a failed test.
|
|
57
|
+
result = evaluate_submission(patch, instance)
|
|
58
|
+
print(json.dumps({
|
|
59
|
+
"instance_id": args.instance_id,
|
|
60
|
+
"success": bool(result.success),
|
|
61
|
+
"status": result.status,
|
|
62
|
+
"error": (result.error or "")[:500],
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
try:
|
|
68
|
+
main()
|
|
69
|
+
except SystemExit:
|
|
70
|
+
raise
|
|
71
|
+
except Exception as exc: # infra/import failure — fail loud, do not emit a fake verdict
|
|
72
|
+
print(f"clbench_codebase_judge: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
73
|
+
sys.exit(2)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Commit0 judge driver: stage one repo at base_commit, apply the worker's diff
|
|
2
|
+
# (read from stdin) into src_dir, run the official commit0 pytest harness on a
|
|
3
|
+
# local Docker backend, and emit {"passed": int, "total": int} as the LAST stdout
|
|
4
|
+
# line. The TS adapter wraps this; scoring (passed+xfail)/total mirrors
|
|
5
|
+
# commit0.harness.evaluate. Fail loud: any harness/Docker error is printed as
|
|
6
|
+
# {"error": "..."} and a nonzero exit — never a fabricated pass count.
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fail(msg: str) -> None:
|
|
18
|
+
print(json.dumps({"error": msg}))
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main() -> None:
|
|
23
|
+
ap = argparse.ArgumentParser(description="commit0 single-instance judge")
|
|
24
|
+
ap.add_argument("--dataset", required=True)
|
|
25
|
+
ap.add_argument("--split", required=True)
|
|
26
|
+
ap.add_argument("--instance", required=True) # e.g. commit-0/wcwidth
|
|
27
|
+
ap.add_argument("--src-dir", required=True)
|
|
28
|
+
ap.add_argument("--backend", default=os.environ.get("COMMIT0_BACKEND", "local"))
|
|
29
|
+
ap.add_argument("--timeout", type=int, default=int(os.environ.get("COMMIT0_TIMEOUT", "1800")))
|
|
30
|
+
args = ap.parse_args()
|
|
31
|
+
|
|
32
|
+
diff = sys.stdin.read()
|
|
33
|
+
if not diff.strip():
|
|
34
|
+
# Empty implementation: nothing to apply → 0 of the suite passes. Stage the
|
|
35
|
+
# repo anyway so `total` reflects the real test count (fail-closed, real).
|
|
36
|
+
diff = ""
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from datasets import load_dataset
|
|
40
|
+
from commit0.harness.setup import main as setup_main
|
|
41
|
+
from commit0.harness.run_pytest_ids import main as run_tests
|
|
42
|
+
from commit0.harness.get_pytest_ids import main as get_tests
|
|
43
|
+
from commit0.harness.constants import BASE_BRANCH, RUN_PYTEST_LOG_DIR
|
|
44
|
+
from commit0.harness.utils import get_hash_string
|
|
45
|
+
except Exception as e: # noqa: BLE001
|
|
46
|
+
fail(f"commit0 import failed: {e}")
|
|
47
|
+
|
|
48
|
+
repo_name = args.instance.split("/")[-1]
|
|
49
|
+
ds = load_dataset(args.dataset, split=args.split)
|
|
50
|
+
example = next((x for x in ds if x["instance_id"] == args.instance), None)
|
|
51
|
+
if example is None:
|
|
52
|
+
fail(f"instance not found in {args.dataset}: {args.instance}")
|
|
53
|
+
|
|
54
|
+
# run_pytest_ids wants the SPACE-joined test-id STRING (e.g. "tests/a.py::t1 ...")
|
|
55
|
+
# — NOT the dataset's test_dir — and keys its report dir on get_hash_string(that
|
|
56
|
+
# same string). Passing test_dir ran zero tests and wrote the report under a
|
|
57
|
+
# different hash, so the judge silently found nothing. get_tests reads the test-ids.
|
|
58
|
+
test_ids = [t for t in get_tests(repo_name, 0) if t.strip()]
|
|
59
|
+
test_ids_str = " ".join(test_ids)
|
|
60
|
+
# setup.main checks out the stubbed repo onto BASE_BRANCH ("commit0") and the
|
|
61
|
+
# worker's diff is committed there; run_pytest_ids resolves `branch` -> a git
|
|
62
|
+
# commit (NOT a checkout) to diff against base_commit, so it must be the
|
|
63
|
+
# branch the work actually lives on. The dataset short name is not a git ref.
|
|
64
|
+
branch = BASE_BRANCH
|
|
65
|
+
|
|
66
|
+
base_dir = tempfile.mkdtemp(prefix="commit0-base-")
|
|
67
|
+
try:
|
|
68
|
+
# Clone + checkout the stubbed base branch for just this repo.
|
|
69
|
+
setup_main(args.dataset, args.split, repo_name, base_dir)
|
|
70
|
+
|
|
71
|
+
repo_dir = Path(base_dir) / repo_name
|
|
72
|
+
if not repo_dir.exists():
|
|
73
|
+
fail(f"commit0 setup did not clone {repo_name} into {base_dir}")
|
|
74
|
+
|
|
75
|
+
# Apply the worker's unified diff into the cloned repo (src only). git apply
|
|
76
|
+
# tolerates a/ b/ prefixes; --reject is avoided so a bad hunk fails loud.
|
|
77
|
+
if diff:
|
|
78
|
+
patch_path = Path(base_dir) / "worker.diff"
|
|
79
|
+
patch_path.write_text(diff)
|
|
80
|
+
r = subprocess.run(
|
|
81
|
+
["git", "apply", "--whitespace=nowarn", str(patch_path)],
|
|
82
|
+
cwd=str(repo_dir),
|
|
83
|
+
capture_output=True,
|
|
84
|
+
text=True,
|
|
85
|
+
)
|
|
86
|
+
if r.returncode != 0:
|
|
87
|
+
# Retry with -p0 (diffs sometimes lack the a/ b/ prefix).
|
|
88
|
+
r2 = subprocess.run(
|
|
89
|
+
["git", "apply", "-p0", "--whitespace=nowarn", str(patch_path)],
|
|
90
|
+
cwd=str(repo_dir),
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
)
|
|
94
|
+
if r2.returncode != 0:
|
|
95
|
+
fail(f"git apply failed: {r.stderr.strip()[:500]} / {r2.stderr.strip()[:500]}")
|
|
96
|
+
subprocess.run(["git", "add", "-A"], cwd=str(repo_dir), check=False)
|
|
97
|
+
subprocess.run(
|
|
98
|
+
["git", "-c", "user.email=bench@local", "-c", "user.name=bench", "commit", "-m", "worker"],
|
|
99
|
+
cwd=str(repo_dir),
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Run the official per-repo pytest harness on the requested backend.
|
|
105
|
+
try:
|
|
106
|
+
run_tests(
|
|
107
|
+
args.dataset,
|
|
108
|
+
args.split,
|
|
109
|
+
base_dir,
|
|
110
|
+
repo_name,
|
|
111
|
+
branch,
|
|
112
|
+
test_ids_str,
|
|
113
|
+
False, # coverage
|
|
114
|
+
args.backend,
|
|
115
|
+
args.timeout,
|
|
116
|
+
1, # num_cpus
|
|
117
|
+
# rebuild_image is honored by the MODAL backend only (force_build).
|
|
118
|
+
# The local Docker execution context just creates a container from
|
|
119
|
+
# the existing wentingzhao/<repo>:v0 image — pre-pull/build it via
|
|
120
|
+
# bench/src/commit0-prereqs.sh or this run dies with ImageNotFound.
|
|
121
|
+
rebuild_image=os.environ.get("COMMIT0_REBUILD_IMAGE") == "1",
|
|
122
|
+
verbose=0,
|
|
123
|
+
)
|
|
124
|
+
except SystemExit:
|
|
125
|
+
# run_pytest_ids ends with sys.exit(pytest_exit_code): 0 = all pass, nonzero
|
|
126
|
+
# = some tests failed — the NORMAL graded/partial-credit case, NOT a harness
|
|
127
|
+
# error. report.json is already written; fall through and read it.
|
|
128
|
+
pass
|
|
129
|
+
except BaseException as e: # noqa: BLE001 — never exit silently on a real backend fault
|
|
130
|
+
if isinstance(e, KeyboardInterrupt):
|
|
131
|
+
raise
|
|
132
|
+
import traceback
|
|
133
|
+
|
|
134
|
+
hint = ""
|
|
135
|
+
if "ImageNotFound" in repr(e) or "No such image" in repr(e):
|
|
136
|
+
hint = f" | missing per-repo Docker image — run: bench/src/commit0-prereqs.sh {repo_name}"
|
|
137
|
+
fail(f"commit0 run_pytest_ids failed for {repo_name}: {e!r}{hint} | {traceback.format_exc()[-1200:]}")
|
|
138
|
+
|
|
139
|
+
hashed = get_hash_string(test_ids_str)
|
|
140
|
+
report_file = RUN_PYTEST_LOG_DIR / repo_name / branch / hashed / "report.json"
|
|
141
|
+
if not report_file.exists():
|
|
142
|
+
fail(f"commit0 wrote no report.json at {report_file}")
|
|
143
|
+
|
|
144
|
+
report = json.loads(report_file.read_text())
|
|
145
|
+
# pytest-json: new form has "created"+"tests"; old form is a flat list.
|
|
146
|
+
if isinstance(report, dict) and "created" in report:
|
|
147
|
+
calls = {x["nodeid"]: x.get("call", {}) for x in report.get("tests", []) if "call" in x}
|
|
148
|
+
outcomes = [c.get("outcome") for c in calls.values()]
|
|
149
|
+
else:
|
|
150
|
+
outcomes = [x["outcome"] for x in report if x.get("when") == "call"]
|
|
151
|
+
|
|
152
|
+
passed = sum(1 for o in outcomes if o in ("passed", "xfailed"))
|
|
153
|
+
total = len(outcomes)
|
|
154
|
+
# Fall back to the declared test-id count when the harness produced no call
|
|
155
|
+
# records (e.g. collection error) so `total` is never a phantom 0.
|
|
156
|
+
if total == 0:
|
|
157
|
+
# COUNT of test ids, not their character lengths (get_tests returns a flat
|
|
158
|
+
# list of node-id strings). Only fires on a collection error, where passed
|
|
159
|
+
# is already 0 — but a phantom inflated total would still mislead a reader.
|
|
160
|
+
total = len(test_ids)
|
|
161
|
+
|
|
162
|
+
print(json.dumps({"passed": passed, "total": total}))
|
|
163
|
+
finally:
|
|
164
|
+
import shutil
|
|
165
|
+
|
|
166
|
+
shutil.rmtree(base_dir, ignore_errors=True)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|