@tangle-network/agent-bench 0.8.10 → 0.8.15

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +14 -0
  3. package/dist/adapters.js +2 -2
  4. package/dist/benchmarks/appworld.js +1 -1
  5. package/dist/benchmarks/appworld.js.map +1 -1
  6. package/dist/benchmarks/cadbench.js +1 -1
  7. package/dist/benchmarks/cadgenbench.js +1 -1
  8. package/dist/benchmarks/finresearchbench.js +1 -1
  9. package/dist/benchmarks/finsearchcomp.js +1 -1
  10. package/dist/benchmarks/frames.js +1 -1
  11. package/dist/benchmarks/simpleqa.js +1 -1
  12. package/dist/benchmarks/trata-hedge.js +1 -1
  13. package/dist/{cadbench-BLSyxR1N.js → cadbench-BRF-59Mt.js} +2 -2
  14. package/dist/{cadbench-BLSyxR1N.js.map → cadbench-BRF-59Mt.js.map} +1 -1
  15. package/dist/{cadgenbench-x2OFkf8y.js → cadgenbench-DXtGkuW3.js} +2 -2
  16. package/dist/{cadgenbench-x2OFkf8y.js.map → cadgenbench-DXtGkuW3.js.map} +1 -1
  17. package/dist/index.js +10 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/{router-turn-C2wMiDoo.js → router-turn-uTYO6KQ1.js} +10 -8
  20. package/dist/router-turn-uTYO6KQ1.js.map +1 -0
  21. package/package.json +8 -7
  22. package/src/agent-graphs-improve.mts +1 -1
  23. package/src/atom-mcp-e2e.mts +1 -1
  24. package/src/benchmarks/appworld.ts +1 -1
  25. package/src/commit0-gate.mts +1 -1
  26. package/src/humaneval-repair-gate.mts +1 -1
  27. package/src/mcp-mount-probe.mts +1 -1
  28. package/src/quant-arena/quant-loop.mts +1 -1
  29. package/src/router-turn.ts +10 -1
  30. package/src/run-benchmarks.ts +12 -8
  31. package/src/swe-arena/arms.ts +1 -1
  32. package/dist/router-turn-C2wMiDoo.js.map +0 -1
  33. package/src/aec-gate.mts +0 -238
  34. package/src/atom-humaneval.mts +0 -218
  35. package/src/david-attribution.mts +0 -97
  36. package/src/david-goliath.mts +0 -168
  37. package/src/decoder-live.mts +0 -133
  38. package/src/diverse-gate.mjs +0 -112
  39. package/src/hev-eval.mts +0 -101
  40. package/src/hev-improve.mts +0 -245
  41. package/src/humaneval-object-ablation.mts +0 -239
  42. package/src/trata-gate.mts +0 -243
