@tangle-network/agent-bench 0.7.1 → 0.8.0
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 +8 -0
- package/HARNESS.md +1 -1
- package/dist/adapters.js +2 -2
- package/dist/benchmarks/appworld.d.ts +61 -2
- package/dist/benchmarks/appworld.js +111 -40
- package/dist/benchmarks/appworld.js.map +1 -1
- package/dist/benchmarks/cadbench.js +1 -1
- package/dist/benchmarks/cadgenbench.js +1 -1
- package/dist/benchmarks/finresearchbench.js +16 -22
- package/dist/benchmarks/finresearchbench.js.map +1 -1
- package/dist/benchmarks/finsearchcomp.js +16 -22
- package/dist/benchmarks/finsearchcomp.js.map +1 -1
- package/dist/benchmarks/frames.js +18 -20
- package/dist/benchmarks/frames.js.map +1 -1
- package/dist/benchmarks/simpleqa.js +15 -19
- package/dist/benchmarks/simpleqa.js.map +1 -1
- package/dist/benchmarks/trata-hedge.js +20 -23
- package/dist/benchmarks/trata-hedge.js.map +1 -1
- package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
- package/dist/cadbench-BLSyxR1N.js.map +1 -0
- package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
- package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/router-turn-C2wMiDoo.js +31 -0
- package/dist/router-turn-C2wMiDoo.js.map +1 -0
- package/package.json +7 -7
- package/scripts/appworld_driver.py +1 -170
- package/scripts/run-package-tests.mjs +56 -36
- package/scripts/run-package-tests.test.mjs +33 -0
- package/scripts/trata-hedge/README.md +3 -3
- package/scripts/trata-hedge/run.sh +6 -2
- package/src/aec-gate.mts +30 -9
- package/src/agent-graphs-gen2.mts +523 -0
- package/src/agent-graphs-gen3.mts +660 -0
- package/src/agent-graphs-improve/offline-seams.mts +128 -0
- package/src/agent-graphs-improve.mts +747 -0
- package/src/atom-humaneval.mts +76 -55
- package/src/atom-mcp-e2e.mts +27 -13
- package/src/benchmarks/appworld.test.mts +119 -1
- package/src/benchmarks/appworld.ts +156 -33
- package/src/benchmarks/cadbench.ts +21 -8
- package/src/benchmarks/finresearchbench.ts +20 -16
- package/src/benchmarks/finsearchcomp.ts +20 -20
- package/src/benchmarks/frames.ts +19 -18
- package/src/benchmarks/simpleqa.ts +19 -17
- package/src/benchmarks/trata-hedge.ts +26 -18
- package/src/browser/ui-reviewer.ts +1 -1
- package/src/clbench-context-gate.mts +45 -9
- package/src/cloud-loop.mts +24 -6
- package/src/commit0-env-run.mts +6 -3
- package/src/commit0-env.ts +2 -3
- package/src/commit0-gate.mts +53 -24
- package/src/corpus.ts +3 -3
- package/src/david-attribution.mts +28 -9
- package/src/david-goliath.mts +31 -12
- package/src/egress-probe.mts +3 -3
- package/src/examples/lean-proof-gate.mts +10 -3
- package/src/examples/math-demo.mts +10 -3
- package/src/examples/strategy-demo.mts +10 -4
- package/src/fleet.mts +30 -8
- package/src/gate-cli.mts +9 -4
- package/src/gate.test.mts +7 -2
- package/src/gate.ts +23 -75
- package/src/generate-eval/certify.ts +13 -5
- package/src/hev-eval.mts +39 -10
- package/src/hev-improve.mts +28 -16
- package/src/hev-structural.mts +28 -20
- package/src/humaneval-gate.mts +24 -8
- package/src/humaneval-object-ablation.mts +52 -14
- package/src/humaneval-repair-gate.mts +53 -14
- package/src/mbpp-structural.mts +28 -20
- package/src/mcp-mount-probe.mts +23 -11
- package/src/official-optimizer-config.mts +61 -12
- package/src/official-optimizer-config.test.mts +72 -3
- package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
- package/src/quant-arena/quant-loop.mts +88 -61
- package/src/research-shot.ts +21 -11
- package/src/resolve-client.ts +6 -8
- package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
- package/src/router-executor.ts +1 -1
- package/src/router-turn.ts +135 -0
- package/src/run-benchmarks.ts +7 -2
- package/src/sandbox-run.ts +23 -41
- package/src/search-bench/bridge.ts +66 -8
- package/src/search-bench/parametric-check.mts +19 -4
- package/src/search-bench/profiles.ts +24 -9
- package/src/search-bench/run.mts +3 -4
- package/src/smoke-structural-rollout.mts +21 -5
- package/src/supervisor-arena.mts +28 -20
- package/src/swe-arena/arms.ts +73 -15
- package/src/swe-arena/capacity.ts +44 -41
- package/src/swe-arena/diagnosis-ensemble.ts +51 -48
- package/src/swe-arena/execution.test.mts +12 -12
- package/src/swe-arena/gepa-seat.mts +12 -5
- package/src/swe-arena/gepa-seat.test.mts +45 -33
- package/src/swe-arena/outer-loop.mts +94 -70
- package/src/swe-arena/premeasured-from-cells.mts +16 -1
- package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
- package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
- package/src/swe-arena/profiles/default-author.profile.json +9 -1
- package/src/swe-arena/proposer-fanout.mts +52 -99
- package/src/swe-arena/proposer-fanout.test.mts +92 -57
- package/src/swe-arena/proposer-provenance.mts +1 -1
- package/src/swe-code-improve.mts +30 -52
- package/src/swe-emit-patch.mts +26 -5
- package/src/swe-improve.mts +27 -8
- package/src/swe-jail.test.ts +128 -0
- package/src/swe-jail.ts +106 -22
- package/src/swe-local-proof.mts +27 -5
- package/src/swe-repro-calibrate.mts +5 -0
- package/src/swe-self-improve.mts +45 -10
- package/src/swe-stream.mts +68 -6
- package/src/swe-structural.mts +46 -7
- package/src/tb-container-executor.mts +38 -6
- package/src/tb-container-executor.test.mts +11 -3
- package/src/tb-supervisor-sidecar.mts +11 -27
- package/src/terminal-compare.ts +38 -18
- package/src/trata-gate.mts +26 -26
- package/src/trata-gepa.mts +25 -16
- package/src/trata-hedge-solve.mts +80 -0
- package/src/worker-blender.ts +21 -11
- package/src/worker-browser.ts +18 -5
- package/src/worker-build123d.ts +17 -8
- package/src/worker-cad.ts +31 -12
- package/src/worker.ts +1 -1
- package/tb_agents/opencode_router_agent.py +84 -42
- package/dist/cadbench-BrpwOU6A.js.map +0 -1
- package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
- package/scripts/trata-hedge/solve.py +0 -83
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { collectAgentTurn, createExecutor, streamAgentTurn } from "@tangle-network/agent-runtime/kernel";
|
|
2
|
+
import "@tangle-network/agent-interface";
|
|
3
|
+
//#region src/router-turn.ts
|
|
4
|
+
/**
|
|
5
|
+
* The benchmark-side entry to Runtime's canonical one-turn path.
|
|
6
|
+
* It is only an ergonomic composition: Runtime still parses the exact profile,
|
|
7
|
+
* materializes the executor, records identity/usage/result events, and refuses
|
|
8
|
+
* profile axes the direct Router backend cannot carry.
|
|
9
|
+
*/
|
|
10
|
+
async function runBenchRouterTurn(config, input) {
|
|
11
|
+
if (!config.profile.model?.default) throw new Error("runBenchRouterTurn: profile.model.default is required");
|
|
12
|
+
const turn = await collectAgentTurn(streamAgentTurn({
|
|
13
|
+
kind: "executor",
|
|
14
|
+
factory: createExecutor({
|
|
15
|
+
backend: "router",
|
|
16
|
+
routerBaseUrl: config.routerBaseUrl,
|
|
17
|
+
routerKey: config.routerKey,
|
|
18
|
+
...config.tools ? { tools: config.tools } : {}
|
|
19
|
+
}),
|
|
20
|
+
profile: config.profile
|
|
21
|
+
}, input, {
|
|
22
|
+
...config.timeoutMs === void 0 ? {} : { timeoutMs: config.timeoutMs },
|
|
23
|
+
...config.signal ? { signal: config.signal } : {}
|
|
24
|
+
}));
|
|
25
|
+
if (turn.status !== "completed") throw new Error(turn.error?.message ?? `Router turn ended with status ${turn.status}`);
|
|
26
|
+
return turn;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { runBenchRouterTurn as t };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=router-turn-C2wMiDoo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router-turn-C2wMiDoo.js","names":[],"sources":["../src/router-turn.ts"],"sourcesContent":["import {\n type AgentProfile,\n agentProfileSchema,\n type ReasoningEffort,\n} from '@tangle-network/agent-interface'\nimport {\n collectAgentTurn,\n createExecutor,\n streamAgentTurn,\n type CollectedAgentTurn,\n type ToolSpec,\n} from '@tangle-network/agent-runtime/kernel'\n\n/** Bench-local target shorthand; Runtime still executes only the exact profile below. */\nexport interface BenchRouterTarget {\n routerBaseUrl: string\n routerKey: string\n profile: AgentProfile\n}\n\nexport interface BenchRouterTurnConfig extends BenchRouterTarget {\n tools?: ReadonlyArray<ToolSpec>\n timeoutMs?: number\n signal?: AbortSignal\n}\n\nexport interface BenchProfileSettings {\n systemPrompt?: string\n temperature?: number\n maxTokens?: number\n retry?: {\n maxAttempts?: number\n initialBackoffMs?: number\n maxBackoffMs?: number\n jitter?: number\n retryStatuses?: ReadonlyArray<number>\n requestTimeoutMs?: number\n }\n maxTurns?: number\n seed?: number\n reasoningEffort?: ReasoningEffort\n extraBody?: Readonly<Record<string, unknown>>\n toolChoice?: 'auto' | 'required' | 'none'\n}\n\n/** Author an exact direct-Router profile for a benchmark. This is profile construction only;\n * execution still accepts no model or generation fields outside the returned AgentProfile. */\nexport function benchRouterProfile(\n name: string,\n model: string,\n settings: BenchProfileSettings = {},\n): AgentProfile {\n return withBenchProfile(\n {\n name,\n harness: 'cli-base',\n model: { provider: 'tangle-router', default: model },\n },\n settings,\n )\n}\n\n/** Derive another exact profile while preserving all untouched canonical axes. */\nexport function withBenchProfile(\n base: AgentProfile,\n settings: BenchProfileSettings & { name?: string },\n): AgentProfile {\n const metadata = {\n ...(base.model?.metadata ?? {}),\n ...(settings.temperature !== undefined ? { temperature: settings.temperature } : {}),\n ...(settings.maxTokens !== undefined ? { maxTokens: settings.maxTokens } : {}),\n ...(settings.retry !== undefined ? { retry: settings.retry } : {}),\n ...(settings.maxTurns !== undefined ? { maxTurns: settings.maxTurns } : {}),\n ...(settings.seed !== undefined ? { seed: settings.seed } : {}),\n ...(settings.extraBody !== undefined ? { extraBody: settings.extraBody } : {}),\n ...(settings.toolChoice !== undefined ? { toolChoice: settings.toolChoice } : {}),\n }\n return agentProfileSchema.parse({\n ...base,\n ...(settings.name ? { name: settings.name } : {}),\n model: {\n ...base.model,\n ...(settings.reasoningEffort !== undefined\n ? { reasoningEffort: settings.reasoningEffort }\n : {}),\n ...(Object.keys(metadata).length > 0 ? { metadata } : {}),\n },\n ...(settings.systemPrompt !== undefined\n ? { prompt: { ...base.prompt, systemPrompt: settings.systemPrompt } }\n : {}),\n })\n}\n\nexport function benchProfileModel(profile: AgentProfile): string {\n const model = profile.model?.default\n if (typeof model !== 'string' || model.length === 0 || model === 'runtime-selected') {\n throw new Error('benchmark AgentProfile.model.default must be concrete')\n }\n return model\n}\n\n/**\n * The benchmark-side entry to Runtime's canonical one-turn path.\n * It is only an ergonomic composition: Runtime still parses the exact profile,\n * materializes the executor, records identity/usage/result events, and refuses\n * profile axes the direct Router backend cannot carry.\n */\nexport async function runBenchRouterTurn(\n config: BenchRouterTurnConfig,\n input: string | { readonly messages: ReadonlyArray<Readonly<Record<string, unknown>>> },\n): Promise<CollectedAgentTurn> {\n if (!config.profile.model?.default) {\n throw new Error('runBenchRouterTurn: profile.model.default is required')\n }\n const factory = createExecutor({\n backend: 'router',\n routerBaseUrl: config.routerBaseUrl,\n routerKey: config.routerKey,\n ...(config.tools ? { tools: config.tools } : {}),\n })\n const turn = await collectAgentTurn(\n streamAgentTurn(\n { kind: 'executor', factory, profile: config.profile },\n input,\n {\n ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),\n ...(config.signal ? { signal: config.signal } : {}),\n },\n ),\n )\n if (turn.status !== 'completed') {\n throw new Error(turn.error?.message ?? `Router turn ended with status ${turn.status}`)\n }\n return turn\n}\n"],"mappings":";;;;;;;;;AA2GA,eAAsB,mBACpB,QACA,OAC6B;CAC7B,IAAI,CAAC,OAAO,QAAQ,OAAO,SACzB,MAAM,IAAI,MAAM,uDAAuD;CAQzE,MAAM,OAAO,MAAM,iBACjB,gBACE;EAAE,MAAM;EAAY,SARR,eAAe;GAC7B,SAAS;GACT,eAAe,OAAO;GACtB,WAAW,OAAO;GAClB,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EAChD,CAG8B;EAAG,SAAS,OAAO;CAAQ,GACrD,OACA;EACE,GAAI,OAAO,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,OAAO,UAAU;EACxE,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;CACnD,CACF,CACF;CACA,IAAI,KAAK,WAAW,aAClB,MAAM,IAAI,MAAM,KAAK,OAAO,WAAW,iCAAiC,KAAK,QAAQ;CAEvF,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-bench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Benchmark adapters and execution for agent-runtime across coding, tool-use, RAG, memory, browser, and terminal tasks.",
|
|
6
6
|
"repository": {
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@tangle-network/agent-eval": "0.
|
|
29
|
-
"@tangle-network/agent-interface": "0.
|
|
30
|
-
"@tangle-network/agent-knowledge": "7.
|
|
31
|
-
"@tangle-network/sandbox": "0.
|
|
32
|
-
"@tangle-network/agent-runtime": "0.
|
|
28
|
+
"@tangle-network/agent-eval": "0.144.6",
|
|
29
|
+
"@tangle-network/agent-interface": "0.46.1",
|
|
30
|
+
"@tangle-network/agent-knowledge": "7.1.2",
|
|
31
|
+
"@tangle-network/sandbox": "0.19.4",
|
|
32
|
+
"@tangle-network/agent-runtime": "0.131.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@arethetypeswrong/cli": "0.18.5",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"run-benchmarks": "tsx src/run-benchmarks-cli.mts",
|
|
70
70
|
"gate-report": "tsx src/corpus-report.mts corpus/finsearch.jsonl",
|
|
71
71
|
"terminal-compare": "tsx src/terminal-compare.ts",
|
|
72
|
-
"test": "node scripts/run-package-tests.mjs && node --test scripts/wait-for-published-dependencies.test.mjs",
|
|
72
|
+
"test": "node scripts/run-package-tests.mjs && node --test scripts/run-package-tests.test.mjs scripts/wait-for-published-dependencies.test.mjs",
|
|
73
73
|
"typecheck:public": "tsc -p tsconfig.public.json",
|
|
74
74
|
"verify:package": "pnpm run verify:package:static && node scripts/verify-packed-consumer.mjs",
|
|
75
75
|
"verify:package:local-runtime": "pnpm run verify:package:static && node scripts/verify-packed-consumer.mjs --local-runtime",
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
11
|
import json
|
|
12
|
-
import os
|
|
13
|
-
import re
|
|
14
12
|
import sys
|
|
15
|
-
import time
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
def fail(msg: str) -> None:
|
|
@@ -20,169 +17,9 @@ def fail(msg: str) -> None:
|
|
|
20
17
|
sys.exit(1)
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
_CODE_RE = re.compile(r"```(?:python|py)?\s*\n(.*?)```", re.DOTALL)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _extract_code(text: str) -> str:
|
|
27
|
-
blocks = _CODE_RE.findall(text or "")
|
|
28
|
-
return (blocks[-1] if blocks else "").strip()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _router_chat(base: str, key: str, model: str, messages: list, timeout: float = 180.0):
|
|
32
|
-
"""One router chat-completion with retry on transient/429/5xx. Returns
|
|
33
|
-
(content, input_tokens, output_tokens). Raises on exhausted retries."""
|
|
34
|
-
import httpx
|
|
35
|
-
|
|
36
|
-
url = base.rstrip("/") + "/chat/completions"
|
|
37
|
-
last = None
|
|
38
|
-
for attempt in range(4):
|
|
39
|
-
try:
|
|
40
|
-
r = httpx.post(
|
|
41
|
-
url,
|
|
42
|
-
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
|
43
|
-
json={"model": model, "messages": messages},
|
|
44
|
-
timeout=timeout,
|
|
45
|
-
)
|
|
46
|
-
if r.status_code in (429, 500, 502, 503, 504):
|
|
47
|
-
last = f"{r.status_code}: {r.text[:160]}"
|
|
48
|
-
time.sleep(2**attempt)
|
|
49
|
-
continue
|
|
50
|
-
r.raise_for_status()
|
|
51
|
-
d = r.json()
|
|
52
|
-
content = (d["choices"][0]["message"].get("content") or "")
|
|
53
|
-
usage = d.get("usage") or {}
|
|
54
|
-
return content, int(usage.get("prompt_tokens", 0) or 0), int(usage.get("completion_tokens", 0) or 0)
|
|
55
|
-
except Exception as e: # noqa: BLE001
|
|
56
|
-
last = str(e)
|
|
57
|
-
if attempt < 3:
|
|
58
|
-
time.sleep(2**attempt)
|
|
59
|
-
continue
|
|
60
|
-
raise RuntimeError(f"router_chat failed after retries: {last}")
|
|
61
|
-
raise RuntimeError(f"router_chat exhausted: {last}")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _build_system(directive: str, world) -> str:
|
|
65
|
-
sup = world.task.supervisor
|
|
66
|
-
apps = list(getattr(world.task, "allowed_apps", []) or [])
|
|
67
|
-
descs = getattr(world.task, "app_descriptions", "")
|
|
68
|
-
desc_str = json.dumps(descs) if isinstance(descs, (dict, list)) else str(descs)
|
|
69
|
-
return (
|
|
70
|
-
f"You are an AI agent completing a digital task for your supervisor "
|
|
71
|
-
f"{getattr(sup, 'first_name', '')} {getattr(sup, 'last_name', '')} "
|
|
72
|
-
f"(email {getattr(sup, 'email', '')}, phone {getattr(sup, 'phone_number', '')}) "
|
|
73
|
-
"by WRITING PYTHON that calls app APIs (the apis.<app>.<function>(...) surface).\n\n"
|
|
74
|
-
f"Available apps: {', '.join(apps)}.\n"
|
|
75
|
-
f"App descriptions: {desc_str[:1500]}\n\n"
|
|
76
|
-
"How to work, one step per turn:\n"
|
|
77
|
-
"- Discover APIs with apis.api_docs.show_api_descriptions(app_name='<app>') and "
|
|
78
|
-
"apis.api_docs.show_api_doc(app_name='<app>', api_name='<api>') BEFORE calling them.\n"
|
|
79
|
-
"- Get the supervisor's app passwords with apis.supervisor.show_account_passwords(), then log in "
|
|
80
|
-
"to each app you use to obtain its access_token.\n"
|
|
81
|
-
"- Write ONE short Python code block per turn. After it runs you SEE its OUTPUT (or error "
|
|
82
|
-
"traceback) — use that to decide the next step. Print intermediate values you need.\n"
|
|
83
|
-
"- Iterate: inspect -> authenticate -> act -> verify. Do not guess API names or arguments.\n"
|
|
84
|
-
"- When the task is fully done call apis.supervisor.complete_task(answer=<answer>) (include the "
|
|
85
|
-
"answer if the task asks a question, otherwise apis.supervisor.complete_task()).\n"
|
|
86
|
-
"- Reply with EXACTLY ONE fenced ```python block per turn and nothing else.\n\n"
|
|
87
|
-
f"{directive}"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def cmd_react(args) -> None:
|
|
92
|
-
"""Multi-turn REPL agent: the model writes a python block, the engine executes
|
|
93
|
-
it in the PERSISTENT world, the output is fed back, and it iterates until it
|
|
94
|
-
completes the task or hits max-turns. Then AppWorld's own evaluator scores it.
|
|
95
|
-
Config (directive, model, router creds, max_turns) arrives as JSON on stdin so
|
|
96
|
-
the candidate directive can be arbitrarily long. The directive is the optimized
|
|
97
|
-
surface; the loop + contract are fixed."""
|
|
98
|
-
cfg = {}
|
|
99
|
-
raw = sys.stdin.read()
|
|
100
|
-
if raw.strip():
|
|
101
|
-
try:
|
|
102
|
-
cfg = json.loads(raw)
|
|
103
|
-
except Exception as e: # noqa: BLE001
|
|
104
|
-
fail(f"react config JSON parse failed: {e}")
|
|
105
|
-
directive = str(cfg.get("directive", ""))
|
|
106
|
-
model = str(cfg.get("model", "gpt-4o"))
|
|
107
|
-
max_turns = int(cfg.get("max_turns", 8))
|
|
108
|
-
router_base = str(cfg.get("router_base", "https://router.tangle.tools/v1"))
|
|
109
|
-
router_key = str(cfg.get("router_key") or os.environ.get("TANGLE_API_KEY", ""))
|
|
110
|
-
if not router_key:
|
|
111
|
-
fail("react: router_key/TANGLE_API_KEY required")
|
|
112
|
-
|
|
113
|
-
try:
|
|
114
|
-
from appworld import AppWorld
|
|
115
|
-
except Exception as e: # noqa: BLE001
|
|
116
|
-
fail(f"appworld import failed: {e}")
|
|
117
|
-
|
|
118
|
-
in_tok = 0
|
|
119
|
-
out_tok = 0
|
|
120
|
-
turns = 0
|
|
121
|
-
turns_log: list = []
|
|
122
|
-
try:
|
|
123
|
-
with AppWorld(
|
|
124
|
-
task_id=args.task_id,
|
|
125
|
-
experiment_name="bench-react",
|
|
126
|
-
raise_on_failure=False,
|
|
127
|
-
) as world:
|
|
128
|
-
messages = [
|
|
129
|
-
{"role": "system", "content": _build_system(directive, world)},
|
|
130
|
-
{"role": "user", "content": f"Task: {world.task.instruction}"},
|
|
131
|
-
]
|
|
132
|
-
for turn in range(max_turns):
|
|
133
|
-
turns = turn + 1
|
|
134
|
-
content, ui, uo = _router_chat(router_base, router_key, model, messages)
|
|
135
|
-
in_tok += ui
|
|
136
|
-
out_tok += uo
|
|
137
|
-
code = _extract_code(content)
|
|
138
|
-
messages.append({"role": "assistant", "content": content})
|
|
139
|
-
if not code:
|
|
140
|
-
messages.append({
|
|
141
|
-
"role": "user",
|
|
142
|
-
"content": "Reply with exactly one ```python block that makes progress, "
|
|
143
|
-
"or call apis.supervisor.complete_task().",
|
|
144
|
-
})
|
|
145
|
-
continue
|
|
146
|
-
output = world.execute(code)
|
|
147
|
-
turns_log.append({"code": code[:600], "output": str(output)[:600]})
|
|
148
|
-
messages.append({"role": "user", "content": "OUTPUT:\n" + str(output)[:4000]})
|
|
149
|
-
if world.task_completed():
|
|
150
|
-
break
|
|
151
|
-
evaluation = world.evaluate().to_dict()
|
|
152
|
-
except Exception as e: # noqa: BLE001
|
|
153
|
-
fail(f"react of {args.task_id} failed: {e}")
|
|
154
|
-
|
|
155
|
-
if "success" not in evaluation or "num_tests" not in evaluation:
|
|
156
|
-
fail(f"evaluation dict missing success/num_tests keys: {sorted(evaluation.keys())}")
|
|
157
|
-
passes = evaluation.get("passes", [])
|
|
158
|
-
failures = evaluation.get("failures", [])
|
|
159
|
-
n_pass = len(passes) if isinstance(passes, list) else int(passes or 0)
|
|
160
|
-
n_fail = len(failures) if isinstance(failures, list) else int(failures or 0)
|
|
161
|
-
print(
|
|
162
|
-
json.dumps(
|
|
163
|
-
{
|
|
164
|
-
"success": bool(evaluation["success"]),
|
|
165
|
-
"passes": n_pass,
|
|
166
|
-
"fails": n_fail,
|
|
167
|
-
"num_tests": int(evaluation["num_tests"]),
|
|
168
|
-
# Failed sub-test names — the evidence a trace analyst steers on.
|
|
169
|
-
"failure_names": [str(f)[:160] for f in failures][:8]
|
|
170
|
-
if isinstance(failures, list)
|
|
171
|
-
else [],
|
|
172
|
-
"turns": turns,
|
|
173
|
-
"input_tokens": in_tok,
|
|
174
|
-
"output_tokens": out_tok,
|
|
175
|
-
"transcript": "\n---\n".join(
|
|
176
|
-
f"CODE:\n{t['code']}\nOUTPUT:\n{t['output']}" for t in turns_log[-3:]
|
|
177
|
-
)[:1600],
|
|
178
|
-
}
|
|
179
|
-
)
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
|
|
183
20
|
def cmd_session(args) -> None:
|
|
184
21
|
"""Dumb world shim: a persistent AppWorld session driven over stdin JSONL.
|
|
185
|
-
NO LLM calls here — the agent loop lives in
|
|
22
|
+
NO LLM calls here — the profile-bound agent loop lives in Runtime;
|
|
186
23
|
this process only owns world state. One JSON object per line, both ways:
|
|
187
24
|
{"op":"execute","code":"..."} -> {"output":"...","task_completed":bool}
|
|
188
25
|
{"op":"evaluate"} -> the evaluate verdict JSON (+failure_names)
|
|
@@ -336,10 +173,6 @@ def main() -> None:
|
|
|
336
173
|
p_eval.add_argument("--task-id", required=True)
|
|
337
174
|
p_eval.add_argument("--split", required=True)
|
|
338
175
|
|
|
339
|
-
p_react = sub.add_parser("react")
|
|
340
|
-
p_react.add_argument("--task-id", required=True)
|
|
341
|
-
p_react.add_argument("--split", required=True)
|
|
342
|
-
|
|
343
176
|
p_session = sub.add_parser("session")
|
|
344
177
|
p_session.add_argument("--task-id", required=True)
|
|
345
178
|
p_session.add_argument("--split", required=True)
|
|
@@ -349,8 +182,6 @@ def main() -> None:
|
|
|
349
182
|
cmd_load(args)
|
|
350
183
|
elif args.cmd == "evaluate":
|
|
351
184
|
cmd_evaluate(args)
|
|
352
|
-
elif args.cmd == "react":
|
|
353
|
-
cmd_react(args)
|
|
354
185
|
elif args.cmd == "session":
|
|
355
186
|
cmd_session(args)
|
|
356
187
|
|
|
@@ -8,6 +8,19 @@ const execFileAsync = promisify(execFile)
|
|
|
8
8
|
const benchDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
9
9
|
const sourceDir = path.join(benchDir, 'src')
|
|
10
10
|
|
|
11
|
+
export function resolvePackageTestTimeoutMs(env = process.env) {
|
|
12
|
+
const raw = env.AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS
|
|
13
|
+
if (raw === undefined) return undefined
|
|
14
|
+
if (!/^(?:0|[1-9]\d*)$/.test(raw)) {
|
|
15
|
+
throw new Error('AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS must be a non-negative safe integer')
|
|
16
|
+
}
|
|
17
|
+
const timeoutMs = Number(raw)
|
|
18
|
+
if (!Number.isSafeInteger(timeoutMs)) {
|
|
19
|
+
throw new Error('AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS must be a non-negative safe integer')
|
|
20
|
+
}
|
|
21
|
+
return timeoutMs
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
async function collectTests(dir) {
|
|
12
25
|
const files = []
|
|
13
26
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
@@ -18,13 +31,14 @@ async function collectTests(dir) {
|
|
|
18
31
|
return files.sort()
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
async function run(command, args, env = process.env) {
|
|
34
|
+
export async function run(command, args, env = process.env) {
|
|
35
|
+
const timeoutMs = resolvePackageTestTimeoutMs(env)
|
|
22
36
|
try {
|
|
23
37
|
await execFileAsync(command, args, {
|
|
24
38
|
cwd: benchDir,
|
|
25
39
|
env,
|
|
26
40
|
maxBuffer: 10 * 1024 * 1024,
|
|
27
|
-
timeout:
|
|
41
|
+
...(timeoutMs === undefined ? {} : { timeout: timeoutMs }),
|
|
28
42
|
})
|
|
29
43
|
} catch (error) {
|
|
30
44
|
if (error?.stdout) process.stdout.write(error.stdout)
|
|
@@ -35,44 +49,50 @@ async function run(command, args, env = process.env) {
|
|
|
35
49
|
}
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
async function main() {
|
|
53
|
+
const python = path.join(benchDir, '.venv', 'bin', 'python')
|
|
54
|
+
try {
|
|
55
|
+
await access(python)
|
|
56
|
+
} catch {
|
|
57
|
+
await run('python3', ['-m', 'venv', '.venv'])
|
|
58
|
+
}
|
|
44
59
|
|
|
45
|
-
const tests = await collectTests(sourceDir)
|
|
46
|
-
if (tests.length === 0) throw new Error('no package tests found under src/')
|
|
60
|
+
const tests = await collectTests(sourceDir)
|
|
61
|
+
if (tests.length === 0) throw new Error('no package tests found under src/')
|
|
47
62
|
|
|
48
|
-
// Two test runtimes coexist under src/: node:test files run under `node --test`;
|
|
49
|
-
// vitest files (the swe-arena suite) crash there (`vitest` APIs need the vitest
|
|
50
|
-
// worker), so partition by the framework each file actually imports.
|
|
51
|
-
const nodeTests = []
|
|
52
|
-
const vitestTests = []
|
|
53
|
-
for (const file of tests) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
63
|
+
// Two test runtimes coexist under src/: node:test files run under `node --test`;
|
|
64
|
+
// vitest files (the swe-arena suite) crash there (`vitest` APIs need the vitest
|
|
65
|
+
// worker), so partition by the framework each file actually imports.
|
|
66
|
+
const nodeTests = []
|
|
67
|
+
const vitestTests = []
|
|
68
|
+
for (const file of tests) {
|
|
69
|
+
const body = await readFile(file, 'utf8')
|
|
70
|
+
if (/from\s+['"]vitest['"]/.test(body)) vitestTests.push(file)
|
|
71
|
+
else nodeTests.push(file)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (nodeTests.length > 0) {
|
|
75
|
+
await run(
|
|
76
|
+
process.execPath,
|
|
77
|
+
['--test', '--import', 'tsx', ...nodeTests.map((file) => path.relative(benchDir, file))],
|
|
78
|
+
{
|
|
79
|
+
...process.env,
|
|
80
|
+
TSX_TSCONFIG_PATH: 'tsconfig.public.json',
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (vitestTests.length > 0) {
|
|
86
|
+
await run('npx', ['vitest', 'run', ...vitestTests.map((file) => path.relative(benchDir, file))])
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await run(python, ['-m', 'unittest', 'discover', '-s', 'pier_agents', '-p', '*_test.py'])
|
|
58
90
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
process.execPath,
|
|
62
|
-
['--test', '--import', 'tsx', ...nodeTests.map((file) => path.relative(benchDir, file))],
|
|
63
|
-
{
|
|
64
|
-
...process.env,
|
|
65
|
-
TSX_TSCONFIG_PATH: 'tsconfig.public.json',
|
|
66
|
-
},
|
|
91
|
+
console.log(
|
|
92
|
+
`package tests passed: ${tests.length}/${tests.length} TypeScript files (${nodeTests.length} node:test + ${vitestTests.length} vitest) + Pier bridge`,
|
|
67
93
|
)
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
if (
|
|
71
|
-
await
|
|
96
|
+
if (path.resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url)) {
|
|
97
|
+
await main()
|
|
72
98
|
}
|
|
73
|
-
|
|
74
|
-
await run(python, ['-m', 'unittest', 'discover', '-s', 'pier_agents', '-p', '*_test.py'])
|
|
75
|
-
|
|
76
|
-
console.log(
|
|
77
|
-
`package tests passed: ${tests.length}/${tests.length} TypeScript files (${nodeTests.length} node:test + ${vitestTests.length} vitest) + Pier bridge`,
|
|
78
|
-
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { resolvePackageTestTimeoutMs, run } from './run-package-tests.mjs'
|
|
4
|
+
|
|
5
|
+
test('package test timeout is optional and caller-controlled', () => {
|
|
6
|
+
assert.equal(resolvePackageTestTimeoutMs({}), undefined)
|
|
7
|
+
assert.equal(resolvePackageTestTimeoutMs({ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: '0' }), 0)
|
|
8
|
+
assert.equal(resolvePackageTestTimeoutMs({ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: '900000' }), 900_000)
|
|
9
|
+
|
|
10
|
+
for (const value of ['', '-1', '1.5', 'Infinity', '9007199254740992']) {
|
|
11
|
+
assert.throws(
|
|
12
|
+
() => resolvePackageTestTimeoutMs({ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: value }),
|
|
13
|
+
/must be a non-negative safe integer/,
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('package test timeout reaches the child process', async () => {
|
|
19
|
+
const env = {
|
|
20
|
+
...process.env,
|
|
21
|
+
AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: '100',
|
|
22
|
+
}
|
|
23
|
+
const startedAt = Date.now()
|
|
24
|
+
|
|
25
|
+
await assert.rejects(
|
|
26
|
+
run(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], env),
|
|
27
|
+
(error) => {
|
|
28
|
+
assert.equal(error.cause?.killed, true)
|
|
29
|
+
return true
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
assert.ok(Date.now() - startedAt < 2_000)
|
|
33
|
+
})
|
|
@@ -21,8 +21,8 @@ sparse (1 iff all themes). No deployable ground-truth checker — it's an **orac
|
|
|
21
21
|
|
|
22
22
|
## Status (2026-06-06): pipeline PROVEN end-to-end
|
|
23
23
|
|
|
24
|
-
Our solver → their **real** Gemini-3.1-pro judge → a genuine graded result. Every link
|
|
25
|
-
works. The naive **single-shot** baseline (
|
|
24
|
+
Our Runtime-backed solver → their **real** Gemini-3.1-pro judge → a genuine graded result. Every link
|
|
25
|
+
works. The naive **single-shot** baseline (DeepSeek V4 Flash, ~3 of N corpus files in one context
|
|
26
26
|
window) scores **0/4** — a floor: it hit only 1/3 moves on a few themes with
|
|
27
27
|
hallucinations flagged, because it could not explore the full corpus. The bench is built
|
|
28
28
|
for **agentic** exploration; a fair baseline needs our sandbox runtime as the solver
|
|
@@ -35,7 +35,7 @@ for **agentic** exploration; a fair baseline needs our sandbox runtime as the so
|
|
|
35
35
|
git clone https://github.com/Trata-Inc/trata-hedge-bench /tmp/thb
|
|
36
36
|
|
|
37
37
|
dotenvx run -f ~/company/devops/secrets/.env.keys -f ~/company/devops/secrets/agent-state.env -- \
|
|
38
|
-
bash bench/scripts/trata-hedge/run.sh /tmp/thb/environments/<env-name>
|
|
38
|
+
bash bench/scripts/trata-hedge/run.sh /tmp/thb/environments/<env-name> deepseek-v4-flash
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
## Gotchas (each cost a debugging cycle)
|
|
@@ -17,14 +17,18 @@
|
|
|
17
17
|
set -euo pipefail
|
|
18
18
|
|
|
19
19
|
ENV="${1:?usage: run.sh <trata-env-dir> [model]}"
|
|
20
|
-
MODEL="${2:-${WORKER_MODEL:-
|
|
20
|
+
MODEL="${2:-${WORKER_MODEL:-}}"
|
|
21
|
+
if [[ -z "$MODEL" ]]; then
|
|
22
|
+
echo "WORKER_MODEL is required (or pass it as argument 2)" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
fi
|
|
21
25
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
26
|
ANS=/tmp/thb-answer.txt
|
|
23
27
|
REWARD=/tmp/thb-reward.txt
|
|
24
28
|
DETAILS=/tmp/thb-details.json
|
|
25
29
|
|
|
26
30
|
echo "[trata] solve: $ENV (model=$MODEL)"
|
|
27
|
-
WORKER_MODEL="$MODEL"
|
|
31
|
+
WORKER_MODEL="$MODEL" pnpm --dir "$HERE/../../.." exec tsx "$HERE/../../src/trata-hedge-solve.mts" "$ENV" "$ANS"
|
|
28
32
|
|
|
29
33
|
sudo -n mkdir -p /app && sudo -n cp "$ANS" /app/answer.txt && sudo -n chmod 644 /app/answer.txt
|
|
30
34
|
|
package/src/aec-gate.mts
CHANGED
|
@@ -22,7 +22,13 @@ import { resolveAdapter } from './adapters'
|
|
|
22
22
|
import type { BenchmarkAdapter, BenchTask } from './benchmarks/types'
|
|
23
23
|
import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
|
|
24
24
|
import { composeStrategies } from './directives'
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
benchProfileModel,
|
|
27
|
+
benchRouterProfile,
|
|
28
|
+
type BenchRouterTarget,
|
|
29
|
+
runBenchRouterTurn,
|
|
30
|
+
withBenchProfile,
|
|
31
|
+
} from './router-turn'
|
|
26
32
|
import { pool } from './stats.mts'
|
|
27
33
|
|
|
28
34
|
function must(name: string): string {
|
|
@@ -52,7 +58,7 @@ interface AttemptOutcome {
|
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
async function runAttempt(
|
|
55
|
-
cfg:
|
|
61
|
+
cfg: BenchRouterTarget,
|
|
56
62
|
adapter: BenchmarkAdapter,
|
|
57
63
|
task: BenchTask,
|
|
58
64
|
prompt: string,
|
|
@@ -65,8 +71,15 @@ async function runAttempt(
|
|
|
65
71
|
let lastErr: unknown
|
|
66
72
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
67
73
|
try {
|
|
68
|
-
const res = await
|
|
69
|
-
|
|
74
|
+
const res = await runBenchRouterTurn(
|
|
75
|
+
{
|
|
76
|
+
routerBaseUrl: cfg.routerBaseUrl,
|
|
77
|
+
routerKey: cfg.routerKey,
|
|
78
|
+
profile: withBenchProfile(cfg.profile, { name: 'aec-worker' }),
|
|
79
|
+
},
|
|
80
|
+
prompt,
|
|
81
|
+
)
|
|
82
|
+
const content = res.finalText
|
|
70
83
|
const verdict = await adapter.judge(task, content)
|
|
71
84
|
return {
|
|
72
85
|
prompt,
|
|
@@ -74,8 +87,10 @@ async function runAttempt(
|
|
|
74
87
|
score: verdict.score,
|
|
75
88
|
resolved: verdict.resolved,
|
|
76
89
|
wallMs: Date.now() - startedAt,
|
|
77
|
-
...(res.costUsd !== undefined ? { costUsd: res.costUsd } : {}),
|
|
78
|
-
...(res.usage
|
|
90
|
+
...(res.usage.costUsd !== undefined ? { costUsd: res.usage.costUsd } : {}),
|
|
91
|
+
...(res.usage.tokensKnown === false
|
|
92
|
+
? {}
|
|
93
|
+
: { tokensIn: res.usage.input, tokensOut: res.usage.output }),
|
|
79
94
|
}
|
|
80
95
|
} catch (err) {
|
|
81
96
|
lastErr = err
|
|
@@ -115,7 +130,7 @@ interface ArmResult {
|
|
|
115
130
|
|
|
116
131
|
async function runArm(
|
|
117
132
|
arm: ArmSpec,
|
|
118
|
-
cfg:
|
|
133
|
+
cfg: BenchRouterTarget,
|
|
119
134
|
adapter: BenchmarkAdapter,
|
|
120
135
|
tasks: BenchTask[],
|
|
121
136
|
k: number,
|
|
@@ -144,7 +159,7 @@ async function runArm(
|
|
|
144
159
|
benchmark: adapter.name,
|
|
145
160
|
instanceId: task.id,
|
|
146
161
|
condition: arm.condition,
|
|
147
|
-
model: cfg.
|
|
162
|
+
model: benchProfileModel(cfg.profile),
|
|
148
163
|
// k-attempt outcome = any usable attempt resolved (the oracle@k ceiling for
|
|
149
164
|
// this run; the deployable selector is scored separately by corpus-replay).
|
|
150
165
|
resolved: taskOutcomes.some((o) => o.resolved),
|
|
@@ -175,7 +190,13 @@ async function main(): Promise<void> {
|
|
|
175
190
|
if (!Number.isFinite(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
|
|
176
191
|
if (!Number.isFinite(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
|
|
177
192
|
|
|
178
|
-
const cfg:
|
|
193
|
+
const cfg: BenchRouterTarget = {
|
|
194
|
+
routerBaseUrl,
|
|
195
|
+
routerKey,
|
|
196
|
+
profile: benchRouterProfile('aec-worker', model, {
|
|
197
|
+
retry: { maxAttempts: Number(process.env.MAX_ATTEMPTS ?? 3) },
|
|
198
|
+
}),
|
|
199
|
+
}
|
|
179
200
|
const bench = process.env.BENCH ?? 'aec-bench'
|
|
180
201
|
const adapter = resolveAdapter(bench)
|
|
181
202
|
|