@tangle-network/agent-bench 0.4.1 → 0.4.4
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 +14 -0
- package/dist/adapters.d.ts +4 -11
- package/dist/adapters.js +78 -41
- package/dist/adapters.js.map +1 -1
- package/dist/benchmarks/_harness.d.ts +48 -66
- package/dist/benchmarks/_harness.js +329 -33
- package/dist/benchmarks/_harness.js.map +1 -1
- package/dist/benchmarks/aec-bench.d.ts +4 -25
- package/dist/benchmarks/aec-bench.js +242 -7
- package/dist/benchmarks/aec-bench.js.map +1 -1
- package/dist/benchmarks/agentbench.d.ts +5 -13
- package/dist/benchmarks/agentbench.js +114 -9
- package/dist/benchmarks/agentbench.js.map +1 -1
- package/dist/benchmarks/appworld.d.ts +9 -29
- package/dist/benchmarks/appworld.js +317 -12
- package/dist/benchmarks/appworld.js.map +1 -1
- package/dist/benchmarks/bfcl.d.ts +5 -15
- package/dist/benchmarks/bfcl.js +264 -9
- package/dist/benchmarks/bfcl.js.map +1 -1
- package/dist/benchmarks/cad-design.d.ts +16 -41
- package/dist/benchmarks/cad-design.js +512 -6
- package/dist/benchmarks/cad-design.js.map +1 -1
- package/dist/benchmarks/cadbench.d.ts +4 -17
- package/dist/benchmarks/cadbench.js +2 -8
- package/dist/benchmarks/cadgenbench.d.ts +4 -20
- package/dist/benchmarks/cadgenbench.js +2 -8
- package/dist/benchmarks/commit0.d.ts +5 -27
- package/dist/benchmarks/commit0.js +187 -9
- package/dist/benchmarks/commit0.js.map +1 -1
- package/dist/benchmarks/crag.d.ts +4 -12
- package/dist/benchmarks/crag.js +110 -8
- package/dist/benchmarks/crag.js.map +1 -1
- package/dist/benchmarks/dabstep.d.ts +5 -15
- package/dist/benchmarks/dabstep.js +177 -9
- package/dist/benchmarks/dabstep.js.map +1 -1
- package/dist/benchmarks/enterpriseops-gym.d.ts +5 -34
- package/dist/benchmarks/enterpriseops-gym.js +236 -9
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -1
- package/dist/benchmarks/finresearchbench.d.ts +4 -13
- package/dist/benchmarks/finresearchbench.js +218 -7
- package/dist/benchmarks/finresearchbench.js.map +1 -1
- package/dist/benchmarks/finsearchcomp.d.ts +8 -39
- package/dist/benchmarks/finsearchcomp.js +267 -6
- package/dist/benchmarks/finsearchcomp.js.map +1 -1
- package/dist/benchmarks/frames.d.ts +15 -37
- package/dist/benchmarks/frames.js +408 -11
- package/dist/benchmarks/frames.js.map +1 -1
- package/dist/benchmarks/hotpotqa.d.ts +4 -24
- package/dist/benchmarks/hotpotqa.js +250 -14
- package/dist/benchmarks/hotpotqa.js.map +1 -1
- package/dist/benchmarks/humaneval.d.ts +19 -37
- package/dist/benchmarks/humaneval.js +279 -16
- package/dist/benchmarks/humaneval.js.map +1 -1
- package/dist/benchmarks/mind2web.d.ts +7 -34
- package/dist/benchmarks/mind2web.js +257 -8
- package/dist/benchmarks/mind2web.js.map +1 -1
- package/dist/benchmarks/nomiracl.d.ts +4 -13
- package/dist/benchmarks/nomiracl.js +146 -8
- package/dist/benchmarks/nomiracl.js.map +1 -1
- package/dist/benchmarks/open-rag-bench.d.ts +4 -12
- package/dist/benchmarks/open-rag-bench.js +110 -8
- package/dist/benchmarks/open-rag-bench.js.map +1 -1
- package/dist/benchmarks/programbench.d.ts +5 -29
- package/dist/benchmarks/programbench.js +161 -9
- package/dist/benchmarks/programbench.js.map +1 -1
- package/dist/benchmarks/rag-shared.d.ts +21 -20
- package/dist/benchmarks/rag-shared.js +245 -38
- package/dist/benchmarks/rag-shared.js.map +1 -1
- package/dist/benchmarks/ragbench.d.ts +4 -14
- package/dist/benchmarks/ragbench.js +127 -8
- package/dist/benchmarks/ragbench.js.map +1 -1
- package/dist/benchmarks/simpleqa.d.ts +17 -44
- package/dist/benchmarks/simpleqa.js +293 -10
- package/dist/benchmarks/simpleqa.js.map +1 -1
- package/dist/benchmarks/swe-bench.d.ts +23 -36
- package/dist/benchmarks/swe-bench.js +234 -13
- package/dist/benchmarks/swe-bench.js.map +1 -1
- package/dist/benchmarks/t2-ragbench.d.ts +4 -12
- package/dist/benchmarks/t2-ragbench.js +118 -8
- package/dist/benchmarks/t2-ragbench.js.map +1 -1
- package/dist/benchmarks/tau-bench-shared.d.ts +14 -22
- package/dist/benchmarks/tau-bench-shared.js +169 -9
- package/dist/benchmarks/tau-bench-shared.js.map +1 -1
- package/dist/benchmarks/tau2-bench.d.ts +5 -5
- package/dist/benchmarks/tau2-bench.js +28 -10
- package/dist/benchmarks/tau2-bench.js.map +1 -1
- package/dist/benchmarks/tau3-banking.d.ts +4 -13
- package/dist/benchmarks/tau3-banking.js +28 -8
- package/dist/benchmarks/tau3-banking.js.map +1 -1
- package/dist/benchmarks/terminal-bench.d.ts +4 -22
- package/dist/benchmarks/terminal-bench.js +140 -7
- package/dist/benchmarks/terminal-bench.js.map +1 -1
- package/dist/benchmarks/toollm.d.ts +5 -13
- package/dist/benchmarks/toollm.js +184 -9
- package/dist/benchmarks/toollm.js.map +1 -1
- package/dist/benchmarks/trata-hedge.d.ts +4 -30
- package/dist/benchmarks/trata-hedge.js +336 -6
- package/dist/benchmarks/trata-hedge.js.map +1 -1
- package/dist/benchmarks/types.d.ts +85 -94
- package/dist/benchmarks/types.js +1 -1
- package/dist/benchmarks/webarena-verified.d.ts +5 -13
- package/dist/benchmarks/webarena-verified.js +152 -9
- package/dist/benchmarks/webarena-verified.js.map +1 -1
- package/dist/cadbench-DpQWZHp4.js +285 -0
- package/dist/cadbench-DpQWZHp4.js.map +1 -0
- package/dist/cadgenbench-DRhczfsG.js +151 -0
- package/dist/cadgenbench-DRhczfsG.js.map +1 -0
- package/dist/index.d.ts +245 -293
- package/dist/index.js +1669 -1791
- package/dist/index.js.map +1 -1
- package/package.json +18 -15
- package/scripts/verify-packed-consumer.mjs +20 -17
- package/src/corpus.test.mts +13 -0
- package/src/corpus.ts +4 -0
- package/src/pier-trial-controller.test.mts +15 -7
- package/src/profile-coordinates.ts +2 -2
- package/src/rollout-ledger/backfill-swe-arena.mts +5 -1
- package/src/rollout-ledger/settle-capture.mts +7 -1
- package/src/search-bench/profiles.ts +1 -1
- package/src/skill-sandbox-smoke.mts +2 -1
- package/src/swe-arena/gepa-seat.mts +1 -1
- package/src/swe-arena/ledger-orphans.test.mts +36 -34
- package/dist/benchmarks/cadbench.js.map +0 -1
- package/dist/benchmarks/cadgenbench.js.map +0 -1
- package/dist/benchmarks/types.js.map +0 -1
- package/dist/chunk-53UPUNBZ.js +0 -325
- package/dist/chunk-53UPUNBZ.js.map +0 -1
- package/dist/chunk-5FEQDSCT.js +0 -211
- package/dist/chunk-5FEQDSCT.js.map +0 -1
- package/dist/chunk-5H5XV76F.js +0 -240
- package/dist/chunk-5H5XV76F.js.map +0 -1
- package/dist/chunk-67ACKDCX.js +0 -118
- package/dist/chunk-67ACKDCX.js.map +0 -1
- package/dist/chunk-7FKBWOQT.js +0 -147
- package/dist/chunk-7FKBWOQT.js.map +0 -1
- package/dist/chunk-BEN6IF2X.js +0 -221
- package/dist/chunk-BEN6IF2X.js.map +0 -1
- package/dist/chunk-BZY5QARD.js +0 -120
- package/dist/chunk-BZY5QARD.js.map +0 -1
- package/dist/chunk-C7T7WEK2.js +0 -103
- package/dist/chunk-C7T7WEK2.js.map +0 -1
- package/dist/chunk-CLIKAXKH.js +0 -276
- package/dist/chunk-CLIKAXKH.js.map +0 -1
- package/dist/chunk-CWIOBFSP.js +0 -197
- package/dist/chunk-CWIOBFSP.js.map +0 -1
- package/dist/chunk-CXDUTWQE.js +0 -318
- package/dist/chunk-CXDUTWQE.js.map +0 -1
- package/dist/chunk-DWALFME7.js +0 -182
- package/dist/chunk-DWALFME7.js.map +0 -1
- package/dist/chunk-EEOC6QPJ.js +0 -144
- package/dist/chunk-EEOC6QPJ.js.map +0 -1
- package/dist/chunk-EIETHPD5.js +0 -321
- package/dist/chunk-EIETHPD5.js.map +0 -1
- package/dist/chunk-GC2EPS6L.js +0 -130
- package/dist/chunk-GC2EPS6L.js.map +0 -1
- package/dist/chunk-GCHL6XPM.js +0 -169
- package/dist/chunk-GCHL6XPM.js.map +0 -1
- package/dist/chunk-HQ5HCCKF.js +0 -142
- package/dist/chunk-HQ5HCCKF.js.map +0 -1
- package/dist/chunk-HVW25KSX.js +0 -208
- package/dist/chunk-HVW25KSX.js.map +0 -1
- package/dist/chunk-INNOYXCP.js +0 -387
- package/dist/chunk-INNOYXCP.js.map +0 -1
- package/dist/chunk-J6BU3NTM.js +0 -251
- package/dist/chunk-J6BU3NTM.js.map +0 -1
- package/dist/chunk-JSQOUKXS.js +0 -149
- package/dist/chunk-JSQOUKXS.js.map +0 -1
- package/dist/chunk-JTHWEDEW.js +0 -32
- package/dist/chunk-JTHWEDEW.js.map +0 -1
- package/dist/chunk-NRMGT25X.js +0 -116
- package/dist/chunk-NRMGT25X.js.map +0 -1
- package/dist/chunk-PA2ZKHJC.js +0 -230
- package/dist/chunk-PA2ZKHJC.js.map +0 -1
- package/dist/chunk-PUIRNYI7.js +0 -189
- package/dist/chunk-PUIRNYI7.js.map +0 -1
- package/dist/chunk-QZZEAHWJ.js +0 -136
- package/dist/chunk-QZZEAHWJ.js.map +0 -1
- package/dist/chunk-SEVJPLZC.js +0 -260
- package/dist/chunk-SEVJPLZC.js.map +0 -1
- package/dist/chunk-TBKU5XQI.js +0 -228
- package/dist/chunk-TBKU5XQI.js.map +0 -1
- package/dist/chunk-UPAMRDX4.js +0 -233
- package/dist/chunk-UPAMRDX4.js.map +0 -1
- package/dist/chunk-VQRS7VUC.js +0 -342
- package/dist/chunk-VQRS7VUC.js.map +0 -1
- package/dist/chunk-WG7TM7UV.js +0 -30
- package/dist/chunk-WG7TM7UV.js.map +0 -1
- package/dist/chunk-X3BTXCJ4.js +0 -262
- package/dist/chunk-X3BTXCJ4.js.map +0 -1
- package/dist/chunk-XXFF3RRD.js +0 -162
- package/dist/chunk-XXFF3RRD.js.map +0 -1
- package/dist/chunk-ZFNOM7WR.js +0 -27
- package/dist/chunk-ZFNOM7WR.js.map +0 -1
- package/dist/chunk-ZNCCYTFG.js +0 -170
- package/dist/chunk-ZNCCYTFG.js.map +0 -1
|
@@ -1,10 +1,185 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { benchRoot } from "./_harness.js";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
//#region src/benchmarks/toollm.ts
|
|
5
|
+
/**
|
|
6
|
+
* ToolLLM/ToolBench adapter.
|
|
7
|
+
*
|
|
8
|
+
* ToolBench task loading is useful for breadth, but the official ToolEval pass
|
|
9
|
+
* rate evaluator is LLM-based and stochastic. This adapter therefore scores
|
|
10
|
+
* only ToolBench's deterministic API-selection labels (`relevant APIs`). It
|
|
11
|
+
* never records a full ToolEval pass-rate score.
|
|
12
|
+
*/
|
|
13
|
+
const FIXTURES = join(benchRoot, "fixtures", "toollm.json");
|
|
14
|
+
const DEFAULT_QUERY_REL = join("data_example", "instruction", "G1_query.json");
|
|
15
|
+
const toolbenchDir = () => process.env.TOOLBENCH_DIR;
|
|
16
|
+
const queryFile = (dir) => process.env.TOOLLM_QUERY_FILE ?? join(dir, DEFAULT_QUERY_REL);
|
|
17
|
+
const toollmOutput = { parse(events) {
|
|
18
|
+
let text = "";
|
|
19
|
+
for (const ev of events) {
|
|
20
|
+
const d = ev?.data;
|
|
21
|
+
const t = d?.finalText ?? d?.text ?? d?.result;
|
|
22
|
+
if (typeof t === "string" && t.length > 0) text = t;
|
|
23
|
+
}
|
|
24
|
+
return text.trim();
|
|
25
|
+
} };
|
|
26
|
+
function rowToTask(row) {
|
|
27
|
+
const relevantApis = normalizeApiPairs(row["relevant APIs"] ?? []);
|
|
28
|
+
const meta = {
|
|
29
|
+
queryId: String(row.query_id),
|
|
30
|
+
apiList: row.api_list ?? [],
|
|
31
|
+
relevantApis,
|
|
32
|
+
deterministicJudge: "api-selection"
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
id: String(row.query_id),
|
|
36
|
+
prompt: [
|
|
37
|
+
"Solve this ToolLLM/ToolBench API-use task.",
|
|
38
|
+
"Use only the listed APIs/tools and return the completed tool-use trace plus final answer.",
|
|
39
|
+
"",
|
|
40
|
+
`Query: ${row.query}`,
|
|
41
|
+
"",
|
|
42
|
+
`Available APIs: ${JSON.stringify(row.api_list ?? [], null, 2)}`,
|
|
43
|
+
"",
|
|
44
|
+
"Return the APIs you used as JSON: {\"api_calls\":[{\"tool_name\":\"...\",\"api_name\":\"...\"}]}."
|
|
45
|
+
].join("\n"),
|
|
46
|
+
metadata: meta
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function normalizeApiPart(value) {
|
|
50
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
51
|
+
}
|
|
52
|
+
function apiKey(pair) {
|
|
53
|
+
return `${normalizeApiPart(pair[0])}.${normalizeApiPart(pair[1])}`;
|
|
54
|
+
}
|
|
55
|
+
function normalizeApiPairs(value) {
|
|
56
|
+
if (!Array.isArray(value)) return [];
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const item of value) if (Array.isArray(item) && typeof item[0] === "string" && typeof item[1] === "string") out.push([item[0], item[1]]);
|
|
59
|
+
else if (item && typeof item === "object" && typeof item.tool_name === "string" && typeof item.api_name === "string") out.push([item.tool_name, item.api_name]);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function readMeta(task) {
|
|
63
|
+
const md = task.metadata;
|
|
64
|
+
if (!md || !Array.isArray(md.relevantApis)) throw new Error(`ToolLLM task ${task.id} missing metadata — loadTasks did not populate deterministic API-selection labels`);
|
|
65
|
+
return md;
|
|
66
|
+
}
|
|
67
|
+
function selectRows(rows, opts) {
|
|
68
|
+
let tasks = rows.map(rowToTask);
|
|
69
|
+
if (opts.ids) {
|
|
70
|
+
const want = new Set(opts.ids);
|
|
71
|
+
tasks = tasks.filter((task) => want.has(task.id));
|
|
72
|
+
} else if (opts.limit !== void 0) tasks = tasks.slice(0, opts.limit);
|
|
73
|
+
if (tasks.length === 0) throw new Error(`ToolLLM: no tasks matched ${JSON.stringify(opts)}`);
|
|
74
|
+
return tasks;
|
|
75
|
+
}
|
|
76
|
+
function assertDeterministicSubset(tasks, source) {
|
|
77
|
+
const missing = tasks.filter((task) => readMeta(task).relevantApis.length === 0).map((task) => task.id);
|
|
78
|
+
if (missing.length > 0) throw new Error(`ToolLLM deterministic API-selection labels missing for ${missing.length}/${tasks.length} task(s) from ${source}: ${missing.slice(0, 5).join(", ")}. Use a ToolBench query file that includes "relevant APIs" labels, or do not score ToolLLM in agent-bench.`);
|
|
79
|
+
}
|
|
80
|
+
async function loadFixtures(opts) {
|
|
81
|
+
const rows = JSON.parse(await readFile(FIXTURES, "utf8"));
|
|
82
|
+
console.warn(`[toollm] TOOLLM_FIXTURES=1 — loading ${rows.length} adapter fixtures`);
|
|
83
|
+
const tasks = selectRows(rows, opts);
|
|
84
|
+
assertDeterministicSubset(tasks, FIXTURES);
|
|
85
|
+
return tasks;
|
|
86
|
+
}
|
|
87
|
+
async function loadOfficialTasks(dir, opts) {
|
|
88
|
+
const source = queryFile(dir);
|
|
89
|
+
const tasks = selectRows(JSON.parse(await readFile(source, "utf8")), opts);
|
|
90
|
+
assertDeterministicSubset(tasks, source);
|
|
91
|
+
return tasks;
|
|
92
|
+
}
|
|
93
|
+
function extractJsonBlock(text) {
|
|
94
|
+
const raw = ([...text.matchAll(/```(?:json)?\s*\n([\s\S]*?)```/g)].at(-1)?.[1] ?? text).trim();
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(raw);
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function extractCalledApis(text, expected) {
|
|
102
|
+
const parsed = extractJsonBlock(text);
|
|
103
|
+
if (parsed && typeof parsed === "object") {
|
|
104
|
+
const raw = parsed;
|
|
105
|
+
const fromApiCalls = normalizeApiPairs(raw.api_calls);
|
|
106
|
+
if (fromApiCalls.length > 0) return {
|
|
107
|
+
pairs: fromApiCalls,
|
|
108
|
+
source: "structured-json"
|
|
109
|
+
};
|
|
110
|
+
const fromCalls = normalizeApiPairs(raw.calls);
|
|
111
|
+
if (fromCalls.length > 0) return {
|
|
112
|
+
pairs: fromCalls,
|
|
113
|
+
source: "structured-json"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const lower = text.toLowerCase();
|
|
117
|
+
return {
|
|
118
|
+
pairs: expected.filter(([tool, api]) => {
|
|
119
|
+
const toolNeedle = normalizeApiPart(tool);
|
|
120
|
+
const apiNeedle = normalizeApiPart(api);
|
|
121
|
+
return lower.replace(/[^a-z0-9]+/g, "").includes(`${toolNeedle}${apiNeedle}`) || lower.includes(tool.toLowerCase()) && lower.includes(api.toLowerCase());
|
|
122
|
+
}),
|
|
123
|
+
source: "text-mention"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function scoreApiSelection(task, artifact) {
|
|
127
|
+
const meta = readMeta(task);
|
|
128
|
+
if (meta.relevantApis.length === 0) throw new Error(`ToolLLM task ${task.id} has no deterministic API-selection labels; refusing to score`);
|
|
129
|
+
const expected = new Set(meta.relevantApis.map(apiKey));
|
|
130
|
+
const extracted = extractCalledApis(artifact, meta.relevantApis);
|
|
131
|
+
const calledPairs = extracted.pairs;
|
|
132
|
+
const called = new Set(calledPairs.map(apiKey));
|
|
133
|
+
const truePositives = [...called].filter((key) => expected.has(key)).length;
|
|
134
|
+
const precision = called.size === 0 ? 0 : truePositives / called.size;
|
|
135
|
+
const recall = truePositives / expected.size;
|
|
136
|
+
const score = expected.size === 0 ? 0 : recall;
|
|
137
|
+
return {
|
|
138
|
+
resolved: extracted.source === "structured-json" && recall === 1 && precision === 1,
|
|
139
|
+
score,
|
|
140
|
+
detail: JSON.stringify({
|
|
141
|
+
scoring: "api-selection-only",
|
|
142
|
+
extractionSource: extracted.source,
|
|
143
|
+
queryId: meta.queryId,
|
|
144
|
+
expected: meta.relevantApis,
|
|
145
|
+
called: calledPairs,
|
|
146
|
+
precision,
|
|
147
|
+
recall,
|
|
148
|
+
fullToolEvalScore: null
|
|
149
|
+
})
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function createToolLlmAdapter() {
|
|
153
|
+
const fixturesMode = process.env.TOOLLM_FIXTURES === "1";
|
|
154
|
+
return {
|
|
155
|
+
name: "toollm",
|
|
156
|
+
output: toollmOutput,
|
|
157
|
+
async preflight() {
|
|
158
|
+
if (fixturesMode) return;
|
|
159
|
+
const dir = toolbenchDir();
|
|
160
|
+
if (!dir) throw new Error("TOOLBENCH_DIR is required. Fix: clone https://github.com/OpenBMB/ToolBench and set TOOLBENCH_DIR=/path/to/ToolBench.");
|
|
161
|
+
await loadOfficialTasks(dir, { limit: 1 });
|
|
162
|
+
},
|
|
163
|
+
async loadTasks(opts = {}) {
|
|
164
|
+
if (fixturesMode) return loadFixtures(opts);
|
|
165
|
+
const dir = toolbenchDir();
|
|
166
|
+
if (!dir) throw new Error("TOOLBENCH_DIR is required to load ToolLLM tasks");
|
|
167
|
+
return loadOfficialTasks(dir, opts);
|
|
168
|
+
},
|
|
169
|
+
async goldArtifact(task) {
|
|
170
|
+
const meta = readMeta(task);
|
|
171
|
+
if (meta.relevantApis.length === 0) return void 0;
|
|
172
|
+
return JSON.stringify({ api_calls: meta.relevantApis.map(([tool_name, api_name]) => ({
|
|
173
|
+
tool_name,
|
|
174
|
+
api_name
|
|
175
|
+
})) }, null, 2);
|
|
176
|
+
},
|
|
177
|
+
async judge(task, artifact) {
|
|
178
|
+
return scoreApiSelection(task, artifact);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { createToolLlmAdapter, toollmOutput };
|
|
184
|
+
|
|
10
185
|
//# sourceMappingURL=toollm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"file":"toollm.js","names":[],"sources":["../../src/benchmarks/toollm.ts"],"sourcesContent":["/**\n * ToolLLM/ToolBench adapter.\n *\n * ToolBench task loading is useful for breadth, but the official ToolEval pass\n * rate evaluator is LLM-based and stochastic. This adapter therefore scores\n * only ToolBench's deterministic API-selection labels (`relevant APIs`). It\n * never records a full ToolEval pass-rate score.\n */\n\nimport { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { OutputAdapter } from '@tangle-network/agent-runtime/loops'\nimport { benchRoot } from './_harness'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst FIXTURES = join(benchRoot, 'fixtures', 'toollm.json')\nconst DEFAULT_QUERY_REL = join('data_example', 'instruction', 'G1_query.json')\n\ninterface ToolApi {\n category_name?: string\n tool_name: string\n api_name: string\n api_description?: string\n required_parameters?: unknown[]\n optional_parameters?: unknown[]\n method?: string\n}\n\ninterface ToolBenchRow {\n query_id: number | string\n query: string\n api_list?: ToolApi[]\n 'relevant APIs'?: Array<[string, string]>\n}\n\ninterface ToolBenchMeta {\n queryId: string\n apiList: ToolApi[]\n relevantApis: Array<[string, string]>\n deterministicJudge: 'api-selection'\n}\n\nconst toolbenchDir = (): string | undefined => process.env.TOOLBENCH_DIR\nconst queryFile = (dir: string): string => process.env.TOOLLM_QUERY_FILE ?? join(dir, DEFAULT_QUERY_REL)\n\nexport const toollmOutput: OutputAdapter<string> = {\n parse(events) {\n let text = ''\n for (const ev of events) {\n const d = (ev as { data?: Record<string, unknown> })?.data\n const t = d?.finalText ?? d?.text ?? d?.result\n if (typeof t === 'string' && t.length > 0) text = t\n }\n return text.trim()\n },\n}\n\nfunction rowToTask(row: ToolBenchRow): BenchTask {\n const relevantApis = normalizeApiPairs(row['relevant APIs'] ?? [])\n const meta: ToolBenchMeta = {\n queryId: String(row.query_id),\n apiList: row.api_list ?? [],\n relevantApis,\n deterministicJudge: 'api-selection',\n }\n return {\n id: String(row.query_id),\n prompt: [\n 'Solve this ToolLLM/ToolBench API-use task.',\n 'Use only the listed APIs/tools and return the completed tool-use trace plus final answer.',\n '',\n `Query: ${row.query}`,\n '',\n `Available APIs: ${JSON.stringify(row.api_list ?? [], null, 2)}`,\n '',\n 'Return the APIs you used as JSON: {\"api_calls\":[{\"tool_name\":\"...\",\"api_name\":\"...\"}]}.',\n ].join('\\n'),\n metadata: meta as unknown as Record<string, unknown>,\n }\n}\n\nfunction normalizeApiPart(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, '')\n}\n\nfunction apiKey(pair: readonly [string, string]): string {\n return `${normalizeApiPart(pair[0])}.${normalizeApiPart(pair[1])}`\n}\n\nfunction normalizeApiPairs(value: unknown): Array<[string, string]> {\n if (!Array.isArray(value)) return []\n const out: Array<[string, string]> = []\n for (const item of value) {\n if (Array.isArray(item) && typeof item[0] === 'string' && typeof item[1] === 'string') {\n out.push([item[0], item[1]])\n } else if (\n item && typeof item === 'object'\n && typeof (item as { tool_name?: unknown }).tool_name === 'string'\n && typeof (item as { api_name?: unknown }).api_name === 'string'\n ) {\n out.push([(item as { tool_name: string }).tool_name, (item as { api_name: string }).api_name])\n }\n }\n return out\n}\n\nfunction readMeta(task: BenchTask): ToolBenchMeta {\n const md = task.metadata\n if (!md || !Array.isArray(md.relevantApis)) {\n throw new Error(`ToolLLM task ${task.id} missing metadata — loadTasks did not populate deterministic API-selection labels`)\n }\n return md as unknown as ToolBenchMeta\n}\n\nfunction selectRows(rows: ToolBenchRow[], opts: LoadOptions): BenchTask[] {\n let tasks = rows.map(rowToTask)\n if (opts.ids) {\n const want = new Set(opts.ids)\n tasks = tasks.filter((task) => want.has(task.id))\n } else if (opts.limit !== undefined) {\n tasks = tasks.slice(0, opts.limit)\n }\n if (tasks.length === 0) throw new Error(`ToolLLM: no tasks matched ${JSON.stringify(opts)}`)\n return tasks\n}\n\nfunction assertDeterministicSubset(tasks: readonly BenchTask[], source: string): void {\n const missing = tasks.filter((task) => readMeta(task).relevantApis.length === 0).map((task) => task.id)\n if (missing.length > 0) {\n throw new Error(\n `ToolLLM deterministic API-selection labels missing for ${missing.length}/${tasks.length} task(s) from ${source}: ${missing.slice(0, 5).join(', ')}. ` +\n 'Use a ToolBench query file that includes \"relevant APIs\" labels, or do not score ToolLLM in agent-bench.',\n )\n }\n}\n\nasync function loadFixtures(opts: LoadOptions): Promise<BenchTask[]> {\n const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as ToolBenchRow[]\n console.warn(`[toollm] TOOLLM_FIXTURES=1 — loading ${rows.length} adapter fixtures`)\n const tasks = selectRows(rows, opts)\n assertDeterministicSubset(tasks, FIXTURES)\n return tasks\n}\n\nasync function loadOfficialTasks(dir: string, opts: LoadOptions): Promise<BenchTask[]> {\n const source = queryFile(dir)\n const tasks = selectRows(JSON.parse(await readFile(source, 'utf8')) as ToolBenchRow[], opts)\n assertDeterministicSubset(tasks, source)\n return tasks\n}\n\nfunction extractJsonBlock(text: string): unknown {\n const fences = [...text.matchAll(/```(?:json)?\\s*\\n([\\s\\S]*?)```/g)]\n const raw = (fences.at(-1)?.[1] ?? text).trim()\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n}\n\ninterface ExtractedApis {\n pairs: Array<[string, string]>\n source: 'structured-json' | 'text-mention'\n}\n\nfunction extractCalledApis(text: string, expected: readonly [string, string][]): ExtractedApis {\n const parsed = extractJsonBlock(text)\n if (parsed && typeof parsed === 'object') {\n const raw = parsed as Record<string, unknown>\n const fromApiCalls = normalizeApiPairs(raw.api_calls)\n if (fromApiCalls.length > 0) return { pairs: fromApiCalls, source: 'structured-json' }\n const fromCalls = normalizeApiPairs(raw.calls)\n if (fromCalls.length > 0) return { pairs: fromCalls, source: 'structured-json' }\n }\n\n const lower = text.toLowerCase()\n return {\n pairs: expected.filter(([tool, api]) => {\n const toolNeedle = normalizeApiPart(tool)\n const apiNeedle = normalizeApiPart(api)\n const compactText = lower.replace(/[^a-z0-9]+/g, '')\n return compactText.includes(`${toolNeedle}${apiNeedle}`) || (lower.includes(tool.toLowerCase()) && lower.includes(api.toLowerCase()))\n }),\n source: 'text-mention',\n }\n}\n\nfunction scoreApiSelection(task: BenchTask, artifact: string): BenchScore {\n const meta = readMeta(task)\n if (meta.relevantApis.length === 0) {\n throw new Error(`ToolLLM task ${task.id} has no deterministic API-selection labels; refusing to score`)\n }\n const expected = new Set(meta.relevantApis.map(apiKey))\n const extracted = extractCalledApis(artifact, meta.relevantApis)\n const calledPairs = extracted.pairs\n const called = new Set(calledPairs.map(apiKey))\n const truePositives = [...called].filter((key) => expected.has(key)).length\n const precision = called.size === 0 ? 0 : truePositives / called.size\n const recall = truePositives / expected.size\n const score = expected.size === 0 ? 0 : recall\n const resolved = extracted.source === 'structured-json' && recall === 1 && precision === 1\n return {\n resolved,\n score,\n detail: JSON.stringify({\n scoring: 'api-selection-only',\n extractionSource: extracted.source,\n queryId: meta.queryId,\n expected: meta.relevantApis,\n called: calledPairs,\n precision,\n recall,\n fullToolEvalScore: null,\n }),\n }\n}\n\nexport function createToolLlmAdapter(): BenchmarkAdapter {\n const fixturesMode = process.env.TOOLLM_FIXTURES === '1'\n\n return {\n name: 'toollm',\n output: toollmOutput,\n\n async preflight() {\n if (fixturesMode) return\n const dir = toolbenchDir()\n if (!dir) {\n throw new Error('TOOLBENCH_DIR is required. Fix: clone https://github.com/OpenBMB/ToolBench and set TOOLBENCH_DIR=/path/to/ToolBench.')\n }\n await loadOfficialTasks(dir, { limit: 1 })\n },\n\n async loadTasks(opts: LoadOptions = {}) {\n if (fixturesMode) return loadFixtures(opts)\n const dir = toolbenchDir()\n if (!dir) throw new Error('TOOLBENCH_DIR is required to load ToolLLM tasks')\n return loadOfficialTasks(dir, opts)\n },\n\n async goldArtifact(task: BenchTask) {\n const meta = readMeta(task)\n if (meta.relevantApis.length === 0) return undefined\n return JSON.stringify({\n api_calls: meta.relevantApis.map(([tool_name, api_name]) => ({ tool_name, api_name })),\n }, null, 2)\n },\n\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n return scoreApiSelection(task, artifact)\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,32 +1,6 @@
|
|
|
1
|
-
import { BenchmarkAdapter } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Trata Hedge-Bench adapter (Trata-Inc/trata-hedge-bench). 102 financial
|
|
6
|
-
* analysis tasks across 6 domains (private equity, managed care, industrials,
|
|
7
|
-
* vertical SaaS, REITs, insurance). Each task bundles real earnings-call
|
|
8
|
-
* transcripts, financial statements, press releases, and SEC filings under
|
|
9
|
-
* `environment/data/`; the agent must produce a grounded analysis citing those
|
|
10
|
-
* files.
|
|
11
|
-
*
|
|
12
|
-
* Harbor architecture: the original benchmark runs each task in a Docker
|
|
13
|
-
* container with file-read tools. This adapter skips Harbor and embeds the data
|
|
14
|
-
* files directly in the worker prompt so any router backend can score it. Large
|
|
15
|
-
* files (> 30 KB) are truncated to fit within model context limits. The judge
|
|
16
|
-
* reads the actual files from disk for citation verification.
|
|
17
|
-
*
|
|
18
|
-
* Judge: a 3-stage LLM cascade faithful to grade.py —
|
|
19
|
-
* Task 1: hallucination check (cited-file context + agent answer)
|
|
20
|
-
* Task 2: per-move coverage check (ground truth + agent answer + flagged claims)
|
|
21
|
-
* Task 3: synthesis check (ground truth + agent answer)
|
|
22
|
-
* Score 0–4: themes_covered == num_themes && synthesis → 4, all themes → 3,
|
|
23
|
-
* ≥2 themes → 2, ≥1 theme → 1, else → 0. Normalized to 0..1 for BenchScore;
|
|
24
|
-
* resolved = score 4 (sparse reward).
|
|
25
|
-
*
|
|
26
|
-
* Requires TANGLE_API_KEY and TRATA_BENCH_ROOT=/tmp/trata-hedge-bench.
|
|
27
|
-
* Clone: git clone --depth 1 https://github.com/Trata-Inc/trata-hedge-bench /tmp/trata-hedge-bench
|
|
28
|
-
*/
|
|
29
|
-
|
|
1
|
+
import { BenchmarkAdapter } from "./types.js";
|
|
2
|
+
//#region src/benchmarks/trata-hedge.d.ts
|
|
30
3
|
declare function createTrataHedgeAdapter(): BenchmarkAdapter;
|
|
31
|
-
|
|
4
|
+
//#endregion
|
|
32
5
|
export { createTrataHedgeAdapter };
|
|
6
|
+
//# sourceMappingURL=trata-hedge.d.ts.map
|
|
@@ -1,7 +1,337 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
//#region src/benchmarks/trata-hedge.ts
|
|
4
|
+
/**
|
|
5
|
+
* Trata Hedge-Bench adapter (Trata-Inc/trata-hedge-bench). 102 financial
|
|
6
|
+
* analysis tasks across 6 domains (private equity, managed care, industrials,
|
|
7
|
+
* vertical SaaS, REITs, insurance). Each task bundles real earnings-call
|
|
8
|
+
* transcripts, financial statements, press releases, and SEC filings under
|
|
9
|
+
* `environment/data/`; the agent must produce a grounded analysis citing those
|
|
10
|
+
* files.
|
|
11
|
+
*
|
|
12
|
+
* Harbor architecture: the original benchmark runs each task in a Docker
|
|
13
|
+
* container with file-read tools. This adapter skips Harbor and embeds the data
|
|
14
|
+
* files directly in the worker prompt so any router backend can score it. Large
|
|
15
|
+
* files (> 30 KB) are truncated to fit within model context limits. The judge
|
|
16
|
+
* reads the actual files from disk for citation verification.
|
|
17
|
+
*
|
|
18
|
+
* Judge: a 3-stage LLM cascade faithful to grade.py —
|
|
19
|
+
* Task 1: hallucination check (cited-file context + agent answer)
|
|
20
|
+
* Task 2: per-move coverage check (ground truth + agent answer + flagged claims)
|
|
21
|
+
* Task 3: synthesis check (ground truth + agent answer)
|
|
22
|
+
* Score 0–4: themes_covered == num_themes && synthesis → 4, all themes → 3,
|
|
23
|
+
* ≥2 themes → 2, ≥1 theme → 1, else → 0. Normalized to 0..1 for BenchScore;
|
|
24
|
+
* resolved = score 4 (sparse reward).
|
|
25
|
+
*
|
|
26
|
+
* Requires TANGLE_API_KEY and TRATA_BENCH_ROOT=/tmp/trata-hedge-bench.
|
|
27
|
+
* Clone: git clone --depth 1 https://github.com/Trata-Inc/trata-hedge-bench /tmp/trata-hedge-bench
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_BENCH_ROOT = "/tmp/trata-hedge-bench";
|
|
30
|
+
const MAX_FILE_BYTES = 8e3;
|
|
31
|
+
const MAX_DATA_CHARS = 85e3;
|
|
32
|
+
const WORKER_SUFFIX = [
|
|
33
|
+
"",
|
|
34
|
+
"The data files above are your only source. Cite each claim with the filename that supports it.",
|
|
35
|
+
"Write your complete analysis to the text block that starts with \"ANALYSIS:\" on a line by itself.",
|
|
36
|
+
"Take a clear position on the topic. Every factual claim must name the file it comes from."
|
|
37
|
+
].join("\n");
|
|
38
|
+
const JUDGE_MODEL = "gemini-2.5-pro";
|
|
39
|
+
function readText(p, maxBytes = MAX_FILE_BYTES) {
|
|
40
|
+
try {
|
|
41
|
+
const buf = readFileSync(p);
|
|
42
|
+
if (buf.length <= maxBytes) return buf.toString("utf8");
|
|
43
|
+
return buf.subarray(0, maxBytes).toString("utf8") + `\n[… truncated at ${maxBytes} bytes …]`;
|
|
44
|
+
} catch {
|
|
45
|
+
return `[unreadable: ${p}]`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function listFilesRec(dir) {
|
|
49
|
+
const out = [];
|
|
50
|
+
try {
|
|
51
|
+
for (const name of readdirSync(dir)) {
|
|
52
|
+
const full = join(dir, name);
|
|
53
|
+
try {
|
|
54
|
+
if (statSync(full).isDirectory()) out.push(...listFilesRec(full));
|
|
55
|
+
else out.push(full);
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
return out.sort();
|
|
60
|
+
}
|
|
61
|
+
function buildDataBlock(dataDir) {
|
|
62
|
+
const files = listFilesRec(dataDir);
|
|
63
|
+
if (files.length === 0) return "(no data files found)";
|
|
64
|
+
const priority = (f) => {
|
|
65
|
+
if (f.includes("company_profiles")) return 0;
|
|
66
|
+
if (f.includes("earnings_call")) return 1;
|
|
67
|
+
if (/financials\/(income|cash_flow|balance)/.test(f) && !/bam_|bx_|kkr_|ares_|cg_|owl_|fsk_|cat_|de_|tdg_|car_/.test(f)) return 2;
|
|
68
|
+
if (f.includes("sec_filings")) return 3;
|
|
69
|
+
if (f.includes("investor_pres")) return 4;
|
|
70
|
+
if (f.includes("press_release")) return 5;
|
|
71
|
+
return 6;
|
|
72
|
+
};
|
|
73
|
+
const sorted = [...files].sort((a, b) => priority(a) - priority(b) || a.localeCompare(b));
|
|
74
|
+
const blocks = [];
|
|
75
|
+
let total = 0;
|
|
76
|
+
for (const f of sorted) {
|
|
77
|
+
const chunk = `\n=== FILE: data/${f.slice(dataDir.length + 1)} ===\n${readText(f)}`;
|
|
78
|
+
if (total + chunk.length > MAX_DATA_CHARS) {
|
|
79
|
+
blocks.push(`\n[… ${sorted.length - blocks.length} more files omitted to stay within context limit …]`);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
blocks.push(chunk);
|
|
83
|
+
total += chunk.length;
|
|
84
|
+
}
|
|
85
|
+
return blocks.join("");
|
|
86
|
+
}
|
|
87
|
+
function buildCitedDataBlock(answerText, dataDir) {
|
|
88
|
+
const files = listFilesRec(dataDir);
|
|
89
|
+
const cited = [];
|
|
90
|
+
for (const f of files) {
|
|
91
|
+
const rel = f.slice(dataDir.length + 1);
|
|
92
|
+
const name = rel.split("/").pop() ?? "";
|
|
93
|
+
if (answerText.includes(rel) || answerText.includes(name)) cited.push(f);
|
|
94
|
+
}
|
|
95
|
+
if (cited.length === 0) return "(The agent did not cite any data files, or no cited file could be located.)";
|
|
96
|
+
const blocks = [];
|
|
97
|
+
for (const f of cited) {
|
|
98
|
+
const rel = f.slice(dataDir.length + 1);
|
|
99
|
+
blocks.push(`\n=== FILE: data/${rel} ===\n${readText(f)}`);
|
|
100
|
+
}
|
|
101
|
+
return blocks.join("");
|
|
102
|
+
}
|
|
103
|
+
function parseThemeMoveCounts(groundTruth) {
|
|
104
|
+
const lines = groundTruth.split("\n");
|
|
105
|
+
const themeStarts = lines.reduce((acc, l, i) => {
|
|
106
|
+
if (/^\d+\./.test(l.trim())) acc.push(i);
|
|
107
|
+
return acc;
|
|
108
|
+
}, []);
|
|
109
|
+
return themeStarts.map((start, k) => {
|
|
110
|
+
const end = k + 1 < themeStarts.length ? themeStarts[k + 1] : lines.length;
|
|
111
|
+
const block = lines.slice(start, end).join("\n");
|
|
112
|
+
return {
|
|
113
|
+
label: lines[start].replace(/^\d+\.\s*/, "").trim(),
|
|
114
|
+
nMoves: (block.match(/^\s*\[[a-z]\]\s+/gm) ?? []).length
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function coverageThreshold(nMoves) {
|
|
119
|
+
return Math.max(1, Math.min(nMoves - 1, 3));
|
|
120
|
+
}
|
|
121
|
+
function judgeRouter() {
|
|
122
|
+
const key = process.env.TANGLE_API_KEY;
|
|
123
|
+
if (!key) throw new Error("TANGLE_API_KEY is required for the Trata hedge-bench judge");
|
|
124
|
+
const model = process.env.JUDGE_MODEL ?? JUDGE_MODEL;
|
|
125
|
+
return {
|
|
126
|
+
baseUrl: process.env.ROUTER_BASE ?? "https://router.tangle.tools/v1",
|
|
127
|
+
key,
|
|
128
|
+
model
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function parseJsonFallback(raw) {
|
|
132
|
+
let s = raw.trim();
|
|
133
|
+
if (s.startsWith("```")) s = s.split("\n").slice(1).join("\n");
|
|
134
|
+
if (s.endsWith("```")) s = s.slice(0, s.lastIndexOf("```"));
|
|
135
|
+
s = s.replace(/,(\s*[}\]])/g, "$1").trim();
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(s);
|
|
138
|
+
} catch {
|
|
139
|
+
try {
|
|
140
|
+
const m = s.match(/\{[\s\S]*\}/);
|
|
141
|
+
if (m) return JSON.parse(m[0]);
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
async function callJudge(router, prompt, maxAttempts = 2) {
|
|
147
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
148
|
+
const res = await fetch(`${router.baseUrl}/chat/completions`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"content-type": "application/json",
|
|
152
|
+
authorization: `Bearer ${router.key}`
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
model: router.model,
|
|
156
|
+
temperature: 0,
|
|
157
|
+
max_tokens: 16384,
|
|
158
|
+
messages: [{
|
|
159
|
+
role: "user",
|
|
160
|
+
content: prompt
|
|
161
|
+
}]
|
|
162
|
+
})
|
|
163
|
+
});
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
if (i < maxAttempts - 1) continue;
|
|
166
|
+
throw new Error(`Trata judge HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
167
|
+
}
|
|
168
|
+
const content = (await res.json()).choices?.[0]?.message?.content;
|
|
169
|
+
if (typeof content !== "string") continue;
|
|
170
|
+
const parsed = parseJsonFallback(content);
|
|
171
|
+
if (parsed !== null) return parsed;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
async function task1HallucinationCheck(meta, answer, router) {
|
|
176
|
+
const dataContext = `The block below contains the contents of the data files the agent cited in their answer. Treat this as the ground truth for factual verification.
|
|
177
|
+
|
|
178
|
+
<cited_data>\n${buildCitedDataBlock(answer, meta.dataDir)}\n</cited_data>`;
|
|
179
|
+
return await callJudge(router, meta.gradingTask1.replace("{data_context}", dataContext).replace("{agent_answer}", answer)) ?? {
|
|
180
|
+
hallucinations_detected: false,
|
|
181
|
+
unverifiable_claims: []
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function task2PerMoveCheck(meta, answer, flaggedClaims, router) {
|
|
185
|
+
const claimsBlock = flaggedClaims && flaggedClaims.length > 0 ? JSON.stringify(flaggedClaims, null, 2) : "(No claims were flagged as hallucinations.)";
|
|
186
|
+
return await callJudge(router, meta.gradingTask2.replace("{ground_truth}", meta.groundTruth).replace("{agent_answer}", answer).replace("{flagged_claims_block}", claimsBlock).replace("{num_themes}", String(meta.themeMoveCounts.length))) ?? { themes: [] };
|
|
187
|
+
}
|
|
188
|
+
async function task3SynthesisCheck(meta, answer, router) {
|
|
189
|
+
return await callJudge(router, meta.gradingTask3.replace("{ground_truth}", meta.groundTruth).replace("{agent_answer}", answer)) ?? { synthesis_found: null };
|
|
190
|
+
}
|
|
191
|
+
async function runTriataJudge(meta, answer, router) {
|
|
192
|
+
if (!answer.trim()) return {
|
|
193
|
+
resolved: false,
|
|
194
|
+
score: 0,
|
|
195
|
+
detail: JSON.stringify({ error: "empty answer" })
|
|
196
|
+
};
|
|
197
|
+
const t1 = await task1HallucinationCheck(meta, answer, router);
|
|
198
|
+
const flaggedClaims = t1.unverifiable_claims ?? [];
|
|
199
|
+
const [t2, t3] = await Promise.all([task2PerMoveCheck(meta, answer, flaggedClaims, router), task3SynthesisCheck(meta, answer, router)]);
|
|
200
|
+
const judgeThemes = t2.themes ?? [];
|
|
201
|
+
const themesHit = [];
|
|
202
|
+
const themesMissed = [];
|
|
203
|
+
for (let i = 0; i < meta.themeMoveCounts.length; i++) {
|
|
204
|
+
const { label, nMoves } = meta.themeMoveCounts[i];
|
|
205
|
+
const judge = judgeThemes[i] ?? {};
|
|
206
|
+
const movesHit = (judge.moves_hit ?? []).map((m) => m.replace(/[\[\]]/g, ""));
|
|
207
|
+
const tainted = new Set((judge.moves_tainted ?? []).map((m) => m.replace(/[\[\]]/g, "")));
|
|
208
|
+
const validHit = movesHit.filter((m) => !tainted.has(m));
|
|
209
|
+
const threshold = coverageThreshold(nMoves);
|
|
210
|
+
if (validHit.length >= threshold) themesHit.push(label);
|
|
211
|
+
else themesMissed.push(label);
|
|
212
|
+
}
|
|
213
|
+
const numThemes = meta.themeMoveCounts.length;
|
|
214
|
+
const synth = t3.synthesis_found;
|
|
215
|
+
const hasSynthesis = typeof synth === "string" && synth.length > 0 && synth !== "null";
|
|
216
|
+
let rawScore;
|
|
217
|
+
if (numThemes === 0) rawScore = 0;
|
|
218
|
+
else if (themesHit.length === numThemes && hasSynthesis) rawScore = 4;
|
|
219
|
+
else if (themesHit.length === numThemes) rawScore = 3;
|
|
220
|
+
else if (themesHit.length >= 2) rawScore = 2;
|
|
221
|
+
else if (themesHit.length >= 1) rawScore = 1;
|
|
222
|
+
else rawScore = 0;
|
|
223
|
+
const hallRaw = t1.hallucinations_detected;
|
|
224
|
+
const hallucinations = hallRaw === true || hallRaw === "true";
|
|
225
|
+
return {
|
|
226
|
+
resolved: rawScore === 4,
|
|
227
|
+
score: numThemes > 0 ? rawScore / 4 : 0,
|
|
228
|
+
detail: JSON.stringify({
|
|
229
|
+
rawScore,
|
|
230
|
+
themesHit,
|
|
231
|
+
themesMissed,
|
|
232
|
+
hallucinations,
|
|
233
|
+
unverifiableClaims: flaggedClaims.length,
|
|
234
|
+
synthesis: synth ?? null,
|
|
235
|
+
judgeModel: router.model
|
|
236
|
+
})
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function benchRoot() {
|
|
240
|
+
return process.env.TRATA_BENCH_ROOT ?? DEFAULT_BENCH_ROOT;
|
|
241
|
+
}
|
|
242
|
+
function loadTask(taskId) {
|
|
243
|
+
const taskDir = join(benchRoot(), "environments", taskId);
|
|
244
|
+
const dataDir = join(taskDir, "environment", "data");
|
|
245
|
+
const testsDir = join(taskDir, "tests");
|
|
246
|
+
const instruction = readText(join(taskDir, "instruction.md"), Infinity);
|
|
247
|
+
const dataBlock = buildDataBlock(dataDir);
|
|
248
|
+
const groundTruth = readText(join(testsDir, "ground_truth.txt"), Infinity);
|
|
249
|
+
const gradingTask1 = readText(join(testsDir, "grading_prompt_task1.md"), Infinity);
|
|
250
|
+
const gradingTask2 = readText(join(testsDir, "grading_prompt_task2.md"), Infinity);
|
|
251
|
+
const gradingTask3 = readText(join(testsDir, "grading_prompt_task3.md"), Infinity);
|
|
252
|
+
const themeMoveCounts = parseThemeMoveCounts(groundTruth);
|
|
253
|
+
const prompt = [
|
|
254
|
+
instruction.replace("You are a financial analyst with access to the data in `/app/data/`.", "You are a financial analyst. The relevant financial data files are provided inline below.").replace(/Write your full analysis to `\/app\/answer\.txt`\.?/, "Write your full analysis below.").replace(/`\/app\/data\/`/g, "the data files below"),
|
|
255
|
+
"",
|
|
256
|
+
"## Data files (inline)",
|
|
257
|
+
dataBlock,
|
|
258
|
+
WORKER_SUFFIX
|
|
259
|
+
].join("\n");
|
|
260
|
+
const meta = {
|
|
261
|
+
taskDir,
|
|
262
|
+
taskId,
|
|
263
|
+
dataDir,
|
|
264
|
+
groundTruth,
|
|
265
|
+
gradingTask1,
|
|
266
|
+
gradingTask2,
|
|
267
|
+
gradingTask3,
|
|
268
|
+
themeMoveCounts
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
task: {
|
|
272
|
+
id: taskId,
|
|
273
|
+
prompt,
|
|
274
|
+
metadata: meta
|
|
275
|
+
},
|
|
276
|
+
meta
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function readMeta(task) {
|
|
280
|
+
const md = task.metadata;
|
|
281
|
+
if (!md || typeof md.taskDir !== "string" || typeof md.groundTruth !== "string") throw new Error(`Trata task ${task.id} missing judge metadata — was it loaded by this adapter?`);
|
|
282
|
+
return md;
|
|
283
|
+
}
|
|
284
|
+
/** List all task ids from the environments directory. */
|
|
285
|
+
function listTaskIds(root) {
|
|
286
|
+
try {
|
|
287
|
+
return readdirSync(join(root, "environments")).filter((name) => {
|
|
288
|
+
try {
|
|
289
|
+
return statSync(join(root, "environments", name)).isDirectory();
|
|
290
|
+
} catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}).sort();
|
|
294
|
+
} catch (err) {
|
|
295
|
+
throw new Error(`Trata benchmark environments not found at ${join(root, "environments")}: ${err instanceof Error ? err.message : err}.\nClone the repo: git clone --depth 1 https://github.com/Trata-Inc/trata-hedge-bench ${root}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function extractAnalysis(artifact) {
|
|
299
|
+
if (artifact.startsWith("ANALYSIS:")) return artifact.slice(9).trim();
|
|
300
|
+
const marker = artifact.indexOf("\nANALYSIS:");
|
|
301
|
+
if (marker !== -1) return artifact.slice(marker + 10).trim();
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
304
|
+
function createTrataHedgeAdapter() {
|
|
305
|
+
return {
|
|
306
|
+
name: "trata-hedge",
|
|
307
|
+
async preflight() {
|
|
308
|
+
const root = benchRoot();
|
|
309
|
+
const envDir = join(root, "environments");
|
|
310
|
+
try {
|
|
311
|
+
if (!statSync(envDir).isDirectory()) throw new Error("not a directory");
|
|
312
|
+
} catch {
|
|
313
|
+
throw new Error(`Trata hedge-bench not found at ${envDir}.\nClone: git clone --depth 1 https://github.com/Trata-Inc/trata-hedge-bench ${root}`);
|
|
314
|
+
}
|
|
315
|
+
judgeRouter();
|
|
316
|
+
const ids = listTaskIds(root);
|
|
317
|
+
if (ids.length === 0) throw new Error(`No task directories found under ${envDir}`);
|
|
318
|
+
console.warn(`[trata-hedge] ${ids.length} tasks loaded from ${root}. Worker model reads embedded data inline (Harbor/Docker not required). Judge uses ${process.env.JUDGE_MODEL ?? JUDGE_MODEL} via Tangle router.`);
|
|
319
|
+
},
|
|
320
|
+
async loadTasks(opts = {}) {
|
|
321
|
+
let ids = listTaskIds(benchRoot());
|
|
322
|
+
if (opts.ids) {
|
|
323
|
+
const want = new Set(opts.ids);
|
|
324
|
+
ids = ids.filter((id) => want.has(id));
|
|
325
|
+
} else if (opts.limit !== void 0) ids = ids.slice(0, opts.limit);
|
|
326
|
+
return ids.map((id) => loadTask(id).task);
|
|
327
|
+
},
|
|
328
|
+
async goldArtifact(_task) {},
|
|
329
|
+
async judge(task, artifact) {
|
|
330
|
+
return runTriataJudge(readMeta(task), extractAnalysis(artifact), judgeRouter());
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
export { createTrataHedgeAdapter };
|
|
336
|
+
|
|
7
337
|
//# sourceMappingURL=trata-hedge.js.map
|