@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,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runBenchmarks` — the bench unifier.
|
|
3
|
+
*
|
|
4
|
+
* `gate.ts` runs ONE adapter as a diverse-vs-blind research gate through the recursive atom.
|
|
5
|
+
* This is the other half it references: run a SUBSET of the registry's benchmarks over a MATRIX of
|
|
6
|
+
* agent cells (harness × model × persona), each scored by the benchmark's OWN deterministic judge,
|
|
7
|
+
* and return a ranked leaderboard. It is the "which harness/model/persona combination wins on which
|
|
8
|
+
* benchmark" question, answered over an arbitrary subset in one call.
|
|
9
|
+
*
|
|
10
|
+
* It owns no new mechanism. Each cell is one `openSandboxRun` shot (the same per-run primitive the
|
|
11
|
+
* SWE worker uses) driven by `resolveBenchClient` (off-box router completion OR in-box Sandbox; the
|
|
12
|
+
* harness rides `sandboxOverrides.backend.type`). The deliverable is the adapter's OWN parser
|
|
13
|
+
* (`adapter.output`), defaulting to the final answer text — so `runBenchmarks` needs no
|
|
14
|
+
* per-benchmark branching. Concurrency is the shared `runPool`. The number comes from
|
|
15
|
+
* `adapter.judge`, never a self-authored judge.
|
|
16
|
+
*
|
|
17
|
+
* Subset = the `benchmarks` and `cells` arrays plus `n`/`ids`/`split`. A benchmark whose
|
|
18
|
+
* `preflight()` fails (missing Docker/venv/dataset) is recorded as unavailable and skipped — the
|
|
19
|
+
* sweep never aborts because one bench's harness is absent.
|
|
20
|
+
*
|
|
21
|
+
* const report = await runBenchmarks({
|
|
22
|
+
* benchmarks: ['humaneval', 'swe-bench'],
|
|
23
|
+
* cells: [
|
|
24
|
+
* { label: 'opencode/glm-4.6', model: 'glm-4.6', harness: 'opencode' },
|
|
25
|
+
* { label: 'codex/gpt-5', model: 'gpt-5', harness: 'codex' },
|
|
26
|
+
* ],
|
|
27
|
+
* routerBaseUrl, routerKey, n: 20,
|
|
28
|
+
* })
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
32
|
+
import type {
|
|
33
|
+
AgentProfile,
|
|
34
|
+
AgentRunSpec,
|
|
35
|
+
Deliverable,
|
|
36
|
+
OpenSandboxRunOptions,
|
|
37
|
+
} from '@tangle-network/agent-runtime/loops'
|
|
38
|
+
import { openSandboxRun } from '@tangle-network/agent-runtime/loops'
|
|
39
|
+
import type { SandboxEvent } from '@tangle-network/sandbox'
|
|
40
|
+
import { resolveAdapter } from './adapters'
|
|
41
|
+
import type { BenchmarkAdapter, BenchScore, BenchTask } from './benchmarks/types'
|
|
42
|
+
import { runRefineLoop } from './refine-loop'
|
|
43
|
+
import { resolveBenchClient } from './resolve-client'
|
|
44
|
+
import { runPool } from './run-pool'
|
|
45
|
+
|
|
46
|
+
/** One agent under test: a profile (prompt/tools/persona) plus the harness + model that run it. */
|
|
47
|
+
export interface BenchCell {
|
|
48
|
+
/** Leaderboard row label, e.g. `opencode/glm-4.6` or `tax-agent@v1`. */
|
|
49
|
+
readonly label: string
|
|
50
|
+
/** Model id for this cell. */
|
|
51
|
+
readonly model: string
|
|
52
|
+
/** Coding harness for the in-box path (`opencode`/`codex`/`claude-code`/`kimi-code`). Defaults to
|
|
53
|
+
* `profile.metadata.backendType`, then `opencode`. Ignored on the `router` transport. */
|
|
54
|
+
readonly harness?: string
|
|
55
|
+
/** Transport: `router` (off-box completion, default), `sandbox`, or a BackendType for in-box. */
|
|
56
|
+
readonly backend?: string
|
|
57
|
+
/** Web-search provider for the `router` transport (turns the leaf into a `router-tools` loop). */
|
|
58
|
+
readonly searchProvider?: string
|
|
59
|
+
/** The agent under test. Defaults to a minimal `{ name, metadata.backendType }` profile. */
|
|
60
|
+
readonly profile?: AgentProfile
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Runs one (adapter, task, cell) shot and returns the deliverable text. The default uses
|
|
64
|
+
* `openSandboxRun`; tests inject a deterministic stub so the matrix runs offline. */
|
|
65
|
+
export type BenchShot = (input: {
|
|
66
|
+
readonly adapter: BenchmarkAdapter
|
|
67
|
+
readonly task: BenchTask
|
|
68
|
+
readonly cell: BenchCell
|
|
69
|
+
/** Prompt to hand to the worker. Defaults to `task.prompt`; looped runs pass revised prompts. */
|
|
70
|
+
readonly prompt?: string
|
|
71
|
+
/** 1-based attempt index for looped runs. */
|
|
72
|
+
readonly attempt?: number
|
|
73
|
+
readonly routerBaseUrl: string
|
|
74
|
+
readonly routerKey: string
|
|
75
|
+
readonly bridgeUrl?: string
|
|
76
|
+
readonly bridgeBearer?: string
|
|
77
|
+
readonly sandboxBaseUrl?: string
|
|
78
|
+
readonly timeoutMs?: number
|
|
79
|
+
readonly resolveClient?: typeof resolveBenchClient
|
|
80
|
+
}) => Promise<{ artifact: string; ok: boolean; detail?: string }>
|
|
81
|
+
|
|
82
|
+
export interface RunBenchmarksOptions {
|
|
83
|
+
/** Registry keys (`resolveAdapter`) — the benchmark subset to run. */
|
|
84
|
+
readonly benchmarks: readonly string[]
|
|
85
|
+
/** The agent cells to rank. */
|
|
86
|
+
readonly cells: readonly BenchCell[]
|
|
87
|
+
readonly routerBaseUrl: string
|
|
88
|
+
readonly routerKey: string
|
|
89
|
+
readonly bridgeUrl?: string
|
|
90
|
+
readonly bridgeBearer?: string
|
|
91
|
+
readonly sandboxBaseUrl?: string
|
|
92
|
+
/** Tasks per benchmark (the n). */
|
|
93
|
+
readonly n?: number
|
|
94
|
+
readonly ids?: string[]
|
|
95
|
+
readonly split?: string
|
|
96
|
+
/** Replicates per (benchmark × cell × task). Default 1. */
|
|
97
|
+
readonly reps?: number
|
|
98
|
+
/** Bounded concurrency across all shots. Default 4. */
|
|
99
|
+
readonly concurrency?: number
|
|
100
|
+
/** Per-shot wall-clock (ms). */
|
|
101
|
+
readonly timeoutMs?: number
|
|
102
|
+
/** Test seam: resolve the runtime transport. Defaults to `resolveBenchClient`. */
|
|
103
|
+
readonly resolveClient?: typeof resolveBenchClient
|
|
104
|
+
/** Max attempts per (benchmark × cell × task). Default 1. Attempts after the first receive
|
|
105
|
+
* non-answer checker feedback and the previous artifacts; the loop stops early on pass. */
|
|
106
|
+
readonly loopAttempts?: number
|
|
107
|
+
/** Self-verify each benchmark's judge against its gold artifact on the first task before spending
|
|
108
|
+
* model tokens; a benchmark whose judge rejects its own gold is recorded unavailable. Default true. */
|
|
109
|
+
readonly verifyJudge?: boolean
|
|
110
|
+
/** Test seam: a deterministic shot runner. Defaults to the `openSandboxRun` leaf. */
|
|
111
|
+
readonly runShot?: BenchShot
|
|
112
|
+
/** Test seam: resolve a benchmark key to an adapter. Defaults to the registry `resolveAdapter`. */
|
|
113
|
+
readonly resolveAdapter?: (key: string) => BenchmarkAdapter
|
|
114
|
+
readonly onResult?: (r: BenchCellTaskResult) => void
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface BenchCellTaskResult {
|
|
118
|
+
readonly benchmark: string
|
|
119
|
+
readonly cell: string
|
|
120
|
+
readonly taskId: string
|
|
121
|
+
readonly rep: number
|
|
122
|
+
readonly resolved: boolean
|
|
123
|
+
readonly score: number
|
|
124
|
+
/** false = the shot threw or produced no artifact (infra/empty), excluded from the resolve
|
|
125
|
+
* denominator so a harness outage can't masquerade as a 0% capability result. */
|
|
126
|
+
readonly ok: boolean
|
|
127
|
+
readonly detail?: string
|
|
128
|
+
readonly wallMs: number
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface BenchLeaderboardRow {
|
|
132
|
+
readonly benchmark: string
|
|
133
|
+
readonly cell: string
|
|
134
|
+
readonly n: number
|
|
135
|
+
readonly resolved: number
|
|
136
|
+
readonly errored: number
|
|
137
|
+
/** resolved / (n - errored). */
|
|
138
|
+
readonly resolveRate: number
|
|
139
|
+
/** Mean graded score over non-errored shots (partial credit where the judge supports it). */
|
|
140
|
+
readonly meanScore: number
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface RunBenchmarksReport {
|
|
144
|
+
/** One row per (benchmark × cell), sorted by benchmark then descending resolveRate. */
|
|
145
|
+
readonly rows: readonly BenchLeaderboardRow[]
|
|
146
|
+
readonly perTask: readonly BenchCellTaskResult[]
|
|
147
|
+
readonly benchmarks: readonly string[]
|
|
148
|
+
readonly cells: readonly string[]
|
|
149
|
+
/** Benchmarks skipped because `preflight`/judge-self-check failed, with the reason. */
|
|
150
|
+
readonly unavailable: ReadonlyArray<{ readonly benchmark: string; readonly reason: string }>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Last assistant text across the common event shapes (delta accumulation, then a terminal
|
|
154
|
+
* `result`/`done`/`agent` snapshot). The structured-deliverable case is handled by the adapter's
|
|
155
|
+
* own `output.parse`; this is the research/QA fallback. */
|
|
156
|
+
function finalText(events: readonly SandboxEvent[]): string {
|
|
157
|
+
let text = ''
|
|
158
|
+
for (const ev of events) {
|
|
159
|
+
const e = ev as { type?: string; data?: Record<string, unknown> }
|
|
160
|
+
const data = e.data ?? {}
|
|
161
|
+
if (e.type === 'message.part.updated') {
|
|
162
|
+
const part = data.part as { type?: string; text?: string } | undefined
|
|
163
|
+
const partType = (data.partType as string | undefined) ?? part?.type
|
|
164
|
+
if (partType === 'text') {
|
|
165
|
+
if (typeof data.delta === 'string') text += data.delta
|
|
166
|
+
else if (typeof part?.text === 'string') text = part.text
|
|
167
|
+
}
|
|
168
|
+
} else if (typeof data.finalText === 'string') text = data.finalText
|
|
169
|
+
else if (typeof data.response === 'string') text = data.response
|
|
170
|
+
}
|
|
171
|
+
return text.trim()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The default real-agent shot: one `openSandboxRun` over the cell's harness+model, deliverable
|
|
175
|
+
* extracted by the adapter's parser (or final text), abortable on `timeoutMs`. */
|
|
176
|
+
const openSandboxShot: BenchShot = async ({ adapter, task, cell, prompt, routerBaseUrl, routerKey, bridgeUrl, bridgeBearer, sandboxBaseUrl, timeoutMs, resolveClient }) => {
|
|
177
|
+
const client = (resolveClient ?? resolveBenchClient)({
|
|
178
|
+
backend: cell.backend ?? 'router',
|
|
179
|
+
routerBaseUrl,
|
|
180
|
+
routerKey,
|
|
181
|
+
model: cell.model,
|
|
182
|
+
...(bridgeUrl ? { bridgeUrl } : {}),
|
|
183
|
+
...(bridgeBearer ? { bridgeBearer } : {}),
|
|
184
|
+
...(sandboxBaseUrl ? { sandboxBaseUrl } : {}),
|
|
185
|
+
...(cell.searchProvider ? { searchProvider: cell.searchProvider } : {}),
|
|
186
|
+
...(timeoutMs ? { timeoutMs } : {}),
|
|
187
|
+
})
|
|
188
|
+
const harness = cell.harness ?? (cell.profile?.metadata?.backendType as string | undefined) ?? 'opencode'
|
|
189
|
+
const profile: AgentProfile = cell.profile ?? { name: cell.label, metadata: { backendType: harness } }
|
|
190
|
+
// Unique per shot: the same (adapter, task) runs concurrently across cells and reps, so the box
|
|
191
|
+
// name and runId must not collide.
|
|
192
|
+
const uniq = Math.random().toString(36).slice(2, 8)
|
|
193
|
+
const agentRun: AgentRunSpec<string> = {
|
|
194
|
+
profile,
|
|
195
|
+
name: cell.label,
|
|
196
|
+
taskToPrompt: () => '',
|
|
197
|
+
sandboxOverrides: {
|
|
198
|
+
name: `bench-${adapter.name}-${task.id}-${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 60),
|
|
199
|
+
environment: 'universal',
|
|
200
|
+
backend: { type: harness as never, model: { provider: 'openai', model: cell.model, baseUrl: routerBaseUrl } },
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
const deliverable: Deliverable<string> = {
|
|
204
|
+
kind: 'events',
|
|
205
|
+
fromEvents: (events) => (adapter.output ? adapter.output.parse(events) : finalText(events)),
|
|
206
|
+
}
|
|
207
|
+
const controller = new AbortController()
|
|
208
|
+
const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : undefined
|
|
209
|
+
const runOptions: OpenSandboxRunOptions = {
|
|
210
|
+
agentRun,
|
|
211
|
+
signal: controller.signal,
|
|
212
|
+
runId: `bench:${adapter.name}:${task.id}:${uniq}`,
|
|
213
|
+
scenarioId: task.id,
|
|
214
|
+
}
|
|
215
|
+
const boxSetup = adapter.boxSetup
|
|
216
|
+
if (boxSetup) {
|
|
217
|
+
runOptions.beforeStart = async ({ box, sessionId }) => {
|
|
218
|
+
const setup = boxSetup(task)
|
|
219
|
+
const sres = await box.exec(setup.command, {
|
|
220
|
+
timeoutMs: 300_000,
|
|
221
|
+
sessionId,
|
|
222
|
+
...(setup.cwd ? { cwd: setup.cwd } : {}),
|
|
223
|
+
})
|
|
224
|
+
if (sres.exitCode !== 0)
|
|
225
|
+
throw new Error(
|
|
226
|
+
`boxSetup failed (exit ${sres.exitCode}): ${(sres.stderr ?? '').slice(0, 200)}`,
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const run = await openSandboxRun(client, runOptions, deliverable)
|
|
231
|
+
try {
|
|
232
|
+
const turn = await run.start(prompt ?? task.prompt)
|
|
233
|
+
// Event-stream deliverable (adapter.output ?? finalText) — the FALLBACK.
|
|
234
|
+
let artifact = (turn.out ?? '').trim()
|
|
235
|
+
let boxExtractError: string | undefined
|
|
236
|
+
// Primary deliverable for benchmarks whose real artifact lives in the box FS
|
|
237
|
+
// (SWE-bench: a git diff of the agent's edits). Run the adapter's extraction
|
|
238
|
+
// command in the STILL-ALIVE box (valid until run.close() below) and prefer its
|
|
239
|
+
// stdout; the event-stream parse remains the fallback when the box yields nothing.
|
|
240
|
+
if (adapter.boxExtract) {
|
|
241
|
+
try {
|
|
242
|
+
const ex = adapter.boxExtract(task)
|
|
243
|
+
// The agent runs under a DRIVER SESSION whose workspace is a remapped
|
|
244
|
+
// virtual root; an exec WITHOUT that sessionId lands on the host FS and
|
|
245
|
+
// cannot see the agent's edits. Thread run.sessionId so the extraction
|
|
246
|
+
// runs in the SAME workspace the agent wrote to.
|
|
247
|
+
const res = await run.box.exec(ex.command, {
|
|
248
|
+
timeoutMs: 120_000,
|
|
249
|
+
sessionId: run.sessionId,
|
|
250
|
+
...(ex.cwd ? { cwd: ex.cwd } : {}),
|
|
251
|
+
})
|
|
252
|
+
const boxArtifact = (res.stdout ?? '').trim()
|
|
253
|
+
if (res.exitCode !== 0)
|
|
254
|
+
boxExtractError = `exit ${res.exitCode}: ${(res.stderr ?? '').slice(0, 160)}`
|
|
255
|
+
else if (boxArtifact.length > 0) artifact = boxArtifact
|
|
256
|
+
if (process.env.BENCH_ARTIFACT_DIR) {
|
|
257
|
+
try {
|
|
258
|
+
mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true })
|
|
259
|
+
const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, '_')
|
|
260
|
+
const map = await run.box
|
|
261
|
+
.exec(
|
|
262
|
+
'echo "PWD:"; pwd; echo "LS:"; ls -la; echo "GITROOTS:"; find / -maxdepth 5 -type d -name .git 2>/dev/null; echo "SETTINGS:"; find / -maxdepth 8 -name global_settings.py -path "*conf*" 2>/dev/null',
|
|
263
|
+
{ timeoutMs: 60_000, sessionId: run.sessionId },
|
|
264
|
+
)
|
|
265
|
+
.catch((e: unknown) => ({ exitCode: -1, stdout: '', stderr: String(e) }))
|
|
266
|
+
writeFileSync(
|
|
267
|
+
`${process.env.BENCH_ARTIFACT_DIR}/${safe}.exec.json`,
|
|
268
|
+
JSON.stringify(
|
|
269
|
+
{ sessionId: run.sessionId, extract: res, map: { exitCode: map.exitCode, stdout: map.stdout, stderr: map.stderr } },
|
|
270
|
+
null,
|
|
271
|
+
2,
|
|
272
|
+
),
|
|
273
|
+
)
|
|
274
|
+
} catch {
|
|
275
|
+
// debug-only
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch (err) {
|
|
279
|
+
boxExtractError = err instanceof Error ? err.message.slice(0, 160) : String(err)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const detail =
|
|
283
|
+
turn.readError !== undefined
|
|
284
|
+
? `read: ${turn.readError.slice(0, 160)}`
|
|
285
|
+
: boxExtractError !== undefined
|
|
286
|
+
? `boxExtract: ${boxExtractError}`
|
|
287
|
+
: undefined
|
|
288
|
+
// Debug affordance: dump the judged artifact (the exact model_patch the judge
|
|
289
|
+
// will score) so a scoring failure can be diagnosed off the real bytes without
|
|
290
|
+
// re-running the agent. Off by default; set BENCH_ARTIFACT_DIR to enable.
|
|
291
|
+
if (process.env.BENCH_ARTIFACT_DIR) {
|
|
292
|
+
try {
|
|
293
|
+
mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true })
|
|
294
|
+
const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, '_')
|
|
295
|
+
writeFileSync(`${process.env.BENCH_ARTIFACT_DIR}/${safe}.patch`, artifact)
|
|
296
|
+
} catch {
|
|
297
|
+
// debug-only; never fail the shot on a dump error
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
artifact,
|
|
302
|
+
ok: artifact.length > 0,
|
|
303
|
+
...(detail ? { detail } : {}),
|
|
304
|
+
}
|
|
305
|
+
} finally {
|
|
306
|
+
if (timer) clearTimeout(timer)
|
|
307
|
+
await run.close()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function parseMaybeJson(value: string): unknown {
|
|
312
|
+
try {
|
|
313
|
+
return JSON.parse(value) as unknown
|
|
314
|
+
} catch {
|
|
315
|
+
return value
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function redactJudgeLeak(value: unknown): unknown {
|
|
320
|
+
if (Array.isArray(value)) return value.map(redactJudgeLeak)
|
|
321
|
+
if (!value || typeof value !== 'object') return value
|
|
322
|
+
const out: Record<string, unknown> = {}
|
|
323
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
324
|
+
const normalizedKey = key.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase()
|
|
325
|
+
if (/(^|_)(gold|expected|reference|solution|answer)(_|$)/.test(normalizedKey)) continue
|
|
326
|
+
out[key] = redactJudgeLeak(child)
|
|
327
|
+
}
|
|
328
|
+
return out
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function safeFeedback(score: BenchScore): Record<string, unknown> {
|
|
332
|
+
return {
|
|
333
|
+
resolved: score.resolved,
|
|
334
|
+
score: score.score,
|
|
335
|
+
...(score.detail ? { detail: redactJudgeLeak(parseMaybeJson(score.detail)) } : {}),
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function truncate(value: string, max = 4_000): string {
|
|
340
|
+
return value.length <= max ? value : `${value.slice(0, max)}\n...[truncated ${value.length - max} chars]`
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function retryPrompt(task: BenchTask, history: ReadonlyArray<{ round: number; artifact: string }>, scores: ReadonlyMap<number, BenchScore>): string {
|
|
344
|
+
const attempts = history
|
|
345
|
+
.map((h) => {
|
|
346
|
+
const score = scores.get(h.round)
|
|
347
|
+
return [
|
|
348
|
+
`Attempt ${h.round}:`,
|
|
349
|
+
'Artifact:',
|
|
350
|
+
truncate(h.artifact),
|
|
351
|
+
score ? `Checker feedback: ${JSON.stringify(safeFeedback(score))}` : undefined,
|
|
352
|
+
]
|
|
353
|
+
.filter(Boolean)
|
|
354
|
+
.join('\n')
|
|
355
|
+
})
|
|
356
|
+
.join('\n\n')
|
|
357
|
+
return [
|
|
358
|
+
'Retry the benchmark task. The previous artifact did not pass the checker.',
|
|
359
|
+
'Use only the original task statement and supplied context. Do not invent facts.',
|
|
360
|
+
'Return the corrected artifact in exactly the format requested by the original task.',
|
|
361
|
+
'',
|
|
362
|
+
'Original task:',
|
|
363
|
+
task.prompt,
|
|
364
|
+
'',
|
|
365
|
+
'Previous attempts and safe checker feedback:',
|
|
366
|
+
attempts,
|
|
367
|
+
].join('\n')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function loopedShot(
|
|
371
|
+
input: Parameters<BenchShot>[0],
|
|
372
|
+
shot: BenchShot,
|
|
373
|
+
attempts: number,
|
|
374
|
+
): Promise<{ artifact: string; ok: boolean; detail?: string }> {
|
|
375
|
+
const scores = new Map<number, BenchScore>()
|
|
376
|
+
const result = await runRefineLoop<string>({
|
|
377
|
+
rounds: attempts,
|
|
378
|
+
prompt: (round, history) => (round === 1 ? input.task.prompt : retryPrompt(input.task, history, scores)),
|
|
379
|
+
runShot: async (prompt, round) => {
|
|
380
|
+
const out = await shot({ ...input, prompt, attempt: round })
|
|
381
|
+
return { artifact: out.artifact, note: out.detail }
|
|
382
|
+
},
|
|
383
|
+
judge: async (artifact, round) => {
|
|
384
|
+
const score = await input.adapter.judge(input.task, artifact)
|
|
385
|
+
scores.set(round, score)
|
|
386
|
+
return { valid: score.resolved, score: score.score }
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
const best = result.rounds.reduce((winner, candidate) => {
|
|
391
|
+
const a = scores.get(winner.round)
|
|
392
|
+
const b = scores.get(candidate.round)
|
|
393
|
+
if (!a) return candidate
|
|
394
|
+
if (!b) return winner
|
|
395
|
+
if (b.resolved && !a.resolved) return candidate
|
|
396
|
+
if (b.resolved === a.resolved && b.score > a.score) return candidate
|
|
397
|
+
return winner
|
|
398
|
+
}, result.rounds[0]!)
|
|
399
|
+
const bestScore = scores.get(best.round)
|
|
400
|
+
return {
|
|
401
|
+
artifact: best.artifact,
|
|
402
|
+
ok: best.artifact.trim().length > 0,
|
|
403
|
+
detail: JSON.stringify({
|
|
404
|
+
mode: 'refine-loop',
|
|
405
|
+
attempts: result.rounds.length,
|
|
406
|
+
selectedAttempt: best.round,
|
|
407
|
+
resolvedDuringLoop: result.resolved,
|
|
408
|
+
selectedScore: bestScore?.score ?? null,
|
|
409
|
+
rounds: result.rounds.map((round) => ({
|
|
410
|
+
attempt: round.round,
|
|
411
|
+
score: scores.get(round.round)?.score ?? null,
|
|
412
|
+
resolved: scores.get(round.round)?.resolved ?? null,
|
|
413
|
+
note: round.note ?? null,
|
|
414
|
+
})),
|
|
415
|
+
}),
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function combineDetails(runDetail: string | undefined, scoreDetail: string | undefined): string | undefined {
|
|
420
|
+
if (runDetail && scoreDetail) {
|
|
421
|
+
return JSON.stringify({ run: parseMaybeJson(runDetail), score: parseMaybeJson(scoreDetail) })
|
|
422
|
+
}
|
|
423
|
+
return runDetail ?? scoreDetail
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
interface Job {
|
|
427
|
+
readonly benchmark: string
|
|
428
|
+
readonly adapter: BenchmarkAdapter
|
|
429
|
+
readonly cell: BenchCell
|
|
430
|
+
readonly task: BenchTask
|
|
431
|
+
readonly rep: number
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Resolve + preflight + (optionally) self-verify each benchmark once; load its tasks. A benchmark
|
|
435
|
+
* whose harness is absent or whose judge rejects its own gold is recorded unavailable, not run. */
|
|
436
|
+
async function prepareBenchmarks(
|
|
437
|
+
benchmarks: readonly string[],
|
|
438
|
+
resolve: (key: string) => BenchmarkAdapter,
|
|
439
|
+
opts: Pick<RunBenchmarksOptions, 'n' | 'ids' | 'split' | 'verifyJudge'>,
|
|
440
|
+
): Promise<{ ready: Array<{ benchmark: string; adapter: BenchmarkAdapter; tasks: BenchTask[] }>; unavailable: Array<{ benchmark: string; reason: string }> }> {
|
|
441
|
+
const ready: Array<{ benchmark: string; adapter: BenchmarkAdapter; tasks: BenchTask[] }> = []
|
|
442
|
+
const unavailable: Array<{ benchmark: string; reason: string }> = []
|
|
443
|
+
for (const benchmark of benchmarks) {
|
|
444
|
+
const adapter = resolve(benchmark) // throws on an unknown key — fail loud on a typo
|
|
445
|
+
try {
|
|
446
|
+
await adapter.preflight()
|
|
447
|
+
const tasks = await adapter.loadTasks({
|
|
448
|
+
...(opts.n !== undefined ? { limit: opts.n } : {}),
|
|
449
|
+
...(opts.ids ? { ids: opts.ids } : {}),
|
|
450
|
+
...(opts.split ? { split: opts.split } : {}),
|
|
451
|
+
})
|
|
452
|
+
if (tasks.length === 0) {
|
|
453
|
+
unavailable.push({ benchmark, reason: 'loadTasks returned no tasks' })
|
|
454
|
+
continue
|
|
455
|
+
}
|
|
456
|
+
if (opts.verifyJudge !== false) {
|
|
457
|
+
const gold = await adapter.goldArtifact(tasks[0]!)
|
|
458
|
+
if (gold !== undefined) {
|
|
459
|
+
const verdict = await adapter.judge(tasks[0]!, gold)
|
|
460
|
+
if (!verdict.resolved) {
|
|
461
|
+
unavailable.push({ benchmark, reason: `judge rejected its own gold on ${tasks[0]!.id} — judge is miscalibrated` })
|
|
462
|
+
continue
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
ready.push({ benchmark, adapter, tasks })
|
|
467
|
+
} catch (err) {
|
|
468
|
+
unavailable.push({ benchmark, reason: err instanceof Error ? err.message : String(err) })
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { ready, unavailable }
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export async function runBenchmarks(opts: RunBenchmarksOptions): Promise<RunBenchmarksReport> {
|
|
475
|
+
if (opts.benchmarks.length === 0) throw new Error('runBenchmarks: no benchmarks selected')
|
|
476
|
+
if (opts.cells.length === 0) throw new Error('runBenchmarks: no cells to run')
|
|
477
|
+
const reps = Math.max(1, opts.reps ?? 1)
|
|
478
|
+
const loopAttempts = Math.max(1, opts.loopAttempts ?? 1)
|
|
479
|
+
const shot = opts.runShot ?? openSandboxShot
|
|
480
|
+
|
|
481
|
+
const { ready, unavailable } = await prepareBenchmarks(opts.benchmarks, opts.resolveAdapter ?? resolveAdapter, opts)
|
|
482
|
+
|
|
483
|
+
const jobs: Job[] = []
|
|
484
|
+
for (const { benchmark, adapter, tasks } of ready)
|
|
485
|
+
for (const cell of opts.cells) for (const task of tasks) for (let rep = 0; rep < reps; rep += 1) jobs.push({ benchmark, adapter, cell, task, rep })
|
|
486
|
+
|
|
487
|
+
const perTask: BenchCellTaskResult[] = []
|
|
488
|
+
await runPool(jobs, Math.max(1, opts.concurrency ?? 4), async (job, index) => {
|
|
489
|
+
const startedAt = Date.now()
|
|
490
|
+
let result: BenchCellTaskResult
|
|
491
|
+
try {
|
|
492
|
+
const shotInput = {
|
|
493
|
+
adapter: job.adapter,
|
|
494
|
+
task: job.task,
|
|
495
|
+
cell: job.cell,
|
|
496
|
+
routerBaseUrl: opts.routerBaseUrl,
|
|
497
|
+
routerKey: opts.routerKey,
|
|
498
|
+
...(opts.bridgeUrl ? { bridgeUrl: opts.bridgeUrl } : {}),
|
|
499
|
+
...(opts.bridgeBearer ? { bridgeBearer: opts.bridgeBearer } : {}),
|
|
500
|
+
...(opts.sandboxBaseUrl ? { sandboxBaseUrl: opts.sandboxBaseUrl } : {}),
|
|
501
|
+
...(opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {}),
|
|
502
|
+
...(opts.resolveClient ? { resolveClient: opts.resolveClient } : {}),
|
|
503
|
+
}
|
|
504
|
+
const out = loopAttempts > 1 ? await loopedShot(shotInput, shot, loopAttempts) : await shot(shotInput)
|
|
505
|
+
const score: BenchScore = await job.adapter.judge(job.task, out.artifact)
|
|
506
|
+
result = {
|
|
507
|
+
benchmark: job.benchmark,
|
|
508
|
+
cell: job.cell.label,
|
|
509
|
+
taskId: job.task.id,
|
|
510
|
+
rep: job.rep,
|
|
511
|
+
resolved: out.ok && score.resolved,
|
|
512
|
+
score: out.ok ? score.score : 0,
|
|
513
|
+
ok: out.ok,
|
|
514
|
+
...(out.detail ?? score.detail ? { detail: combineDetails(out.detail, score.detail) } : {}),
|
|
515
|
+
wallMs: Date.now() - startedAt,
|
|
516
|
+
}
|
|
517
|
+
} catch (err) {
|
|
518
|
+
// A thrown shot/judge is infra error for THIS cell-task: ok=false excludes it from the
|
|
519
|
+
// resolve denominator (never a silent 0% that hides a harness outage).
|
|
520
|
+
result = {
|
|
521
|
+
benchmark: job.benchmark,
|
|
522
|
+
cell: job.cell.label,
|
|
523
|
+
taskId: job.task.id,
|
|
524
|
+
rep: job.rep,
|
|
525
|
+
resolved: false,
|
|
526
|
+
score: 0,
|
|
527
|
+
ok: false,
|
|
528
|
+
detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
|
|
529
|
+
wallMs: Date.now() - startedAt,
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
void index
|
|
533
|
+
perTask.push(result)
|
|
534
|
+
opts.onResult?.(result)
|
|
535
|
+
return result
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
const rows = aggregate(perTask)
|
|
539
|
+
return {
|
|
540
|
+
rows,
|
|
541
|
+
perTask,
|
|
542
|
+
benchmarks: ready.map((r) => r.benchmark),
|
|
543
|
+
cells: opts.cells.map((c) => c.label),
|
|
544
|
+
unavailable,
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function aggregate(perTask: readonly BenchCellTaskResult[]): BenchLeaderboardRow[] {
|
|
549
|
+
const byKey = new Map<string, { benchmark: string; cell: string; n: number; resolved: number; errored: number; scoreSum: number }>()
|
|
550
|
+
for (const r of perTask) {
|
|
551
|
+
const key = `${r.benchmark}\u0000${r.cell}`
|
|
552
|
+
const e = byKey.get(key) ?? { benchmark: r.benchmark, cell: r.cell, n: 0, resolved: 0, errored: 0, scoreSum: 0 }
|
|
553
|
+
e.n += 1
|
|
554
|
+
if (!r.ok) e.errored += 1
|
|
555
|
+
else {
|
|
556
|
+
if (r.resolved) e.resolved += 1
|
|
557
|
+
e.scoreSum += r.score
|
|
558
|
+
}
|
|
559
|
+
byKey.set(key, e)
|
|
560
|
+
}
|
|
561
|
+
const rows: BenchLeaderboardRow[] = [...byKey.values()].map((e) => {
|
|
562
|
+
const denom = Math.max(1, e.n - e.errored)
|
|
563
|
+
return {
|
|
564
|
+
benchmark: e.benchmark,
|
|
565
|
+
cell: e.cell,
|
|
566
|
+
n: e.n,
|
|
567
|
+
resolved: e.resolved,
|
|
568
|
+
errored: e.errored,
|
|
569
|
+
resolveRate: e.resolved / denom,
|
|
570
|
+
meanScore: e.scoreSum / denom,
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
rows.sort((a, b) => (a.benchmark === b.benchmark ? b.resolveRate - a.resolveRate : a.benchmark < b.benchmark ? -1 : 1))
|
|
574
|
+
return rows
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** Render the leaderboard as a fixed-width table for a CLI/log. */
|
|
578
|
+
export function printBenchmarksReport(report: RunBenchmarksReport): string {
|
|
579
|
+
const lines: string[] = []
|
|
580
|
+
const w = Math.max(8, ...report.rows.map((r) => r.cell.length))
|
|
581
|
+
const b = Math.max(9, ...report.rows.map((r) => r.benchmark.length))
|
|
582
|
+
lines.push(`${'benchmark'.padEnd(b)} ${'cell'.padEnd(w)} resolve mean n err`)
|
|
583
|
+
for (const r of report.rows)
|
|
584
|
+
lines.push(
|
|
585
|
+
`${r.benchmark.padEnd(b)} ${r.cell.padEnd(w)} ${(r.resolveRate * 100).toFixed(1).padStart(6)}% ${r.meanScore.toFixed(3).padStart(5)} ${String(r.n).padStart(3)} ${String(r.errored).padStart(3)}`,
|
|
586
|
+
)
|
|
587
|
+
for (const u of report.unavailable) lines.push(`(skipped ${u.benchmark}: ${u.reason})`)
|
|
588
|
+
return lines.join('\n')
|
|
589
|
+
}
|