@tangle-network/agent-bench 0.3.5 → 0.3.7
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 +13 -0
- package/README.md +13 -1
- 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 +62 -0
- package/dist/benchmarks/humaneval.js +17 -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-5H5XV76F.js +240 -0
- package/dist/chunk-5H5XV76F.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-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.js.map +1 -0
- package/dist/chunk-PWQVGAJB.js +144 -0
- package/dist/chunk-PWQVGAJB.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/package.json +26 -9
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +12 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +788 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/calibrate.ts +116 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +405 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +104 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2145 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +489 -0
- package/src/swe-arena/proposer-fanout.test.mts +372 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +361 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +498 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +166 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { describe, it } from 'node:test'
|
|
6
|
+
import { runPythonProgram } from './humaneval'
|
|
7
|
+
|
|
8
|
+
describe('HumanEval Python isolation', () => {
|
|
9
|
+
it('runs the exact program through the resource-capped networkless container', async () => {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), 'humaneval-docker-test-'))
|
|
11
|
+
const fakeDocker = join(dir, 'docker')
|
|
12
|
+
const capture = join(dir, 'capture.jsonl')
|
|
13
|
+
writeFileSync(
|
|
14
|
+
fakeDocker,
|
|
15
|
+
`#!/usr/bin/env node
|
|
16
|
+
const fs = require('node:fs')
|
|
17
|
+
const path = require('node:path')
|
|
18
|
+
const args = process.argv.slice(2)
|
|
19
|
+
if (args[0] === 'rm') process.exit(0)
|
|
20
|
+
if (process.env.FAKE_DOCKER_MISSING === '1') {
|
|
21
|
+
process.stderr.write('docker: Error response from daemon: No such image: python:3.12-slim')
|
|
22
|
+
process.exit(125)
|
|
23
|
+
}
|
|
24
|
+
const mount = args[args.indexOf('-v') + 1]
|
|
25
|
+
const hostDir = mount.slice(0, -':/w:ro'.length)
|
|
26
|
+
fs.appendFileSync(process.env.FAKE_DOCKER_CAPTURE, JSON.stringify({
|
|
27
|
+
args,
|
|
28
|
+
program: fs.readFileSync(path.join(hostDir, 'p.py'), 'utf8'),
|
|
29
|
+
}) + '\\n')
|
|
30
|
+
if (process.env.FAKE_DOCKER_NO_START === '1') {
|
|
31
|
+
process.stderr.write('docker: Error response from daemon: unable to start container process')
|
|
32
|
+
process.exit(125)
|
|
33
|
+
}
|
|
34
|
+
process.stdout.write(args[args.length - 2] + '\\n')
|
|
35
|
+
if (process.env.FAKE_DOCKER_CANDIDATE === 'daemon-text') {
|
|
36
|
+
process.stderr.write('Cannot connect to the Docker daemon')
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
if (process.env.FAKE_DOCKER_CANDIDATE === 'exit-125') process.exit(125)
|
|
40
|
+
process.stdout.write('CONTAINER_OK\\n')
|
|
41
|
+
`,
|
|
42
|
+
{ mode: 0o755 },
|
|
43
|
+
)
|
|
44
|
+
const originalPath = process.env.PATH
|
|
45
|
+
const originalCapture = process.env.FAKE_DOCKER_CAPTURE
|
|
46
|
+
const originalMissing = process.env.FAKE_DOCKER_MISSING
|
|
47
|
+
const originalCandidate = process.env.FAKE_DOCKER_CANDIDATE
|
|
48
|
+
const originalNoStart = process.env.FAKE_DOCKER_NO_START
|
|
49
|
+
process.env.PATH = `${dir}:${originalPath ?? ''}`
|
|
50
|
+
process.env.FAKE_DOCKER_CAPTURE = capture
|
|
51
|
+
try {
|
|
52
|
+
const program = 'print("exact bytes")\n'
|
|
53
|
+
const result = await runPythonProgram(program, 2_000)
|
|
54
|
+
assert.equal(result.exitCode, 0)
|
|
55
|
+
assert.match(result.stdout, /CONTAINER_OK/)
|
|
56
|
+
|
|
57
|
+
const firstCall = JSON.parse(readFileSync(capture, 'utf8').trim().split('\n')[0]!) as {
|
|
58
|
+
args: string[]
|
|
59
|
+
program: string
|
|
60
|
+
}
|
|
61
|
+
assert.equal(firstCall.program, program)
|
|
62
|
+
assert.deepEqual(firstCall.args.slice(0, 2), ['run', '--rm'])
|
|
63
|
+
assert.ok(firstCall.args.includes('--network=none'))
|
|
64
|
+
assert.ok(firstCall.args.includes('--cpus=1'))
|
|
65
|
+
assert.ok(firstCall.args.includes('--memory=512m'))
|
|
66
|
+
assert.ok(firstCall.args.includes('--pids-limit=64'))
|
|
67
|
+
assert.ok(firstCall.args.includes('--cap-drop=ALL'))
|
|
68
|
+
assert.ok(firstCall.args.includes('--security-opt=no-new-privileges'))
|
|
69
|
+
assert.ok(firstCall.args.includes('--pull=never'))
|
|
70
|
+
assert.ok(firstCall.args.includes('--read-only'))
|
|
71
|
+
assert.equal(
|
|
72
|
+
firstCall.args[firstCall.args.indexOf('--tmpfs') + 1],
|
|
73
|
+
'/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777',
|
|
74
|
+
)
|
|
75
|
+
assert.equal(firstCall.args[firstCall.args.indexOf('--user') + 1], '65534:65534')
|
|
76
|
+
assert.match(firstCall.args[firstCall.args.indexOf('-v') + 1] ?? '', /:\/w:ro$/)
|
|
77
|
+
const command = firstCall.args.slice(firstCall.args.indexOf('python:3.12-slim'))
|
|
78
|
+
assert.equal(command[0], 'python:3.12-slim')
|
|
79
|
+
assert.equal(command[1], 'sh')
|
|
80
|
+
assert.equal(command[2], '-c')
|
|
81
|
+
assert.match(command[3] ?? '', /command -v timeout/)
|
|
82
|
+
assert.equal(command[4], 'agent-runtime-checker')
|
|
83
|
+
assert.match(command[5] ?? '', /^__AGENT_RUNTIME_CANDIDATE_STARTED_hev-/)
|
|
84
|
+
assert.equal(command[6], '2s')
|
|
85
|
+
|
|
86
|
+
process.env.FAKE_DOCKER_MISSING = '1'
|
|
87
|
+
await assert.rejects(
|
|
88
|
+
runPythonProgram('print("must not score")\n', 2_000),
|
|
89
|
+
/docker image python:3\.12-slim unavailable/,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
delete process.env.FAKE_DOCKER_MISSING
|
|
93
|
+
process.env.FAKE_DOCKER_CANDIDATE = 'daemon-text'
|
|
94
|
+
const daemonText = await runPythonProgram('raise SystemExit(1)\n', 2_000)
|
|
95
|
+
assert.equal(daemonText.exitCode, 1)
|
|
96
|
+
assert.match(daemonText.stderr, /Cannot connect to the Docker daemon/)
|
|
97
|
+
|
|
98
|
+
process.env.FAKE_DOCKER_CANDIDATE = 'exit-125'
|
|
99
|
+
const exit125 = await runPythonProgram('raise SystemExit(125)\n', 2_000)
|
|
100
|
+
assert.equal(exit125.exitCode, 125)
|
|
101
|
+
|
|
102
|
+
delete process.env.FAKE_DOCKER_CANDIDATE
|
|
103
|
+
process.env.FAKE_DOCKER_NO_START = '1'
|
|
104
|
+
await assert.rejects(
|
|
105
|
+
runPythonProgram('print("never started")\n', 2_000),
|
|
106
|
+
/did not start the candidate/,
|
|
107
|
+
)
|
|
108
|
+
} finally {
|
|
109
|
+
if (originalPath === undefined) delete process.env.PATH
|
|
110
|
+
else process.env.PATH = originalPath
|
|
111
|
+
if (originalCapture === undefined) delete process.env.FAKE_DOCKER_CAPTURE
|
|
112
|
+
else process.env.FAKE_DOCKER_CAPTURE = originalCapture
|
|
113
|
+
if (originalMissing === undefined) delete process.env.FAKE_DOCKER_MISSING
|
|
114
|
+
else process.env.FAKE_DOCKER_MISSING = originalMissing
|
|
115
|
+
if (originalCandidate === undefined) delete process.env.FAKE_DOCKER_CANDIDATE
|
|
116
|
+
else process.env.FAKE_DOCKER_CANDIDATE = originalCandidate
|
|
117
|
+
if (originalNoStart === undefined) delete process.env.FAKE_DOCKER_NO_START
|
|
118
|
+
else process.env.FAKE_DOCKER_NO_START = originalNoStart
|
|
119
|
+
rmSync(dir, { recursive: true, force: true })
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { execFile } from 'node:child_process'
|
|
20
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
20
|
+
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
21
21
|
import { tmpdir } from 'node:os'
|
|
22
22
|
import { join } from 'node:path'
|
|
23
23
|
import { gunzipSync } from 'node:zlib'
|
|
@@ -111,19 +111,36 @@ export interface CheckResult {
|
|
|
111
111
|
detail?: string
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
export interface PythonProgramResult {
|
|
115
|
+
exitCode: number
|
|
116
|
+
stdout: string
|
|
117
|
+
stderr: string
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
/** Run one candidate's deployable test program in an isolated container:
|
|
115
|
-
*
|
|
121
|
+
* read-only root, bounded writable `/tmp`, no network, one CPU, and 512 MiB.
|
|
116
122
|
* Exit 0 → pass. A docker invocation error (binary missing, daemon down, image
|
|
117
|
-
*
|
|
123
|
+
* unavailable) is NOT a test failure — it throws so the harness fails loud rather
|
|
118
124
|
* than scoring every candidate 0 from a broken checker. */
|
|
119
125
|
let dockerRunSeq = 0
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
/** Run arbitrary Python in the same isolated container used by the HumanEval
|
|
128
|
+
* checker. This is also the execution-tool primitive for experiments that let
|
|
129
|
+
* a model test its own snippets; model-written code must never run on the host. */
|
|
130
|
+
export function runPythonProgram(
|
|
131
|
+
program: string,
|
|
132
|
+
timeoutMs = dockerTimeoutMs,
|
|
133
|
+
): Promise<PythonProgramResult> {
|
|
122
134
|
const dir = mkdtempSync(join(tmpdir(), 'hev-'))
|
|
123
|
-
|
|
135
|
+
// Rootless Docker maps container root to a subordinate host uid that cannot
|
|
136
|
+
// traverse mkdtemp's default 0700 directory. The directory contains only the
|
|
137
|
+
// disposable candidate script and is mounted read-only into the container.
|
|
138
|
+
chmodSync(dir, 0o755)
|
|
139
|
+
writeFileSync(join(dir, 'p.py'), program, { mode: 0o644 })
|
|
124
140
|
// Unique container name so we can force-reap it regardless of the docker client's state.
|
|
125
141
|
const name = `hev-${process.pid}-${dockerRunSeq++}`
|
|
126
|
-
|
|
142
|
+
const startMarker = `__AGENT_RUNTIME_CANDIDATE_STARTED_${name}__`
|
|
143
|
+
return new Promise<PythonProgramResult>((resolvePromise, reject) => {
|
|
127
144
|
let settled = false
|
|
128
145
|
const cleanup = () => {
|
|
129
146
|
rmSync(dir, { recursive: true, force: true })
|
|
@@ -132,7 +149,7 @@ export function runChecker(task: HumanEvalTask, candidate: string): Promise<Chec
|
|
|
132
149
|
// the name is unique, so no reuse race).
|
|
133
150
|
execFile('docker', ['rm', '-f', name], () => {})
|
|
134
151
|
}
|
|
135
|
-
const finish = (res:
|
|
152
|
+
const finish = (res: PythonProgramResult) => {
|
|
136
153
|
if (settled) return
|
|
137
154
|
settled = true
|
|
138
155
|
clearTimeout(backstop)
|
|
@@ -146,56 +163,106 @@ export function runChecker(task: HumanEvalTask, candidate: string): Promise<Chec
|
|
|
146
163
|
cleanup()
|
|
147
164
|
reject(e)
|
|
148
165
|
}
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
const
|
|
166
|
+
// Candidate timeouts are enforced inside the container. If the outer Docker
|
|
167
|
+
// client or daemon misses this larger deadline, that is infrastructure failure,
|
|
168
|
+
// not a wrong answer.
|
|
169
|
+
const outerTimeoutMs = timeoutMs + 3_000
|
|
170
|
+
const backstop = setTimeout(
|
|
171
|
+
() => fail(new Error(`docker checker did not return within ${outerTimeoutMs + 3_000}ms`)),
|
|
172
|
+
outerTimeoutMs + 3_000,
|
|
173
|
+
)
|
|
174
|
+
const inContainerSeconds = Math.max(1, Math.ceil(timeoutMs / 1_000))
|
|
153
175
|
execFile(
|
|
154
176
|
'docker',
|
|
155
177
|
[
|
|
156
178
|
'run',
|
|
157
179
|
'--rm',
|
|
180
|
+
'--pull=never',
|
|
158
181
|
'--name',
|
|
159
182
|
name,
|
|
160
183
|
'--network=none',
|
|
161
184
|
'--cpus=1',
|
|
162
185
|
'--memory=512m',
|
|
186
|
+
'--pids-limit=64',
|
|
187
|
+
'--cap-drop=ALL',
|
|
188
|
+
'--security-opt=no-new-privileges',
|
|
189
|
+
'--read-only',
|
|
190
|
+
'--tmpfs',
|
|
191
|
+
'/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777',
|
|
192
|
+
'--user',
|
|
193
|
+
'65534:65534',
|
|
163
194
|
'-v',
|
|
164
195
|
`${dir}:/w:ro`,
|
|
165
196
|
'-w',
|
|
166
197
|
'/w',
|
|
167
198
|
dockerImage,
|
|
168
|
-
'
|
|
169
|
-
'
|
|
199
|
+
'sh',
|
|
200
|
+
'-c',
|
|
201
|
+
'command -v timeout >/dev/null 2>&1 && command -v python >/dev/null 2>&1 && [ -r /w/p.py ] || exit 126; printf "%s\\n" "$1"; exec timeout -s KILL "$2" python /w/p.py',
|
|
202
|
+
'agent-runtime-checker',
|
|
203
|
+
startMarker,
|
|
204
|
+
`${inContainerSeconds}s`,
|
|
170
205
|
],
|
|
171
|
-
{ timeout:
|
|
172
|
-
(err,
|
|
206
|
+
{ timeout: outerTimeoutMs, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
|
|
207
|
+
(err, stdout, stderr) => {
|
|
208
|
+
const markerLine = `${startMarker}\n`
|
|
209
|
+
const candidateStarted = stdout?.startsWith(markerLine) === true
|
|
210
|
+
const candidateStdout = candidateStarted ? stdout.slice(markerLine.length) : (stdout ?? '')
|
|
173
211
|
if (err) {
|
|
174
|
-
const e = err as
|
|
212
|
+
const e = err as Error & { killed?: boolean; code?: number | string }
|
|
175
213
|
if (e.code === 'ENOENT') {
|
|
176
214
|
fail(new Error('docker binary not found on PATH — cannot run the deployable checker'))
|
|
177
215
|
return
|
|
178
216
|
}
|
|
179
|
-
if (
|
|
180
|
-
fail(new Error(`docker
|
|
217
|
+
if (e.killed) {
|
|
218
|
+
fail(new Error(`docker checker exceeded its ${outerTimeoutMs}ms outer timeout`))
|
|
181
219
|
return
|
|
182
220
|
}
|
|
183
|
-
if (
|
|
184
|
-
|
|
221
|
+
if (!candidateStarted) {
|
|
222
|
+
if (
|
|
223
|
+
/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(
|
|
224
|
+
stderr,
|
|
225
|
+
)
|
|
226
|
+
) {
|
|
227
|
+
fail(new Error(`docker daemon unreachable: ${stderr.slice(0, 200)}`))
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
if (/unable to find image|no such image|pull access denied|manifest unknown/i.test(stderr)) {
|
|
231
|
+
fail(new Error(`docker image ${dockerImage} unavailable: ${stderr.slice(0, 200)}`))
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
fail(
|
|
235
|
+
new Error(
|
|
236
|
+
`docker checker did not start the candidate: ${stderr.slice(0, 200) || e.message}`,
|
|
237
|
+
),
|
|
238
|
+
)
|
|
185
239
|
return
|
|
186
240
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
241
|
+
const exitCode = typeof e.code === 'number' ? e.code : 1
|
|
242
|
+
finish({
|
|
243
|
+
exitCode,
|
|
244
|
+
stdout: candidateStdout,
|
|
245
|
+
stderr: stderr ?? '',
|
|
246
|
+
})
|
|
191
247
|
return
|
|
192
248
|
}
|
|
193
|
-
|
|
249
|
+
if (!candidateStarted) {
|
|
250
|
+
fail(new Error('docker checker exited without starting the candidate'))
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
finish({ exitCode: 0, stdout: candidateStdout, stderr: stderr ?? '' })
|
|
194
254
|
},
|
|
195
255
|
)
|
|
196
256
|
})
|
|
197
257
|
}
|
|
198
258
|
|
|
259
|
+
export async function runChecker(task: HumanEvalTask, candidate: string): Promise<CheckResult> {
|
|
260
|
+
const result = await runPythonProgram(buildProgram(task, candidate))
|
|
261
|
+
return result.exitCode === 0
|
|
262
|
+
? { pass: 1 }
|
|
263
|
+
: { pass: 0, detail: result.stderr.slice(-600) || 'timed out (no output)' }
|
|
264
|
+
}
|
|
265
|
+
|
|
199
266
|
/** A HumanEval task carries its checker inputs in metadata so the deterministic
|
|
200
267
|
* judge can rebuild the deployable program from a `BenchTask` alone. */
|
|
201
268
|
interface HumanEvalMeta extends Record<string, unknown> {
|
|
@@ -229,14 +296,20 @@ export function createHumanEvalAdapter(): BenchmarkAdapter {
|
|
|
229
296
|
return {
|
|
230
297
|
name: 'humaneval',
|
|
231
298
|
async preflight() {
|
|
232
|
-
//
|
|
233
|
-
//
|
|
299
|
+
// Prove the daemon and exact local image before any model call. Scoring uses
|
|
300
|
+
// --pull=never so a registry or cold pull cannot become a candidate outcome.
|
|
234
301
|
await new Promise<void>((resolve, reject) => {
|
|
235
302
|
execFile('docker', ['version', '--format', '{{.Server.Version}}'], (err) => {
|
|
236
303
|
if (err) reject(new Error('HumanEval judge needs a running Docker daemon (python:3.12-slim, --network=none)'))
|
|
237
304
|
else resolve()
|
|
238
305
|
})
|
|
239
306
|
})
|
|
307
|
+
await new Promise<void>((resolve, reject) => {
|
|
308
|
+
execFile('docker', ['image', 'inspect', dockerImage], (err) => {
|
|
309
|
+
if (err) reject(new Error(`HumanEval judge needs the cached Docker image ${dockerImage}`))
|
|
310
|
+
else resolve()
|
|
311
|
+
})
|
|
312
|
+
})
|
|
240
313
|
},
|
|
241
314
|
async loadTasks(opts?: LoadOptions) {
|
|
242
315
|
const offset = Number(process.env.OFFSET ?? 0)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAVID mechanism attribution — decompose the cheap-model harness's held-out
|
|
3
|
+
* accuracy into what SAMPLING buys vs what VERIFICATION-SELECTION buys, so a
|
|
4
|
+
* David-Goliath win is credited to the right lever (not just best-of-N luck).
|
|
5
|
+
*
|
|
6
|
+
* For each task, generate N candidate solutions + the model's own tests, then
|
|
7
|
+
* report four numbers on the HIDDEN test:
|
|
8
|
+
* pass@1 — first candidate (no harness).
|
|
9
|
+
* mean-cand — expected accuracy of a RANDOM candidate (sampling floor).
|
|
10
|
+
* oracle@N — a correct candidate exists among the N (ceiling of selection).
|
|
11
|
+
* verify-select — the candidate the self-tests picked (the actual David).
|
|
12
|
+
* verify-select − mean-cand = what VERIFICATION adds over blind sampling;
|
|
13
|
+
* oracle@N − verify-select = the selection gap left on the table.
|
|
14
|
+
*
|
|
15
|
+
* Run from cwd=bench: env DAVID=groq/llama-3.1-8b-instant N=8 T=5 NTASKS=60 \
|
|
16
|
+
* node_modules/.bin/tsx src/david-attribution.mts
|
|
17
|
+
*/
|
|
18
|
+
import { execFile } from 'node:child_process'
|
|
19
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
|
20
|
+
import { tmpdir } from 'node:os'
|
|
21
|
+
import { join } from 'node:path'
|
|
22
|
+
import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
|
|
23
|
+
|
|
24
|
+
const KEY = process.env.TANGLE_API_KEY!
|
|
25
|
+
const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
26
|
+
const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
|
|
27
|
+
const N = Number(process.env.N ?? 8)
|
|
28
|
+
const T = Number(process.env.T ?? 5)
|
|
29
|
+
const NTASKS = Number(process.env.NTASKS ?? 60)
|
|
30
|
+
const CONC = Number(process.env.CONCURRENCY ?? 6)
|
|
31
|
+
|
|
32
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
33
|
+
async function chat(messages: { role: string; content: string }[], temp: number): Promise<string> {
|
|
34
|
+
for (let a = 0; ; a++) {
|
|
35
|
+
try {
|
|
36
|
+
const r = await fetch(`${ROUTER}/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify({ model: DAVID, messages, temperature: temp, max_tokens: 1000 }), signal: AbortSignal.timeout(60_000) })
|
|
37
|
+
if ([408, 429, 500, 502, 503, 504, 520, 522, 524].includes(r.status)) { if (a >= 5) return ''; await sleep(700 * 2 ** a); continue }
|
|
38
|
+
if (!r.ok) return ''
|
|
39
|
+
return (((await r.json()) as { choices?: { message?: { content?: string } }[] }).choices?.[0]?.message?.content) ?? ''
|
|
40
|
+
} catch { if (a >= 5) return ''; await sleep(700 * 2 ** a) }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const exec = (f: string, a: string[], o: object) => new Promise<number>((res) => execFile(f, a, { ...o, maxBuffer: 8e6 }, (e) => res((e as { code?: number } | null)?.code ?? (e ? 1 : 0))))
|
|
44
|
+
async function runPy(p: string): Promise<boolean> { const d = mkdtempSync(join(tmpdir(), 'da-')); try { writeFileSync(join(d, 'p.py'), p); return (await exec('python3', [join(d, 'p.py')], { cwd: d, timeout: 6000 })) === 0 } finally { rmSync(d, { recursive: true, force: true }) } }
|
|
45
|
+
const SOLVE = 'Expert Python. Output the COMPLETE function in one ```python block, no prose, no tests.'
|
|
46
|
+
const genSol = async (t: HumanEvalTask, temp: number) => extractCode(await chat([{ role: 'system', content: SOLVE }, { role: 'user', content: `Complete:\n\n\`\`\`python\n${t.prompt}\`\`\`` }], temp))
|
|
47
|
+
async function genTests(t: HumanEvalTask): Promise<string[]> {
|
|
48
|
+
const b = extractCode(await chat([{ role: 'system', content: 'Write Python assert unit tests. Output ONLY a ```python block of `assert <entry>(...) == ...` lines. No function, no prose.' }, { role: 'user', content: `entry: ${t.entryPoint}\n\n\`\`\`python\n${t.prompt}\`\`\`` }], 0.4))
|
|
49
|
+
return b.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('assert ') && l.includes(t.entryPoint)).slice(0, T + 3)
|
|
50
|
+
}
|
|
51
|
+
const judge = async (t: HumanEvalTask, code: string) => code.trim() ? runPy(`${code}\n\n${t.test}\n\ncheck(${t.entryPoint})\n`) : false
|
|
52
|
+
async function scoreTests(code: string, tests: string[]): Promise<number> { if (!code.trim() || !tests.length) return 0; let p = 0; for (const a of tests) if (await runPy(`${code}\n\n${a}\n`)) p++; return p }
|
|
53
|
+
async function pool<T2, R>(xs: T2[], n: number, fn: (x: T2) => Promise<R>): Promise<R[]> { const o = new Array<R>(xs.length); let i = 0; await Promise.all(Array.from({ length: n }, async () => { while (i < xs.length) { const k = i++; o[k] = await fn(xs[k]!) } })); return o }
|
|
54
|
+
|
|
55
|
+
async function main(): Promise<void> {
|
|
56
|
+
const tasks = await loadHumanEval(NTASKS, 0)
|
|
57
|
+
console.error(`=== ATTRIBUTION · ${DAVID} · N=${N} sols + ${T} tests · n=${tasks.length} ===`)
|
|
58
|
+
let done = 0
|
|
59
|
+
const rows = await pool(tasks, CONC, async (t) => {
|
|
60
|
+
const cands = (await Promise.all(Array.from({ length: N }, () => genSol(t, 0.7)))).filter((c) => c.trim())
|
|
61
|
+
if (!cands.length) return { p1: 0, mean: 0, oracle: 0, sel: 0 }
|
|
62
|
+
const tests = await genTests(t)
|
|
63
|
+
const hidden = await Promise.all(cands.map((c) => judge(t, c))) // hidden-test pass per candidate (for attribution only)
|
|
64
|
+
const selScores = tests.length ? await Promise.all(cands.map((c) => scoreTests(c, tests))) : cands.map(() => 0)
|
|
65
|
+
let bi = 0; for (let i = 1; i < cands.length; i++) if (selScores[i]! > selScores[bi]! || (selScores[i]! === selScores[bi]! && cands[i]!.length > cands[bi]!.length)) bi = i
|
|
66
|
+
if (++done % 15 === 0) console.error(` ${done}/${tasks.length}`)
|
|
67
|
+
return { p1: hidden[0] ? 1 : 0, mean: hidden.filter(Boolean).length / cands.length, oracle: hidden.some(Boolean) ? 1 : 0, sel: hidden[bi] ? 1 : 0 }
|
|
68
|
+
})
|
|
69
|
+
const n = rows.length, avg = (f: (r: typeof rows[number]) => number) => (rows.reduce((s, r) => s + f(r), 0) / n) * 100
|
|
70
|
+
console.log('\n=== ATTRIBUTION (held-out) ===')
|
|
71
|
+
console.log(` pass@1 (no harness) : ${avg((r) => r.p1).toFixed(1)}%`)
|
|
72
|
+
console.log(` mean random candidate : ${avg((r) => r.mean).toFixed(1)}% (sampling floor)`)
|
|
73
|
+
console.log(` verify-select (DAVID) : ${avg((r) => r.sel).toFixed(1)}%`)
|
|
74
|
+
console.log(` oracle@N (a correct exists): ${avg((r) => r.oracle).toFixed(1)}% (selection ceiling)`)
|
|
75
|
+
console.log(` --> verification adds over random sampling: +${(avg((r) => r.sel) - avg((r) => r.mean)).toFixed(1)}pp`)
|
|
76
|
+
console.log(` --> selection gap still on table (oracle-select): ${(avg((r) => r.oracle) - avg((r) => r.sel)).toFixed(1)}pp`)
|
|
77
|
+
}
|
|
78
|
+
main().catch((e) => { console.error('MAIN:', e instanceof Error ? e.stack : e); process.exit(1) })
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAVID vs GOLIATH — the program's northstar, tested head-on: does a CHEAP model
|
|
3
|
+
* with a self-verification harness beat a FRONTIER model running solo, at EQUAL OR
|
|
4
|
+
* LOWER dollar cost, on held-out code?
|
|
5
|
+
*
|
|
6
|
+
* GOLIATH — a strong model, ONE shot. The "just use the big model" baseline.
|
|
7
|
+
* DAVID — a cheap/weak model + test-time compute: generate N candidate
|
|
8
|
+
* solutions AND M of its own unit tests, EXECUTE every candidate
|
|
9
|
+
* against the generated tests, and submit the candidate that passes
|
|
10
|
+
* the most (CodeT-style execution self-selection). No ground-truth
|
|
11
|
+
* test is ever used to select — only the model's own generated tests.
|
|
12
|
+
*
|
|
13
|
+
* Both are graded by the HIDDEN HumanEval test (never shown). Cost is the real
|
|
14
|
+
* token spend × the router-reported/priced rate per arm. The win condition is a
|
|
15
|
+
* Pareto beat: David's held-out pass rate >= Goliath's AND David's $ <= Goliath's.
|
|
16
|
+
* Mechanism under test: EXECUTION-BASED VERIFICATION is the lever that lets a weak
|
|
17
|
+
* generator punch above its solo weight — the standing "verification is live" claim
|
|
18
|
+
* at its most dramatic. Paired McNemar on per-task discordant pairs for significance.
|
|
19
|
+
*
|
|
20
|
+
* Run from cwd=bench: env DAVID=groq/llama-3.1-8b-instant GOLIATH=anthropic/claude-haiku-4-5-20251001 \
|
|
21
|
+
* N=8 T=5 NTASKS=164 REPS=2 node_modules/.bin/tsx src/david-goliath.mts
|
|
22
|
+
*/
|
|
23
|
+
import { execFile } from 'node:child_process'
|
|
24
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
|
25
|
+
import { tmpdir } from 'node:os'
|
|
26
|
+
import { join } from 'node:path'
|
|
27
|
+
import { loadHumanEval, extractCode, type HumanEvalTask } from './benchmarks/humaneval'
|
|
28
|
+
|
|
29
|
+
const KEY = process.env.TANGLE_API_KEY
|
|
30
|
+
if (!KEY) throw new Error('TANGLE_API_KEY required')
|
|
31
|
+
const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
32
|
+
const DAVID = process.env.DAVID ?? 'groq/llama-3.1-8b-instant'
|
|
33
|
+
const GOLIATH = process.env.GOLIATH ?? 'anthropic/claude-haiku-4-5-20251001'
|
|
34
|
+
const N = Number(process.env.N ?? 8) // David candidate solutions
|
|
35
|
+
const T = Number(process.env.T ?? 5) // David generated tests
|
|
36
|
+
const NTASKS = Number(process.env.NTASKS ?? 164)
|
|
37
|
+
const REPS = Number(process.env.REPS ?? 2)
|
|
38
|
+
const CONC = Number(process.env.CONCURRENCY ?? 6)
|
|
39
|
+
const EXEC_TIMEOUT = Number(process.env.EXEC_TIMEOUT_MS ?? 6000)
|
|
40
|
+
|
|
41
|
+
// Approx $/1M tokens (in,out) for cost accounting — the router does not price
|
|
42
|
+
// every model inline, so use public rates; a cheap/frontier gap of ~20-30x is the
|
|
43
|
+
// point, and the ratio is robust to small rate errors. Override via PRICES env.
|
|
44
|
+
const PRICES: Record<string, [number, number]> = {
|
|
45
|
+
'groq/llama-3.1-8b-instant': [0.05, 0.08],
|
|
46
|
+
'google/gemini-2.5-flash-lite': [0.10, 0.40],
|
|
47
|
+
'openai/gpt-4o-mini': [0.15, 0.60],
|
|
48
|
+
'anthropic/claude-haiku-4-5-20251001': [1.0, 5.0],
|
|
49
|
+
'glm-5.2': [0.60, 2.20],
|
|
50
|
+
}
|
|
51
|
+
const priceOf = (m: string) => PRICES[m] ?? [0.5, 1.5]
|
|
52
|
+
|
|
53
|
+
interface Usage { in: number; out: number }
|
|
54
|
+
const zero = (): Usage => ({ in: 0, out: 0 })
|
|
55
|
+
const addU = (a: Usage, b: Usage) => { a.in += b.in; a.out += b.out }
|
|
56
|
+
const usd = (m: string, u: Usage) => { const [pi, po] = priceOf(m); return (u.in * pi + u.out * po) / 1e6 }
|
|
57
|
+
|
|
58
|
+
async function chat(model: string, messages: { role: string; content: string }[], temperature: number, usage: Usage): Promise<string> {
|
|
59
|
+
for (let a = 0; ; a++) {
|
|
60
|
+
try {
|
|
61
|
+
const r = await fetch(`${ROUTER}/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${KEY}` }, body: JSON.stringify({ model, messages, temperature, max_tokens: 1000 }), signal: AbortSignal.timeout(60_000) })
|
|
62
|
+
if ([408, 429, 500, 502, 503, 504, 520, 522, 524].includes(r.status)) { if (a >= 5) return ''; await sleep(700 * 2 ** a); continue }
|
|
63
|
+
if (!r.ok) return ''
|
|
64
|
+
const j = (await r.json()) as { choices?: { message?: { content?: string } }[]; usage?: { prompt_tokens?: number; completion_tokens?: number } }
|
|
65
|
+
addU(usage, { in: j.usage?.prompt_tokens ?? 0, out: j.usage?.completion_tokens ?? 0 })
|
|
66
|
+
return j.choices?.[0]?.message?.content ?? ''
|
|
67
|
+
} catch { if (a >= 5) return ''; await sleep(700 * 2 ** a) }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
71
|
+
const exec = (file: string, args: string[], o: object) => new Promise<{ code: number; stdout: string }>((res) => execFile(file, args, { ...o, maxBuffer: 8 * 1024 * 1024 }, (e, stdout) => res({ code: (e as { code?: number } | null)?.code ?? (e ? 1 : 0), stdout: String(stdout) })))
|
|
72
|
+
async function runPy(program: string): Promise<{ ok: boolean }> {
|
|
73
|
+
const d = mkdtempSync(join(tmpdir(), 'dg-'))
|
|
74
|
+
try { writeFileSync(join(d, 'p.py'), program); const r = await exec('python3', [join(d, 'p.py')], { cwd: d, timeout: EXEC_TIMEOUT }); return { ok: r.code === 0 } } finally { rmSync(d, { recursive: true, force: true }) }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const SOLVE = 'You are an expert Python programmer. Output the COMPLETE function (signature + body + imports) in a single ```python block. No prose, no tests.'
|
|
78
|
+
async function genSolution(model: string, t: HumanEvalTask, temp: number, u: Usage): Promise<string> {
|
|
79
|
+
return extractCode(await chat(model, [{ role: 'system', content: SOLVE }, { role: 'user', content: `Complete:\n\n\`\`\`python\n${t.prompt}\`\`\`` }], temp, u))
|
|
80
|
+
}
|
|
81
|
+
// David writes its OWN tests (never sees the hidden test). Parse assert lines.
|
|
82
|
+
async function genTests(model: string, t: HumanEvalTask, u: Usage): Promise<string[]> {
|
|
83
|
+
const reply = await chat(model, [
|
|
84
|
+
{ role: 'system', content: 'Write Python assert-based unit tests for the described function. Output ONLY a ```python block of standalone `assert <entry>(...) == ...` lines (at least a few, covering normal + edge cases). No function definition, no prose.' },
|
|
85
|
+
{ role: 'user', content: `Function to test (entry point: ${t.entryPoint}):\n\n\`\`\`python\n${t.prompt}\`\`\`` },
|
|
86
|
+
], 0.4, u)
|
|
87
|
+
const block = extractCode(reply) || reply
|
|
88
|
+
return block.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('assert ') && l.includes(t.entryPoint)).slice(0, T + 3)
|
|
89
|
+
}
|
|
90
|
+
// Hidden held-out judge — the truth. Never used for selection.
|
|
91
|
+
async function judge(t: HumanEvalTask, code: string): Promise<boolean> {
|
|
92
|
+
if (!code.trim()) return false
|
|
93
|
+
return (await runPy(`${code}\n\n${t.test}\n\ncheck(${t.entryPoint})\n`)).ok
|
|
94
|
+
}
|
|
95
|
+
// David's self-selection: score each candidate by how many of ITS OWN tests it passes.
|
|
96
|
+
async function scoreOnTests(code: string, tests: string[]): Promise<number> {
|
|
97
|
+
if (!code.trim() || tests.length === 0) return 0
|
|
98
|
+
let pass = 0
|
|
99
|
+
// one program per test keeps a crash on one test from voiding the rest
|
|
100
|
+
for (const a of tests) if ((await runPy(`${code}\n\n${a}\n`)).ok) pass++
|
|
101
|
+
return pass
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function davidArm(t: HumanEvalTask, u: Usage): Promise<string> {
|
|
105
|
+
const cands = (await Promise.all(Array.from({ length: N }, () => genSolution(DAVID, t, 0.7, u)))).filter((c) => c.trim())
|
|
106
|
+
if (cands.length === 0) return ''
|
|
107
|
+
const tests = await genTests(DAVID, t, u)
|
|
108
|
+
if (tests.length === 0) return cands[0]! // no verifier signal → first sample
|
|
109
|
+
const scored = await Promise.all(cands.map(async (c) => ({ c, s: await scoreOnTests(c, tests) })))
|
|
110
|
+
scored.sort((a, b) => b.s - a.s || b.c.length - a.c.length)
|
|
111
|
+
return scored[0]!.c
|
|
112
|
+
}
|
|
113
|
+
const goliathArm = (t: HumanEvalTask, u: Usage) => genSolution(GOLIATH, t, 0.2, u)
|
|
114
|
+
|
|
115
|
+
async function pool<T2, R>(xs: T2[], n: number, fn: (x: T2, i: number) => Promise<R>): Promise<R[]> {
|
|
116
|
+
const o = new Array<R>(xs.length); let i = 0
|
|
117
|
+
await Promise.all(Array.from({ length: Math.min(n, xs.length) }, async () => { while (i < xs.length) { const k = i++; o[k] = await fn(xs[k]!, k) } }))
|
|
118
|
+
return o
|
|
119
|
+
}
|
|
120
|
+
function mcnemar(b: number, c: number): number { const n = b + c; if (n === 0) return 1; const k = Math.min(b, c); const lf = (x: number) => { let s = 0; for (let i = 2; i <= x; i++) s += Math.log(i); return s }; let tl = 0; for (let i = 0; i <= k; i++) tl += Math.exp(lf(n) - lf(i) - lf(n - i) - n * Math.log(2)); return Math.min(1, 2 * tl) }
|
|
121
|
+
|
|
122
|
+
async function main(): Promise<void> {
|
|
123
|
+
if (['1', 'true'].includes((process.env.SMOKE ?? '').toLowerCase())) { console.error('SMOKE ok: david-goliath loaded'); return }
|
|
124
|
+
const tasks = await loadHumanEval(NTASKS, 0)
|
|
125
|
+
console.error(`=== DAVID(${DAVID}, N=${N} sols + ${T} self-tests) vs GOLIATH(${GOLIATH}, 1 shot) · HumanEval n=${tasks.length} · reps=${REPS} ===`)
|
|
126
|
+
const dU = zero(), gU = zero()
|
|
127
|
+
const units = tasks.flatMap((task) => Array.from({ length: REPS }, () => task))
|
|
128
|
+
let done = 0
|
|
129
|
+
const res = await pool(units, CONC, async (task) => {
|
|
130
|
+
const safe = async (fn: () => Promise<string>) => { try { return await fn() } catch { return '' } }
|
|
131
|
+
const [dCode, gCode] = await Promise.all([safe(() => davidArm(task, dU)), safe(() => goliathArm(task, gU))])
|
|
132
|
+
const [d, g] = await Promise.all([judge(task, dCode), judge(task, gCode)])
|
|
133
|
+
if (++done % 20 === 0) console.error(` ${done}/${units.length} units`)
|
|
134
|
+
return { d, g }
|
|
135
|
+
})
|
|
136
|
+
const n = res.length, dPass = res.filter((r) => r.d).length, gPass = res.filter((r) => r.g).length
|
|
137
|
+
const b = res.filter((r) => r.d && !r.g).length, c = res.filter((r) => !r.d && r.g).length
|
|
138
|
+
const p = mcnemar(b, c)
|
|
139
|
+
const dCost = usd(DAVID, dU), gCost = usd(GOLIATH, gU)
|
|
140
|
+
console.log('\n=== RESULT (held-out HumanEval) ===')
|
|
141
|
+
console.log(` GOLIATH ${GOLIATH} solo : ${gPass}/${n} = ${(gPass / n * 100).toFixed(1)}% $${gCost.toFixed(4)}`)
|
|
142
|
+
console.log(` DAVID ${DAVID} + verify: ${dPass}/${n} = ${(dPass / n * 100).toFixed(1)}% $${dCost.toFixed(4)}`)
|
|
143
|
+
console.log(` accuracy: David ${dPass >= gPass ? '>=' : '<'} Goliath (${(dPass / n * 100).toFixed(1)} vs ${(gPass / n * 100).toFixed(1)}); paired McNemar David-only=${b} Goliath-only=${c} p=${p.toFixed(4)}`)
|
|
144
|
+
console.log(` cost: David is ${(gCost / Math.max(dCost, 1e-9)).toFixed(1)}x CHEAPER ($${dCost.toFixed(4)} vs $${gCost.toFixed(4)})`)
|
|
145
|
+
const paretoBeat = dPass >= gPass && dCost <= gCost
|
|
146
|
+
const sigBeat = dPass > gPass && p < 0.05
|
|
147
|
+
console.log(` VERDICT: ${sigBeat ? 'DAVID SIGNIFICANTLY BEATS GOLIATH' : paretoBeat ? 'DAVID PARETO-DOMINATES (>= accuracy, <= cost)' : dPass >= gPass ? 'David matches accuracy (check cost)' : 'Goliath wins accuracy'}`)
|
|
148
|
+
}
|
|
149
|
+
main().catch((e) => { console.error('MAIN:', e instanceof Error ? (e.stack ?? e.message) : e); process.exit(1) })
|
package/src/hev-improve.mts
CHANGED
|
@@ -65,8 +65,11 @@ async function main(): Promise<void> {
|
|
|
65
65
|
const trainN = Number(process.env.TRAIN_N ?? 12)
|
|
66
66
|
const holdoutN = Number(process.env.HOLDOUT_N ?? 12)
|
|
67
67
|
const offset = Number(process.env.OFFSET ?? 80)
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// generations=1 never exercises the GEPA Pareto/combine path (the frontier
|
|
69
|
+
// needs >=1 completed generation before combine can fire) — default to a
|
|
70
|
+
// multi-generation budget so the default run measures the full loop.
|
|
71
|
+
const generations = Number(process.env.GENERATIONS ?? 6)
|
|
72
|
+
const population = Number(process.env.POPULATION ?? 4)
|
|
70
73
|
const workerMaxTokens = Number(process.env.MAX_TOKENS ?? 6000)
|
|
71
74
|
const reflectMaxTokens = Number(process.env.REFLECT_MAX_TOKENS ?? 8000)
|
|
72
75
|
const maxConcurrency = Number(process.env.MAX_CONCURRENCY ?? 4)
|
|
@@ -115,9 +118,21 @@ async function main(): Promise<void> {
|
|
|
115
118
|
console.log(` [judge] ${scenario.id} pass=0 (empty)`)
|
|
116
119
|
return { dimensions: { pass: 0 }, composite: 0, notes: 'empty' }
|
|
117
120
|
}
|
|
118
|
-
const { pass } = await runChecker(t, code)
|
|
121
|
+
const { pass, detail } = await runChecker(t, code)
|
|
119
122
|
console.log(` [judge] ${scenario.id} pass=${pass}`)
|
|
120
|
-
return { dimensions: { pass }, composite: pass, notes:
|
|
123
|
+
if (pass === 1) return { dimensions: { pass }, composite: pass, notes: 'passed' }
|
|
124
|
+
// Trajectory-grounded failure note: the checker's traceback/assertion tail
|
|
125
|
+
// plus the model's own emitted code, so GEPA reflection sees WHAT failed and
|
|
126
|
+
// WHAT the model wrote — not just the word 'failed'. The candidate's full
|
|
127
|
+
// raw reply additionally reaches the proposer via the campaign breakdown's
|
|
128
|
+
// `emitted` field (carried automatically from the string artifact).
|
|
129
|
+
const traceback = (detail ?? 'checker produced no output (timeout or silent non-zero exit)').slice(-800)
|
|
130
|
+
const excerpt = code.slice(0, 700)
|
|
131
|
+
return {
|
|
132
|
+
dimensions: { pass },
|
|
133
|
+
composite: pass,
|
|
134
|
+
notes: `${traceback}\n--- emitted code (first 700 chars) ---\n${excerpt}`,
|
|
135
|
+
}
|
|
121
136
|
},
|
|
122
137
|
}
|
|
123
138
|
|
|
@@ -146,12 +161,17 @@ async function main(): Promise<void> {
|
|
|
146
161
|
judge,
|
|
147
162
|
agent,
|
|
148
163
|
expectUsage: 'warn',
|
|
164
|
+
// rawTraceContext stays OFF deliberately: it swaps the distilled findings for
|
|
165
|
+
// filesystem paths + grep/cat instructions (rawTraceDistiller), which only a
|
|
166
|
+
// coding harness can execute. This run's proposer is prompt-tier (gepaProposer
|
|
167
|
+
// — a single LLM call that cannot run grep), so the trace evidence arrives via
|
|
168
|
+
// the judge's traceback notes + the breakdown's `emitted` excerpt instead.
|
|
149
169
|
budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency, reps: 1 },
|
|
150
170
|
llm: { baseUrl: reflectBase, apiKey: reflectKey, model: reflectModel },
|
|
151
171
|
})
|
|
152
172
|
|
|
153
173
|
console.log('\n═══ RESULT ═══')
|
|
154
|
-
console.log(`
|
|
174
|
+
console.log(`gateDecision=${out.gateDecision} shipped=${out.shipped} lift=${out.lift}`)
|
|
155
175
|
console.log(`baseline holdout pass-rate = ${out.raw.baseline.compositeMean}`)
|
|
156
176
|
console.log(`winner holdout pass-rate = ${out.raw.winner.compositeMean}`)
|
|
157
177
|
console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
|
|
@@ -160,7 +180,6 @@ async function main(): Promise<void> {
|
|
|
160
180
|
if ((out.raw.winner as { surface?: unknown }).surface) {
|
|
161
181
|
console.log(`winner instruction:\n${String((out.raw.winner as { surface?: unknown }).surface).slice(0, 1200)}`)
|
|
162
182
|
}
|
|
163
|
-
console.log(`live instruction unchanged: ${profile.prompt?.systemPrompt}`)
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
main().catch((e) => {
|