@@ -1,239 +0,0 @@
1
- /**
2
- * OBJECT-OF-IMPROVEMENT ablation on HumanEval — the head-on test of the claim our
3
- * whole self-improvement line rests on: at EQUAL budget, does adding CAPABILITY
4
- * (a real code-execution tool) beat adding IMPROVER-cleverness (blind prompt
5
- * self-refinement)? Both arms get the same model, the same K rounds per task, the
6
- * same held-out tasks. The ONLY difference is what the budget buys:
7
- *
8
- * IMPROVER arm — K rounds of "critique your own function and rewrite it",
9
- * with NO execution. The classic reflect-without-a-tool loop.
10
- * CAPABILITY arm — K rounds WITH a `run_python` tool: the agent executes its
11
- * function on inputs it chooses, sees real output/errors, fixes.
12
- *
13
- * Grading is a DETERMINISTIC hidden test (never shown): the task's own `check`.
14
- * If the capability arm wins the held-out pass rate, the object of improvement
15
- * (what you can DO) dominates the improver (how cleverly you rewrite) — the
16
- * finding the prompt-only self-improvement runs kept nulling on.
17
- *
18
- * Fast by construction: HumanEval tasks are tiny, graded in an isolated Python
19
- * container with a hard timeout — seconds per task. Paired
20
- * McNemar over the per-task pass/fail difference gives the significance.
21
- *
22
- * Run from cwd=bench: env WORKER_MODEL=deepseek-v4-flash N=60 K=3 \
23
- * REPS=2 node_modules/.bin/tsx src/humaneval-object-ablation.mts
24
- */
25
- import {
26
- loadHumanEval,
27
- extractCode,
28
- runPythonProgram,
29
- type HumanEvalTask,
30
- } from './benchmarks/humaneval'
31
- import { runBenchRouterTurn } from './router-turn'
32
-
33
- const ROUTER = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
34
- const KEY = process.env.TANGLE_API_KEY
35
- if (!KEY) throw new Error('TANGLE_API_KEY required')
36
- const ROUTER_KEY = KEY
37
- const MODEL = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
38
- const N = Number(process.env.N ?? 60)
39
- const OFFSET = Number(process.env.OFFSET ?? 0)
40
- const K = Number(process.env.K ?? 3) // rounds/budget per task (equal for both arms)
41
- const REPS = Number(process.env.REPS ?? 2)
42
- const CONC = Number(process.env.CONCURRENCY ?? 6)
43
- const EXEC_TIMEOUT = Number(process.env.EXEC_TIMEOUT_MS ?? 8000)
44
-
45
- interface ChatMsg extends Record<string, unknown> { role: string; content: string; tool_calls?: unknown; tool_call_id?: string; name?: string }
46
- interface Tool { type: 'function'; function: { name: string; description: string; parameters: unknown } }
47
-
48
- async function router(messages: ChatMsg[], tools?: Tool[]): Promise<{ content: string; toolCalls: { id: string; name: string; args: Record<string, unknown> }[] }> {
49
- for (let attempt = 0; ; attempt++) {
50
- try {
51
- const system = messages.find((message) => message.role === 'system')?.content
52
- const result = await runBenchRouterTurn(
53
- {
54
- routerBaseUrl: ROUTER,
55
- routerKey: ROUTER_KEY,
56
- profile: {
57
- name: 'humaneval-object-ablation-worker',
58
- harness: 'cli-base',
59
- model: {
60
- provider: 'tangle-router',
61
- default: MODEL,
62
- metadata: {
63
- temperature: 0.4,
64
- ...(tools ? { toolChoice: 'auto' } : {}),
65
- },
66
- },
67
- ...(system ? { prompt: { systemPrompt: system } } : {}),
68
- ...(tools
69
- ? { tools: Object.fromEntries(tools.map((tool) => [tool.function.name, true])) }
70
- : {}),
71
- },
72
- ...(tools ? { tools } : {}),
73
- timeoutMs: Number(process.env.LLM_TIMEOUT_MS ?? 60_000),
74
- },
75
- { messages: messages.filter((message) => message.role !== 'system') },
76
- )
77
- const toolCalls = result.toolCalls.map((call) => {
78
- if (call.id === undefined) {
79
- throw new Error(`router tool call '${call.name}' omitted its required id`)
80
- }
81
- let args: Record<string, unknown> = {}
82
- try {
83
- args = JSON.parse(call.arguments) as Record<string, unknown>
84
- } catch {
85
- // Keep the empty argument object; the tool returns a useful error.
86
- }
87
- return { id: call.id, name: call.name, args }
88
- })
89
- return { content: result.finalText, toolCalls }
90
- } catch (error) {
91
- const message = error instanceof Error ? error.message : String(error)
92
- const status = Number(/router (\d+)/.exec(message)?.[1])
93
- const transient =
94
- !Number.isFinite(status) || [408, 429, 500, 502, 503, 504, 520, 522, 524].includes(status)
95
- if (!transient || attempt >= 5) throw error
96
- await sleep(800 * 2 ** attempt)
97
- }
98
- }
99
- }
100
- const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
101
- /** Run model-written Python in the shared networkless, resource-capped container. */
102
- async function runPython(program: string): Promise<{ stdout: string; stderr: string; ok: boolean }> {
103
- const result = await runPythonProgram(program, EXEC_TIMEOUT)
104
- return {
105
- stdout: result.stdout.slice(0, 2000),
106
- stderr: result.stderr.slice(0, 2000),
107
- ok: result.exitCode === 0,
108
- }
109
- }
110
-
111
- /** The HIDDEN judge: candidate full function + the task's own check. Never shown. */
112
- async function judge(task: HumanEvalTask, candidate: string): Promise<boolean> {
113
- if (!candidate.trim()) return false
114
- const program = `${candidate}\n\n${task.test}\n\ncheck(${task.entryPoint})\nprint("PASS")\n`
115
- const r = await runPython(program)
116
- return r.ok && r.stdout.includes('PASS')
117
- }
118
-
119
- const SYSTEM = 'You are an expert Python programmer. Output the COMPLETE function definition (signature + body, plus any imports) in a single ```python block. No tests, no prose outside the block.'
120
- const userPrompt = (t: HumanEvalTask) => `Complete this function:\n\n\`\`\`python\n${t.prompt}\`\`\``
121
-
122
- /** IMPROVER arm: K rounds of blind self-refinement — no execution, just "review and rewrite". */
123
- async function improverArm(task: HumanEvalTask): Promise<string> {
124
- const messages: ChatMsg[] = [{ role: 'system', content: SYSTEM }, { role: 'user', content: userPrompt(task) }]
125
- let code = ''
126
- for (let round = 0; round < K; round++) {
127
- const { content } = await router(messages)
128
- code = extractCode(content) || content
129
- if (round < K - 1) {
130
- messages.push({ role: 'assistant', content })
131
- messages.push({ role: 'user', content: 'Carefully review your function for correctness bugs and edge cases. If it can be improved, output the corrected COMPLETE function in a python block; if it is already correct, output it again unchanged.' })
132
- }
133
- }
134
- return code
135
- }
136
-
137
- const RUN_TOOL: Tool = { type: 'function', function: { name: 'run_python', description: 'Execute a Python snippet and return its stdout/stderr. Use it to test your function on example inputs from the docstring before finalizing.', parameters: { type: 'object', properties: { code: { type: 'string', description: 'python source to run' } }, required: ['code'] } } }
138
-
139
- /** CAPABILITY arm: K rounds WITH a real code-execution tool — write, run, see real output, fix. */
140
- async function capabilityArm(task: HumanEvalTask): Promise<string> {
141
- const messages: ChatMsg[] = [
142
- { role: 'system', content: `${SYSTEM}\nYou have a run_python tool: test your function on the docstring's example inputs before giving your final answer. Fix any failures you observe.` },
143
- { role: 'user', content: userPrompt(task) },
144
- ]
145
- let code = ''
146
- // (K-1) tool-exploration rounds + 1 forced final answer = K calls total, matching
147
- // the improver arm's K blind-refine rounds (equal budget).
148
- for (let round = 0; round < Math.max(1, K - 1); round++) {
149
- const { content, toolCalls } = await router(messages, [RUN_TOOL])
150
- if (content && extractCode(content)) code = extractCode(content)
151
- messages.push({ role: 'assistant', content: content || '', ...(toolCalls.length ? { tool_calls: toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: JSON.stringify(t.args) } })) } : {}) })
152
- if (toolCalls.length) {
153
- for (const tc of toolCalls) {
154
- const snippet = String(tc.args.code ?? '')
155
- const r = await runPython(snippet)
156
- messages.push({ role: 'tool', tool_call_id: tc.id, name: tc.name, content: `stdout:\n${r.stdout}\nstderr:\n${r.stderr}\nexit_ok=${r.ok}` })
157
- }
158
- } else {
159
- messages.push({ role: 'user', content: 'Test your function with run_python on the docstring examples before finalizing.' })
160
- }
161
- }
162
- // FAIR FINAL ANSWER: the tool rounds are exploration; force one no-tool call to
163
- // emit the complete function. Without this, an agent that ends mid-tool-call
164
- // yields empty code and is unfairly scored 0 (an extraction artifact, not a
165
- // real "the tool hurt" signal). Only override if it produces a real block.
166
- {
167
- messages.push({ role: 'user', content: 'Now output your FINAL complete function in a single ```python block, no tools, no prose.' })
168
- const { content } = await router(messages)
169
- const finalCode = extractCode(content)
170
- if (finalCode.trim()) code = finalCode
171
- }
172
- return code
173
- }
174
-
175
- async function pool<T, R>(items: T[], limit: number, fn: (t: T, i: number) => Promise<R>): Promise<R[]> {
176
- const out = new Array<R>(items.length)
177
- let next = 0
178
- await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => {
179
- while (next < items.length) { const i = next++; out[i] = await fn(items[i]!, i) }
180
- }))
181
- return out
182
- }
183
-
184
- // McNemar exact (paired): b = capability-only wins, c = improver-only wins.
185
- function mcnemarP(b: number, c: number): number {
186
- const n = b + c; if (n === 0) return 1
187
- const k = Math.min(b, c)
188
- const lf = (x: number) => { let s = 0; for (let i = 2; i <= x; i++) s += Math.log(i); return s }
189
- let tail = 0; for (let i = 0; i <= k; i++) tail += Math.exp(lf(n) - lf(i) - lf(n - i) - n * Math.log(2))
190
- return Math.min(1, 2 * tail)
191
- }
192
-
193
- async function main(): Promise<void> {
194
- if (['1', 'true'].includes((process.env.SMOKE ?? '').toLowerCase())) { console.error('SMOKE ok: humaneval-object-ablation loaded'); return }
195
- const tasks = await loadHumanEval(N, OFFSET)
196
- console.error(`=== OBJECT-OF-IMPROVEMENT ablation · HumanEval n=${tasks.length} (offset ${OFFSET}) · model=${MODEL} · K=${K} rounds · reps=${REPS} · equal budget ===`)
197
- // self-check the local grader on the gold solution of task 0 (never shown to the model)
198
- if (tasks[0]?.canonicalSolution) {
199
- const gold = `${tasks[0].prompt}${tasks[0].canonicalSolution}`
200
- console.error(` grader self-check (gold passes): ${await judge(tasks[0], gold)}`)
201
- }
202
-
203
- // per (task, rep): run both arms; record pass/fail.
204
- const units = tasks.flatMap((task) => Array.from({ length: REPS }, (_, rep) => ({ task, rep })))
205
- let done = 0
206
- let capEmpty = 0
207
- const results = await pool(units, CONC, async ({ task }) => {
208
- // Per-unit resilience: a transient model failure (e.g. a weak model emitting a
209
- // malformed tool call → router 400) scores that arm 0 for this unit, never
210
- // crashes the whole run. Both arms wrapped identically so neither is favored.
211
- const safe = async (fn: () => Promise<string>) => { try { return await fn() } catch { return '' } }
212
- const [impCode, capCode] = await Promise.all([safe(() => improverArm(task)), safe(() => capabilityArm(task))])
213
- const [imp, cap] = await Promise.all([judge(task, impCode), judge(task, capCode)])
214
- done++
215
- // AUTOPSY: a capability-arm failure with EMPTY final code is an extraction
216
- // artifact (ended mid-tool-call, never emitted a final function), NOT a real
217
- // "the tool hurt" signal. Count it so the effect can be separated.
218
- if (!cap && !capCode.trim()) { capEmpty++; if (imp) console.error(` [cap-empty] ${task.taskId} (improver passed)`) }
219
- if (done % 20 === 0) console.error(` ${done}/${units.length} units`)
220
- return { id: task.taskId, imp, cap }
221
- })
222
- console.error(` capability-arm empty-final-code (artifact) count: ${capEmpty}/${results.length}`)
223
-
224
- const impPass = results.filter((r) => r.imp).length
225
- const capPass = results.filter((r) => r.cap).length
226
- const b = results.filter((r) => r.cap && !r.imp).length // capability-only wins
227
- const c = results.filter((r) => !r.cap && r.imp).length // improver-only wins
228
- const p = mcnemarP(b, c)
229
- const n = results.length
230
- const liftPp = ((capPass - impPass) / n) * 100
231
-
232
- console.log('')
233
- console.log('=== RESULT (held-out HumanEval, equal budget) ===')
234
- console.log(` IMPROVER (blind self-refine, no tool): ${impPass}/${n} = ${((impPass / n) * 100).toFixed(1)}%`)
235
- console.log(` CAPABILITY (self-built execution tool) : ${capPass}/${n} = ${((capPass / n) * 100).toFixed(1)}%`)
236
- console.log(` lift = ${liftPp >= 0 ? '+' : ''}${liftPp.toFixed(1)}pp paired McNemar: capability-only=${b} improver-only=${c} p=${p.toFixed(4)}`)
237
- console.log(` verdict: ${p < 0.05 && capPass > impPass ? 'CAPABILITY > IMPROVER (significant)' : p < 0.05 && impPass > capPass ? 'IMPROVER > CAPABILITY (significant)' : 'no significant difference'}`)
238
- }
239
- main().catch((e) => { console.error('MAIN:', e instanceof Error ? (e.stack ?? e.message) : e); process.exit(1) })
@@ -1,243 +0,0 @@
1
- /**
2
- * Trata hedge-bench gate — direct router completions (no sandbox/opencode).
3
- *
4
- * Trata is a text-analysis benchmark: each task is a financial analysis brief
5
- * over embedded source docs. The worker is a single router completion (not an
6
- * agentic loop), which is appropriate here: tasks are scored by an LLM judge on
7
- * theme coverage, not on code execution or tool use.
8
- *
9
- * Usage:
10
- * BENCH=trata-hedge WORKER_MODEL=deepseek-v4-flash JUDGE_MODEL=gemini-2.5-flash \
11
- * TRATA_BENCH_ROOT=/tmp/trata-hedge-bench N=10 CONCURRENCY=5 \
12
- * dotenvx run -f ~/company/devops/secrets/agent-state.env -- \
13
- * pnpm exec tsx bench/src/trata-gate.mts
14
- *
15
- * Key env vars:
16
- * BENCH adapter key (default: trata-hedge)
17
- * WORKER_MODEL model for the analysis completion (default: deepseek-v4-flash)
18
- * JUDGE_MODEL model for the 3-stage judge (default: gemini-2.5-pro)
19
- * TRATA_BENCH_ROOT path to cloned trata-hedge-bench (default: /tmp/trata-hedge-bench)
20
- * N how many tasks to run (default: 10; 0 = all 102)
21
- * CONCURRENCY parallel worker slots (default: 5)
22
- * IDS comma-separated task ids to run a specific subset
23
- * ROUTER_BASE router base URL (default: https://router.tangle.tools/v1)
24
- * CORPUS path to write JSONL run records (optional)
25
- *
26
- * Score is 0–4 / 4 = 0–100%; resolved = 4/4 (all themes + synthesis = sparse reward).
27
- * Partial credit (1-3) is reported in the score distribution.
28
- */
29
-
30
- import { appendFileSync } from 'node:fs'
31
- import { resolveAdapter } from './adapters'
32
- import type { BenchScore, BenchTask } from './benchmarks/types'
33
- import { runBenchRouterTurn } from './router-turn'
34
- import { runPool } from './run-pool'
35
-
36
- function must(name: string): string {
37
- const v = process.env[name]
38
- if (!v) throw new Error(`env ${name} is required`)
39
- return v
40
- }
41
-
42
- // GEPA-optimised surface — +8.6pp holdout lift over the minimal baseline on deepseek-v4-flash
43
- // (2 independent runs, CI [0, 12.5pp]; gate holds for caution, surface ships as default).
44
- const ANALYST_SYSTEM = process.env.SYSTEM_PROMPT ?? [
45
- 'You are a senior financial analyst producing a structured investment memo.',
46
- 'Begin your response with exactly "ANALYSIS:" on its own line (nothing before it), then write your full analysis.',
47
- 'Structure your analysis with clearly labeled sections for each distinct analytical theme',
48
- '(e.g., valuation, capital allocation, competitive dynamics, risk factors)',
49
- 'so that no major investment consideration is merged or omitted.',
50
- 'Benchmark the company explicitly against named sector peers, citing specific metrics such as',
51
- 'EV/EBITDA, P/E ratios, margin differentials, and growth premiums from the peer financial files.',
52
- 'Every factual claim must cite the filename (e.g., "per earnings_call/apo_q4_2025_earnings_call.txt").',
53
- 'Identify and verbatim-cite specific numerical targets from management guidance such as',
54
- 'earnings per share targets, margin percentages, growth rates, or AUM figures rather than paraphrasing approximately.',
55
- 'When evaluating capital allocation options, explicitly compute implied returns or internal rates of return (IRRs),',
56
- 'showing the arithmetic using price levels and targets from the source data.',
57
- 'Take a clear, decisive position — do not hedge with "it depends". Reconcile conflicting data points explicitly.',
58
- ].join(' ')
59
-
60
- async function workerComplete(
61
- task: BenchTask,
62
- cfg: { routerBaseUrl: string; routerKey: string; model: string; timeoutMs: number },
63
- ): Promise<{ answer: string; inputTokens: number; outputTokens: number; durationMs: number }> {
64
- const startedAt = Date.now()
65
- const result = await runBenchRouterTurn(
66
- {
67
- routerBaseUrl: cfg.routerBaseUrl,
68
- routerKey: cfg.routerKey,
69
- profile: {
70
- name: 'trata-financial-analyst',
71
- harness: 'cli-base',
72
- model: {
73
- provider: 'tangle-router',
74
- default: cfg.model,
75
- metadata: {
76
- temperature: 0,
77
- maxTokens: Number(process.env.WORKER_MAX_TOKENS ?? 4096),
78
- },
79
- },
80
- prompt: { systemPrompt: ANALYST_SYSTEM },
81
- },
82
- timeoutMs: cfg.timeoutMs,
83
- },
84
- task.prompt,
85
- )
86
- if (result.usage.tokensKnown === false) throw new Error('worker provider omitted token usage')
87
- return {
88
- answer: result.finalText,
89
- inputTokens: result.usage.input,
90
- outputTokens: result.usage.output,
91
- durationMs: Date.now() - startedAt,
92
- }
93
- }
94
-
95
- interface TaskResult {
96
- taskId: string
97
- answer: string
98
- score: BenchScore
99
- rawScore: number
100
- inputTokens: number
101
- outputTokens: number
102
- durationMs: number
103
- error?: string
104
- }
105
-
106
- async function runTask(
107
- task: BenchTask,
108
- workerCfg: { routerBaseUrl: string; routerKey: string; model: string; timeoutMs: number },
109
- adapter: ReturnType<typeof resolveAdapter>,
110
- ): Promise<TaskResult> {
111
- let answer = ''
112
- let inputTokens = 0
113
- let outputTokens = 0
114
- let durationMs = 0
115
- let error: string | undefined
116
-
117
- try {
118
- const result = await workerComplete(task, workerCfg)
119
- answer = result.answer
120
- inputTokens = result.inputTokens
121
- outputTokens = result.outputTokens
122
- durationMs = result.durationMs
123
- } catch (err) {
124
- error = err instanceof Error ? err.message : String(err)
125
- }
126
-
127
- const score = answer.trim()
128
- ? await adapter.judge(task, answer).catch((err) => ({
129
- resolved: false as const,
130
- score: 0,
131
- detail: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
132
- }))
133
- : { resolved: false as const, score: 0, detail: JSON.stringify({ error: error ?? 'empty answer' }) }
134
-
135
- const detail = score.detail ? (JSON.parse(score.detail) as { rawScore?: number }) : {}
136
- return {
137
- taskId: task.id,
138
- answer,
139
- score,
140
- rawScore: detail.rawScore ?? (score.resolved ? 4 : 0),
141
- inputTokens,
142
- outputTokens,
143
- durationMs,
144
- error,
145
- }
146
- }
147
-
148
- async function main(): Promise<void> {
149
- const benchName = process.env.BENCH ?? 'trata-hedge'
150
- const adapter = resolveAdapter(benchName)
151
- await adapter.preflight()
152
-
153
- const n = Number(process.env.N ?? 10)
154
- const concurrency = Number(process.env.CONCURRENCY ?? 5)
155
- const model = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
156
- const routerBase = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
157
- const routerKey = must('TANGLE_API_KEY')
158
- const corpusPath = process.env.CORPUS
159
- const idsEnv = process.env.IDS
160
-
161
- const loadOpts = idsEnv
162
- ? { ids: idsEnv.split(',').map((s) => s.trim()).filter(Boolean) }
163
- : n > 0
164
- ? { limit: n }
165
- : {}
166
- const tasks = await adapter.loadTasks(loadOpts)
167
- if (tasks.length === 0) throw new Error('no tasks loaded')
168
-
169
- const workerCfg = { routerBaseUrl: routerBase, routerKey, model, timeoutMs: 180_000 }
170
-
171
- console.log(
172
- `trata-gate: ${tasks.length} tasks | model=${model} | judge=${process.env.JUDGE_MODEL ?? 'gemini-2.5-pro'} | concurrency=${concurrency}`,
173
- )
174
-
175
- let done = 0
176
- const results: TaskResult[] = []
177
- await runPool(tasks, concurrency, async (task) => {
178
- const r = await runTask(task, workerCfg, adapter)
179
- results.push(r)
180
- done++
181
- const icon = r.rawScore === 4 ? '✓' : r.rawScore >= 2 ? '~' : '·'
182
- process.stdout.write(
183
- ` [${done}/${tasks.length}] ${task.id.slice(0, 60)}: ${icon} (${r.rawScore}/4)\n`,
184
- )
185
- return r
186
- })
187
-
188
- // Score distribution
189
- const byRaw = [0, 0, 0, 0, 0] // index = rawScore 0-4
190
- let totalInputTok = 0
191
- let totalOutputTok = 0
192
- let totalMs = 0
193
- for (const r of results) {
194
- byRaw[Math.min(r.rawScore, 4)]++
195
- totalInputTok += r.inputTokens
196
- totalOutputTok += r.outputTokens
197
- totalMs += r.durationMs
198
- }
199
- const resolved = byRaw[4] ?? 0
200
- const partial = (byRaw[2] ?? 0) + (byRaw[3] ?? 0)
201
- const n_ = results.length
202
- const pct = (x: number) => (n_ > 0 ? `${((x / n_) * 100).toFixed(1)}%` : 'n/a')
203
- const meanScore = n_ > 0 ? results.reduce((s, r) => s + r.score.score, 0) / n_ : 0
204
-
205
- console.log(`\n=== ${benchName} — trata-gate (n=${n_}, model=${model}) ===`)
206
- console.log(` resolved (4/4): ${pct(resolved)} (${resolved}/${n_})`)
207
- console.log(` partial credit (2-3): ${pct(partial)} (${partial}/${n_})`)
208
- console.log(` mean score (0..1): ${meanScore.toFixed(3)}`)
209
- console.log(` score dist [0..4]: ${byRaw.join(' | ')}`)
210
- console.log(
211
- ` tokens (in/out): ${(totalInputTok / 1000).toFixed(0)}k / ${(totalOutputTok / 1000).toFixed(0)}k`,
212
- )
213
- console.log(` wall time: ${(totalMs / 1000).toFixed(0)}s total`)
214
- console.log(` errors: ${results.filter((r) => r.error).length}`)
215
-
216
- if (corpusPath) {
217
- for (const r of results) {
218
- appendFileSync(
219
- corpusPath,
220
- JSON.stringify({
221
- benchmark: benchName,
222
- instanceId: r.taskId,
223
- condition: `blind@1`,
224
- model,
225
- resolved: r.score.resolved,
226
- rawScore: r.rawScore,
227
- score: r.score.score,
228
- detail: r.score.detail,
229
- inputTokens: r.inputTokens,
230
- outputTokens: r.outputTokens,
231
- durationMs: r.durationMs,
232
- ...(r.error ? { error: r.error } : {}),
233
- }) + '\n',
234
- )
235
- }
236
- console.log(`\ncorpus written → ${corpusPath}`)
237
- }
238
- }
239
-
240
- main().catch((err) => {
241
- console.error(err instanceof Error ? (err.stack ?? err.message) : String(err))
242
- process.exit(1)
243
- })