@tangle-network/agent-bench 0.3.8 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- package/dist/benchmarks/_harness.d.ts +4 -1
- package/dist/benchmarks/_harness.js +3 -1
- package/dist/benchmarks/aec-bench.js +2 -2
- package/dist/benchmarks/agentbench.js +2 -2
- package/dist/benchmarks/appworld.js +2 -2
- package/dist/benchmarks/bfcl.js +2 -2
- package/dist/benchmarks/commit0.js +2 -2
- package/dist/benchmarks/crag.js +2 -2
- package/dist/benchmarks/dabstep.js +2 -2
- package/dist/benchmarks/enterpriseops-gym.js +2 -2
- package/dist/benchmarks/finresearchbench.js +2 -2
- package/dist/benchmarks/nomiracl.js +2 -2
- package/dist/benchmarks/open-rag-bench.js +2 -2
- package/dist/benchmarks/programbench.js +2 -2
- package/dist/benchmarks/ragbench.js +2 -2
- package/dist/benchmarks/swe-bench.js +2 -2
- package/dist/benchmarks/t2-ragbench.js +2 -2
- package/dist/benchmarks/tau-bench-shared.js +2 -2
- package/dist/benchmarks/tau2-bench.js +3 -3
- package/dist/benchmarks/tau3-banking.js +3 -3
- package/dist/benchmarks/terminal-bench.js +2 -2
- package/dist/benchmarks/toollm.js +2 -2
- package/dist/benchmarks/webarena-verified.js +2 -2
- package/dist/{chunk-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
- package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
- package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
- package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
- package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
- package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
- package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
- package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
- package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
- package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
- package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
- package/dist/chunk-EIETHPD5.js.map +1 -0
- package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
- package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
- package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
- package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
- package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
- package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
- package/dist/chunk-JSQOUKXS.js.map +1 -0
- package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
- package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
- package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
- package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
- package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
- package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
- package/dist/index.js +23 -23
- package/package.json +4 -4
- package/pier_agents/candidate_contract.py +238 -24
- package/pier_agents/tangle_candidate.py +75 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +65 -1
- package/scripts/verify-pier-agent.mts +6 -4
- package/src/benchmarks/_harness.test.mts +16 -1
- package/src/benchmarks/_harness.ts +9 -2
- package/src/benchmarks/terminal-bench.ts +2 -1
- package/src/gate.ts +1 -1
- package/src/hev-eval.mts +5 -2
- package/src/hev-improve.mts +118 -73
- package/src/official-optimizer-config.mts +89 -0
- package/src/official-optimizer-config.test.mts +88 -0
- package/src/profiles.ts +2 -2
- package/src/rollout-ledger/backfill-swe-arena.test.mts +28 -24
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +1 -4
- package/src/swe-arena/activation.test.mts +10 -13
- package/src/swe-arena/gepa-seat.mts +425 -131
- package/src/swe-arena/gepa-seat.test.mts +524 -100
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/outer-loop.mts +103 -76
- package/src/swe-arena/proposer-fanout.mts +51 -36
- package/src/swe-arena/proposer-fanout.test.mts +0 -1
- package/src/swe-arena/proposer-provenance.mts +11 -16
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -0
- package/src/swe-code-improve.mts +24 -25
- package/src/swe-improve.mts +129 -96
- package/src/swe-local-proof.mts +6 -1
- package/src/swe-stream.mts +4 -2
- package/src/tb-container-executor.test.mts +30 -6
- package/src/tb-supervisor-sidecar.mts +2 -1
- package/src/trata-gepa.mts +182 -245
- package/dist/chunk-WSKWVEQB.js.map +0 -1
- package/dist/chunk-XYA4XSNU.js.map +0 -1
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- package/src/swe-arena/lineage-record.mts +0 -164
- package/src/swe-arena/lineage-record.test.mts +0 -115
- /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
- /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
- /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
- /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
- /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
- /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
- /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
- /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
- /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
- /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
- /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
- /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
- /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
- /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
- /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
- /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
- /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
- /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
- /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
- /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
- /package/dist/{chunk-Z4TZ76N7.js.map → chunk-ZNCCYTFG.js.map} +0 -0
|
@@ -4,7 +4,13 @@ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
|
4
4
|
import test from 'node:test'
|
|
5
5
|
import { tmpdir } from 'node:os'
|
|
6
6
|
import { join } from 'node:path'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
benchRoot,
|
|
9
|
+
resolveBenchPython,
|
|
10
|
+
runStagedJudge,
|
|
11
|
+
venvBin,
|
|
12
|
+
venvBinAt,
|
|
13
|
+
} from './_harness'
|
|
8
14
|
|
|
9
15
|
const digest = (bytes: Uint8Array): `sha256:${string}` =>
|
|
10
16
|
`sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
|
@@ -31,6 +37,15 @@ test('resolveBenchPython rejects relative and empty interpreter paths', () => {
|
|
|
31
37
|
)
|
|
32
38
|
})
|
|
33
39
|
|
|
40
|
+
test('venv executable paths support package-owned and external environments', () => {
|
|
41
|
+
assert.equal(venvBinAt('/srv/terminal-bench', 'tb'), join('/srv/terminal-bench', 'bin', 'tb'))
|
|
42
|
+
assert.equal(
|
|
43
|
+
venvBinAt('.venv-commit0', 'python'),
|
|
44
|
+
join(benchRoot, '.venv-commit0', 'bin', 'python'),
|
|
45
|
+
)
|
|
46
|
+
assert.equal(venvBin('python'), venvBinAt('.venv', 'python'))
|
|
47
|
+
})
|
|
48
|
+
|
|
34
49
|
test('runStagedJudge terminates a hung evaluator at its explicit timeout', async () => {
|
|
35
50
|
const started = Date.now()
|
|
36
51
|
await assert.rejects(
|
|
@@ -82,13 +82,20 @@ export const venvPython = resolveBenchPython()
|
|
|
82
82
|
/** Interpreter for a NAMED isolated venv (e.g. `.venv-commit0`). Benches whose pip
|
|
83
83
|
* deps conflict with the shared `.venv` (commit0 downgrades pydantic/sqlalchemy)
|
|
84
84
|
* get their own venv and pass its python explicitly — keeping the shared one clean. */
|
|
85
|
-
export const venvPythonAt = (venvDir: string): string =>
|
|
85
|
+
export const venvPythonAt = (venvDir: string): string => venvBinAt(venvDir, 'python')
|
|
86
|
+
|
|
87
|
+
/** Resolve an executable in an isolated venv. Relative venv paths are package-owned;
|
|
88
|
+
* absolute paths allow installed consumers to keep large environments elsewhere. */
|
|
89
|
+
export function venvBinAt(venvDir: string, name: string): string {
|
|
90
|
+
return join(resolve(benchRoot, venvDir), 'bin', name)
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
/** Report/transcript reads are large; 256 MiB matches the SWE harness budget. */
|
|
87
94
|
export const bigBuffer = 1024 * 1024 * 256
|
|
88
95
|
|
|
89
96
|
/** Path to a named executable inside the bench venv (e.g. `venvBin('tb')`). */
|
|
90
97
|
export function venvBin(name: string): string {
|
|
91
|
-
return
|
|
98
|
+
return venvBinAt('.venv', name)
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
/**
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
runVenvPython,
|
|
26
26
|
safeRunId,
|
|
27
27
|
stageFile,
|
|
28
|
+
venvBinAt,
|
|
28
29
|
venvPythonAt,
|
|
29
30
|
} from './_harness'
|
|
30
31
|
import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
|
|
@@ -34,7 +35,7 @@ import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './typ
|
|
|
34
35
|
// override the env without reloading this module.
|
|
35
36
|
const terminalBenchVenvDir = (): string => process.env.TERMINAL_BENCH_VENV ?? '.venv-terminal-bench'
|
|
36
37
|
const terminalBenchPython = (): string => venvPythonAt(terminalBenchVenvDir())
|
|
37
|
-
const terminalBenchBin = (): string =>
|
|
38
|
+
const terminalBenchBin = (): string => venvBinAt(terminalBenchVenvDir(), 'tb')
|
|
38
39
|
|
|
39
40
|
// Pinned dataset: the 0.1.1 core set is patched for terminal-bench >=0.2.4 (the
|
|
40
41
|
// installed CLI) and is the published launch task set. name==version is what `tb
|
package/src/gate.ts
CHANGED
|
@@ -427,7 +427,7 @@ export async function runGate(opts: RunGateOptions): Promise<GateReport> {
|
|
|
427
427
|
root: a.label,
|
|
428
428
|
nodes: [],
|
|
429
429
|
total: acc.get(a.label)!.spend,
|
|
430
|
-
statusCounts: { done: 0, failed: 0, cancelled: 0, pending: 0 },
|
|
430
|
+
statusCounts: { done: 0, failed: 0, cancelled: 0, pending: 0, waiting: 0 },
|
|
431
431
|
},
|
|
432
432
|
}))
|
|
433
433
|
const equalK = equalKOnCost(equalKArms)
|
package/src/hev-eval.mts
CHANGED
|
@@ -27,6 +27,7 @@ async function complete(base: string, key: string, model: string, prompt: string
|
|
|
27
27
|
async function main(): Promise<void> {
|
|
28
28
|
const key = process.env.TANGLE_API_KEY
|
|
29
29
|
if (!key) throw new Error('TANGLE_API_KEY required')
|
|
30
|
+
const apiKey: string = key
|
|
30
31
|
const base = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
|
|
31
32
|
const model = process.env.WORKER_MODEL ?? 'meta-llama/Meta-Llama-3-8B-Instruct-Lite'
|
|
32
33
|
const instruction = process.env.INSTRUCTION_FILE
|
|
@@ -51,8 +52,10 @@ async function main(): Promise<void> {
|
|
|
51
52
|
let i = 0
|
|
52
53
|
async function worker(): Promise<void> {
|
|
53
54
|
while (i < tasks.length) {
|
|
54
|
-
const t = tasks[i
|
|
55
|
-
|
|
55
|
+
const t = tasks[i]
|
|
56
|
+
i += 1
|
|
57
|
+
if (!t) continue
|
|
58
|
+
const reply = await complete(base, apiKey, model, `${instruction}\n\n\`\`\`python\n${t.prompt}\`\`\``, maxTokens)
|
|
56
59
|
const { pass: p } = await runChecker(t, extractCode(reply))
|
|
57
60
|
if (p === 1) pass += 1
|
|
58
61
|
else fails.push(t.taskId)
|
package/src/hev-improve.mts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* held-out gate), but the worker is a single chat completion and the judge is the
|
|
5
|
-
* deterministic Docker checker (run the function against its own hidden unit tests).
|
|
2
|
+
* Official GEPA prompt optimization on HumanEval. The worker is a single chat
|
|
3
|
+
* completion and the judge is the deterministic Docker checker.
|
|
6
4
|
*
|
|
7
5
|
* WHY this exists: on SWE-bench the same GEPA loop was NULL because the grading test
|
|
8
6
|
* is withheld — the worker cannot verify, so prompt wording cannot move resolve.
|
|
@@ -13,11 +11,22 @@
|
|
|
13
11
|
* Worker + reflect models call the zai coding endpoint directly (no tangle router,
|
|
14
12
|
* no WAF, no 503): TANGLE_API_KEY=$ZAI_API_KEY ROUTER_BASE=https://api.z.ai/api/coding/paas/v4
|
|
15
13
|
*/
|
|
16
|
-
import {
|
|
17
|
-
|
|
14
|
+
import {
|
|
15
|
+
improve,
|
|
16
|
+
officialGepa,
|
|
17
|
+
type ReadonlyAgentProfile,
|
|
18
|
+
} from '@tangle-network/agent-runtime'
|
|
19
|
+
import {
|
|
20
|
+
canonicalCandidateDigest,
|
|
21
|
+
type AgentProfile,
|
|
22
|
+
} from '@tangle-network/agent-interface'
|
|
18
23
|
import type { DispatchContext, JudgeConfig, Scenario } from '@tangle-network/agent-eval/contract'
|
|
19
|
-
import { gepaProposer } from '@tangle-network/agent-eval/campaign'
|
|
20
24
|
import { extractCode, loadHumanEval, runChecker, type HumanEvalTask } from './benchmarks/humaneval'
|
|
25
|
+
import {
|
|
26
|
+
assertCompleteCost,
|
|
27
|
+
officialOptimizerModel,
|
|
28
|
+
requiredTokenPricing,
|
|
29
|
+
} from './official-optimizer-config.mjs'
|
|
21
30
|
|
|
22
31
|
// The SEED instruction GEPA evolves. Byte-identical to humaneval.ts basePrompt's
|
|
23
32
|
// solveInstruction so the baseline arm reproduces the plain-prompt denominator.
|
|
@@ -26,7 +35,6 @@ const SEED_INSTRUCTION =
|
|
|
26
35
|
|
|
27
36
|
interface Completion {
|
|
28
37
|
text: string
|
|
29
|
-
usd: number
|
|
30
38
|
tokIn: number
|
|
31
39
|
tokOut: number
|
|
32
40
|
}
|
|
@@ -45,10 +53,7 @@ async function complete(base: string, key: string, model: string, prompt: string
|
|
|
45
53
|
const text = d.choices?.[0]?.message?.content ?? ''
|
|
46
54
|
const tokIn = d.usage?.prompt_tokens ?? 0
|
|
47
55
|
const tokOut = d.usage?.completion_tokens ?? 0
|
|
48
|
-
|
|
49
|
-
// stub-guard sees a real backend. Exact cost is not the metric (pass-rate is).
|
|
50
|
-
const usd = (tokIn * 0.6 + tokOut * 2.2) / 1_000_000
|
|
51
|
-
return { text, usd, tokIn, tokOut }
|
|
56
|
+
return { text, tokIn, tokOut }
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
async function main(): Promise<void> {
|
|
@@ -63,51 +68,81 @@ async function main(): Promise<void> {
|
|
|
63
68
|
const reflectBase = process.env.REFLECT_BASE ?? base
|
|
64
69
|
const reflectKey = process.env.REFLECT_KEY ?? key
|
|
65
70
|
const trainN = Number(process.env.TRAIN_N ?? 12)
|
|
66
|
-
const
|
|
71
|
+
const selectionN = Number(process.env.SELECTION_N ?? 12)
|
|
72
|
+
const testN = Number(process.env.TEST_N ?? 12)
|
|
67
73
|
const offset = Number(process.env.OFFSET ?? 80)
|
|
68
|
-
|
|
69
|
-
|
|
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)
|
|
74
|
+
const maxEvaluations = Number(process.env.MAX_EVALUATIONS ?? 24)
|
|
75
|
+
const maxProposerCostUsd = Number(process.env.MAX_PROPOSER_COST_USD ?? 5)
|
|
73
76
|
const workerMaxTokens = Number(process.env.MAX_TOKENS ?? 6000)
|
|
74
77
|
const reflectMaxTokens = Number(process.env.REFLECT_MAX_TOKENS ?? 8000)
|
|
75
78
|
const maxConcurrency = Number(process.env.MAX_CONCURRENCY ?? 4)
|
|
79
|
+
const runDir = process.env.RUN_DIR ?? '.runs/humaneval-official-gepa'
|
|
80
|
+
if (process.env.DRYRUN) {
|
|
81
|
+
console.log(
|
|
82
|
+
`DRYRUN: imports OK (improve=${typeof improve}, officialGepa=${typeof officialGepa})`,
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
const workerPricing = requiredTokenPricing(process.env, 'WORKER')
|
|
87
|
+
const optimizer = officialOptimizerModel({
|
|
88
|
+
env: process.env,
|
|
89
|
+
model: reflectModel,
|
|
90
|
+
baseUrl: reflectBase,
|
|
91
|
+
apiKey: reflectKey,
|
|
92
|
+
maxCostUsd: maxProposerCostUsd,
|
|
93
|
+
maxOutputTokensPerRequest: reflectMaxTokens,
|
|
94
|
+
})
|
|
76
95
|
|
|
77
|
-
//
|
|
96
|
+
// All three partitions are disjoint slices of the harder middle band.
|
|
78
97
|
const train = await loadHumanEval(trainN, offset)
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
98
|
+
const selection = await loadHumanEval(selectionN, offset + trainN)
|
|
99
|
+
const testCases = await loadHumanEval(testN, offset + trainN + selectionN)
|
|
100
|
+
const byId = new Map<string, HumanEvalTask>(
|
|
101
|
+
[...train, ...selection, ...testCases].map((t) => [t.taskId, t]),
|
|
102
|
+
)
|
|
82
103
|
|
|
83
|
-
console.log('
|
|
84
|
-
console.log(`worker=${workerModel}
|
|
104
|
+
console.log('=== HumanEval prompt optimization with official GEPA ===')
|
|
105
|
+
console.log(`worker=${workerModel} reflect=${reflectModel} base=${base}`)
|
|
85
106
|
console.log(`train=[${train.map((t) => t.taskId).join(', ')}]`)
|
|
86
|
-
console.log(`
|
|
87
|
-
console.log(`
|
|
88
|
-
console.log(
|
|
107
|
+
console.log(`selection=[${selection.map((t) => t.taskId).join(', ')}]`)
|
|
108
|
+
console.log(`test=[${testCases.map((t) => t.taskId).join(', ')}]`)
|
|
109
|
+
console.log(`maxEvaluations=${maxEvaluations} maxProposerCostUsd=${maxProposerCostUsd} offset=${offset} maxTokens=${workerMaxTokens}`)
|
|
110
|
+
console.log(`runDir=${runDir}\n`)
|
|
89
111
|
|
|
90
112
|
const stats = { n: 0 }
|
|
91
|
-
const agent = async (
|
|
92
|
-
const instr =
|
|
113
|
+
const agent = async (candidate: ReadonlyAgentProfile, scenario: Scenario, ctx: DispatchContext): Promise<string | null> => {
|
|
114
|
+
const instr = candidate.prompt?.systemPrompt
|
|
115
|
+
if (instr === undefined) throw new Error('agent: candidate profile has no system prompt')
|
|
93
116
|
const t = byId.get(scenario.id)
|
|
94
117
|
if (!t) throw new Error(`agent: unknown scenario ${scenario.id}`)
|
|
95
118
|
const prompt = `${instr}\n\n\`\`\`python\n${t.prompt}\`\`\``
|
|
96
119
|
const t0 = Date.now()
|
|
97
|
-
const
|
|
98
|
-
|
|
120
|
+
const paid = await ctx.cost.runPaidCall({
|
|
121
|
+
channel: 'agent',
|
|
122
|
+
actor: 'humaneval-worker',
|
|
123
|
+
model: workerModel,
|
|
124
|
+
execute: () => complete(base, key, workerModel, prompt, workerMaxTokens),
|
|
125
|
+
receipt: (result) => {
|
|
126
|
+
const usageUnknown = result.tokIn === 0 && result.tokOut === 0
|
|
127
|
+
return {
|
|
128
|
+
model: workerModel,
|
|
129
|
+
inputTokens: result.tokIn,
|
|
130
|
+
outputTokens: result.tokOut,
|
|
131
|
+
customTokenPricing: workerPricing,
|
|
132
|
+
...(usageUnknown ? { usageUnknown: true } : {}),
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
if (!paid.succeeded) throw paid.error
|
|
137
|
+
const r = paid.value
|
|
99
138
|
const hasText = r.text.trim().length > 0
|
|
100
|
-
ctx.cost.observe(zeroUsage && hasText ? Math.max(r.usd, 0.0001) : r.usd, workerModel)
|
|
101
|
-
ctx.cost.observeTokens(
|
|
102
|
-
zeroUsage && hasText ? { input: Math.max(r.tokIn, 1), output: Math.max(r.tokOut, 1) } : { input: r.tokIn, output: r.tokOut },
|
|
103
|
-
)
|
|
104
139
|
stats.n += 1
|
|
105
140
|
const codeLen = extractCode(r.text).length
|
|
106
141
|
console.log(` [agent] ${scenario.id} instr=${instr.length}c code=${codeLen}b tok=in:${r.tokIn}/out:${r.tokOut} ${Math.round((Date.now() - t0) / 1000)}s`)
|
|
107
142
|
return hasText ? r.text : null
|
|
108
143
|
}
|
|
109
144
|
|
|
110
|
-
const judge: JudgeConfig<string, Scenario> = {
|
|
145
|
+
const judge: JudgeConfig<string | null, Scenario> = {
|
|
111
146
|
name: 'humaneval-docker',
|
|
112
147
|
dimensions: [{ key: 'pass', description: 'the completed function passes its hidden unit tests (deterministic Docker checker)' }],
|
|
113
148
|
async score({ artifact, scenario }) {
|
|
@@ -137,49 +172,59 @@ async function main(): Promise<void> {
|
|
|
137
172
|
}
|
|
138
173
|
|
|
139
174
|
const profile: AgentProfile = { name: 'hev-solver', prompt: { systemPrompt: SEED_INSTRUCTION } }
|
|
140
|
-
const
|
|
141
|
-
llm: { baseUrl: reflectBase, apiKey: reflectKey },
|
|
142
|
-
model: reflectModel,
|
|
143
|
-
target:
|
|
144
|
-
'the instruction/system prompt strategy for a SMALL model completing Python functions to pass hidden unit tests. ' +
|
|
145
|
-
'Propose SUBSTANTIALLY different strategies, not wording tweaks: e.g. require the model to first reason step-by-step ' +
|
|
146
|
-
'about the algorithm and edge cases (empty inputs, off-by-one, boundary values, types) in a brief plan or comments ' +
|
|
147
|
-
'BEFORE writing the code; provide a short worked example; or add an explicit self-check against the docstring. ' +
|
|
148
|
-
'Bold rewrites that change model BEHAVIOR beat cosmetic edits.',
|
|
149
|
-
maxTokens: reflectMaxTokens,
|
|
150
|
-
temperature: 0.7,
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
const scenarios: Scenario[] = allIds.map((id) => ({ id, kind: 'humaneval' }))
|
|
154
|
-
const holdoutScenarios: Scenario[] = holdout.map((t) => ({ id: t.taskId, kind: 'humaneval' }))
|
|
175
|
+
const scenario = (task: HumanEvalTask): Scenario => ({ id: task.taskId, kind: 'humaneval' })
|
|
155
176
|
|
|
156
|
-
const out = await improve(profile,
|
|
177
|
+
const out = await improve(profile, {
|
|
157
178
|
surface: 'prompt',
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
executionRef: canonicalCandidateDigest({
|
|
180
|
+
callback: 'bench/hev-improve',
|
|
181
|
+
model: workerModel,
|
|
182
|
+
endpoint: new URL(base).origin,
|
|
183
|
+
maxTokens: workerMaxTokens,
|
|
184
|
+
checker: 'local-python',
|
|
185
|
+
}),
|
|
186
|
+
method: officialGepa<Scenario, string | null>({
|
|
187
|
+
objective:
|
|
188
|
+
'Improve the complete instruction for a small model that writes Python functions which pass hidden unit tests.',
|
|
189
|
+
background:
|
|
190
|
+
'Prefer behavioral strategies over wording changes. Address algorithm choice, edge cases, boundary values, type behavior, and self-checking. Return only the complete instruction.',
|
|
191
|
+
recipe: {
|
|
192
|
+
kind: 'engine',
|
|
193
|
+
run: {
|
|
194
|
+
engine: 'gepa',
|
|
195
|
+
maxEvaluations,
|
|
196
|
+
maxProposerCostUsd,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
optimizer,
|
|
200
|
+
resume: 'if-compatible',
|
|
201
|
+
trustResumeState: true,
|
|
202
|
+
describeScenario: (item) => ({ prompt: byId.get(item.id)?.prompt ?? item.id }),
|
|
203
|
+
}),
|
|
204
|
+
trainScenarios: train.map(scenario),
|
|
205
|
+
selectionScenarios: selection.map(scenario),
|
|
206
|
+
testScenarios: testCases.map(scenario),
|
|
207
|
+
judges: [judge],
|
|
162
208
|
agent,
|
|
163
209
|
expectUsage: 'warn',
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
210
|
+
maxConcurrency,
|
|
211
|
+
reps: 1,
|
|
212
|
+
runDir,
|
|
213
|
+
optimizationRunOptions: {
|
|
214
|
+
expectUsage: 'warn',
|
|
215
|
+
maxConcurrency,
|
|
216
|
+
reps: 1,
|
|
217
|
+
},
|
|
171
218
|
})
|
|
172
219
|
|
|
173
|
-
|
|
174
|
-
console.log(
|
|
175
|
-
console.log(`
|
|
176
|
-
console.log(`
|
|
177
|
-
console.log(`
|
|
178
|
-
console.log(`
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
console.log(`winner instruction:\n${String((out.raw.winner as { surface?: unknown }).surface).slice(0, 1200)}`)
|
|
182
|
-
}
|
|
220
|
+
assertCompleteCost('humaneval official GEPA run', out.cost)
|
|
221
|
+
console.log('\n=== RESULT ===')
|
|
222
|
+
console.log(`decision=${out.decision} lift=${out.lift} interval=[${out.liftInterval.low}, ${out.liftInterval.high}]`)
|
|
223
|
+
console.log(`baseline test pass-rate=${out.raw.best.baselineComposite}`)
|
|
224
|
+
console.log(`winner test pass-rate=${out.raw.best.winnerComposite}`)
|
|
225
|
+
console.log(`test scenarios=${JSON.stringify(out.raw.best.scenarioScores)}`)
|
|
226
|
+
console.log(`cost=${JSON.stringify(out.cost)}`)
|
|
227
|
+
console.log(`winner instruction:\n${String(out.candidate.value).slice(0, 2000)}`)
|
|
183
228
|
}
|
|
184
229
|
|
|
185
230
|
main().catch((e) => {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function requiredNonNegativeNumber(
|
|
2
|
+
env: NodeJS.ProcessEnv,
|
|
3
|
+
name: string,
|
|
4
|
+
): number {
|
|
5
|
+
const raw = env[name]
|
|
6
|
+
if (raw === undefined || raw.trim() === '') {
|
|
7
|
+
throw new Error(`env ${name} is required`)
|
|
8
|
+
}
|
|
9
|
+
const value = Number(raw)
|
|
10
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
11
|
+
throw new Error(`env ${name} must be a finite non-negative number`)
|
|
12
|
+
}
|
|
13
|
+
return value
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function positiveInteger(
|
|
17
|
+
env: NodeJS.ProcessEnv,
|
|
18
|
+
name: string,
|
|
19
|
+
fallback: number,
|
|
20
|
+
): number {
|
|
21
|
+
const value = Number(env[name] ?? fallback)
|
|
22
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
23
|
+
throw new Error(`env ${name} must be a positive integer`)
|
|
24
|
+
}
|
|
25
|
+
return value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function requiredTokenPricing(
|
|
29
|
+
env: NodeJS.ProcessEnv,
|
|
30
|
+
prefix: string,
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
inputUsdPerMillion: requiredNonNegativeNumber(
|
|
34
|
+
env,
|
|
35
|
+
`${prefix}_INPUT_USD_PER_MILLION`,
|
|
36
|
+
),
|
|
37
|
+
cachedInputUsdPerMillion: requiredNonNegativeNumber(
|
|
38
|
+
env,
|
|
39
|
+
`${prefix}_CACHED_INPUT_USD_PER_MILLION`,
|
|
40
|
+
),
|
|
41
|
+
cacheWriteUsdPerMillion: requiredNonNegativeNumber(
|
|
42
|
+
env,
|
|
43
|
+
`${prefix}_CACHE_WRITE_USD_PER_MILLION`,
|
|
44
|
+
),
|
|
45
|
+
outputUsdPerMillion: requiredNonNegativeNumber(
|
|
46
|
+
env,
|
|
47
|
+
`${prefix}_OUTPUT_USD_PER_MILLION`,
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function officialOptimizerModel(options: {
|
|
53
|
+
env: NodeJS.ProcessEnv
|
|
54
|
+
model: string
|
|
55
|
+
baseUrl: string
|
|
56
|
+
apiKey: string
|
|
57
|
+
maxCostUsd: number
|
|
58
|
+
maxOutputTokensPerRequest: number
|
|
59
|
+
envPrefix?: string
|
|
60
|
+
}) {
|
|
61
|
+
const { env } = options
|
|
62
|
+
const envPrefix = options.envPrefix ?? 'REFLECT'
|
|
63
|
+
return {
|
|
64
|
+
model: options.model,
|
|
65
|
+
baseUrl: options.baseUrl,
|
|
66
|
+
apiKey: options.apiKey,
|
|
67
|
+
budget: {
|
|
68
|
+
maxCostUsd: options.maxCostUsd,
|
|
69
|
+
maxRequests: positiveInteger(env, `${envPrefix}_MAX_REQUESTS`, 100),
|
|
70
|
+
maxRequestBytes: positiveInteger(env, `${envPrefix}_MAX_REQUEST_BYTES`, 2_000_000),
|
|
71
|
+
maxResponseBytes: positiveInteger(env, `${envPrefix}_MAX_RESPONSE_BYTES`, 2_000_000),
|
|
72
|
+
maxOutputTokensPerRequest: options.maxOutputTokensPerRequest,
|
|
73
|
+
requestTimeoutMs: positiveInteger(env, `${envPrefix}_REQUEST_TIMEOUT_MS`, 300_000),
|
|
74
|
+
pricing: requiredTokenPricing(env, envPrefix),
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function assertCompleteCost(
|
|
80
|
+
label: string,
|
|
81
|
+
cost: { accountingComplete: boolean; incompleteReasons: readonly string[] },
|
|
82
|
+
): void {
|
|
83
|
+
if (cost.accountingComplete) return
|
|
84
|
+
const reasons =
|
|
85
|
+
cost.incompleteReasons.length > 0
|
|
86
|
+
? cost.incompleteReasons.join('; ')
|
|
87
|
+
: 'no incomplete reason was recorded'
|
|
88
|
+
throw new Error(`${label}: cost accounting is incomplete: ${reasons}`)
|
|
89
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
assertCompleteCost,
|
|
4
|
+
officialOptimizerModel,
|
|
5
|
+
requiredTokenPricing,
|
|
6
|
+
} from './official-optimizer-config.mts'
|
|
7
|
+
|
|
8
|
+
const pricingEnv = {
|
|
9
|
+
OPT_INPUT_USD_PER_MILLION: '1',
|
|
10
|
+
OPT_CACHED_INPUT_USD_PER_MILLION: '0.1',
|
|
11
|
+
OPT_CACHE_WRITE_USD_PER_MILLION: '1.25',
|
|
12
|
+
OPT_OUTPUT_USD_PER_MILLION: '5',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('official optimizer configuration', () => {
|
|
16
|
+
it('builds a bounded model configuration from an arbitrary environment prefix', () => {
|
|
17
|
+
const model = officialOptimizerModel({
|
|
18
|
+
env: {
|
|
19
|
+
...pricingEnv,
|
|
20
|
+
OPT_MAX_REQUESTS: '7',
|
|
21
|
+
OPT_MAX_REQUEST_BYTES: '1000',
|
|
22
|
+
OPT_MAX_RESPONSE_BYTES: '2000',
|
|
23
|
+
OPT_REQUEST_TIMEOUT_MS: '3000',
|
|
24
|
+
},
|
|
25
|
+
envPrefix: 'OPT',
|
|
26
|
+
model: 'test-model',
|
|
27
|
+
baseUrl: 'http://127.0.0.1:8080/v1',
|
|
28
|
+
apiKey: 'test-key',
|
|
29
|
+
maxCostUsd: 2,
|
|
30
|
+
maxOutputTokensPerRequest: 4000,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
expect(model).toEqual({
|
|
34
|
+
model: 'test-model',
|
|
35
|
+
baseUrl: 'http://127.0.0.1:8080/v1',
|
|
36
|
+
apiKey: 'test-key',
|
|
37
|
+
budget: {
|
|
38
|
+
maxCostUsd: 2,
|
|
39
|
+
maxRequests: 7,
|
|
40
|
+
maxRequestBytes: 1000,
|
|
41
|
+
maxResponseBytes: 2000,
|
|
42
|
+
maxOutputTokensPerRequest: 4000,
|
|
43
|
+
requestTimeoutMs: 3000,
|
|
44
|
+
pricing: {
|
|
45
|
+
inputUsdPerMillion: 1,
|
|
46
|
+
cachedInputUsdPerMillion: 0.1,
|
|
47
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
48
|
+
outputUsdPerMillion: 5,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('requires every token price instead of inventing cost data', () => {
|
|
55
|
+
expect(() =>
|
|
56
|
+
requiredTokenPricing(
|
|
57
|
+
{
|
|
58
|
+
...pricingEnv,
|
|
59
|
+
OPT_OUTPUT_USD_PER_MILLION: undefined,
|
|
60
|
+
},
|
|
61
|
+
'OPT',
|
|
62
|
+
),
|
|
63
|
+
).toThrow('env OPT_OUTPUT_USD_PER_MILLION is required')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('rejects invalid request limits before constructing the optimizer', () => {
|
|
67
|
+
expect(() =>
|
|
68
|
+
officialOptimizerModel({
|
|
69
|
+
env: { ...pricingEnv, OPT_MAX_REQUESTS: '0' },
|
|
70
|
+
envPrefix: 'OPT',
|
|
71
|
+
model: 'test-model',
|
|
72
|
+
baseUrl: 'http://127.0.0.1:8080/v1',
|
|
73
|
+
apiKey: 'test-key',
|
|
74
|
+
maxCostUsd: 2,
|
|
75
|
+
maxOutputTokensPerRequest: 4000,
|
|
76
|
+
}),
|
|
77
|
+
).toThrow('env OPT_MAX_REQUESTS must be a positive integer')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('rejects incomplete cost records', () => {
|
|
81
|
+
expect(() =>
|
|
82
|
+
assertCompleteCost('official optimizer', {
|
|
83
|
+
accountingComplete: false,
|
|
84
|
+
incompleteReasons: ['provider omitted usage'],
|
|
85
|
+
}),
|
|
86
|
+
).toThrow('official optimizer: cost accounting is incomplete: provider omitted usage')
|
|
87
|
+
})
|
|
88
|
+
})
|
package/src/profiles.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* "operator type" or "analyst type" — there are profiles + the operator toolbox they use to manage
|
|
6
6
|
* each other (in-process via the Scope, in a sandbox via the same verbs exposed as MCP tools).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Complete agent-eval methods optimize these profiles over explicit train,
|
|
9
|
+
* selection, and final-test partitions.
|
|
10
10
|
* The trace-analyst's findings are the optimizer's input — the loop improves the profile that the
|
|
11
11
|
* findings say is weak.
|
|
12
12
|
*/
|
|
@@ -285,31 +285,35 @@ describe('backfillSweArena', () => {
|
|
|
285
285
|
expect(stats.workerSessionsJoined).toBe(2)
|
|
286
286
|
})
|
|
287
287
|
|
|
288
|
-
it(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
it(
|
|
289
|
+
'marks a session with no readable parts as an incomplete gap line',
|
|
290
|
+
async () => {
|
|
291
|
+
await buildFixtureTree()
|
|
292
|
+
await buildFixtureDb()
|
|
293
|
+
const { DatabaseSync } = await import('node:sqlite')
|
|
294
|
+
const db = new DatabaseSync(dbPath)
|
|
295
|
+
// Session row survives; its message parts do not.
|
|
296
|
+
db.exec("DELETE FROM part; DELETE FROM message")
|
|
297
|
+
db.close()
|
|
296
298
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
299
|
+
const { lines, stats } = await backfillSweArena(outDir, {
|
|
300
|
+
opencodeDb: dbPath,
|
|
301
|
+
claudeProjectsDir: projectsDir,
|
|
302
|
+
})
|
|
303
|
+
const joinedCwd = lines.filter((l) => l.role === 'worker' && l.outcome.metrics.has_session === true)
|
|
304
|
+
expect(joinedCwd.length).toBeGreaterThan(0)
|
|
305
|
+
for (const worker of joinedCwd) {
|
|
306
|
+
expect(worker.messages).toHaveLength(0)
|
|
307
|
+
expect(worker.outcome.is_completed).toBe(false)
|
|
308
|
+
expect(worker.provenance.gap).toMatch(/no readable message parts/)
|
|
309
|
+
}
|
|
310
|
+
// The store-integrity failure is counted apart from the cwd-join failure.
|
|
311
|
+
expect(stats.workerSessionsEmpty).toBe(2)
|
|
312
|
+
expect(stats.workerCwdsMissed).toBe(1)
|
|
313
|
+
expect(stats.workerSessionsJoined).toBe(0)
|
|
314
|
+
},
|
|
315
|
+
15_000,
|
|
316
|
+
)
|
|
313
317
|
|
|
314
318
|
it('fails loud on a cell cache that carries no scenarioId/rep identity', async () => {
|
|
315
319
|
await buildFixtureTree()
|