@tangle-network/agent-eval 0.140.1 → 0.141.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 +15 -0
- package/dist/analyst/index.d.ts +74 -6
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +6 -6
- package/dist/{benchmark-command-Bo_hkhQf.js → benchmark-command-D8xXNVVh.js} +2239 -1820
- package/dist/benchmark-command-D8xXNVVh.js.map +1 -0
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-HwoBE32G.js → benchmarks-Ce8GdJbc.js} +4 -4
- package/dist/{benchmarks-HwoBE32G.js.map → benchmarks-Ce8GdJbc.js.map} +1 -1
- package/dist/campaign/index.js +3 -3
- package/dist/{campaign-BzjYNYVZ.js → campaign-COFTVy3Q.js} +3 -3
- package/dist/{campaign-BzjYNYVZ.js.map → campaign-COFTVy3Q.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +56 -13
- package/dist/contract/index.js.map +1 -1
- package/dist/{default-registry-BOiBXNFh.js → default-registry-BupFSXSN.js} +108 -20
- package/dist/default-registry-BupFSXSN.js.map +1 -0
- package/dist/default-registry-Ci7wAAR8.d.ts.map +1 -1
- package/dist/{dspy-rlm-engine-Bkak4nzo.js → dspy-rlm-engine-Bw8Zz9CX.js} +7 -4
- package/dist/{dspy-rlm-engine-Bkak4nzo.js.map → dspy-rlm-engine-Bw8Zz9CX.js.map} +1 -1
- package/dist/{extract-usage-C5vMw-0R.js → extract-usage-7l1Xq5ti.js} +2 -2
- package/dist/{extract-usage-C5vMw-0R.js.map → extract-usage-7l1Xq5ti.js.map} +1 -1
- package/dist/index.d.ts +68 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -46
- package/dist/index.js.map +1 -1
- package/dist/{kind-factory-DB7nIs35.js → kind-factory-Bvwe3pup.js} +11 -1
- package/dist/kind-factory-Bvwe3pup.js.map +1 -0
- package/dist/multishot/index.d.ts +29 -6
- package/dist/multishot/index.d.ts.map +1 -1
- package/dist/multishot/index.js +53 -3
- package/dist/multishot/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/replay-BqTgoioO.d.ts.map +1 -1
- package/dist/{replay-k2MsOmv5.js → replay-DjUfTrHD.js} +40 -48
- package/dist/replay-DjUfTrHD.js.map +1 -0
- package/dist/{single-run-lock-CiQThJxB.js → single-run-lock-BcvPQY57.js} +65 -15
- package/dist/single-run-lock-BcvPQY57.js.map +1 -0
- package/dist/{skill-usage-CVVnoIx-.d.ts → skill-usage-CNZTk_LT.d.ts} +8 -4
- package/dist/{skill-usage-CVVnoIx-.d.ts.map → skill-usage-CNZTk_LT.d.ts.map} +1 -1
- package/dist/{skillopt-optimization-method-CSBQ8Qma.js → skillopt-optimization-method-DV6c4ooy.js} +2 -2
- package/dist/{skillopt-optimization-method-CSBQ8Qma.js.map → skillopt-optimization-method-DV6c4ooy.js.map} +1 -1
- package/dist/{store-otlp-vRByAR6h.js → store-otlp-D4I90_vR.js} +2 -2
- package/dist/{store-otlp-vRByAR6h.js.map → store-otlp-D4I90_vR.js.map} +1 -1
- package/dist/traces.js +4 -4
- package/docs/adapters-observability.md +35 -21
- package/docs/concepts.md +1 -0
- package/docs/distributed-driver.md +7 -3
- package/docs/feature-guide.md +1 -0
- package/docs/insight-report.md +1 -1
- package/docs/trace-analysis.md +11 -0
- package/docs/wire-protocol.md +70 -2
- package/package.json +5 -3
- package/dist/benchmark-command-Bo_hkhQf.js.map +0 -1
- package/dist/default-registry-BOiBXNFh.js.map +0 -1
- package/dist/kind-factory-DB7nIs35.js.map +0 -1
- package/dist/replay-k2MsOmv5.js.map +0 -1
- package/dist/single-run-lock-CiQThJxB.js.map +0 -1
|
@@ -2,14 +2,14 @@ import { c as ValidationError, t as AgentEvalError } from "./errors-D-LKuDhb.js"
|
|
|
2
2
|
import { s as resolveModelPricing } from "./metrics-C9YY1OcL.js";
|
|
3
3
|
import { a as CostLedgerPersistenceError, i as CostLedger, n as CostCallConflictError, o as CostReceiptCaptureError, r as CostCeilingReachedError, s as CostReservationExceededError, t as CostAccountingIncompleteError } from "./cost-ledger-DMFxsLKr.js";
|
|
4
4
|
import { c as callLlmJson, f as maximumChargeForLlmRequest, l as costReceiptFromLlm, r as LlmResponseError, t as LlmCallError, u as costReceiptFromLlmError } from "./llm-client-B3WXSH5Y.js";
|
|
5
|
-
import { O as evidenceRefsFromRawFinding, h as TRACE_ANALYSIS_LIMITS, i as runTraceAnalyst } from "./kind-factory-
|
|
5
|
+
import { O as evidenceRefsFromRawFinding, h as TRACE_ANALYSIS_LIMITS, i as runTraceAnalyst } from "./kind-factory-Bvwe3pup.js";
|
|
6
6
|
import { o as makeFinding, r as usageReceiptFromCostLedger } from "./usage-receipt-CgxMEBZq.js";
|
|
7
7
|
import { i as hashCanonical, r as canonicalString } from "./canonical-D011XM8r.js";
|
|
8
|
-
import { n as createRunCostLedger, r as fsCampaignStorage, t as acquireSingleRunLock } from "./single-run-lock-
|
|
9
|
-
import { t as createDspyRlmTraceEngine } from "./dspy-rlm-engine-
|
|
8
|
+
import { n as createRunCostLedger, r as fsCampaignStorage, t as acquireSingleRunLock } from "./single-run-lock-BcvPQY57.js";
|
|
9
|
+
import { t as createDspyRlmTraceEngine } from "./dspy-rlm-engine-Bw8Zz9CX.js";
|
|
10
10
|
import { c as writeLedgerFileAtomically, s as withLedgerFileLock } from "./ledger-core-Dxz0Rkwa.js";
|
|
11
11
|
import { E as pairedBootstrap } from "./statistics-ByxzSiOM.js";
|
|
12
|
-
import { i as otlpTextToTraceAnalysisStore, r as createOtlpBufferTraceStore, t as DEFAULT_MAX_TRACE_FILE_BYTES } from "./store-otlp-
|
|
12
|
+
import { i as otlpTextToTraceAnalysisStore, r as createOtlpBufferTraceStore, t as DEFAULT_MAX_TRACE_FILE_BYTES } from "./store-otlp-D4I90_vR.js";
|
|
13
13
|
import { i as summarizeAnalystBenchmarkRunner, n as runAnalystBenchmark, r as traceStoreEvidenceResolver } from "./benchmark-CYtcIF2V.js";
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { constants, existsSync, lstatSync, readFileSync } from "node:fs";
|
|
@@ -1068,6 +1068,7 @@ const artifactSchema = z.strictObject({
|
|
|
1068
1068
|
execution: z.strictObject({
|
|
1069
1069
|
repetitions: positiveInteger$1,
|
|
1070
1070
|
concurrency: positiveInteger$1,
|
|
1071
|
+
rlmSamples: positiveInteger$1.optional(),
|
|
1071
1072
|
model: nonEmptyString,
|
|
1072
1073
|
maxOutputTokens: positiveInteger$1,
|
|
1073
1074
|
timeoutMs: positiveInteger$1,
|
|
@@ -1172,7 +1173,7 @@ const ANALYST_BENCHMARK_DEPENDENCY_LOCK_FILES = Object.freeze([
|
|
|
1172
1173
|
"package.json",
|
|
1173
1174
|
"pnpm-lock.yaml"
|
|
1174
1175
|
]);
|
|
1175
|
-
const ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256 = "
|
|
1176
|
+
const ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256 = "106219b86a612a0352fabf44eb34bc1bbffd5c842ed97ae6132343af88f89c94";
|
|
1176
1177
|
/** The published benchmark evidence was produced at this package version, by
|
|
1177
1178
|
* the retired one-shot direct runner, before trace analysts moved to the
|
|
1178
1179
|
* recursive DSPy RLM engine. Both evidence digests below are historical facts
|
|
@@ -1198,8 +1199,10 @@ const ANALYST_BENCHMARK_IMPLEMENTATION_FILES = Object.freeze([
|
|
|
1198
1199
|
"src/analyst/benchmark-dataset-utils.ts",
|
|
1199
1200
|
"src/analyst/benchmark-datasets.ts",
|
|
1200
1201
|
"src/analyst/benchmark-evidence-validation.ts",
|
|
1202
|
+
"src/analyst/benchmark-instructions-override.ts",
|
|
1201
1203
|
"src/analyst/benchmark-public-adapters.ts",
|
|
1202
1204
|
"src/analyst/benchmark-public-calibration.ts",
|
|
1205
|
+
"src/analyst/benchmark-public-consensus.ts",
|
|
1203
1206
|
"src/analyst/benchmark-public-data.ts",
|
|
1204
1207
|
"src/analyst/benchmark-public-errors.ts",
|
|
1205
1208
|
"src/analyst/benchmark-public-model.ts",
|
|
@@ -1266,7 +1269,7 @@ const ANALYST_BENCHMARK_IMPLEMENTATION_FILES = Object.freeze([
|
|
|
1266
1269
|
"src/trace/otlp-attributes.ts",
|
|
1267
1270
|
"src/trace/raw-provider-sink.ts"
|
|
1268
1271
|
]);
|
|
1269
|
-
const ANALYST_BENCHMARK_IMPLEMENTATION_SHA256 = "
|
|
1272
|
+
const ANALYST_BENCHMARK_IMPLEMENTATION_SHA256 = "a3f5a8203f0a7c74736170c53100c50c4db1cf84381533002f0539ef1e7a4c6d";
|
|
1270
1273
|
function analystBenchmarkImplementationDigest() {
|
|
1271
1274
|
return ANALYST_BENCHMARK_IMPLEMENTATION_SHA256;
|
|
1272
1275
|
}
|
|
@@ -2104,37 +2107,44 @@ const TRACE_PROJECTION_ATTRIBUTE_BYTE_CAPS = [
|
|
|
2104
2107
|
64
|
|
2105
2108
|
];
|
|
2106
2109
|
const CODE_TRACE_BENCH_ANALYST_PROMPT = `Analyze exactly one coding-agent trajectory and its attached final verification.
|
|
2107
|
-
Your task is the CodeTraceBench incorrect-step task: identify every wrong state-changing
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
+
Your task is the CodeTraceBench incorrect-step task: identify every incorrect step, defined as a wrong state-changing intervention given the evidence — a mislocalized edit, a wrong hypothesis that drives an action, a regression, an irrelevant change, or an incorrect dependency or configuration choice.
|
|
2111
|
+
If the final verification failed, the trajectory MUST contain at least one incorrect step. Never return an empty findings array on a failing trajectory; trace backward until you find the root cause.
|
|
2112
|
+
Work backward, the way this benchmark was annotated, never by scanning forward for suspicious steps: start from the final verification outcome or the latest observed failure evidence, identify the immediately preceding step whose action or output produced that observed error, then recursively ask which earlier decision led to each intermediate failure, until the preceding steps contain no error or the cause is unrelated to the trajectory's own decisions.
|
|
2113
|
+
Each backward chain terminates at an error-critical step — the earliest decision that triggered the downstream cascade — and that step is the block's first_step: the step that committed the mistake, not the step that planned it and not a later step that repeats it.
|
|
2114
|
+
A block is a maximal contiguous sequence of strictly incorrect steps. A step belongs in the block ONLY if it introduces, propagates, or compounds the error.
|
|
2115
|
+
Do NOT include steps that merely "act on", diagnose, or react to the error. A diagnostic command, a test run exposing the bug, or a correct exploratory read is a CORRECT step.
|
|
2116
|
+
If an incorrect step is followed by a correct diagnostic step and then another incorrect step, you MUST emit two separate blocks. NEVER bridge correct steps by grouping them into a single block with incorrect steps. Over-blocking drastically hurts your precision.
|
|
2117
|
+
After identifying first_step, extend last_step forward ONLY through consecutive steps that independently introduce, propagate, or compound the mistake. A cascade of repeated failed attempts at the same wrong approach is one maximal block, provided EVERY step is independently incorrect.
|
|
2118
|
+
Do not end a block merely because the agent tried a variation of the same wrong approach; a variation that still carries the error stays inside the block.
|
|
2119
|
+
A partially correct or ambiguous fix still counts as incorrect; the block ends only at the first step free of the error — a clean diagnostic read, the corrective action that closes the issue and needs no further rework, or a genuine abandonment of the wrong approach.
|
|
2120
|
+
Block extent follows the traced chain and this forward extension, nothing else.
|
|
2110
2121
|
Report each failure block as exactly one finding whose first_step is the block's first incorrect step and whose last_step is its last, covering every consecutive step between them.
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2122
|
+
Every step inside a block is scored on its own: naming a correct step costs exactly as much as missing an incorrect one, and naming only the first step of a longer block forfeits every unnamed step. Because of this, carefully verify every step between first_step and last_step. Only include steps that introduce, propagate, or compound the error.
|
|
2123
|
+
Report blocks separated by at least one correct step as separate findings, and never let two blocks overlap. If there are multiple separate failure cascades, emit a separate finding for each one.
|
|
2124
|
+
Prefer anchored blocks: a block whose chain traces back from observed failure evidence — a failing command or verification, an error observation, a regression, or, on a solved trajectory, a later step that reverts or supersedes it — outranks one without.
|
|
2125
|
+
When an action is clearly wrong on its own evidence but you cannot trace such an anchor, report the block anyway with proportionally lower confidence.
|
|
2126
|
+
A solved trajectory still carries every mistake made along the way: inspect its final patching and verification stages for a state-changing action that a later step reverted, superseded, or corrected — a wrong edit just before the final fix is incorrect even when every test ends green.
|
|
2127
|
+
Before emitting a candidate block, check its boundaries.
|
|
2128
|
+
Neighbor check: ask whether the accusation fits one step earlier (the decision rather than its consequence) or one step later (the next step still acts on or reworks the same error) better than where you placed it, and move the boundary when it does; a boundary off by one step scores zero at that step.
|
|
2129
|
+
Completeness check: a block must cover the maximal contiguous sequence of incorrect steps. If an agent fails at step 10, tries to fix it at 11, fails, and tries again at 12 and 13, all four steps are incorrect and must be included in the block. Never truncate a cascade. If you miss the later steps of a cascade, your recall drops to zero for them.
|
|
2130
|
+
Counterfactual check: ask which step's correct execution would have made the downstream failure or rework disappear, and move first_step onto that step; use this check only to move a boundary, never to delete a block.
|
|
2131
|
+
Width check: inspect the step immediately before first_step and the step immediately after last_step; when either neighbor commits to, compounds, or propagates the same mistake, it belongs inside the block — move that boundary outward and repeat until both neighbors are free of the error.
|
|
2132
|
+
For each block you keep, name as consequence_step the step number whose action or observation shows the damage — a failing command, a wrong file state, a repeated failure, or rework the agent had to do because of this block; that step is the block's own last step when its observation already shows the damage, and a later step otherwise.
|
|
2133
|
+
When you cannot name that step number from the trace you were given, drop the block; a plausible story about why a step looks wrong is not evidence that it was.
|
|
2134
|
+
A passing final verification is not evidence that a block caused nothing, and a failing final verification is not evidence that any particular block caused it.
|
|
2123
2135
|
For every block, decide whether the agent escaped the failure.
|
|
2124
|
-
Mark escape_status "escaped" only when you can name the single later step that fully reversed the block, the agent needed no other step to recover, and nothing after that step revisits the same file, command, or hypothesis; write that step number in the rationale.
|
|
2136
|
+
Mark escape_status "escaped" only when you can name the single later step that fully reversed the block, the agent needed no other step to recover, and nothing after that step revisits the same file, command, or hypothesis; write that step number in the rationale. If the agent required multiple steps to fix the issue, or if you are unsure, it is not escaped.
|
|
2125
2137
|
Mark escape_status "unescaped" in every other case, including whenever you are unsure.
|
|
2126
|
-
A passing final verification never makes a block escaped
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
Do not label a diagnostic probe merely because it exposes an earlier defect.
|
|
2130
|
-
Do not label a redundant but correct read or search; CodeTraceBench scores unuseful steps separately, and this run scores incorrect steps only.
|
|
2138
|
+
A passing final verification never makes a block escaped.
|
|
2139
|
+
Do not label a diagnostic probe or test run merely because its output exposes an earlier defect.
|
|
2140
|
+
Do not label a redundant but correct read or search; CodeTraceBench scores unuseful steps separately, and this run scores incorrect steps only. Never include a correct step in a block just to bridge two incorrect steps; instead, emit two separate blocks.
|
|
2131
2141
|
Do not label a step solely because final verification failed.
|
|
2132
|
-
When final verification is unavailable,
|
|
2142
|
+
When final verification is unavailable, trace backward from the latest failure evidence inside the trajectory itself.
|
|
2133
2143
|
Every step in a reported block MUST be the positive integer n from an existing assistant LLM span named step-<n>.
|
|
2134
2144
|
Never select an EVALUATOR, TOOL, CHAIN, final-verification, benchmark-verification, or message-<n> span.
|
|
2135
2145
|
Before emitting a finding, inspect every covered span's attributes.content and describe only the actions shown there.
|
|
2136
|
-
Report at most 16 blocks and at most 12 steps in one block; when more candidates than that exist, report the ones
|
|
2137
|
-
When the trajectory has no incorrect steps, return an empty findings array.`;
|
|
2146
|
+
Report at most 16 blocks and at most 12 steps in one block; when more candidates than that exist, report the ones whose chains carry the clearest downstream evidence.
|
|
2147
|
+
When the trajectory has no incorrect steps — its final verification passed and the final-stage sweep found no reverted, superseded, or corrected action — return an empty findings array.`;
|
|
2138
2148
|
const AGENT_RX_PROMPT = `Analyze exactly one failed agent trajectory.
|
|
2139
2149
|
Find the first unrecoverable critical failure, not every later symptom.
|
|
2140
2150
|
Inspect the complete supplied trace data.
|
|
@@ -2175,8 +2185,8 @@ The excerpt must quote the assistant action exactly.`;
|
|
|
2175
2185
|
const CODE_TRACE_RLM_CONTRACT = `Use the trace tools rather than asking for the whole trajectory in the prompt.
|
|
2176
2186
|
Keep retrieved trace objects in Python variables.
|
|
2177
2187
|
Never print an entire trace, full source file, or more than 12000 characters in one iteration.
|
|
2178
|
-
|
|
2179
|
-
|
|
2188
|
+
Read the final verification and the latest failure evidence first, then build a compact table of assistant step ids, actions, and following observations.
|
|
2189
|
+
Trace backward from that evidence with viewSpans or searchSpan, confirming each candidate step's own action content, instead of repeatedly printing the table.
|
|
2180
2190
|
This runner emits no JSON fields, so the block is encoded in the finding's subject.
|
|
2181
2191
|
Only findings_json is scored; your prose answer is ignored, so every incorrect block you identify must appear as a finding, never only in the answer.
|
|
2182
2192
|
Emit exactly one finding per contiguous failure block.
|
|
@@ -2235,774 +2245,1314 @@ function publicBenchmarkProtocolSha256(dataset) {
|
|
|
2235
2245
|
}));
|
|
2236
2246
|
}
|
|
2237
2247
|
//#endregion
|
|
2238
|
-
//#region src/analyst/benchmark-
|
|
2239
|
-
|
|
2248
|
+
//#region src/analyst/benchmark-instructions-override.ts
|
|
2249
|
+
/** Build an override from instruction text. Blank text is a caller error. */
|
|
2250
|
+
function analystInstructionsOverrideFromText(text) {
|
|
2251
|
+
if (typeof text !== "string" || !text.trim()) throw new Error("analyst instructions override must contain non-empty instruction text");
|
|
2240
2252
|
return {
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
return {
|
|
2244
|
-
findings: [],
|
|
2245
|
-
usage: {
|
|
2246
|
-
calls: 0,
|
|
2247
|
-
tokens: {
|
|
2248
|
-
input: 0,
|
|
2249
|
-
output: 0
|
|
2250
|
-
},
|
|
2251
|
-
cost: {
|
|
2252
|
-
kind: "observed",
|
|
2253
|
-
usd: 0
|
|
2254
|
-
}
|
|
2255
|
-
},
|
|
2256
|
-
metadata: { baseline: "emit-no-findings" }
|
|
2257
|
-
};
|
|
2258
|
-
}
|
|
2259
|
-
};
|
|
2260
|
-
}
|
|
2261
|
-
async function adaptPublicBenchmarkFindings(options) {
|
|
2262
|
-
if (options.dataset === "agentrx") return {
|
|
2263
|
-
findings: adaptAgentRxFindings(options.trajectoryId, options.findings, options.analystId),
|
|
2264
|
-
diagnostics: void 0
|
|
2253
|
+
text,
|
|
2254
|
+
sha256: sha256Digest(text)
|
|
2265
2255
|
};
|
|
2266
|
-
return adaptCodeTraceFindings(options.trajectoryId, options.findings, options.analystId, options.store, options.signal);
|
|
2267
|
-
}
|
|
2268
|
-
function adaptAgentRxFindings(trajectoryId, findings, analystId) {
|
|
2269
|
-
if (findings.length === 0) return [];
|
|
2270
|
-
if (findings.length !== 1) throw new Error(`AgentRx model analyst must emit zero or one root cause, received ${findings.length}`);
|
|
2271
|
-
const source = findings[0];
|
|
2272
|
-
if (!source.subject) throw new Error("AgentRx model analyst finding is missing its failure-category subject");
|
|
2273
|
-
const steps = exactFindingSteps(trajectoryId, source);
|
|
2274
|
-
if (steps.length !== 1) throw new Error(`AgentRx model analyst must cite exactly one root-cause step, received ${steps.length}`);
|
|
2275
|
-
const [adapted] = agentRxPredictionsToFindings(trajectoryId, [{
|
|
2276
|
-
failure_case: source.subject,
|
|
2277
|
-
step_number: steps[0],
|
|
2278
|
-
description: source.rationale ?? source.claim
|
|
2279
|
-
}], {
|
|
2280
|
-
analystId,
|
|
2281
|
-
producedAt: source.produced_at,
|
|
2282
|
-
confidence: source.confidence
|
|
2283
|
-
});
|
|
2284
|
-
if (!adapted) throw new Error("AgentRx output adapter produced no root-cause finding");
|
|
2285
|
-
return [{
|
|
2286
|
-
...adapted,
|
|
2287
|
-
metadata: {
|
|
2288
|
-
...adapted.metadata,
|
|
2289
|
-
sourceFindingId: source.finding_id
|
|
2290
|
-
}
|
|
2291
|
-
}];
|
|
2292
2256
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
exactFindingSteps(trajectoryId, clean[0]);
|
|
2299
|
-
return {
|
|
2300
|
-
findings: [],
|
|
2301
|
-
diagnostics: emptyCodeTraceBlockDiagnostics()
|
|
2302
|
-
};
|
|
2303
|
-
}
|
|
2304
|
-
const blocks = [];
|
|
2305
|
-
const rejectedFindings = [];
|
|
2306
|
-
for (const source of findings) try {
|
|
2307
|
-
await validateCodeTraceFindingEvidence({
|
|
2308
|
-
trajectoryId,
|
|
2309
|
-
findings: [source],
|
|
2310
|
-
store,
|
|
2311
|
-
...signal ? { signal } : {}
|
|
2312
|
-
});
|
|
2313
|
-
blocks.push(codeTraceBlockFromFinding(trajectoryId, source));
|
|
2257
|
+
/** Read override instructions from a file. Any read failure is fatal. */
|
|
2258
|
+
function readAnalystInstructionsOverride(path) {
|
|
2259
|
+
let text;
|
|
2260
|
+
try {
|
|
2261
|
+
text = readFileSync(path, "utf8");
|
|
2314
2262
|
} catch (error) {
|
|
2315
|
-
|
|
2263
|
+
throw new Error(`cannot read --instructions-file '${path}': ${error instanceof Error ? error.message : String(error)}`);
|
|
2316
2264
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
blocks,
|
|
2320
|
-
store,
|
|
2321
|
-
analystId,
|
|
2322
|
-
...findings[0] ? { producedAt: findings[0].produced_at } : {},
|
|
2323
|
-
...signal ? { signal } : {}
|
|
2324
|
-
});
|
|
2325
|
-
return {
|
|
2326
|
-
findings: expanded.findings,
|
|
2327
|
-
diagnostics: {
|
|
2328
|
-
...expanded.diagnostics,
|
|
2329
|
-
rejectedFindings
|
|
2330
|
-
}
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
function codeTraceBlockFromFinding(trajectoryId, source) {
|
|
2334
|
-
const parsed = CODE_TRACE_BLOCK_SUBJECT.exec(source.subject ?? "");
|
|
2335
|
-
if (!parsed) throw new Error(`CodeTraceBench model finding '${source.finding_id}' must set subject to incorrect-steps-<first>-<last>-<escaped|unescaped>-consequence-<step>, received '${source.subject ?? ""}'`);
|
|
2336
|
-
const firstStep = Number(parsed[1]);
|
|
2337
|
-
const lastStep = Number(parsed[2]);
|
|
2338
|
-
const consequenceStep = Number(parsed[4]);
|
|
2339
|
-
const cited = exactFindingSteps(trajectoryId, source);
|
|
2340
|
-
for (const step of cited) if (step < firstStep || step > lastStep) throw new Error(`CodeTraceBench model finding '${source.finding_id}' cites step ${step} outside its block ${firstStep}-${lastStep}`);
|
|
2341
|
-
return {
|
|
2342
|
-
firstStep,
|
|
2343
|
-
lastStep,
|
|
2344
|
-
consequenceStep,
|
|
2345
|
-
escapeStatus: parsed[3],
|
|
2346
|
-
severity: source.severity,
|
|
2347
|
-
claim: source.claim,
|
|
2348
|
-
confidence: source.confidence,
|
|
2349
|
-
...source.rationale === void 0 ? {} : { rationale: source.rationale },
|
|
2350
|
-
...source.recommended_action === void 0 ? {} : { recommendedAction: source.recommended_action },
|
|
2351
|
-
metadata: { sourceFindingId: source.finding_id }
|
|
2352
|
-
};
|
|
2265
|
+
if (!text.trim()) throw new Error(`--instructions-file '${path}' is empty; refusing to run without instructions`);
|
|
2266
|
+
return analystInstructionsOverrideFromText(text);
|
|
2353
2267
|
}
|
|
2354
2268
|
/**
|
|
2355
|
-
*
|
|
2269
|
+
* Protocol digest of the run as executed.
|
|
2356
2270
|
*
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2359
|
-
*
|
|
2271
|
+
* Without an override this is exactly `publicBenchmarkProtocolSha256(dataset)`,
|
|
2272
|
+
* so stock runs stay byte-identical to runs recorded before the override
|
|
2273
|
+
* existed. With an override the digest binds the stock protocol digest (which
|
|
2274
|
+
* covers both shipped prompts, including the abstention fallback's direct
|
|
2275
|
+
* prompt) to the exact override text, so the recorded digest always hashes the
|
|
2276
|
+
* instructions that actually ran.
|
|
2360
2277
|
*/
|
|
2361
|
-
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2278
|
+
function effectiveAnalystProtocolSha256(dataset, override) {
|
|
2279
|
+
const stock = publicBenchmarkProtocolSha256(dataset);
|
|
2280
|
+
if (!override) return stock;
|
|
2281
|
+
return sha256Digest(JSON.stringify({
|
|
2282
|
+
kind: "analyst-instructions-override-protocol",
|
|
2283
|
+
dataset,
|
|
2284
|
+
stockProtocolSha256: stock,
|
|
2285
|
+
rlmInstructionsSha256: override.sha256
|
|
2286
|
+
}));
|
|
2287
|
+
}
|
|
2288
|
+
//#endregion
|
|
2289
|
+
//#region src/analyst/benchmark-command-persistence.ts
|
|
2290
|
+
const ANALYST_BENCHMARK_INITIALIZATION_COMPLETE_FILE = "initialization-complete.json";
|
|
2291
|
+
async function openOutputDirectory(outDir, resume) {
|
|
2292
|
+
const directory = resolve(outDir);
|
|
2293
|
+
if (resume) {
|
|
2294
|
+
let outputStat;
|
|
2295
|
+
try {
|
|
2296
|
+
outputStat = await lstat(directory);
|
|
2297
|
+
} catch (error) {
|
|
2298
|
+
if (isNodeError(error, "ENOENT")) throw new Error(`cannot resume missing benchmark output directory: ${directory}`);
|
|
2299
|
+
throw error;
|
|
2383
2300
|
}
|
|
2384
|
-
if (
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
if (
|
|
2391
|
-
|
|
2392
|
-
continue;
|
|
2393
|
-
}
|
|
2394
|
-
byStep.set(step, block);
|
|
2301
|
+
if (!outputStat.isDirectory() || outputStat.isSymbolicLink()) throw new Error(`benchmark output must be a real directory: ${directory}`);
|
|
2302
|
+
} else {
|
|
2303
|
+
await mkdir(dirname(directory), { recursive: true });
|
|
2304
|
+
try {
|
|
2305
|
+
await mkdir(directory);
|
|
2306
|
+
} catch (error) {
|
|
2307
|
+
if (isNodeError(error, "EEXIST")) throw new Error(`refusing to use existing benchmark output directory: ${directory}`);
|
|
2308
|
+
throw error;
|
|
2395
2309
|
}
|
|
2310
|
+
await syncDirectory(dirname(directory));
|
|
2396
2311
|
}
|
|
2397
2312
|
return {
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
recommended_action: block.recommendedAction,
|
|
2408
|
-
metadata: {
|
|
2409
|
-
...block.metadata,
|
|
2410
|
-
block_first_step: block.firstStep,
|
|
2411
|
-
block_last_step: block.lastStep,
|
|
2412
|
-
block_consequence_step: block.consequenceStep,
|
|
2413
|
-
escape_status: block.escapeStatus
|
|
2414
|
-
},
|
|
2415
|
-
...options.producedAt === void 0 ? {} : { produced_at: options.producedAt },
|
|
2416
|
-
id_basis: `incorrect-step-${step}`
|
|
2417
|
-
})),
|
|
2418
|
-
diagnostics
|
|
2313
|
+
directory,
|
|
2314
|
+
initializationComplete: resolve(directory, ANALYST_BENCHMARK_INITIALIZATION_COMPLETE_FILE),
|
|
2315
|
+
manifest: resolve(directory, ANALYST_BENCHMARK_MANIFEST_FILE),
|
|
2316
|
+
observations: resolve(directory, ANALYST_BENCHMARK_OBSERVATIONS_FILE),
|
|
2317
|
+
costLedger: resolve(directory, ANALYST_BENCHMARK_COST_LEDGER_FILE),
|
|
2318
|
+
modelResponses: resolve(directory, "model-responses"),
|
|
2319
|
+
localReceipt: resolve(directory, ANALYST_BENCHMARK_LOCAL_RECEIPT_FILE),
|
|
2320
|
+
result: resolve(directory, "result.json"),
|
|
2321
|
+
report: resolve(directory, "report.md")
|
|
2419
2322
|
};
|
|
2420
2323
|
}
|
|
2421
|
-
function
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
const length = block.lastStep - block.firstStep + 1;
|
|
2426
|
-
if (length > 12) throw new Error(`failure block spans ${length} steps; the maximum is 12`);
|
|
2427
|
-
if (block.consequenceStep < block.firstStep) throw new Error(`failure block consequence_step ${block.consequenceStep} precedes first_step ${block.firstStep}`);
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
function interiorSteps(block) {
|
|
2431
|
-
const steps = [];
|
|
2432
|
-
for (let step = block.firstStep + 1; step < block.lastStep; step += 1) steps.push(step);
|
|
2433
|
-
return steps;
|
|
2324
|
+
async function prepareOutputLockPath(outDir) {
|
|
2325
|
+
const directory = resolve(outDir);
|
|
2326
|
+
await mkdir(dirname(directory), { recursive: true });
|
|
2327
|
+
return `${directory}.lock`;
|
|
2434
2328
|
}
|
|
2435
|
-
function
|
|
2329
|
+
function createRunIdentity(config, prepared) {
|
|
2330
|
+
const caseDefinitions = prepared.cases.map((testCase) => ({
|
|
2331
|
+
id: testCase.id,
|
|
2332
|
+
clusterId: testCase.clusterId,
|
|
2333
|
+
labelState: testCase.labelState,
|
|
2334
|
+
expectedIssues: testCase.expectedIssues,
|
|
2335
|
+
labeledEvidence: testCase.labeledEvidence ?? [],
|
|
2336
|
+
tags: testCase.tags ?? [],
|
|
2337
|
+
metadata: testCase.metadata ?? {}
|
|
2338
|
+
}));
|
|
2436
2339
|
return {
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2340
|
+
config: {
|
|
2341
|
+
dataset: config.dataset,
|
|
2342
|
+
datasetRevision: config.revision,
|
|
2343
|
+
datasetSplit: config.split,
|
|
2344
|
+
model: {
|
|
2345
|
+
id: config.model.model,
|
|
2346
|
+
maxOutputTokens: config.model.maxOutputTokens,
|
|
2347
|
+
timeoutMs: config.model.timeoutMs
|
|
2348
|
+
},
|
|
2349
|
+
limit: config.limit,
|
|
2350
|
+
seed: config.seed,
|
|
2351
|
+
concurrency: config.concurrency,
|
|
2352
|
+
repetitions: config.repetitions,
|
|
2353
|
+
rlmSamples: config.rlmSamples,
|
|
2354
|
+
maxCostUsd: config.maxCostUsd,
|
|
2355
|
+
maxArtifactBytes: config.maxArtifactBytes,
|
|
2356
|
+
analystProtocolSha256: effectiveAnalystProtocolSha256(config.dataset, config.model.instructionsOverride),
|
|
2357
|
+
...config.model.instructionsOverride ? { instructionsOverrideSha256: config.model.instructionsOverride.sha256 } : {},
|
|
2358
|
+
implementationSha256: ANALYST_BENCHMARK_IMPLEMENTATION_SHA256,
|
|
2359
|
+
dependencyLockSha256: ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256,
|
|
2360
|
+
runnerIds: ["empty", config.analyst]
|
|
2361
|
+
},
|
|
2362
|
+
inputs: {
|
|
2363
|
+
labelsSha256: prepared.labelsSha256,
|
|
2364
|
+
sourceRowCount: prepared.sourceRowCount,
|
|
2365
|
+
selectedCaseIds: [...prepared.selectedCaseIds],
|
|
2366
|
+
traceFiles: prepared.traceFiles.map((traceFile) => ({ ...traceFile })),
|
|
2367
|
+
verificationArtifactsSha256: digestCanonical(prepared.verificationArtifacts),
|
|
2368
|
+
caseDefinitionsSha256: digestCanonical(caseDefinitions)
|
|
2369
|
+
}
|
|
2442
2370
|
};
|
|
2443
2371
|
}
|
|
2444
|
-
function
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2372
|
+
function createLocalRunReceipt(config, paths) {
|
|
2373
|
+
return {
|
|
2374
|
+
kind: "agent-eval/analyst-benchmark-local-run",
|
|
2375
|
+
local: {
|
|
2376
|
+
labelsPath: resolve(config.labelsPath),
|
|
2377
|
+
traceDir: resolve(config.traceDir),
|
|
2378
|
+
...config.artifactDir ? { artifactDir: resolve(config.artifactDir) } : {},
|
|
2379
|
+
outputDir: paths.directory,
|
|
2380
|
+
baseUrl: config.model.baseUrl,
|
|
2381
|
+
apiKeyEnvironment: config.apiKeyEnv
|
|
2382
|
+
},
|
|
2383
|
+
command: config.command,
|
|
2384
|
+
environment: {
|
|
2385
|
+
node: process.version,
|
|
2386
|
+
platform: platform(),
|
|
2387
|
+
arch: arch()
|
|
2388
|
+
},
|
|
2389
|
+
files: {
|
|
2390
|
+
manifest: paths.manifest,
|
|
2391
|
+
observations: paths.observations,
|
|
2392
|
+
costLedger: paths.costLedger,
|
|
2393
|
+
modelResponses: paths.modelResponses,
|
|
2394
|
+
result: paths.result,
|
|
2395
|
+
report: paths.report
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2463
2398
|
}
|
|
2464
|
-
function
|
|
2465
|
-
|
|
2466
|
-
|
|
2399
|
+
async function initializeRunFiles(paths, identity, identitySha256, localIdentitySha256, localReceiptInput) {
|
|
2400
|
+
const existingManifest = await readOptionalRegularFile(paths.manifest, "benchmark run manifest");
|
|
2401
|
+
const manifest = existingManifest ? await readAndValidateManifestContent(paths.manifest, existingManifest, identity, identitySha256, localIdentitySha256) : {
|
|
2402
|
+
kind: "agent-eval/analyst-benchmark-run",
|
|
2403
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2404
|
+
identitySha256,
|
|
2405
|
+
localIdentitySha256,
|
|
2406
|
+
identity
|
|
2407
|
+
};
|
|
2408
|
+
const localReceipt = {
|
|
2409
|
+
...localReceiptInput,
|
|
2410
|
+
runIdentitySha256: identitySha256,
|
|
2411
|
+
localIdentitySha256
|
|
2412
|
+
};
|
|
2413
|
+
const manifestContent = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
2414
|
+
const localReceiptContent = `${JSON.stringify(localReceipt, null, 2)}\n`;
|
|
2415
|
+
const initializationCompleteContent = renderInitializationComplete(manifest);
|
|
2416
|
+
await assertAbsentOrExact(paths.observations, "", "benchmark observation log");
|
|
2417
|
+
await assertAbsentOrExact(paths.localReceipt, localReceiptContent, "benchmark local run receipt");
|
|
2418
|
+
await assertAbsentOrExact(paths.manifest, manifestContent, "benchmark run manifest");
|
|
2419
|
+
for (const path of [
|
|
2420
|
+
paths.costLedger,
|
|
2421
|
+
paths.modelResponses,
|
|
2422
|
+
paths.result,
|
|
2423
|
+
paths.report
|
|
2424
|
+
]) if (await regularFileExists(path)) throw new Error(`benchmark initialization marker is missing but later run artifact exists: ${path}`);
|
|
2425
|
+
if (await regularFileExists(paths.initializationComplete)) throw new Error(`benchmark initialization marker already exists during partial initialization: ${paths.initializationComplete}`);
|
|
2426
|
+
await writeExclusiveOrVerify(paths.observations, "");
|
|
2427
|
+
await writeExclusiveOrVerify(paths.localReceipt, localReceiptContent);
|
|
2428
|
+
await writeExclusiveOrVerify(paths.manifest, manifestContent);
|
|
2429
|
+
await writeExclusiveOrVerify(paths.initializationComplete, initializationCompleteContent);
|
|
2430
|
+
return manifest;
|
|
2467
2431
|
}
|
|
2468
|
-
function
|
|
2469
|
-
|
|
2432
|
+
async function readAndValidateResumeFiles(paths, currentIdentity, currentIdentitySha256, currentLocalIdentitySha256, localReceiptInput) {
|
|
2433
|
+
if (!await regularFileExists(paths.initializationComplete)) return initializeRunFiles(paths, currentIdentity, currentIdentitySha256, currentLocalIdentitySha256, localReceiptInput);
|
|
2434
|
+
const manifest = await readAndValidateManifest(paths.manifest, currentIdentity, currentIdentitySha256, currentLocalIdentitySha256);
|
|
2435
|
+
const localReceiptContent = await readRegularFile(paths.localReceipt, "benchmark local run receipt");
|
|
2436
|
+
const value = parseJson(localReceiptContent, paths.localReceipt);
|
|
2437
|
+
if (!isRecord$1(value)) throw new TypeError(`benchmark local run receipt must be an object: ${paths.localReceipt}`);
|
|
2438
|
+
assertExactKeys(value, [
|
|
2439
|
+
"kind",
|
|
2440
|
+
"runIdentitySha256",
|
|
2441
|
+
"localIdentitySha256",
|
|
2442
|
+
"local",
|
|
2443
|
+
"command",
|
|
2444
|
+
"environment",
|
|
2445
|
+
"files"
|
|
2446
|
+
], "benchmark local run receipt");
|
|
2447
|
+
if (value.kind !== "agent-eval/analyst-benchmark-local-run" || value.runIdentitySha256 !== currentIdentitySha256 || value.localIdentitySha256 !== currentLocalIdentitySha256 || !isRecord$1(value.local)) throw new Error("benchmark local run receipt does not match the requested resume");
|
|
2448
|
+
const expectedLocalReceipt = {
|
|
2449
|
+
...localReceiptInput,
|
|
2450
|
+
runIdentitySha256: currentIdentitySha256,
|
|
2451
|
+
localIdentitySha256: currentLocalIdentitySha256
|
|
2452
|
+
};
|
|
2453
|
+
if (digestCanonical(value.local) !== currentLocalIdentitySha256 || canonicalJson(value.local) !== canonicalJson(localReceiptInput.local)) throw new Error("benchmark local paths or endpoint do not match the requested resume");
|
|
2454
|
+
if (localReceiptContent !== `${JSON.stringify(expectedLocalReceipt, null, 2)}\n`) throw new Error(`benchmark local run receipt does not exactly match: ${paths.localReceipt}`);
|
|
2455
|
+
if (await readRegularFile(paths.initializationComplete, "benchmark initialization marker") !== renderInitializationComplete(manifest)) throw new Error(`benchmark initialization marker does not match the run manifest: ${paths.initializationComplete}`);
|
|
2456
|
+
return manifest;
|
|
2470
2457
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
const
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
return parsePublicBenchmarkRows((await readImmutableInputSnapshot(resolve(path), DEFAULT_MAX_PUBLIC_BENCHMARK_LABEL_BYTES)).text, path);
|
|
2458
|
+
async function readAndValidateManifest(path, currentIdentity, currentIdentitySha256, currentLocalIdentitySha256) {
|
|
2459
|
+
const content = await readRegularFile(path, "benchmark run manifest");
|
|
2460
|
+
const manifest = await readAndValidateManifestContent(path, content, currentIdentity, currentIdentitySha256, currentLocalIdentitySha256);
|
|
2461
|
+
if (content !== `${JSON.stringify(manifest, null, 2)}\n`) throw new Error(`benchmark run manifest does not exactly match: ${path}`);
|
|
2462
|
+
return manifest;
|
|
2477
2463
|
}
|
|
2478
|
-
function
|
|
2479
|
-
const
|
|
2480
|
-
if (!
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
if (
|
|
2489
|
-
if (
|
|
2490
|
-
if (isRecord(
|
|
2491
|
-
throw new
|
|
2464
|
+
async function readAndValidateManifestContent(path, content, currentIdentity, currentIdentitySha256, currentLocalIdentitySha256) {
|
|
2465
|
+
const value = parseJson(content, path);
|
|
2466
|
+
if (!isRecord$1(value)) throw new TypeError(`benchmark run manifest must be an object: ${path}`);
|
|
2467
|
+
assertExactKeys(value, [
|
|
2468
|
+
"kind",
|
|
2469
|
+
"createdAt",
|
|
2470
|
+
"identitySha256",
|
|
2471
|
+
"localIdentitySha256",
|
|
2472
|
+
"identity"
|
|
2473
|
+
], "benchmark run manifest");
|
|
2474
|
+
if (value.kind !== "agent-eval/analyst-benchmark-run") throw new TypeError(`unsupported benchmark run manifest: ${path}`);
|
|
2475
|
+
if (typeof value.createdAt !== "string" || !Number.isFinite(Date.parse(value.createdAt))) throw new TypeError(`benchmark run manifest has an invalid createdAt: ${path}`);
|
|
2476
|
+
if (!isSha256(value.identitySha256) || !isSha256(value.localIdentitySha256) || !isRecord$1(value.identity)) throw new TypeError(`benchmark run manifest has an invalid identity: ${path}`);
|
|
2477
|
+
if (digestCanonical(value.identity) !== value.identitySha256) throw new Error(`benchmark run manifest identity digest does not match its contents: ${path}`);
|
|
2478
|
+
if (currentIdentitySha256 !== value.identitySha256 || currentLocalIdentitySha256 !== value.localIdentitySha256 || canonicalJson(currentIdentity) !== canonicalJson(value.identity)) throw new Error(`benchmark resume configuration or inputs do not match ${ANALYST_BENCHMARK_MANIFEST_FILE}`);
|
|
2479
|
+
return {
|
|
2480
|
+
kind: "agent-eval/analyst-benchmark-run",
|
|
2481
|
+
createdAt: value.createdAt,
|
|
2482
|
+
identitySha256: currentIdentitySha256,
|
|
2483
|
+
localIdentitySha256: currentLocalIdentitySha256,
|
|
2484
|
+
identity: currentIdentity
|
|
2485
|
+
};
|
|
2492
2486
|
}
|
|
2493
|
-
function
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
if (byId.has(id)) throw new Error(`public analyst benchmark dataset repeats trajectory id '${id}'`);
|
|
2501
|
-
byId.set(id, row);
|
|
2502
|
-
}
|
|
2503
|
-
return [...byId].sort(([left], [right]) => selectionKey(options.seed, left).localeCompare(selectionKey(options.seed, right)) || left.localeCompare(right)).slice(0, Math.min(options.limit, byId.size)).map(([, row]) => row);
|
|
2487
|
+
function renderInitializationComplete(manifest) {
|
|
2488
|
+
return `${JSON.stringify({
|
|
2489
|
+
kind: "agent-eval/analyst-benchmark-initialization-complete",
|
|
2490
|
+
runIdentitySha256: manifest.identitySha256,
|
|
2491
|
+
localIdentitySha256: manifest.localIdentitySha256,
|
|
2492
|
+
createdAt: manifest.createdAt
|
|
2493
|
+
}, null, 2)}\n`;
|
|
2504
2494
|
}
|
|
2505
|
-
function
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2495
|
+
async function assertAbsentOrExact(path, expected, label) {
|
|
2496
|
+
const existing = await readOptionalRegularFile(path, label);
|
|
2497
|
+
if (existing !== void 0 && existing !== expected) throw new Error(`${label} does not exactly match interrupted initialization: ${path}`);
|
|
2498
|
+
}
|
|
2499
|
+
async function readOptionalRegularFile(path, label) {
|
|
2500
|
+
if (!await regularFileExists(path)) return void 0;
|
|
2501
|
+
return readRegularFile(path, label);
|
|
2502
|
+
}
|
|
2503
|
+
function createObservationAppender(path, runIdentitySha256, progress) {
|
|
2504
|
+
let writes = Promise.resolve();
|
|
2505
|
+
const seen = new Set(progress.observations.map(observationKey));
|
|
2506
|
+
return (observation) => {
|
|
2507
|
+
const write = writes.then(async () => {
|
|
2508
|
+
assertAnalystBenchmarkObservation(observation, "benchmark observation");
|
|
2509
|
+
const key = observationKey(observation);
|
|
2510
|
+
if (seen.has(key)) throw new Error(`refusing duplicate benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}'`);
|
|
2511
|
+
const rowWithoutDigest = {
|
|
2512
|
+
sequence: progress.nextSequence,
|
|
2513
|
+
runIdentitySha256,
|
|
2514
|
+
previousRowSha256: progress.previousRowSha256,
|
|
2515
|
+
observation
|
|
2516
|
+
};
|
|
2517
|
+
const row = {
|
|
2518
|
+
...rowWithoutDigest,
|
|
2519
|
+
rowSha256: digestCanonical(rowWithoutDigest)
|
|
2520
|
+
};
|
|
2521
|
+
await appendDurable(path, `${JSON.stringify(row)}\n`);
|
|
2522
|
+
progress.nextSequence += 1;
|
|
2523
|
+
progress.previousRowSha256 = row.rowSha256;
|
|
2524
|
+
progress.observations.push(observation);
|
|
2525
|
+
seen.add(key);
|
|
2526
|
+
});
|
|
2527
|
+
writes = write;
|
|
2528
|
+
return write;
|
|
2512
2529
|
};
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2530
|
+
}
|
|
2531
|
+
async function readProgress(path, runIdentitySha256, caseIds, repetitions, analystRunnerId) {
|
|
2532
|
+
const rawLines = (await readRegularFile(path, "benchmark observation log")).split("\n");
|
|
2533
|
+
if (rawLines.at(-1) === "") rawLines.pop();
|
|
2534
|
+
const observations = [];
|
|
2535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2536
|
+
const executionIndexes = /* @__PURE__ */ new Set();
|
|
2537
|
+
let previousRowSha256 = null;
|
|
2538
|
+
const allowedCases = new Set(caseIds);
|
|
2539
|
+
const plannedObservationCount = caseIds.length * 2 * repetitions;
|
|
2540
|
+
for (const [index, line] of rawLines.entries()) {
|
|
2541
|
+
if (!line.trim()) throw new Error(`benchmark observation log contains an empty row at line ${index + 1}`);
|
|
2542
|
+
const parsed = parseJson(line, `${path}:${index + 1}`);
|
|
2543
|
+
if (!isRecord$1(parsed)) throw new TypeError(`benchmark observation row ${index + 1} must be an object`);
|
|
2544
|
+
assertExactKeys(parsed, [
|
|
2545
|
+
"sequence",
|
|
2546
|
+
"runIdentitySha256",
|
|
2547
|
+
"previousRowSha256",
|
|
2548
|
+
"observation",
|
|
2549
|
+
"rowSha256"
|
|
2550
|
+
], `benchmark observation row ${index + 1}`);
|
|
2551
|
+
assertAnalystBenchmarkObservation(parsed.observation, `benchmark observation row ${index + 1}.observation`);
|
|
2552
|
+
const observation = parsed.observation;
|
|
2553
|
+
const key = observationKey(observation);
|
|
2554
|
+
if (seen.has(key)) throw new Error(`duplicate benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' at line ${index + 1}`);
|
|
2555
|
+
if (parsed.sequence !== index) throw new Error(`benchmark observation row ${index + 1} has sequence ${String(parsed.sequence)}; expected ${index}`);
|
|
2556
|
+
if (parsed.runIdentitySha256 !== runIdentitySha256) throw new Error(`benchmark observation row ${index + 1} belongs to another run`);
|
|
2557
|
+
if (parsed.previousRowSha256 !== previousRowSha256) throw new Error(`benchmark observation row ${index + 1} breaks the digest chain`);
|
|
2558
|
+
if (!isSha256(parsed.rowSha256)) throw new TypeError(`benchmark observation row ${index + 1} has an invalid digest`);
|
|
2559
|
+
if (digestCanonical({
|
|
2560
|
+
sequence: parsed.sequence,
|
|
2561
|
+
runIdentitySha256: parsed.runIdentitySha256,
|
|
2562
|
+
previousRowSha256: parsed.previousRowSha256,
|
|
2563
|
+
observation
|
|
2564
|
+
}) !== parsed.rowSha256) throw new Error(`benchmark observation row ${index + 1} digest does not match its contents`);
|
|
2565
|
+
if (!allowedCases.has(observation.caseId) || observation.runnerId !== "empty" && observation.runnerId !== analystRunnerId || observation.repetition >= repetitions || observation.executionIndex >= plannedObservationCount) throw new Error(`benchmark observation row ${index + 1} does not match a planned case, runner, and repetition`);
|
|
2566
|
+
if (executionIndexes.has(observation.executionIndex)) throw new Error(`duplicate benchmark executionIndex ${observation.executionIndex} at line ${index + 1}`);
|
|
2567
|
+
observations.push(observation);
|
|
2568
|
+
seen.add(key);
|
|
2569
|
+
executionIndexes.add(observation.executionIndex);
|
|
2570
|
+
previousRowSha256 = parsed.rowSha256;
|
|
2520
2571
|
}
|
|
2521
2572
|
return {
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
difficulty: valueDistribution(values.difficulty),
|
|
2526
|
-
solved: valueDistribution(values.solved)
|
|
2573
|
+
observations,
|
|
2574
|
+
nextSequence: observations.length,
|
|
2575
|
+
previousRowSha256
|
|
2527
2576
|
};
|
|
2528
2577
|
}
|
|
2529
|
-
function
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
selectedCount: selected.length,
|
|
2536
|
-
stratified: false,
|
|
2537
|
-
representativeOfInput: census,
|
|
2538
|
-
source: publicBenchmarkDistributions(dataset, source),
|
|
2539
|
-
selected: publicBenchmarkDistributions(dataset, selected)
|
|
2540
|
-
};
|
|
2541
|
-
}
|
|
2542
|
-
async function preparePublicAnalystBenchmark(options) {
|
|
2543
|
-
const labelsPath = resolve(options.labelsPath);
|
|
2544
|
-
const traceRoot = resolve(options.traceDir);
|
|
2545
|
-
const artifactRoot = options.artifactDir ? resolve(options.artifactDir) : void 0;
|
|
2546
|
-
const labelSnapshot = await readImmutableInputSnapshot(labelsPath, DEFAULT_MAX_PUBLIC_BENCHMARK_LABEL_BYTES);
|
|
2547
|
-
const rows = parsePublicBenchmarkRows(labelSnapshot.text, labelsPath);
|
|
2548
|
-
const selected = selectPublicBenchmarkRows(options.dataset, rows, {
|
|
2549
|
-
limit: options.limit,
|
|
2550
|
-
seed: options.seed
|
|
2551
|
-
});
|
|
2552
|
-
const stores = await indexSelectedSingleTraceFiles(traceRoot, new Set(selected.map((row) => publicBenchmarkRowId(options.dataset, row))));
|
|
2553
|
-
const resolver = traceStoreEvidenceResolver((input) => {
|
|
2554
|
-
if (!input.traceStore) throw new Error("prepared benchmark case has no trace store");
|
|
2555
|
-
return input.traceStore;
|
|
2556
|
-
});
|
|
2557
|
-
const traceFiles = [];
|
|
2558
|
-
const verificationArtifacts = [];
|
|
2559
|
-
const cases = [];
|
|
2560
|
-
for (const row of selected) {
|
|
2561
|
-
const trajectoryId = publicBenchmarkRowId(options.dataset, row);
|
|
2562
|
-
const indexed = stores.get(trajectoryId);
|
|
2563
|
-
if (!indexed) throw new Error(`public analyst benchmark trace directory has no single-trace OTLP JSONL for '${trajectoryId}'`);
|
|
2564
|
-
let modelVisibleOtlp = indexed.text;
|
|
2565
|
-
let traceStore = indexed.store;
|
|
2566
|
-
let artifactDir;
|
|
2567
|
-
let verificationManifest;
|
|
2568
|
-
if (options.dataset === "codetracebench") {
|
|
2569
|
-
if (!options.artifactDir?.trim()) throw new Error("--artifact-dir is required for CodeTraceBench so final verification evidence is not omitted");
|
|
2570
|
-
const artifacts = await loadCodeTraceVerificationArtifacts({
|
|
2571
|
-
artifactDir: options.artifactDir,
|
|
2572
|
-
row,
|
|
2573
|
-
maxBytes: options.maxArtifactBytes ?? 8388608
|
|
2574
|
-
});
|
|
2575
|
-
for (const artifact of artifacts.files) assertNoBenchmarkLabelsInArtifact({
|
|
2576
|
-
traceId: trajectoryId,
|
|
2577
|
-
relativePath: artifact.relativePath,
|
|
2578
|
-
content: artifact.content
|
|
2579
|
-
});
|
|
2580
|
-
verificationManifest = shareableVerificationManifest(artifacts.manifest, artifactRoot ?? resolve(options.artifactDir));
|
|
2581
|
-
const collisions = await indexed.store.hasSpans({
|
|
2582
|
-
trace_id: trajectoryId,
|
|
2583
|
-
span_ids: [...artifacts.manifest.files.map((file) => file.spanId), artifacts.manifest.outcomeSpanId]
|
|
2584
|
-
});
|
|
2585
|
-
if (collisions.length > 0) throw new Error(`CodeTraceBench '${trajectoryId}' trace already contains benchmark verification span '${collisions[0]}'`);
|
|
2586
|
-
modelVisibleOtlp = appendVerificationArtifactsToOtlp(indexed.text, trajectoryId, artifacts, indexed.latestTimestamp);
|
|
2587
|
-
traceStore = otlpTextToTraceAnalysisStore(modelVisibleOtlp);
|
|
2588
|
-
artifactDir = artifacts.manifest.status === "present" ? artifacts.manifest.caseDirectory : void 0;
|
|
2589
|
-
verificationArtifacts.push(verificationManifest);
|
|
2590
|
-
}
|
|
2591
|
-
const labelLeakScan = assertNoBenchmarkLabelsInTrace({
|
|
2592
|
-
traceId: trajectoryId,
|
|
2593
|
-
otlpText: modelVisibleOtlp
|
|
2594
|
-
});
|
|
2595
|
-
const input = {
|
|
2596
|
-
traceStore,
|
|
2597
|
-
artifactDir
|
|
2598
|
-
};
|
|
2599
|
-
const benchmarkCase = options.dataset === "agentrx" ? agentRxBenchmarkCase(row, input, { stepCount: indexed.stepCount }) : codeTraceBenchCase(row, input);
|
|
2600
|
-
for (const evidence of benchmarkCase.labeledEvidence ?? []) if (!await resolver({
|
|
2601
|
-
caseId: benchmarkCase.id,
|
|
2602
|
-
caseInput: input,
|
|
2603
|
-
evidence: {
|
|
2604
|
-
kind: evidence.kind ?? "span",
|
|
2605
|
-
uri: evidence.uri
|
|
2606
|
-
}
|
|
2607
|
-
})) throw new Error(`${benchmarkCase.id}: missing labeled span ${spanIdFromEvidence(evidence.uri) ?? evidence.uri} in ${indexed.path}`);
|
|
2608
|
-
cases.push({
|
|
2609
|
-
...benchmarkCase,
|
|
2610
|
-
metadata: {
|
|
2611
|
-
...benchmarkCase.metadata,
|
|
2612
|
-
traceFileRelativePath: slashRelative(traceRoot, indexed.path),
|
|
2613
|
-
traceFileSha256: indexed.sha256,
|
|
2614
|
-
labelLeakScan,
|
|
2615
|
-
...verificationManifest ? { verificationArtifacts: verificationManifest } : {}
|
|
2616
|
-
}
|
|
2617
|
-
});
|
|
2618
|
-
traceFiles.push({
|
|
2619
|
-
traceId: trajectoryId,
|
|
2620
|
-
relativePath: slashRelative(traceRoot, indexed.path),
|
|
2621
|
-
sha256: indexed.sha256
|
|
2622
|
-
});
|
|
2578
|
+
async function writeExclusiveOrVerify(path, content) {
|
|
2579
|
+
try {
|
|
2580
|
+
await writeExclusive(path, content);
|
|
2581
|
+
} catch (error) {
|
|
2582
|
+
if (!isNodeError(error, "EEXIST")) throw error;
|
|
2583
|
+
if (await readRegularFile(path, "existing benchmark artifact") !== content) throw new Error(`refusing to replace existing benchmark artifact: ${path}`);
|
|
2623
2584
|
}
|
|
2624
|
-
return {
|
|
2625
|
-
cases,
|
|
2626
|
-
sourceRowCount: rows.length,
|
|
2627
|
-
selectedCaseIds: cases.map((testCase) => testCase.id),
|
|
2628
|
-
labelsSha256: labelSnapshot.sha256,
|
|
2629
|
-
traceFiles,
|
|
2630
|
-
verificationArtifacts,
|
|
2631
|
-
selection: publicBenchmarkSelectionReport(options.dataset, rows, selected, options.seed)
|
|
2632
|
-
};
|
|
2633
2585
|
}
|
|
2634
|
-
async function
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
const overview = await store.getOverview();
|
|
2643
|
-
if (overview.total_traces !== 1 || overview.sample_trace_ids.length !== 1) throw new Error(`public analyst benchmark trace file must contain exactly one trace: ${path} contains ${overview.total_traces}`);
|
|
2644
|
-
if (!overview.time_range) throw new Error(`public analyst benchmark trace file has no valid timestamps: ${path}`);
|
|
2645
|
-
const traceId = overview.sample_trace_ids[0];
|
|
2646
|
-
if (!selectedTraceIds.has(traceId)) continue;
|
|
2647
|
-
if (indexed.has(traceId)) throw new Error(`public analyst benchmark trace id '${traceId}' appears in multiple files`);
|
|
2648
|
-
indexed.set(traceId, {
|
|
2649
|
-
path,
|
|
2650
|
-
sha256: snapshot.sha256,
|
|
2651
|
-
store,
|
|
2652
|
-
latestTimestamp: overview.time_range.latest,
|
|
2653
|
-
text: snapshot.text,
|
|
2654
|
-
stepCount: traceStepCount(snapshot.text, path)
|
|
2655
|
-
});
|
|
2586
|
+
async function regularFileExists(path) {
|
|
2587
|
+
try {
|
|
2588
|
+
const fileStat = await lstat(path);
|
|
2589
|
+
if (!fileStat.isFile() || fileStat.isSymbolicLink()) throw new Error(`benchmark artifact path must be a real file: ${path}`);
|
|
2590
|
+
return true;
|
|
2591
|
+
} catch (error) {
|
|
2592
|
+
if (isNodeError(error, "ENOENT")) return false;
|
|
2593
|
+
throw error;
|
|
2656
2594
|
}
|
|
2657
|
-
return indexed;
|
|
2658
2595
|
}
|
|
2659
|
-
async function
|
|
2660
|
-
|
|
2661
|
-
const handle = await open(path, INPUT_OPEN_FLAGS);
|
|
2596
|
+
async function appendDurable(path, content) {
|
|
2597
|
+
const handle = await open(path, constants.O_APPEND | constants.O_WRONLY | constants.O_NOFOLLOW);
|
|
2662
2598
|
try {
|
|
2663
|
-
|
|
2599
|
+
await handle.writeFile(content, "utf8");
|
|
2600
|
+
await handle.sync();
|
|
2664
2601
|
} finally {
|
|
2665
2602
|
await handle.close();
|
|
2666
2603
|
}
|
|
2667
2604
|
}
|
|
2668
|
-
async function
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2605
|
+
async function writeExclusive(path, content) {
|
|
2606
|
+
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
2607
|
+
let handle;
|
|
2608
|
+
try {
|
|
2609
|
+
handle = await open(temporary, "wx");
|
|
2610
|
+
await handle.writeFile(content, "utf8");
|
|
2611
|
+
await handle.sync();
|
|
2612
|
+
await handle.close();
|
|
2613
|
+
handle = void 0;
|
|
2614
|
+
await link(temporary, path);
|
|
2615
|
+
await syncDirectory(dirname(path));
|
|
2616
|
+
} finally {
|
|
2617
|
+
await handle?.close().catch(() => void 0);
|
|
2618
|
+
await unlink(temporary).catch(() => void 0);
|
|
2679
2619
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
if (extra.bytesRead !== 0 || before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs) throw new Error(`public analyst benchmark input changed while being read: ${path}`);
|
|
2684
|
-
let text;
|
|
2620
|
+
}
|
|
2621
|
+
async function readRegularFile(path, label) {
|
|
2622
|
+
let fileStat;
|
|
2685
2623
|
try {
|
|
2686
|
-
|
|
2624
|
+
fileStat = await lstat(path);
|
|
2687
2625
|
} catch (error) {
|
|
2688
|
-
throw new
|
|
2626
|
+
if (isNodeError(error, "ENOENT")) throw new Error(`${label} is missing: ${path}`);
|
|
2627
|
+
throw error;
|
|
2689
2628
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
sha256: sha256Digest(bytes),
|
|
2693
|
-
text
|
|
2694
|
-
});
|
|
2629
|
+
if (!fileStat.isFile() || fileStat.isSymbolicLink()) throw new Error(`${label} must be a real file: ${path}`);
|
|
2630
|
+
return readFile(path, "utf8");
|
|
2695
2631
|
}
|
|
2696
|
-
function
|
|
2697
|
-
const
|
|
2698
|
-
|
|
2699
|
-
|
|
2632
|
+
async function syncDirectory(path) {
|
|
2633
|
+
const directory = await open(path, "r");
|
|
2634
|
+
try {
|
|
2635
|
+
await directory.sync();
|
|
2636
|
+
} finally {
|
|
2637
|
+
await directory.close();
|
|
2638
|
+
}
|
|
2700
2639
|
}
|
|
2701
|
-
function
|
|
2640
|
+
function isNodeError(error, code) {
|
|
2641
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
2642
|
+
}
|
|
2643
|
+
//#endregion
|
|
2644
|
+
//#region src/analyst/benchmark-comparison.ts
|
|
2645
|
+
function compareAnalystRunners(result, options) {
|
|
2646
|
+
const confidence = options.confidence ?? .95;
|
|
2647
|
+
const resamples = options.resamples ?? 2e3;
|
|
2648
|
+
assertComparisonControls(confidence, resamples);
|
|
2649
|
+
const runnerIds = new Set(result.summaries.map((summary) => summary.runnerId));
|
|
2650
|
+
if (!runnerIds.has(options.baselineRunnerId)) throw new TypeError(`unknown baseline analyst runner '${options.baselineRunnerId}'`);
|
|
2651
|
+
if (!runnerIds.has(options.candidateRunnerId)) throw new TypeError(`unknown candidate analyst runner '${options.candidateRunnerId}'`);
|
|
2652
|
+
if (options.baselineRunnerId === options.candidateRunnerId) throw new TypeError("baseline and candidate analyst runners must be different");
|
|
2653
|
+
const baseline = observationsByCase(result.observations, options.baselineRunnerId);
|
|
2654
|
+
const candidate = observationsByCase(result.observations, options.candidateRunnerId);
|
|
2655
|
+
const populationRepresentativenessProven = result.provenance.metadata?.populationRepresentativenessProven === true;
|
|
2656
|
+
const metrics = METRICS.map((metric) => compareMetric({
|
|
2657
|
+
metric,
|
|
2658
|
+
baseline,
|
|
2659
|
+
candidate,
|
|
2660
|
+
confidence,
|
|
2661
|
+
resamples,
|
|
2662
|
+
seed: options.seed,
|
|
2663
|
+
populationRepresentativenessProven
|
|
2664
|
+
}));
|
|
2702
2665
|
return {
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
files: manifest.files.map((file) => ({
|
|
2707
|
-
...file,
|
|
2708
|
-
path: slashRelative(artifactRoot, file.path)
|
|
2709
|
-
}))
|
|
2666
|
+
baselineRunnerId: options.baselineRunnerId,
|
|
2667
|
+
candidateRunnerId: options.candidateRunnerId,
|
|
2668
|
+
metrics
|
|
2710
2669
|
};
|
|
2711
2670
|
}
|
|
2712
|
-
function
|
|
2713
|
-
const
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
const
|
|
2724
|
-
|
|
2725
|
-
let
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2671
|
+
function compareMetric(options) {
|
|
2672
|
+
const pairedCases = [];
|
|
2673
|
+
let eligibleObservations = 0;
|
|
2674
|
+
let pairedObservations = 0;
|
|
2675
|
+
let baselineMissingObservations = 0;
|
|
2676
|
+
let candidateMissingObservations = 0;
|
|
2677
|
+
let asymmetricMissingObservations = 0;
|
|
2678
|
+
const caseIds = /* @__PURE__ */ new Set([...options.baseline.keys(), ...options.candidate.keys()]);
|
|
2679
|
+
for (const caseId of caseIds) {
|
|
2680
|
+
const baselineByRepetition = new Map((options.baseline.get(caseId) ?? []).map((observation) => [observation.repetition, observation]));
|
|
2681
|
+
const candidateByRepetition = new Map((options.candidate.get(caseId) ?? []).map((observation) => [observation.repetition, observation]));
|
|
2682
|
+
const caseBefore = [];
|
|
2683
|
+
const caseAfter = [];
|
|
2684
|
+
let clusterId;
|
|
2685
|
+
const repetitions = /* @__PURE__ */ new Set([...baselineByRepetition.keys(), ...candidateByRepetition.keys()]);
|
|
2686
|
+
for (const repetition of repetitions) {
|
|
2687
|
+
const baselineObservation = baselineByRepetition.get(repetition);
|
|
2688
|
+
const candidateObservation = candidateByRepetition.get(repetition);
|
|
2689
|
+
const identity = baselineObservation ?? candidateObservation;
|
|
2690
|
+
if (!identity || !metricApplies(identity, options.metric)) continue;
|
|
2691
|
+
if (baselineObservation && candidateObservation) assertSameCaseIdentity(baselineObservation, candidateObservation);
|
|
2692
|
+
eligibleObservations += 1;
|
|
2693
|
+
clusterId = identity.clusterId;
|
|
2694
|
+
const baselineValue = baselineObservation ? metricValue(baselineObservation, options.metric) : null;
|
|
2695
|
+
const candidateValue = candidateObservation ? metricValue(candidateObservation, options.metric) : null;
|
|
2696
|
+
const baselineMissing = baselineValue === null;
|
|
2697
|
+
const candidateMissing = candidateValue === null;
|
|
2698
|
+
if (baselineMissing) baselineMissingObservations += 1;
|
|
2699
|
+
if (candidateMissing) candidateMissingObservations += 1;
|
|
2700
|
+
if (baselineMissing !== candidateMissing) asymmetricMissingObservations += 1;
|
|
2701
|
+
if (baselineMissing || candidateMissing) continue;
|
|
2702
|
+
caseBefore.push(baselineValue);
|
|
2703
|
+
caseAfter.push(candidateValue);
|
|
2704
|
+
pairedObservations += 1;
|
|
2705
|
+
}
|
|
2706
|
+
if (caseBefore.length === 0 || !clusterId) continue;
|
|
2707
|
+
pairedCases.push({
|
|
2708
|
+
clusterId,
|
|
2709
|
+
baseline: mean$1(caseBefore),
|
|
2710
|
+
candidate: mean$1(caseAfter)
|
|
2711
|
+
});
|
|
2712
|
+
}
|
|
2713
|
+
const byCluster = /* @__PURE__ */ new Map();
|
|
2714
|
+
for (const pairedCase of pairedCases) {
|
|
2715
|
+
const rows = byCluster.get(pairedCase.clusterId) ?? [];
|
|
2716
|
+
rows.push(pairedCase);
|
|
2717
|
+
byCluster.set(pairedCase.clusterId, rows);
|
|
2718
|
+
}
|
|
2719
|
+
const before = [...byCluster.values()].map((rows) => mean$1(rows.map((row) => row.baseline)));
|
|
2720
|
+
const after = [...byCluster.values()].map((rows) => mean$1(rows.map((row) => row.candidate)));
|
|
2721
|
+
const interval = before.length === 0 ? null : pairedBootstrap(before, after, {
|
|
2722
|
+
confidence: options.confidence,
|
|
2723
|
+
resamples: options.resamples,
|
|
2724
|
+
statistic: "mean",
|
|
2725
|
+
seed: options.seed
|
|
2741
2726
|
});
|
|
2727
|
+
const survivorOnly = pairedObservations < eligibleObservations;
|
|
2728
|
+
const limitations = [];
|
|
2729
|
+
if (!interval?.gateEligible) limitations.push("fewer-than-20-independent-clusters");
|
|
2730
|
+
if (!options.populationRepresentativenessProven) limitations.push("population-representativeness-not-proven");
|
|
2731
|
+
if (survivorOnly) limitations.push("missing-observations");
|
|
2732
|
+
const comparison = {
|
|
2733
|
+
metric: options.metric,
|
|
2734
|
+
direction: LOWER_IS_BETTER.has(options.metric) ? "lower" : "higher",
|
|
2735
|
+
pairedCases: pairedCases.length,
|
|
2736
|
+
pairedClusters: before.length,
|
|
2737
|
+
eligibleObservations,
|
|
2738
|
+
pairedObservations,
|
|
2739
|
+
baselineMissingObservations,
|
|
2740
|
+
candidateMissingObservations,
|
|
2741
|
+
asymmetricMissingObservations,
|
|
2742
|
+
survivorOnly,
|
|
2743
|
+
baselineMean: before.length === 0 ? null : mean$1(before),
|
|
2744
|
+
candidateMean: after.length === 0 ? null : mean$1(after),
|
|
2745
|
+
meanDelta: interval?.mean ?? null,
|
|
2746
|
+
intervalLow: interval?.low ?? null,
|
|
2747
|
+
intervalHigh: interval?.high ?? null,
|
|
2748
|
+
confidence: options.confidence,
|
|
2749
|
+
resamples: options.resamples,
|
|
2750
|
+
minimumSampleMet: interval?.gateEligible ?? false,
|
|
2751
|
+
populationInferenceEligible: limitations.length === 0,
|
|
2752
|
+
inferenceLimitations: limitations
|
|
2753
|
+
};
|
|
2754
|
+
assertValidComparison(comparison);
|
|
2755
|
+
return comparison;
|
|
2742
2756
|
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2757
|
+
const METRICS = [
|
|
2758
|
+
"completion",
|
|
2759
|
+
"issueRecall",
|
|
2760
|
+
"findingPrecision",
|
|
2761
|
+
"f1",
|
|
2762
|
+
"criticalStepAccuracy",
|
|
2763
|
+
"citationCoverage",
|
|
2764
|
+
"citationExcerptCoverage",
|
|
2765
|
+
"citationLabelAgreement",
|
|
2766
|
+
"citationResolution",
|
|
2767
|
+
"trustedNegativeAccuracy",
|
|
2768
|
+
"latencyMs",
|
|
2769
|
+
"calls",
|
|
2770
|
+
"inputTokens",
|
|
2771
|
+
"outputTokens",
|
|
2772
|
+
"reasoningTokens",
|
|
2773
|
+
"cachedTokens",
|
|
2774
|
+
"cacheWriteTokens",
|
|
2775
|
+
"costUsd"
|
|
2776
|
+
];
|
|
2777
|
+
const LOWER_IS_BETTER = /* @__PURE__ */ new Set([
|
|
2778
|
+
"latencyMs",
|
|
2779
|
+
"calls",
|
|
2780
|
+
"inputTokens",
|
|
2781
|
+
"outputTokens",
|
|
2782
|
+
"reasoningTokens",
|
|
2783
|
+
"cachedTokens",
|
|
2784
|
+
"cacheWriteTokens",
|
|
2785
|
+
"costUsd"
|
|
2786
|
+
]);
|
|
2787
|
+
function observationsByCase(observations, runnerId) {
|
|
2788
|
+
const byCase = /* @__PURE__ */ new Map();
|
|
2789
|
+
for (const observation of observations) {
|
|
2790
|
+
if (observation.runnerId !== runnerId) continue;
|
|
2791
|
+
const rows = byCase.get(observation.caseId) ?? [];
|
|
2792
|
+
rows.push(observation);
|
|
2793
|
+
byCase.set(observation.caseId, rows);
|
|
2794
|
+
}
|
|
2795
|
+
return byCase;
|
|
2747
2796
|
}
|
|
2748
|
-
function
|
|
2749
|
-
|
|
2750
|
-
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
2797
|
+
function assertSameCaseIdentity(baseline, candidate) {
|
|
2798
|
+
if (baseline.clusterId !== candidate.clusterId || baseline.labelState !== candidate.labelState) throw new Error(`analyst comparison case identity differs for '${baseline.caseId}' repetition ${baseline.repetition}`);
|
|
2751
2799
|
}
|
|
2752
|
-
function
|
|
2753
|
-
|
|
2800
|
+
function metricApplies(observation, metric) {
|
|
2801
|
+
if (metric === "trustedNegativeAccuracy") return observation.labelState === "trusted-negative";
|
|
2802
|
+
if (metric === "issueRecall" || metric === "findingPrecision" || metric === "f1") return observation.labelState === "positive";
|
|
2803
|
+
if (metric === "criticalStepAccuracy") return observation.labelState === "positive" && observation.score.criticalStepAccuracy !== null;
|
|
2804
|
+
return true;
|
|
2754
2805
|
}
|
|
2755
|
-
function
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
if (
|
|
2760
|
-
|
|
2761
|
-
continue;
|
|
2762
|
-
}
|
|
2763
|
-
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
2806
|
+
function metricValue(observation, metric) {
|
|
2807
|
+
if (metric === "completion") return observation.error ? 0 : 1;
|
|
2808
|
+
if (metric === "latencyMs") return observation.latencyMs;
|
|
2809
|
+
if (metric === "trustedNegativeAccuracy") {
|
|
2810
|
+
if (observation.error) return 0;
|
|
2811
|
+
return observation.score.predictionOnLabelEmptyCase ? 0 : 1;
|
|
2764
2812
|
}
|
|
2765
|
-
return
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2813
|
+
if (observation.error && (metric === "issueRecall" || metric === "findingPrecision" || metric === "f1" || metric === "criticalStepAccuracy")) return 0;
|
|
2814
|
+
if (observation.error && (metric === "citationCoverage" || metric === "citationExcerptCoverage" || metric === "citationLabelAgreement" || metric === "citationResolution")) return null;
|
|
2815
|
+
if (metric === "issueRecall") return observation.score.issueRecall;
|
|
2816
|
+
if (metric === "findingPrecision") return observation.score.findingPrecision;
|
|
2817
|
+
if (metric === "f1") return observation.score.f1;
|
|
2818
|
+
if (metric === "criticalStepAccuracy") return observation.score.criticalStepAccuracy;
|
|
2819
|
+
if (metric === "citationCoverage") return observation.score.citationCoverage;
|
|
2820
|
+
if (metric === "citationExcerptCoverage") return observation.score.citationExcerptCoverage;
|
|
2821
|
+
if (metric === "citationLabelAgreement") return observation.score.citationLabelAgreement;
|
|
2822
|
+
if (metric === "citationResolution") return observation.evidenceResolution?.validity ?? null;
|
|
2823
|
+
if (metric === "calls") return observation.usage?.calls ?? null;
|
|
2824
|
+
if (metric === "inputTokens") return observation.usage?.tokens?.input ?? null;
|
|
2825
|
+
if (metric === "outputTokens") return observation.usage?.tokens?.output ?? null;
|
|
2826
|
+
if (metric === "reasoningTokens") return observation.usage?.tokens?.reasoning ?? null;
|
|
2827
|
+
if (metric === "cachedTokens") return observation.usage?.tokens?.cached ?? null;
|
|
2828
|
+
if (metric === "cacheWriteTokens") return observation.usage?.tokens?.cacheWrite ?? null;
|
|
2829
|
+
if (observation.usage?.cost.kind === "uncaptured") return null;
|
|
2830
|
+
return observation.usage?.cost.usd ?? null;
|
|
2770
2831
|
}
|
|
2771
|
-
function
|
|
2772
|
-
|
|
2773
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2832
|
+
function mean$1(values) {
|
|
2833
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
2774
2834
|
}
|
|
2775
|
-
function
|
|
2776
|
-
|
|
2777
|
-
|
|
2835
|
+
function assertComparisonControls(confidence, resamples) {
|
|
2836
|
+
if (!Number.isSafeInteger(resamples) || resamples <= 0 || resamples > 1e6) throw new Error(`compareAnalystRunners: resamples must be a positive safe integer no greater than 1000000, got ${String(resamples)}`);
|
|
2837
|
+
if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) throw new Error(`compareAnalystRunners: confidence must be a finite number in (0,1), got ${String(confidence)}`);
|
|
2838
|
+
}
|
|
2839
|
+
function assertValidComparison(comparison) {
|
|
2840
|
+
if ([
|
|
2841
|
+
"pairedCases",
|
|
2842
|
+
"pairedClusters",
|
|
2843
|
+
"eligibleObservations",
|
|
2844
|
+
"pairedObservations",
|
|
2845
|
+
"baselineMissingObservations",
|
|
2846
|
+
"candidateMissingObservations",
|
|
2847
|
+
"asymmetricMissingObservations",
|
|
2848
|
+
"confidence",
|
|
2849
|
+
"resamples"
|
|
2850
|
+
].some((field) => !Number.isFinite(comparison[field])) || [
|
|
2851
|
+
"baselineMean",
|
|
2852
|
+
"candidateMean",
|
|
2853
|
+
"meanDelta",
|
|
2854
|
+
"intervalLow",
|
|
2855
|
+
"intervalHigh"
|
|
2856
|
+
].some((field) => comparison[field] !== null && !Number.isFinite(comparison[field]))) throw new Error(`compareAnalystRunners: ${comparison.metric} produced non-finite comparison output`);
|
|
2857
|
+
if (comparison.intervalLow !== null && comparison.intervalHigh !== null && comparison.intervalLow > comparison.intervalHigh) throw new Error(`compareAnalystRunners: ${comparison.metric} produced an invalid confidence interval`);
|
|
2778
2858
|
}
|
|
2779
2859
|
//#endregion
|
|
2780
|
-
//#region src/analyst/benchmark-public-
|
|
2781
|
-
function
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
message: `Provider request failed with HTTP ${error.status}.`
|
|
2787
|
-
};
|
|
2788
|
-
if (error instanceof LlmResponseError) return {
|
|
2789
|
-
class: "LlmResponseError",
|
|
2790
|
-
code: error.code,
|
|
2791
|
-
message: "Provider response did not satisfy the structured output contract."
|
|
2792
|
-
};
|
|
2793
|
-
if (error instanceof z.ZodError) return {
|
|
2794
|
-
class: "ModelOutputValidationError",
|
|
2795
|
-
message: "Provider response did not match the benchmark output schema."
|
|
2796
|
-
};
|
|
2797
|
-
if (error instanceof SyntaxError) return {
|
|
2798
|
-
class: "ModelOutputParseError",
|
|
2799
|
-
message: "Provider response was not valid JSON."
|
|
2800
|
-
};
|
|
2801
|
-
if (error instanceof CostCeilingReachedError || error instanceof CostAccountingIncompleteError || error instanceof CostReservationExceededError) return {
|
|
2802
|
-
class: error.constructor.name,
|
|
2803
|
-
code: error.code,
|
|
2804
|
-
message: redactSensitiveText(error.message, secrets)
|
|
2805
|
-
};
|
|
2806
|
-
if (error instanceof Error && error.name === "AbortError") return {
|
|
2807
|
-
class: "ProviderTimeoutError",
|
|
2808
|
-
message: "Provider request timed out."
|
|
2809
|
-
};
|
|
2810
|
-
if (error instanceof Error && /(?:assistant steps?|finding evidence|selected missing|selected unavailable|no readable spans|requires a trace store)/i.test(error.message)) return {
|
|
2811
|
-
class: "BenchmarkEvidenceError",
|
|
2812
|
-
message: redactSensitiveText(error.message, secrets)
|
|
2813
|
-
};
|
|
2814
|
-
if (error instanceof AgentEvalError) return {
|
|
2815
|
-
class: error.constructor.name,
|
|
2816
|
-
code: error.code,
|
|
2817
|
-
message: redactSensitiveText(error.message, secrets)
|
|
2818
|
-
};
|
|
2819
|
-
if (error instanceof Error) return {
|
|
2820
|
-
class: error.constructor.name || "Error",
|
|
2821
|
-
message: redactSensitiveText(error.message, secrets)
|
|
2822
|
-
};
|
|
2823
|
-
return {
|
|
2824
|
-
class: "Error",
|
|
2825
|
-
message: "Benchmark analyst execution failed."
|
|
2860
|
+
//#region src/analyst/benchmark-public-calibration.ts
|
|
2861
|
+
function summarizeCodeTraceCalibration(result) {
|
|
2862
|
+
return {
|
|
2863
|
+
protocol: "labeled-positive-and-solved-negative",
|
|
2864
|
+
rationale: "Uses rows with incorrect-step labels as positives and solved label-empty rows as trusted negatives. Failed label-empty rows remain in the published result but are not treated as clean controls.",
|
|
2865
|
+
runners: result.provenance.runnerIds.map((runnerId) => summarizeRunner(runnerId, result.observations.filter((observation) => observation.runnerId === runnerId)))
|
|
2826
2866
|
};
|
|
2827
2867
|
}
|
|
2828
|
-
function
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2868
|
+
function renderCodeTraceCalibrationMarkdown(summary) {
|
|
2869
|
+
return [
|
|
2870
|
+
"## CodeTraceBench Calibrated View",
|
|
2871
|
+
"",
|
|
2872
|
+
summary.rationale,
|
|
2873
|
+
"",
|
|
2874
|
+
"| Runner | Completed/selected | Failed | Positive runs | Trusted negative runs | Unlabeled runs | Failed label-empty | Unknown label-empty | Matched/expected steps | Predicted steps | Precision | Recall | F1 | Official all-row F1 | Official rows | Trusted-negative false positives | Trusted-negative failures | Unlabeled predictions | Unlabeled failures |",
|
|
2875
|
+
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
2876
|
+
...summary.runners.map((runner) => `| ${escapeCell$1(runner.runnerId)} | ${runner.completedRuns}/${runner.selectedRuns} | ${runner.failedRuns} | ${runner.positiveRuns} | ${runner.trustedNegativeRuns} | ${runner.unlabeledRuns} | ${runner.failedLabelEmptyRuns} | ${runner.unknownLabelEmptyRuns} | ${runner.matchedIncorrectSteps}/${runner.expectedIncorrectSteps} | ${runner.predictedIncorrectSteps} | ${rate$1(runner.precision)} | ${rate$1(runner.recall)} | ${rate$1(runner.f1)} | ${rate$1(runner.officialAllRowF1)} | ${runner.officialAllRowRuns} | ${rate$1(runner.trustedNegativeFalsePositiveRate)} | ${rate$1(runner.trustedNegativeFailureRate)} | ${rate$1(runner.unlabeledPredictionRate)} | ${rate$1(runner.unlabeledFailureRate)} |`)
|
|
2877
|
+
].join("\n");
|
|
2836
2878
|
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
const
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
runIdentitySha256: z.string().regex(SHA256),
|
|
2874
|
-
caseId: z.string().min(1),
|
|
2875
|
-
repetition: z.number().int().nonnegative()
|
|
2876
|
-
};
|
|
2877
|
-
const SuccessCacheEntryWithoutDigestSchema = z.object({
|
|
2878
|
-
...CacheIdentityShape,
|
|
2879
|
-
status: z.literal("succeeded"),
|
|
2880
|
-
response: z.json(),
|
|
2881
|
-
metadata: ResponseMetadataSchema,
|
|
2882
|
-
receipt: CostReceiptInputSchema
|
|
2883
|
-
}).strict();
|
|
2884
|
-
const FailureCacheEntryWithoutDigestSchema = z.object({
|
|
2885
|
-
...CacheIdentityShape,
|
|
2886
|
-
status: z.literal("failed"),
|
|
2887
|
-
error: BenchmarkErrorSchema,
|
|
2888
|
-
receipt: CostReceiptInputSchema
|
|
2889
|
-
}).strict();
|
|
2890
|
-
const CacheEntryWithoutDigestSchema = z.discriminatedUnion("status", [SuccessCacheEntryWithoutDigestSchema, FailureCacheEntryWithoutDigestSchema]);
|
|
2891
|
-
const CacheEntrySchema = z.discriminatedUnion("status", [SuccessCacheEntryWithoutDigestSchema.extend({ entrySha256: z.string().regex(SHA256) }), FailureCacheEntryWithoutDigestSchema.extend({ entrySha256: z.string().regex(SHA256) })]);
|
|
2892
|
-
function publicBenchmarkCallId(identity) {
|
|
2893
|
-
assertCacheIdentity(identity);
|
|
2894
|
-
return `analyst-benchmark-${hashCanonical({
|
|
2895
|
-
runIdentitySha256: identity.runIdentitySha256,
|
|
2896
|
-
caseId: identity.caseId,
|
|
2897
|
-
repetition: identity.repetition
|
|
2898
|
-
}).slice(7)}`;
|
|
2879
|
+
function summarizeRunner(runnerId, observations) {
|
|
2880
|
+
const positive = observations.filter((observation) => observation.labelState === "positive");
|
|
2881
|
+
const trustedNegative = observations.filter((observation) => observation.labelState === "trusted-negative");
|
|
2882
|
+
const excluded = observations.filter((observation) => observation.labelState === "unlabeled");
|
|
2883
|
+
const selected = [...positive, ...trustedNegative];
|
|
2884
|
+
const expected = sum(positive.map((observation) => observation.score.expectedIssueCount));
|
|
2885
|
+
const predicted = sum(selected.map((observation) => observation.error ? 0 : observation.findings.length));
|
|
2886
|
+
const matched = sum(positive.map((observation) => observation.score.matchedIssueIds.length));
|
|
2887
|
+
const precision = predicted === 0 ? expected > 0 ? 0 : null : matched / predicted;
|
|
2888
|
+
const recall = ratio(matched, expected);
|
|
2889
|
+
const completedTrustedNegative = trustedNegative.filter((observation) => !observation.error);
|
|
2890
|
+
const completedExcluded = excluded.filter((observation) => !observation.error);
|
|
2891
|
+
const officialRows = observations.map(officialCodeTraceF1);
|
|
2892
|
+
return {
|
|
2893
|
+
runnerId,
|
|
2894
|
+
selectedRuns: selected.length,
|
|
2895
|
+
positiveRuns: positive.length,
|
|
2896
|
+
trustedNegativeRuns: trustedNegative.length,
|
|
2897
|
+
unlabeledRuns: excluded.length,
|
|
2898
|
+
failedLabelEmptyRuns: excluded.filter((observation) => observation.caseMetadata?.solved === false).length,
|
|
2899
|
+
unknownLabelEmptyRuns: excluded.filter((observation) => observation.caseMetadata?.solved !== false).length,
|
|
2900
|
+
completedRuns: selected.filter((observation) => !observation.error).length,
|
|
2901
|
+
failedRuns: selected.filter((observation) => observation.error).length,
|
|
2902
|
+
expectedIncorrectSteps: expected,
|
|
2903
|
+
predictedIncorrectSteps: predicted,
|
|
2904
|
+
matchedIncorrectSteps: matched,
|
|
2905
|
+
officialAllRowF1: mean(officialRows),
|
|
2906
|
+
officialAllRowRuns: officialRows.length,
|
|
2907
|
+
precision,
|
|
2908
|
+
recall,
|
|
2909
|
+
f1: harmonicMean(precision, recall),
|
|
2910
|
+
trustedNegativeFalsePositiveRate: ratio(completedTrustedNegative.filter((observation) => observation.score.predictionOnLabelEmptyCase).length, completedTrustedNegative.length),
|
|
2911
|
+
trustedNegativeFailureRate: ratio(trustedNegative.filter((observation) => observation.error).length, trustedNegative.length),
|
|
2912
|
+
unlabeledPredictionRate: ratio(completedExcluded.filter((observation) => observation.findings.length > 0).length, completedExcluded.length),
|
|
2913
|
+
unlabeledFailureRate: ratio(excluded.filter((observation) => Boolean(observation.error)).length, excluded.length)
|
|
2914
|
+
};
|
|
2899
2915
|
}
|
|
2900
|
-
function
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2916
|
+
function officialCodeTraceF1(observation) {
|
|
2917
|
+
const trajectoryId = observation.caseMetadata?.trajectoryId;
|
|
2918
|
+
if (typeof trajectoryId !== "string" || !trajectoryId.trim()) throw new TypeError(`${observation.caseId}: CodeTraceBench trajectoryId metadata is missing`);
|
|
2919
|
+
const expected = new Set([...observation.score.matchedIssueIds, ...observation.score.missedIssueIds].map((issueId) => {
|
|
2920
|
+
const match = /^incorrect:(\d+)$/.exec(issueId);
|
|
2921
|
+
if (!match) throw new TypeError(`${observation.caseId}: invalid incorrect-step label '${issueId}'`);
|
|
2922
|
+
return Number(match[1]);
|
|
2923
|
+
}));
|
|
2924
|
+
const predicted = /* @__PURE__ */ new Set();
|
|
2925
|
+
if (!observation.error) for (const finding of observation.findings) {
|
|
2926
|
+
if (finding.area !== "incorrect") continue;
|
|
2927
|
+
for (const evidence of finding.evidence_refs) {
|
|
2928
|
+
const location = codeTraceStepFromEvidence(evidence.uri);
|
|
2929
|
+
if (!location || location.traceId !== trajectoryId) throw new TypeError(`${observation.caseId}: invalid CodeTraceBench prediction evidence '${evidence.uri}'`);
|
|
2930
|
+
predicted.add(location.step);
|
|
2931
|
+
}
|
|
2911
2932
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
return
|
|
2933
|
+
let matched = 0;
|
|
2934
|
+
for (const step of predicted) if (expected.has(step)) matched += 1;
|
|
2935
|
+
return harmonicMean(predicted.size === 0 ? 0 : matched / predicted.size, expected.size === 0 ? 0 : matched / expected.size) ?? 0;
|
|
2915
2936
|
}
|
|
2916
|
-
function
|
|
2917
|
-
|
|
2918
|
-
if (entry.callId !== expectedCallId) throw new ValidationError("benchmark response cache callId does not match its identity");
|
|
2919
|
-
const validated = JSON.parse(JSON.stringify(CacheEntryWithoutDigestSchema.parse(entry)));
|
|
2920
|
-
const complete = {
|
|
2921
|
-
...validated,
|
|
2922
|
-
entrySha256: hashCanonical(validated).slice(7)
|
|
2923
|
-
};
|
|
2924
|
-
const path = responseCachePath(cacheDirectory, complete.callId);
|
|
2925
|
-
const content = `${canonicalString(complete)}\n`;
|
|
2926
|
-
withLedgerFileLock(path, fileContext(), () => {
|
|
2927
|
-
if (existsSync(path)) {
|
|
2928
|
-
const existing = readPublicBenchmarkResponseCache(cacheDirectory, complete);
|
|
2929
|
-
if (!existing || canonicalString(existing) !== canonicalString(complete)) throw new ValidationError(`benchmark response cache conflicts with existing file: ${path}`);
|
|
2930
|
-
return;
|
|
2931
|
-
}
|
|
2932
|
-
writeLedgerFileAtomically(path, content, fileContext());
|
|
2933
|
-
});
|
|
2934
|
-
return complete;
|
|
2937
|
+
function sum(values) {
|
|
2938
|
+
return values.reduce((total, value) => total + value, 0);
|
|
2935
2939
|
}
|
|
2936
|
-
function
|
|
2937
|
-
|
|
2938
|
-
try {
|
|
2939
|
-
parsed = CacheEntrySchema.parse(value);
|
|
2940
|
-
} catch (error) {
|
|
2941
|
-
throw new ValidationError(`benchmark response cache has an invalid shape: ${path}`, { cause: error });
|
|
2942
|
-
}
|
|
2943
|
-
const { entrySha256, ...withoutDigest } = parsed;
|
|
2944
|
-
if (entrySha256 !== hashCanonical(withoutDigest).slice(7)) throw new ValidationError(`benchmark response cache digest does not match: ${path}`);
|
|
2945
|
-
return parsed;
|
|
2940
|
+
function ratio(numerator, denominator) {
|
|
2941
|
+
return denominator === 0 ? null : numerator / denominator;
|
|
2946
2942
|
}
|
|
2947
|
-
function
|
|
2948
|
-
|
|
2949
|
-
const path = nodePath.resolve(directory, `${hashCanonical(callId).slice(7)}.json`);
|
|
2950
|
-
if (!isPathInsideDirectory(directory, path, nodePath)) throw new ValidationError("benchmark response cache path escapes its directory");
|
|
2951
|
-
return path;
|
|
2943
|
+
function mean(values) {
|
|
2944
|
+
return values.length === 0 ? null : sum(values) / values.length;
|
|
2952
2945
|
}
|
|
2953
|
-
function
|
|
2954
|
-
|
|
2955
|
-
return
|
|
2946
|
+
function harmonicMean(left, right) {
|
|
2947
|
+
if (left === null || right === null) return null;
|
|
2948
|
+
return left + right === 0 ? 0 : 2 * left * right / (left + right);
|
|
2956
2949
|
}
|
|
2957
|
-
function
|
|
2958
|
-
|
|
2959
|
-
if (!identity.caseId.trim()) throw new ValidationError("benchmark response cache requires a case id");
|
|
2960
|
-
if (!Number.isSafeInteger(identity.repetition) || identity.repetition < 0) throw new ValidationError("benchmark response cache repetition must be non-negative");
|
|
2950
|
+
function rate$1(value) {
|
|
2951
|
+
return value === null ? "n/a" : value.toFixed(3);
|
|
2961
2952
|
}
|
|
2962
|
-
function
|
|
2963
|
-
return
|
|
2964
|
-
subject: "benchmark response cache",
|
|
2965
|
-
integrityError: (message, options) => new ValidationError(message, options)
|
|
2966
|
-
};
|
|
2953
|
+
function escapeCell$1(value) {
|
|
2954
|
+
return value.replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
2967
2955
|
}
|
|
2968
2956
|
//#endregion
|
|
2969
|
-
//#region src/analyst/benchmark-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
const
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
2957
|
+
//#region src/analyst/benchmark-command-result.ts
|
|
2958
|
+
async function readAnalystBenchmarkArtifact(path) {
|
|
2959
|
+
const value = parseJson(await readRegularFile(path, "analyst benchmark result"), path);
|
|
2960
|
+
assertAnalystBenchmarkArtifact(value, "analyst benchmark result");
|
|
2961
|
+
return value;
|
|
2962
|
+
}
|
|
2963
|
+
function assertCompletedArtifactMatchesRun(artifact, manifest, observations, prepared) {
|
|
2964
|
+
if (artifact.runIdentitySha256 !== manifest.identitySha256) throw new Error("completed benchmark result belongs to another run");
|
|
2965
|
+
assertSameObservations(artifact.result.observations, observations);
|
|
2966
|
+
const expectedCount = manifest.identity.inputs.selectedCaseIds.length * manifest.identity.config.runnerIds.length * manifest.identity.config.repetitions;
|
|
2967
|
+
if (observations.length !== expectedCount) throw new Error(`completed benchmark result has ${observations.length} observations; expected ${expectedCount}`);
|
|
2968
|
+
const { config, inputs } = manifest.identity;
|
|
2969
|
+
const verificationAvailability = {
|
|
2970
|
+
cases: prepared.verificationArtifacts.length,
|
|
2971
|
+
resultFilesPresent: prepared.verificationArtifacts.filter((artifact) => artifact.status === "present").length,
|
|
2972
|
+
resultFilesMissing: prepared.verificationArtifacts.filter((artifact) => artifact.status === "missing").length,
|
|
2973
|
+
outcomes: {
|
|
2974
|
+
passed: prepared.verificationArtifacts.filter((artifact) => artifact.outcome.status === "passed").length,
|
|
2975
|
+
failed: prepared.verificationArtifacts.filter((artifact) => artifact.outcome.status === "failed").length,
|
|
2976
|
+
unavailable: prepared.verificationArtifacts.filter((artifact) => artifact.outcome.status === "unavailable").length
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
const expectedInputs = {
|
|
2980
|
+
dataset: config.dataset,
|
|
2981
|
+
datasetRevision: config.datasetRevision,
|
|
2982
|
+
datasetSplit: config.datasetSplit,
|
|
2983
|
+
labelsSha256: inputs.labelsSha256,
|
|
2984
|
+
sourceRowCount: inputs.sourceRowCount,
|
|
2985
|
+
traceFiles: inputs.traceFiles.map((traceFile) => ({ ...traceFile })),
|
|
2986
|
+
verificationArtifacts: prepared.verificationArtifacts,
|
|
2987
|
+
verificationAvailability,
|
|
2988
|
+
selection: {
|
|
2989
|
+
limit: config.limit,
|
|
2990
|
+
seed: config.seed,
|
|
2991
|
+
selectedCaseIds: [...inputs.selectedCaseIds],
|
|
2992
|
+
report: prepared.selection
|
|
2993
|
+
},
|
|
2994
|
+
execution: {
|
|
2995
|
+
repetitions: config.repetitions,
|
|
2996
|
+
concurrency: config.concurrency,
|
|
2997
|
+
...config.rlmSamples === void 0 ? {} : { rlmSamples: config.rlmSamples },
|
|
2998
|
+
model: config.model.id,
|
|
2999
|
+
maxOutputTokens: config.model.maxOutputTokens,
|
|
3000
|
+
timeoutMs: config.model.timeoutMs,
|
|
3001
|
+
maxCostUsd: config.maxCostUsd,
|
|
3002
|
+
maxArtifactBytes: config.maxArtifactBytes,
|
|
3003
|
+
analystProtocolSha256: config.analystProtocolSha256,
|
|
3004
|
+
...config.instructionsOverrideSha256 === void 0 ? {} : { instructionsOverrideSha256: config.instructionsOverrideSha256 },
|
|
3005
|
+
implementationSha256: config.implementationSha256,
|
|
3006
|
+
dependencyLockSha256: config.dependencyLockSha256
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
if (canonicalJson(artifact.inputs) !== canonicalJson(expectedInputs)) throw new Error("completed benchmark result inputs do not match the run manifest");
|
|
3010
|
+
const provenance = artifact.result.provenance;
|
|
3011
|
+
const expectedDatasetId = config.dataset === "agentrx" ? "microsoft/AgentRx" : "NJU-LINK/CodeTraceBench";
|
|
3012
|
+
const expectedOutputAdapter = config.dataset === "agentrx" ? "agentrx-taxonomy-and-root-step" : "codetracebench-incorrect-block";
|
|
3013
|
+
if (provenance.id !== `${config.dataset}-real-model-analyst` || provenance.startedAt !== manifest.createdAt || !Number.isFinite(Date.parse(provenance.endedAt)) || Date.parse(provenance.endedAt) < Date.parse(provenance.startedAt) || canonicalJson(provenance.dataset) !== canonicalJson({
|
|
3014
|
+
id: expectedDatasetId,
|
|
3015
|
+
revision: config.datasetRevision,
|
|
3016
|
+
split: config.datasetSplit
|
|
3017
|
+
}) || provenance.caseCount !== inputs.selectedCaseIds.length || canonicalJson(provenance.runnerIds) !== canonicalJson(config.runnerIds) || provenance.repetitions !== config.repetitions || provenance.maxConcurrency !== Math.min(config.concurrency, expectedCount) || provenance.runnerOrderSeed !== config.seed || provenance.metadata?.model !== config.model.id || provenance.metadata?.rlmSamples !== config.rlmSamples || provenance.metadata?.outputAdapter !== expectedOutputAdapter || provenance.metadata?.caseSelection !== prepared.selection.method || provenance.metadata?.caseSelectionSeed !== config.seed || provenance.metadata?.selectionStratified !== prepared.selection.stratified || provenance.metadata?.protocolSha256 !== config.analystProtocolSha256 || provenance.metadata?.implementationSha256 !== config.implementationSha256 || provenance.metadata?.dependencyLockSha256 !== config.dependencyLockSha256 || provenance.metadata?.populationRepresentativenessProven !== false) throw new Error("completed benchmark result provenance does not match the run manifest");
|
|
3018
|
+
const expectedSummaries = config.runnerIds.map((runnerId) => summarizeAnalystBenchmarkRunner(runnerId, observations.filter((observation) => observation.runnerId === runnerId)));
|
|
3019
|
+
if (canonicalJson(artifact.result.summaries) !== canonicalJson(expectedSummaries)) throw new Error("completed benchmark summaries do not match durable observations");
|
|
3020
|
+
const expectedComparisons = [compareAnalystRunners(artifact.result, {
|
|
3021
|
+
baselineRunnerId: "empty",
|
|
3022
|
+
candidateRunnerId: config.runnerIds[1],
|
|
3023
|
+
seed: config.seed
|
|
3024
|
+
})];
|
|
3025
|
+
if (canonicalJson(artifact.comparisons) !== canonicalJson(expectedComparisons)) throw new Error("completed benchmark comparisons do not match durable observations");
|
|
3026
|
+
if (config.dataset === "codetracebench") {
|
|
3027
|
+
if (canonicalJson(artifact.codeTraceCalibration) !== canonicalJson(summarizeCodeTraceCalibration(artifact.result)) || artifact.agentRxCalibration !== void 0) throw new Error("completed CodeTraceBench calibration does not match durable observations");
|
|
3028
|
+
} else if (canonicalJson(artifact.agentRxCalibration) !== canonicalJson(summarizeAgentRxCalibration(artifact.result, "f228165bfec60a801fd5fedd9d8ffe0f9de0c69d")) || artifact.codeTraceCalibration !== void 0) throw new Error("completed AgentRx calibration does not match durable observations");
|
|
3029
|
+
}
|
|
3030
|
+
function assertSameObservations(expected, actual) {
|
|
3031
|
+
if (expected.length !== actual.length) throw new Error(`benchmark result has ${expected.length} observations but the durable log has ${actual.length}`);
|
|
3032
|
+
const expectedByKey = new Map(expected.map((observation) => [observationKey(observation), canonicalJson(observation)]));
|
|
3033
|
+
for (const observation of actual) {
|
|
3034
|
+
const key = observationKey(observation);
|
|
3035
|
+
if (expectedByKey.get(key) !== canonicalJson(observation)) throw new Error(`benchmark result does not match durable observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}'`);
|
|
3036
|
+
expectedByKey.delete(key);
|
|
3037
|
+
}
|
|
3038
|
+
if (expectedByKey.size > 0) throw new Error("benchmark result is missing durable observations");
|
|
3039
|
+
}
|
|
3040
|
+
//#endregion
|
|
3041
|
+
//#region src/analyst/benchmark-public-adapters.ts
|
|
3042
|
+
function emptyPublicBenchmarkRunner() {
|
|
3043
|
+
return {
|
|
3044
|
+
id: "empty",
|
|
3045
|
+
analyze() {
|
|
3046
|
+
return {
|
|
3047
|
+
findings: [],
|
|
3048
|
+
usage: {
|
|
3049
|
+
calls: 0,
|
|
3050
|
+
tokens: {
|
|
3051
|
+
input: 0,
|
|
3052
|
+
output: 0
|
|
3053
|
+
},
|
|
3054
|
+
cost: {
|
|
3055
|
+
kind: "observed",
|
|
3056
|
+
usd: 0
|
|
3057
|
+
}
|
|
3058
|
+
},
|
|
3059
|
+
metadata: { baseline: "emit-no-findings" }
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
async function adaptPublicBenchmarkFindings(options) {
|
|
3065
|
+
if (options.dataset === "agentrx") return {
|
|
3066
|
+
findings: adaptAgentRxFindings(options.trajectoryId, options.findings, options.analystId),
|
|
3067
|
+
diagnostics: void 0
|
|
3068
|
+
};
|
|
3069
|
+
return adaptCodeTraceFindings(options.trajectoryId, options.findings, options.analystId, options.store, options.signal);
|
|
3070
|
+
}
|
|
3071
|
+
function adaptAgentRxFindings(trajectoryId, findings, analystId) {
|
|
3072
|
+
if (findings.length === 0) return [];
|
|
3073
|
+
if (findings.length !== 1) throw new Error(`AgentRx model analyst must emit zero or one root cause, received ${findings.length}`);
|
|
3074
|
+
const source = findings[0];
|
|
3075
|
+
if (!source.subject) throw new Error("AgentRx model analyst finding is missing its failure-category subject");
|
|
3076
|
+
const steps = exactFindingSteps(trajectoryId, source);
|
|
3077
|
+
if (steps.length !== 1) throw new Error(`AgentRx model analyst must cite exactly one root-cause step, received ${steps.length}`);
|
|
3078
|
+
const [adapted] = agentRxPredictionsToFindings(trajectoryId, [{
|
|
3079
|
+
failure_case: source.subject,
|
|
3080
|
+
step_number: steps[0],
|
|
3081
|
+
description: source.rationale ?? source.claim
|
|
3082
|
+
}], {
|
|
3083
|
+
analystId,
|
|
3084
|
+
producedAt: source.produced_at,
|
|
3085
|
+
confidence: source.confidence
|
|
3086
|
+
});
|
|
3087
|
+
if (!adapted) throw new Error("AgentRx output adapter produced no root-cause finding");
|
|
3088
|
+
return [{
|
|
3089
|
+
...adapted,
|
|
3090
|
+
metadata: {
|
|
3091
|
+
...adapted.metadata,
|
|
3092
|
+
sourceFindingId: source.finding_id
|
|
3093
|
+
}
|
|
3094
|
+
}];
|
|
3095
|
+
}
|
|
3096
|
+
const CODE_TRACE_BLOCK_SUBJECT = /^incorrect-steps-(\d+)-(\d+)-(escaped|unescaped)-consequence-(\d+)$/;
|
|
3097
|
+
async function adaptCodeTraceFindings(trajectoryId, findings, analystId, store, signal) {
|
|
3098
|
+
const clean = findings.filter((finding) => finding.subject === "clean");
|
|
3099
|
+
if (clean.length > 0) {
|
|
3100
|
+
if (findings.length !== 1) throw new Error("CodeTraceBench model analyst mixed a clean verdict with incorrect steps");
|
|
3101
|
+
exactFindingSteps(trajectoryId, clean[0]);
|
|
3102
|
+
return {
|
|
3103
|
+
findings: [],
|
|
3104
|
+
diagnostics: emptyCodeTraceBlockDiagnostics(),
|
|
3105
|
+
stepBlocks: []
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
const blocks = [];
|
|
3109
|
+
const rejectedFindings = [];
|
|
3110
|
+
const trimmedCitations = [];
|
|
3111
|
+
for (const source of findings) try {
|
|
3112
|
+
await validateCodeTraceFindingEvidence({
|
|
3113
|
+
trajectoryId,
|
|
3114
|
+
findings: [source],
|
|
3115
|
+
store,
|
|
3116
|
+
...signal ? { signal } : {}
|
|
3117
|
+
});
|
|
3118
|
+
const converted = codeTraceBlockFromFinding(trajectoryId, source);
|
|
3119
|
+
trimmedCitations.push(...converted.trimmedCitations);
|
|
3120
|
+
blocks.push(converted.block);
|
|
3121
|
+
} catch (error) {
|
|
3122
|
+
rejectedFindings.push(`${source.finding_id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3123
|
+
}
|
|
3124
|
+
const expanded = await expandCodeTraceFailureBlocks({
|
|
3125
|
+
trajectoryId,
|
|
3126
|
+
blocks,
|
|
3127
|
+
store,
|
|
3128
|
+
analystId,
|
|
3129
|
+
...findings[0] ? { producedAt: findings[0].produced_at } : {},
|
|
3130
|
+
...signal ? { signal } : {}
|
|
3131
|
+
});
|
|
3132
|
+
return {
|
|
3133
|
+
findings: expanded.findings,
|
|
3134
|
+
diagnostics: {
|
|
3135
|
+
...expanded.diagnostics,
|
|
3136
|
+
rejectedFindings,
|
|
3137
|
+
trimmedCitations
|
|
3138
|
+
},
|
|
3139
|
+
stepBlocks: expanded.stepBlocks
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Block coordinates recoverable from a subject in the block grammar, in the
|
|
3144
|
+
* metadata field names the expanded findings carry. Returns undefined when the
|
|
3145
|
+
* subject does not parse — nothing is invented for a malformed subject.
|
|
3146
|
+
*/
|
|
3147
|
+
function codeTraceBlockMetadataFromSubject(subject) {
|
|
3148
|
+
const parsed = CODE_TRACE_BLOCK_SUBJECT.exec(subject ?? "");
|
|
3149
|
+
if (!parsed) return void 0;
|
|
3150
|
+
return {
|
|
3151
|
+
block_first_step: Number(parsed[1]),
|
|
3152
|
+
block_last_step: Number(parsed[2]),
|
|
3153
|
+
block_consequence_step: Number(parsed[4]),
|
|
3154
|
+
escape_status: parsed[3]
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
function codeTraceBlockFromFinding(trajectoryId, source) {
|
|
3158
|
+
const parsed = CODE_TRACE_BLOCK_SUBJECT.exec(source.subject ?? "");
|
|
3159
|
+
if (!parsed) throw new Error(`CodeTraceBench model finding '${source.finding_id}' must set subject to incorrect-steps-<first>-<last>-<escaped|unescaped>-consequence-<step>, received '${source.subject ?? ""}'`);
|
|
3160
|
+
const firstStep = Number(parsed[1]);
|
|
3161
|
+
const lastStep = Number(parsed[2]);
|
|
3162
|
+
const consequenceStep = Number(parsed[4]);
|
|
3163
|
+
const cited = exactFindingSteps(trajectoryId, source);
|
|
3164
|
+
const outOfRange = cited.filter((step) => step < firstStep || step > lastStep);
|
|
3165
|
+
if (outOfRange.length === cited.length) throw new Error(`CodeTraceBench model finding '${source.finding_id}' cites step ${outOfRange.join(", ")} outside its block ${firstStep}-${lastStep} and no citation falls inside the block`);
|
|
3166
|
+
const trimmedCitations = outOfRange.map((step) => `${source.finding_id}: trimmed citation step ${step} outside block ${firstStep}-${lastStep}`);
|
|
3167
|
+
return {
|
|
3168
|
+
block: {
|
|
3169
|
+
firstStep,
|
|
3170
|
+
lastStep,
|
|
3171
|
+
consequenceStep,
|
|
3172
|
+
escapeStatus: parsed[3],
|
|
3173
|
+
severity: source.severity,
|
|
3174
|
+
claim: source.claim,
|
|
3175
|
+
confidence: source.confidence,
|
|
3176
|
+
...source.rationale === void 0 ? {} : { rationale: source.rationale },
|
|
3177
|
+
...source.recommended_action === void 0 ? {} : { recommendedAction: source.recommended_action },
|
|
3178
|
+
metadata: { sourceFindingId: source.finding_id }
|
|
3179
|
+
},
|
|
3180
|
+
trimmedCitations
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
/**
|
|
3184
|
+
* Expand contiguous failure blocks into one scored finding per member step.
|
|
3185
|
+
*
|
|
3186
|
+
* The official scorer matches on area plus the exact step evidence URI, so
|
|
3187
|
+
* blocks never reach it: every runner reports blocks, and this function turns
|
|
3188
|
+
* them into the per-step findings the benchmark defines.
|
|
3189
|
+
*/
|
|
3190
|
+
async function expandCodeTraceFailureBlocks(options) {
|
|
3191
|
+
const diagnostics = emptyCodeTraceBlockDiagnostics();
|
|
3192
|
+
diagnostics.reportedBlocks = options.blocks.length;
|
|
3193
|
+
if (options.blocks.length === 0) return {
|
|
3194
|
+
findings: [],
|
|
3195
|
+
diagnostics,
|
|
3196
|
+
stepBlocks: []
|
|
3197
|
+
};
|
|
3198
|
+
const blocks = acceptCodeTraceBlockShape(options.blocks, diagnostics);
|
|
3199
|
+
if (blocks.length === 0) return {
|
|
3200
|
+
findings: [],
|
|
3201
|
+
diagnostics,
|
|
3202
|
+
stepBlocks: []
|
|
3203
|
+
};
|
|
3204
|
+
const boundarySteps = blocks.flatMap((block) => [block.firstStep, block.lastStep]);
|
|
3205
|
+
const derivedSteps = blocks.flatMap((block) => [block.consequenceStep, ...interiorSteps(block)]);
|
|
3206
|
+
const evidenceByStep = await resolveAssistantStepEvidence({
|
|
3207
|
+
trajectoryId: options.trajectoryId,
|
|
3208
|
+
steps: boundarySteps,
|
|
3209
|
+
optionalSteps: derivedSteps,
|
|
3210
|
+
store: options.store,
|
|
3211
|
+
...options.signal ? { signal: options.signal } : {}
|
|
3212
|
+
});
|
|
3213
|
+
const byStep = /* @__PURE__ */ new Map();
|
|
3214
|
+
for (const block of blocks) {
|
|
3215
|
+
if (!evidenceByStep.has(block.consequenceStep)) {
|
|
3216
|
+
diagnostics.blocksWithoutConsequenceEvidence.push(block);
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
if (block.escapeStatus === "escaped") diagnostics.escapedBlocks += 1;
|
|
3220
|
+
for (let step = block.firstStep; step <= block.lastStep; step += 1) {
|
|
3221
|
+
if (!evidenceByStep.has(step)) {
|
|
3222
|
+
diagnostics.unresolvedBlockInteriorSteps.push(step);
|
|
3223
|
+
continue;
|
|
3224
|
+
}
|
|
3225
|
+
if (byStep.has(step)) {
|
|
3226
|
+
diagnostics.overlappingBlockSteps.push(step);
|
|
3227
|
+
continue;
|
|
3228
|
+
}
|
|
3229
|
+
byStep.set(step, block);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
const stepBlocks = [...byStep].sort(([left], [right]) => left - right).map(([step, block]) => ({
|
|
3233
|
+
step,
|
|
3234
|
+
block
|
|
3235
|
+
}));
|
|
3236
|
+
return {
|
|
3237
|
+
findings: stepBlocks.map(({ step, block }) => makeFinding({
|
|
3238
|
+
analyst_id: options.analystId,
|
|
3239
|
+
area: "incorrect",
|
|
3240
|
+
subject: `incorrect-step-${step}`,
|
|
3241
|
+
claim: `Step ${step} is incorrect. ${block.claim}`,
|
|
3242
|
+
rationale: block.rationale,
|
|
3243
|
+
severity: block.severity,
|
|
3244
|
+
confidence: block.confidence,
|
|
3245
|
+
evidence_refs: [evidenceByStep.get(step)],
|
|
3246
|
+
recommended_action: block.recommendedAction,
|
|
3247
|
+
metadata: {
|
|
3248
|
+
...block.metadata,
|
|
3249
|
+
block_first_step: block.firstStep,
|
|
3250
|
+
block_last_step: block.lastStep,
|
|
3251
|
+
block_consequence_step: block.consequenceStep,
|
|
3252
|
+
escape_status: block.escapeStatus
|
|
3253
|
+
},
|
|
3254
|
+
...options.producedAt === void 0 ? {} : { produced_at: options.producedAt },
|
|
3255
|
+
id_basis: `incorrect-step-${step}`
|
|
3256
|
+
})),
|
|
3257
|
+
diagnostics,
|
|
3258
|
+
stepBlocks
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
/**
|
|
3262
|
+
* Enforce the protocol's per-block and per-case limits without voiding the
|
|
3263
|
+
* case: an offending block is dropped and named in `diagnostics.droppedBlocks`
|
|
3264
|
+
* while every valid sibling survives. A case whose blocks are ALL invalid ends
|
|
3265
|
+
* empty and carries the diagnostic for each drop. Shape is checked before the
|
|
3266
|
+
* count, so a malformed block never consumes one of the accepted slots.
|
|
3267
|
+
*/
|
|
3268
|
+
function acceptCodeTraceBlockShape(blocks, diagnostics) {
|
|
3269
|
+
const accepted = [];
|
|
3270
|
+
for (const block of blocks) {
|
|
3271
|
+
const reason = codeTraceBlockShapeViolation(block) ?? (accepted.length >= 16 ? `model reported ${blocks.length} failure blocks; the maximum is 16` : void 0);
|
|
3272
|
+
if (reason) {
|
|
3273
|
+
diagnostics.droppedBlocks.push(`block ${block.firstStep}-${block.lastStep} (consequence ${block.consequenceStep}): ${reason}`);
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
accepted.push(block);
|
|
3277
|
+
}
|
|
3278
|
+
return accepted;
|
|
3279
|
+
}
|
|
3280
|
+
function codeTraceBlockShapeViolation(block) {
|
|
3281
|
+
if (block.lastStep < block.firstStep) return `failure block last_step ${block.lastStep} precedes first_step ${block.firstStep}`;
|
|
3282
|
+
const length = block.lastStep - block.firstStep + 1;
|
|
3283
|
+
if (length > 12) return `failure block spans ${length} steps; the maximum is 12`;
|
|
3284
|
+
if (block.consequenceStep < block.firstStep) return `failure block consequence_step ${block.consequenceStep} precedes first_step ${block.firstStep}`;
|
|
3285
|
+
}
|
|
3286
|
+
function interiorSteps(block) {
|
|
3287
|
+
const steps = [];
|
|
3288
|
+
for (let step = block.firstStep + 1; step < block.lastStep; step += 1) steps.push(step);
|
|
3289
|
+
return steps;
|
|
3290
|
+
}
|
|
3291
|
+
function emptyCodeTraceBlockDiagnostics() {
|
|
3292
|
+
return {
|
|
3293
|
+
reportedBlocks: 0,
|
|
3294
|
+
escapedBlocks: 0,
|
|
3295
|
+
blocksWithoutConsequenceEvidence: [],
|
|
3296
|
+
unresolvedBlockInteriorSteps: [],
|
|
3297
|
+
overlappingBlockSteps: [],
|
|
3298
|
+
droppedBlocks: []
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
function exactFindingSteps(trajectoryId, finding) {
|
|
3302
|
+
if (finding.evidence_refs.length === 0) throw new Error(`model finding '${finding.finding_id}' has no step evidence`);
|
|
3303
|
+
const steps = finding.evidence_refs.map((evidence) => {
|
|
3304
|
+
const parsed = codeTraceStepFromEvidence(evidence.uri);
|
|
3305
|
+
if (!parsed || parsed.traceId !== trajectoryId) throw new Error(`model finding '${finding.finding_id}' cites non-case evidence '${evidence.uri}'`);
|
|
3306
|
+
return parsed.step;
|
|
3307
|
+
});
|
|
3308
|
+
return [...new Set(steps)];
|
|
3309
|
+
}
|
|
3310
|
+
//#endregion
|
|
3311
|
+
//#region src/analyst/benchmark-public-errors.ts
|
|
3312
|
+
function publicBenchmarkError(error, secrets = []) {
|
|
3313
|
+
if (error instanceof LlmCallError) return {
|
|
3314
|
+
class: "LlmCallError",
|
|
3315
|
+
code: error.code,
|
|
3316
|
+
status: error.status,
|
|
3317
|
+
message: `Provider request failed with HTTP ${error.status}.`
|
|
3318
|
+
};
|
|
3319
|
+
if (error instanceof LlmResponseError) return {
|
|
3320
|
+
class: "LlmResponseError",
|
|
3321
|
+
code: error.code,
|
|
3322
|
+
message: "Provider response did not satisfy the structured output contract."
|
|
3323
|
+
};
|
|
3324
|
+
if (error instanceof z.ZodError) return {
|
|
3325
|
+
class: "ModelOutputValidationError",
|
|
3326
|
+
message: "Provider response did not match the benchmark output schema."
|
|
3327
|
+
};
|
|
3328
|
+
if (error instanceof SyntaxError) return {
|
|
3329
|
+
class: "ModelOutputParseError",
|
|
3330
|
+
message: "Provider response was not valid JSON."
|
|
3331
|
+
};
|
|
3332
|
+
if (error instanceof CostCeilingReachedError || error instanceof CostAccountingIncompleteError || error instanceof CostReservationExceededError) return {
|
|
3333
|
+
class: error.constructor.name,
|
|
3334
|
+
code: error.code,
|
|
3335
|
+
message: redactSensitiveText(error.message, secrets)
|
|
3336
|
+
};
|
|
3337
|
+
if (error instanceof Error && error.name === "AbortError") return {
|
|
3338
|
+
class: "ProviderTimeoutError",
|
|
3339
|
+
message: "Provider request timed out."
|
|
3340
|
+
};
|
|
3341
|
+
if (error instanceof Error && /(?:assistant steps?|finding evidence|selected missing|selected unavailable|no readable spans|requires a trace store)/i.test(error.message)) return {
|
|
3342
|
+
class: "BenchmarkEvidenceError",
|
|
3343
|
+
message: redactSensitiveText(error.message, secrets)
|
|
3344
|
+
};
|
|
3345
|
+
if (error instanceof AgentEvalError) return {
|
|
3346
|
+
class: error.constructor.name,
|
|
3347
|
+
code: error.code,
|
|
3348
|
+
message: redactSensitiveText(error.message, secrets)
|
|
3349
|
+
};
|
|
3350
|
+
if (error instanceof Error) return {
|
|
3351
|
+
class: error.constructor.name || "Error",
|
|
3352
|
+
message: redactSensitiveText(error.message, secrets)
|
|
3353
|
+
};
|
|
3354
|
+
return {
|
|
3355
|
+
class: "Error",
|
|
3356
|
+
message: "Benchmark analyst execution failed."
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
function redactSensitiveText(value, secrets) {
|
|
3360
|
+
let redacted = value;
|
|
3361
|
+
for (const secret of secrets) if (secret) redacted = redacted.replaceAll(secret, "[REDACTED]");
|
|
3362
|
+
redacted = redacted.replace(/\bBearer\s+[^\s"',;]+/gi, "Bearer [REDACTED]").replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret)\b\s*[:=]\s*[^\s"',;]+/gi, "$1=[REDACTED]");
|
|
3363
|
+
if (redacted.length <= 500) return redacted;
|
|
3364
|
+
const head = redacted.slice(0, 180);
|
|
3365
|
+
const marker = `...[${redacted.length - 460} chars omitted]...`;
|
|
3366
|
+
return `${head}${marker}${redacted.slice(-(500 - head.length - marker.length))}`;
|
|
3367
|
+
}
|
|
3368
|
+
//#endregion
|
|
3369
|
+
//#region src/analyst/benchmark-public-types.ts
|
|
3370
|
+
function requiredString(value, field) {
|
|
3371
|
+
const trimmed = value.trim();
|
|
3372
|
+
if (!trimmed) throw new TypeError(`${field} must be a non-empty string`);
|
|
3373
|
+
return trimmed;
|
|
3374
|
+
}
|
|
3375
|
+
function positiveSafeInteger(value, field) {
|
|
3376
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`${field} must be a positive safe integer`);
|
|
3377
|
+
return value;
|
|
3378
|
+
}
|
|
3379
|
+
function safeInteger(value, field) {
|
|
3380
|
+
if (!Number.isSafeInteger(value)) throw new RangeError(`${field} must be a safe integer`);
|
|
3381
|
+
return value;
|
|
3382
|
+
}
|
|
3383
|
+
function isRecord(value) {
|
|
3384
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3385
|
+
}
|
|
3386
|
+
//#endregion
|
|
3387
|
+
//#region src/analyst/benchmark-response-cache.ts
|
|
3388
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
3389
|
+
const CostReceiptInputSchema = z.object({
|
|
3390
|
+
model: z.string().min(1),
|
|
3391
|
+
inputTokens: z.number().int().nonnegative(),
|
|
3392
|
+
outputTokens: z.number().int().nonnegative(),
|
|
3393
|
+
reasoningTokens: z.number().int().nonnegative().optional(),
|
|
3394
|
+
cachedTokens: z.number().int().nonnegative().optional(),
|
|
3395
|
+
cacheWriteTokens: z.number().int().nonnegative().optional(),
|
|
3396
|
+
customTokenPricing: z.object({
|
|
3397
|
+
inputUsdPerMillion: z.number().nonnegative(),
|
|
3398
|
+
cachedInputUsdPerMillion: z.number().nonnegative().optional(),
|
|
3399
|
+
cacheWriteUsdPerMillion: z.number().nonnegative().optional(),
|
|
3400
|
+
outputUsdPerMillion: z.number().nonnegative()
|
|
3401
|
+
}).strict().optional(),
|
|
3402
|
+
actualCostUsd: z.number().nonnegative().optional(),
|
|
3403
|
+
estimatedCostUsd: z.number().nonnegative().optional(),
|
|
3404
|
+
costUnknown: z.boolean().optional(),
|
|
3405
|
+
usageUnknown: z.boolean().optional()
|
|
3406
|
+
}).strict();
|
|
3407
|
+
const BenchmarkErrorSchema = z.object({
|
|
3408
|
+
class: z.string().min(1),
|
|
3409
|
+
message: z.string(),
|
|
3410
|
+
code: z.string().min(1).optional(),
|
|
3411
|
+
status: z.number().int().min(100).max(599).optional()
|
|
3412
|
+
}).strict();
|
|
3413
|
+
const ResponseMetadataSchema = z.object({
|
|
3414
|
+
providerModel: z.string().min(1),
|
|
3415
|
+
providerDurationMs: z.number().nonnegative(),
|
|
3416
|
+
finishReason: z.string().nullable(),
|
|
3417
|
+
producedAt: z.string().datetime()
|
|
3418
|
+
}).strict();
|
|
3419
|
+
const CacheIdentityShape = {
|
|
3420
|
+
kind: z.literal("agent-eval/public-benchmark-model-response"),
|
|
3421
|
+
callId: z.string().min(1),
|
|
3422
|
+
runIdentitySha256: z.string().regex(SHA256),
|
|
3423
|
+
caseId: z.string().min(1),
|
|
3424
|
+
repetition: z.number().int().nonnegative()
|
|
3425
|
+
};
|
|
3426
|
+
const SuccessCacheEntryWithoutDigestSchema = z.object({
|
|
3427
|
+
...CacheIdentityShape,
|
|
3428
|
+
status: z.literal("succeeded"),
|
|
3429
|
+
response: z.json(),
|
|
3430
|
+
metadata: ResponseMetadataSchema,
|
|
3431
|
+
receipt: CostReceiptInputSchema
|
|
3432
|
+
}).strict();
|
|
3433
|
+
const FailureCacheEntryWithoutDigestSchema = z.object({
|
|
3434
|
+
...CacheIdentityShape,
|
|
3435
|
+
status: z.literal("failed"),
|
|
3436
|
+
error: BenchmarkErrorSchema,
|
|
3437
|
+
receipt: CostReceiptInputSchema
|
|
3438
|
+
}).strict();
|
|
3439
|
+
const CacheEntryWithoutDigestSchema = z.discriminatedUnion("status", [SuccessCacheEntryWithoutDigestSchema, FailureCacheEntryWithoutDigestSchema]);
|
|
3440
|
+
const CacheEntrySchema = z.discriminatedUnion("status", [SuccessCacheEntryWithoutDigestSchema.extend({ entrySha256: z.string().regex(SHA256) }), FailureCacheEntryWithoutDigestSchema.extend({ entrySha256: z.string().regex(SHA256) })]);
|
|
3441
|
+
function publicBenchmarkCallId(identity) {
|
|
3442
|
+
assertCacheIdentity(identity);
|
|
3443
|
+
return `analyst-benchmark-${hashCanonical({
|
|
3444
|
+
runIdentitySha256: identity.runIdentitySha256,
|
|
3445
|
+
caseId: identity.caseId,
|
|
3446
|
+
repetition: identity.repetition
|
|
3447
|
+
}).slice(7)}`;
|
|
3448
|
+
}
|
|
3449
|
+
function readPublicBenchmarkResponseCache(cacheDirectory, identity) {
|
|
3450
|
+
const callId = publicBenchmarkCallId(identity);
|
|
3451
|
+
const path = responseCachePath(cacheDirectory, callId);
|
|
3452
|
+
if (!existsSync(path)) return void 0;
|
|
3453
|
+
const metadata = lstatSync(path);
|
|
3454
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) throw new ValidationError(`benchmark response cache must be a real file: ${path}`);
|
|
3455
|
+
let parsed;
|
|
3456
|
+
try {
|
|
3457
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
3458
|
+
} catch (error) {
|
|
3459
|
+
throw new ValidationError(`benchmark response cache contains invalid JSON: ${path}`, { cause: error });
|
|
3460
|
+
}
|
|
3461
|
+
const entry = parseCacheEntry(parsed, path);
|
|
3462
|
+
if (entry.callId !== callId || entry.runIdentitySha256 !== identity.runIdentitySha256 || entry.caseId !== identity.caseId || entry.repetition !== identity.repetition) throw new ValidationError(`benchmark response cache identity does not match: ${path}`);
|
|
3463
|
+
return entry;
|
|
3464
|
+
}
|
|
3465
|
+
function writePublicBenchmarkResponseCache(cacheDirectory, entry) {
|
|
3466
|
+
const expectedCallId = publicBenchmarkCallId(entry);
|
|
3467
|
+
if (entry.callId !== expectedCallId) throw new ValidationError("benchmark response cache callId does not match its identity");
|
|
3468
|
+
const validated = JSON.parse(JSON.stringify(CacheEntryWithoutDigestSchema.parse(entry)));
|
|
3469
|
+
const complete = {
|
|
3470
|
+
...validated,
|
|
3471
|
+
entrySha256: hashCanonical(validated).slice(7)
|
|
3472
|
+
};
|
|
3473
|
+
const path = responseCachePath(cacheDirectory, complete.callId);
|
|
3474
|
+
const content = `${canonicalString(complete)}\n`;
|
|
3475
|
+
withLedgerFileLock(path, fileContext(), () => {
|
|
3476
|
+
if (existsSync(path)) {
|
|
3477
|
+
const existing = readPublicBenchmarkResponseCache(cacheDirectory, complete);
|
|
3478
|
+
if (!existing || canonicalString(existing) !== canonicalString(complete)) throw new ValidationError(`benchmark response cache conflicts with existing file: ${path}`);
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
writeLedgerFileAtomically(path, content, fileContext());
|
|
3482
|
+
});
|
|
3483
|
+
return complete;
|
|
3484
|
+
}
|
|
3485
|
+
function parseCacheEntry(value, path) {
|
|
3486
|
+
let parsed;
|
|
3487
|
+
try {
|
|
3488
|
+
parsed = CacheEntrySchema.parse(value);
|
|
3489
|
+
} catch (error) {
|
|
3490
|
+
throw new ValidationError(`benchmark response cache has an invalid shape: ${path}`, { cause: error });
|
|
3491
|
+
}
|
|
3492
|
+
const { entrySha256, ...withoutDigest } = parsed;
|
|
3493
|
+
if (entrySha256 !== hashCanonical(withoutDigest).slice(7)) throw new ValidationError(`benchmark response cache digest does not match: ${path}`);
|
|
3494
|
+
return parsed;
|
|
3495
|
+
}
|
|
3496
|
+
function responseCachePath(cacheDirectory, callId) {
|
|
3497
|
+
const directory = nodePath.resolve(cacheDirectory);
|
|
3498
|
+
const path = nodePath.resolve(directory, `${hashCanonical(callId).slice(7)}.json`);
|
|
3499
|
+
if (!isPathInsideDirectory(directory, path, nodePath)) throw new ValidationError("benchmark response cache path escapes its directory");
|
|
3500
|
+
return path;
|
|
3501
|
+
}
|
|
3502
|
+
function isPathInsideDirectory(directory, candidate, pathOperations = nodePath) {
|
|
3503
|
+
const relative = pathOperations.relative(directory, candidate);
|
|
3504
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${pathOperations.sep}`) && !pathOperations.isAbsolute(relative);
|
|
3505
|
+
}
|
|
3506
|
+
function assertCacheIdentity(identity) {
|
|
3507
|
+
if (!SHA256.test(identity.runIdentitySha256)) throw new ValidationError("benchmark response cache requires a SHA-256 run identity");
|
|
3508
|
+
if (!identity.caseId.trim()) throw new ValidationError("benchmark response cache requires a case id");
|
|
3509
|
+
if (!Number.isSafeInteger(identity.repetition) || identity.repetition < 0) throw new ValidationError("benchmark response cache repetition must be non-negative");
|
|
3510
|
+
}
|
|
3511
|
+
function fileContext() {
|
|
3512
|
+
return {
|
|
3513
|
+
subject: "benchmark response cache",
|
|
3514
|
+
integrityError: (message, options) => new ValidationError(message, options)
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
//#endregion
|
|
3518
|
+
//#region src/analyst/benchmark-public-model.ts
|
|
3519
|
+
/** One-shot JSON baseline. This is not a recursive trace analyst. */
|
|
3520
|
+
function createPublicBenchmarkDirectRunner(dataset, config) {
|
|
3521
|
+
if (config.instructionsOverride) throw new Error("the direct runner executes only the stock protocol; an instructions override requires the dspy-rlm runner");
|
|
3522
|
+
const model = requiredString(config.model, "model");
|
|
3523
|
+
const baseUrl = requiredString(config.baseUrl, "baseUrl");
|
|
3524
|
+
const apiKey = requiredString(config.apiKey, "apiKey");
|
|
3525
|
+
const maxOutputTokens = positiveSafeInteger(config.maxOutputTokens, "maxOutputTokens");
|
|
3526
|
+
const timeoutMs = positiveSafeInteger(config.timeoutMs, "timeoutMs");
|
|
3527
|
+
const costLedger = config.costLedger ?? new CostLedger();
|
|
3528
|
+
const durability = config.durability ? {
|
|
3529
|
+
runIdentitySha256: requiredString(config.durability.runIdentitySha256, "durability.runIdentitySha256"),
|
|
3530
|
+
responseCacheDir: requiredString(config.durability.responseCacheDir, "durability.responseCacheDir")
|
|
3531
|
+
} : void 0;
|
|
3532
|
+
const actor = dataset === "agentrx" ? "agentrx-root-cause-localizer" : "codetracebench-step-localizer";
|
|
3533
|
+
const outputAdapter = dataset === "agentrx" ? "agentrx-taxonomy-and-root-step" : "codetracebench-incorrect-block";
|
|
3534
|
+
const llmOptions = {
|
|
3535
|
+
baseUrl,
|
|
3536
|
+
apiKey,
|
|
3537
|
+
maximumAttempts: 1,
|
|
3538
|
+
jsonSchemaTransport: "json-object",
|
|
3539
|
+
jsonPayloadMode: "exact",
|
|
3540
|
+
thinking: "disabled",
|
|
3541
|
+
...config.fetchImpl ? { fetch: config.fetchImpl } : {}
|
|
3542
|
+
};
|
|
3543
|
+
return {
|
|
3544
|
+
id: "direct",
|
|
3545
|
+
async analyze(input, context) {
|
|
3546
|
+
const trajectoryId = trajectoryIdFromCaseId$1(dataset, context.caseId);
|
|
3547
|
+
const costTags = {
|
|
3548
|
+
analystId: actor,
|
|
3549
|
+
benchmarkCaseId: context.caseId,
|
|
3550
|
+
benchmarkRepetition: String(context.repetition)
|
|
3551
|
+
};
|
|
3552
|
+
let rawPredictions = [];
|
|
3553
|
+
let rejectedBlocks = [];
|
|
3554
|
+
let modelFindings = [];
|
|
3555
|
+
let providerModel = model;
|
|
3006
3556
|
let producedAt;
|
|
3007
3557
|
let modelMetadata = {
|
|
3008
3558
|
analysisMode: "direct-baseline",
|
|
@@ -3129,1176 +3679,1021 @@ function createPublicBenchmarkDirectRunner(dataset, config) {
|
|
|
3129
3679
|
responseSource: "provider",
|
|
3130
3680
|
report: response.report,
|
|
3131
3681
|
providerModel: paid.value.result.model,
|
|
3132
|
-
providerDurationMs: paid.value.result.durationMs,
|
|
3133
|
-
finishReason: paid.value.result.finishReason ?? null,
|
|
3134
|
-
cost: costReceiptMetadata(paid.receipt)
|
|
3135
|
-
};
|
|
3136
|
-
}
|
|
3137
|
-
const converted = await publicBenchmarkPredictionsToFindings({
|
|
3138
|
-
dataset,
|
|
3139
|
-
trajectoryId,
|
|
3140
|
-
predictions: rawPredictions,
|
|
3141
|
-
store: input.traceStore,
|
|
3142
|
-
analystId: "direct",
|
|
3143
|
-
providerModel,
|
|
3144
|
-
producedAt: requiredString(producedAt ?? "", "finding producedAt"),
|
|
3145
|
-
...context.signal ? { signal: context.signal } : {}
|
|
3146
|
-
});
|
|
3147
|
-
modelFindings = converted.findings;
|
|
3148
|
-
if (converted.diagnostics) modelMetadata = {
|
|
3149
|
-
...modelMetadata,
|
|
3150
|
-
blockDiagnostics: {
|
|
3151
|
-
...converted.diagnostics,
|
|
3152
|
-
rejectedBlocks
|
|
3153
|
-
}
|
|
3154
|
-
};
|
|
3155
|
-
if (dataset === "codetracebench") await validateCodeTraceFindingEvidence({
|
|
3156
|
-
trajectoryId,
|
|
3157
|
-
findings: modelFindings,
|
|
3158
|
-
store: input.traceStore,
|
|
3159
|
-
...context.signal ? { signal: context.signal } : {}
|
|
3160
|
-
});
|
|
3161
|
-
return {
|
|
3162
|
-
findings: modelFindings,
|
|
3163
|
-
usage: usageReceiptFromCostLedger(costLedger, {
|
|
3164
|
-
channel: "analyst",
|
|
3165
|
-
tags: costTags
|
|
3166
|
-
}),
|
|
3167
|
-
metadata: modelMetadata
|
|
3168
|
-
};
|
|
3169
|
-
} catch (error) {
|
|
3170
|
-
if (context.signal?.aborted) throw error;
|
|
3171
|
-
if (isPaidCallControlError$1(error)) throw error;
|
|
3172
|
-
return {
|
|
3173
|
-
findings: [],
|
|
3174
|
-
usage: usageReceiptFromCostLedger(costLedger, {
|
|
3175
|
-
channel: "analyst",
|
|
3176
|
-
tags: costTags
|
|
3177
|
-
}),
|
|
3178
|
-
error: publicBenchmarkError(error, [apiKey]),
|
|
3179
|
-
metadata: {
|
|
3180
|
-
...modelMetadata,
|
|
3181
|
-
rawPredictions,
|
|
3182
|
-
acceptedFindings: modelFindings
|
|
3183
|
-
}
|
|
3184
|
-
};
|
|
3185
|
-
}
|
|
3186
|
-
}
|
|
3187
|
-
};
|
|
3188
|
-
}
|
|
3189
|
-
function settleCachedResponse(costLedger, cached) {
|
|
3190
|
-
const settled = costLedger.list().find((receipt) => receipt.callId === cached.callId);
|
|
3191
|
-
const pending = costLedger.listPending?.().find((record) => record.callId === cached.callId);
|
|
3192
|
-
if (settled && pending) throw new CostCallConflictError(`benchmark response '${cached.callId}' is both pending and settled`, { callId: cached.callId });
|
|
3193
|
-
const receipt = pending ? costLedger.reconcile(cached.callId, cached.receipt, { ...cached.status === "failed" ? { failed: true } : {} }) : settled;
|
|
3194
|
-
if (!receipt) throw new CostCallConflictError(`benchmark response cache '${cached.callId}' has no matching cost record`, { callId: cached.callId });
|
|
3195
|
-
assertCacheReceiptMatches(cached, receipt);
|
|
3196
|
-
return receipt;
|
|
3197
|
-
}
|
|
3198
|
-
function assertNoSettledResponseWithoutCache(costLedger, callId) {
|
|
3199
|
-
if (!callId) return;
|
|
3200
|
-
if (costLedger.list().some((receipt) => receipt.callId === callId)) throw new CostCallConflictError(`settled benchmark call '${callId}' has no durable response cache`, { callId });
|
|
3201
|
-
}
|
|
3202
|
-
function assertCacheReceiptMatches(cached, receipt) {
|
|
3203
|
-
const expected = cached.receipt;
|
|
3204
|
-
if (receipt.callId !== cached.callId || receipt.model !== expected.model || receipt.inputTokens !== expected.inputTokens || receipt.outputTokens !== expected.outputTokens || (receipt.reasoningTokens ?? 0) !== (expected.reasoningTokens ?? 0) || (receipt.cachedTokens ?? 0) !== (expected.cachedTokens ?? 0) || (receipt.cacheWriteTokens ?? 0) !== (expected.cacheWriteTokens ?? 0) || expected.actualCostUsd !== void 0 && receipt.actualCostUsd !== expected.actualCostUsd || expected.estimatedCostUsd !== void 0 && receipt.estimatedCostUsd !== expected.estimatedCostUsd || expected.costUnknown === true && !receipt.costUnknown || expected.usageUnknown === true && !receipt.usageUnknown || cached.status === "succeeded" && receipt.error !== void 0 || cached.status === "failed" && receipt.error === void 0) throw new CostCallConflictError(`benchmark response cache receipt does not match cost record '${cached.callId}'`, {
|
|
3205
|
-
callId: cached.callId,
|
|
3206
|
-
receipt
|
|
3207
|
-
});
|
|
3208
|
-
}
|
|
3209
|
-
function isPaidCallControlError$1(error) {
|
|
3210
|
-
return error instanceof CostAccountingIncompleteError || error instanceof CostCallConflictError || error instanceof CostCeilingReachedError || error instanceof CostLedgerPersistenceError || error instanceof CostReceiptCaptureError || error instanceof CostReservationExceededError;
|
|
3211
|
-
}
|
|
3212
|
-
function receiptForProviderFailure(error, completedResult, model) {
|
|
3213
|
-
if (completedResult) return costReceiptFromLlm(completedResult);
|
|
3214
|
-
if (error instanceof Error) {
|
|
3215
|
-
const captured = costReceiptFromLlmError(error);
|
|
3216
|
-
if (captured) return captured;
|
|
3217
|
-
}
|
|
3218
|
-
return {
|
|
3219
|
-
model,
|
|
3220
|
-
inputTokens: 0,
|
|
3221
|
-
outputTokens: 0,
|
|
3222
|
-
costUnknown: true,
|
|
3223
|
-
usageUnknown: true
|
|
3224
|
-
};
|
|
3225
|
-
}
|
|
3226
|
-
function costReceiptMetadata(receipt) {
|
|
3227
|
-
if (receipt.actualCostUsd !== void 0) return {
|
|
3228
|
-
source: "provider",
|
|
3229
|
-
actualCostUsd: receipt.actualCostUsd
|
|
3230
|
-
};
|
|
3231
|
-
if (receipt.estimatedCostUsd !== void 0) return {
|
|
3232
|
-
source: "external-estimate",
|
|
3233
|
-
estimatedCostUsd: receipt.estimatedCostUsd
|
|
3234
|
-
};
|
|
3235
|
-
if (receipt.pricing) return {
|
|
3236
|
-
source: "agent-eval-model-pricing",
|
|
3237
|
-
estimatedCostUsd: receipt.costUsd,
|
|
3238
|
-
ratesPerThousandTokens: receipt.pricing
|
|
3239
|
-
};
|
|
3240
|
-
return {
|
|
3241
|
-
source: "unknown",
|
|
3242
|
-
estimatedCostUsd: null
|
|
3243
|
-
};
|
|
3244
|
-
}
|
|
3245
|
-
const ModelSeveritySchema = z.enum([
|
|
3246
|
-
"critical",
|
|
3247
|
-
"high",
|
|
3248
|
-
"medium",
|
|
3249
|
-
"low",
|
|
3250
|
-
"info"
|
|
3251
|
-
]);
|
|
3252
|
-
const AgentRxPredictionSchema = z.object({
|
|
3253
|
-
step: z.number().int().positive(),
|
|
3254
|
-
severity: ModelSeveritySchema,
|
|
3255
|
-
claim: z.string().min(1),
|
|
3256
|
-
confidence: z.number().min(0).max(1),
|
|
3257
|
-
rationale: z.string().min(1).optional(),
|
|
3258
|
-
recommended_action: z.string().min(1).optional()
|
|
3259
|
-
}).strict();
|
|
3260
|
-
const CodeTraceBlockPredictionSchema = z.object({
|
|
3261
|
-
first_step: z.number().int().positive(),
|
|
3262
|
-
last_step: z.number().int().positive(),
|
|
3263
|
-
consequence_step: z.number().int().positive(),
|
|
3264
|
-
escape_status: z.enum(["escaped", "unescaped"]),
|
|
3265
|
-
severity: ModelSeveritySchema,
|
|
3266
|
-
claim: z.string().min(1),
|
|
3267
|
-
confidence: z.number().min(0).max(1),
|
|
3268
|
-
rationale: z.string().min(1).optional(),
|
|
3269
|
-
recommended_action: z.string().min(1).optional()
|
|
3270
|
-
}).strict().superRefine((block, ctx) => {
|
|
3271
|
-
if (block.last_step < block.first_step) {
|
|
3272
|
-
ctx.addIssue({
|
|
3273
|
-
code: z.ZodIssueCode.custom,
|
|
3274
|
-
message: `failure block last_step ${block.last_step} precedes first_step ${block.first_step}`
|
|
3275
|
-
});
|
|
3276
|
-
return;
|
|
3277
|
-
}
|
|
3278
|
-
const length = block.last_step - block.first_step + 1;
|
|
3279
|
-
if (length > 12) ctx.addIssue({
|
|
3280
|
-
code: z.ZodIssueCode.custom,
|
|
3281
|
-
message: `failure block spans ${length} steps; the maximum is 12`
|
|
3282
|
-
});
|
|
3283
|
-
if (block.consequence_step < block.first_step) ctx.addIssue({
|
|
3284
|
-
code: z.ZodIssueCode.custom,
|
|
3285
|
-
message: `failure block consequence_step ${block.consequence_step} precedes first_step ${block.first_step}`
|
|
3286
|
-
});
|
|
3287
|
-
});
|
|
3288
|
-
const AgentRxCategorySchema = z.enum([
|
|
3289
|
-
"instruction-plan-adherence-failure",
|
|
3290
|
-
"invention-of-new-information",
|
|
3291
|
-
"invalid-invocation",
|
|
3292
|
-
"misinterpretation-of-tool-output-handoff-failure",
|
|
3293
|
-
"intent-plan-misalignment",
|
|
3294
|
-
"underspecified-user-intent",
|
|
3295
|
-
"intent-not-supported",
|
|
3296
|
-
"guardrails-triggered",
|
|
3297
|
-
"system-failure",
|
|
3298
|
-
"inconclusive"
|
|
3299
|
-
]);
|
|
3300
|
-
const CodeTraceModelResponseEnvelopeSchema = z.object({
|
|
3301
|
-
report: z.string().min(1).max(4e3),
|
|
3302
|
-
findings: z.array(z.unknown()).max(16)
|
|
3303
|
-
}).strict();
|
|
3304
|
-
const AgentRxModelResponseSchema = z.object({
|
|
3305
|
-
report: z.string().min(1).max(4e3),
|
|
3306
|
-
findings: z.array(AgentRxPredictionSchema.extend({ category: AgentRxCategorySchema }).strict()).max(1)
|
|
3307
|
-
}).strict();
|
|
3308
|
-
function parsePublicBenchmarkModelResponse(dataset, value) {
|
|
3309
|
-
if (dataset === "agentrx") return {
|
|
3310
|
-
...AgentRxModelResponseSchema.parse(value),
|
|
3311
|
-
rejectedBlocks: []
|
|
3312
|
-
};
|
|
3313
|
-
const envelope = CodeTraceModelResponseEnvelopeSchema.parse(value);
|
|
3314
|
-
const findings = [];
|
|
3315
|
-
const rejectedBlocks = [];
|
|
3316
|
-
for (const [index, block] of envelope.findings.entries()) {
|
|
3317
|
-
const parsed = CodeTraceBlockPredictionSchema.safeParse(block);
|
|
3318
|
-
if (parsed.success) {
|
|
3319
|
-
findings.push(parsed.data);
|
|
3320
|
-
continue;
|
|
3321
|
-
}
|
|
3322
|
-
rejectedBlocks.push(`block ${index}: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"} ${issue.message}`).join("; ")}`);
|
|
3323
|
-
}
|
|
3324
|
-
if (findings.length === 0 && envelope.findings.length > 0) throw new ValidationError(`every reported failure block was malformed: ${rejectedBlocks.join(" | ")}`);
|
|
3325
|
-
return {
|
|
3326
|
-
report: envelope.report,
|
|
3327
|
-
findings,
|
|
3328
|
-
rejectedBlocks
|
|
3329
|
-
};
|
|
3330
|
-
}
|
|
3331
|
-
async function publicBenchmarkPredictionsToFindings(options) {
|
|
3332
|
-
if (options.predictions.length === 0 && options.dataset === "agentrx") return {
|
|
3333
|
-
findings: [],
|
|
3334
|
-
diagnostics: void 0
|
|
3335
|
-
};
|
|
3336
|
-
if (options.dataset === "agentrx") {
|
|
3337
|
-
const prediction = options.predictions[0];
|
|
3338
|
-
if (!("step" in prediction)) throw new Error("AgentRx model output must name a single root-cause step");
|
|
3339
|
-
if (!prediction.category) throw new Error("AgentRx model output is missing its failure category");
|
|
3340
|
-
const evidenceByStep = await resolveAssistantStepEvidence({
|
|
3341
|
-
trajectoryId: options.trajectoryId,
|
|
3342
|
-
steps: [prediction.step],
|
|
3343
|
-
store: options.store,
|
|
3344
|
-
...options.signal ? { signal: options.signal } : {}
|
|
3345
|
-
});
|
|
3346
|
-
const [finding] = agentRxPredictionsToFindings(options.trajectoryId, [{
|
|
3347
|
-
failure_case: prediction.category,
|
|
3348
|
-
step_number: prediction.step,
|
|
3349
|
-
description: prediction.rationale ?? prediction.claim
|
|
3350
|
-
}], {
|
|
3351
|
-
analystId: options.analystId,
|
|
3352
|
-
producedAt: options.producedAt,
|
|
3353
|
-
confidence: prediction.confidence
|
|
3354
|
-
});
|
|
3355
|
-
if (!finding) throw new Error("AgentRx output adapter produced no root-cause finding");
|
|
3356
|
-
return {
|
|
3357
|
-
findings: [{
|
|
3358
|
-
...finding,
|
|
3359
|
-
evidence_refs: [evidenceByStep.get(prediction.step)],
|
|
3360
|
-
metadata: {
|
|
3361
|
-
...finding.metadata,
|
|
3362
|
-
model: options.providerModel
|
|
3682
|
+
providerDurationMs: paid.value.result.durationMs,
|
|
3683
|
+
finishReason: paid.value.result.finishReason ?? null,
|
|
3684
|
+
cost: costReceiptMetadata(paid.receipt)
|
|
3685
|
+
};
|
|
3363
3686
|
}
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
const blocks = options.predictions.map((prediction) => {
|
|
3369
|
-
if (!("first_step" in prediction)) throw new Error("CodeTraceBench model output must report first_step/last_step failure blocks");
|
|
3370
|
-
return {
|
|
3371
|
-
firstStep: prediction.first_step,
|
|
3372
|
-
lastStep: prediction.last_step,
|
|
3373
|
-
consequenceStep: prediction.consequence_step,
|
|
3374
|
-
escapeStatus: prediction.escape_status,
|
|
3375
|
-
severity: prediction.severity,
|
|
3376
|
-
claim: prediction.claim,
|
|
3377
|
-
confidence: prediction.confidence,
|
|
3378
|
-
...prediction.rationale === void 0 ? {} : { rationale: prediction.rationale },
|
|
3379
|
-
...prediction.recommended_action === void 0 ? {} : { recommendedAction: prediction.recommended_action },
|
|
3380
|
-
metadata: {
|
|
3381
|
-
analysis_mode: "direct-baseline",
|
|
3382
|
-
model: options.providerModel
|
|
3383
|
-
}
|
|
3384
|
-
};
|
|
3385
|
-
});
|
|
3386
|
-
return expandCodeTraceFailureBlocks({
|
|
3387
|
-
trajectoryId: options.trajectoryId,
|
|
3388
|
-
blocks,
|
|
3389
|
-
store: options.store,
|
|
3390
|
-
analystId: options.analystId,
|
|
3391
|
-
producedAt: options.producedAt,
|
|
3392
|
-
...options.signal ? { signal: options.signal } : {}
|
|
3393
|
-
});
|
|
3394
|
-
}
|
|
3395
|
-
async function prepareSingleTraceContext(store, context) {
|
|
3396
|
-
const storeContext = context.signal ? { signal: context.signal } : void 0;
|
|
3397
|
-
const overview = await store.getOverview(void 0, storeContext);
|
|
3398
|
-
if (overview.total_traces !== 1 || overview.sample_trace_ids.length !== 1) throw new Error(`public model benchmark requires exactly one trace, received ${overview.total_traces}`);
|
|
3399
|
-
const traceId = overview.sample_trace_ids[0];
|
|
3400
|
-
for (const perAttributeByteCap of TRACE_PROJECTION_ATTRIBUTE_BYTE_CAPS) {
|
|
3401
|
-
const viewed = await store.viewTrace({
|
|
3402
|
-
trace_id: traceId,
|
|
3403
|
-
per_attribute_byte_cap: perAttributeByteCap
|
|
3404
|
-
}, storeContext);
|
|
3405
|
-
if (!viewed.spans) continue;
|
|
3406
|
-
return JSON.stringify({
|
|
3407
|
-
trace_id: traceId,
|
|
3408
|
-
per_attribute_byte_cap: perAttributeByteCap,
|
|
3409
|
-
spans: viewed.spans
|
|
3410
|
-
});
|
|
3411
|
-
}
|
|
3412
|
-
}
|
|
3413
|
-
function trajectoryIdFromCaseId$1(dataset, caseId) {
|
|
3414
|
-
const prefix = dataset === "agentrx" ? "agentrx:" : "codetrace:";
|
|
3415
|
-
if (!caseId.startsWith(prefix) || caseId.length === prefix.length) throw new Error(`unexpected ${dataset} benchmark case id '${caseId}'`);
|
|
3416
|
-
return caseId.slice(prefix.length);
|
|
3417
|
-
}
|
|
3418
|
-
//#endregion
|
|
3419
|
-
//#region src/analyst/benchmark-public-rlm.ts
|
|
3420
|
-
/** Public benchmark candidate that runs the actual recursive trace analyst. */
|
|
3421
|
-
function createPublicBenchmarkRlmRunner(dataset, config) {
|
|
3422
|
-
const costLedger = config.costLedger ?? new CostLedger();
|
|
3423
|
-
const limits = {
|
|
3424
|
-
maxIterations: config.dspyRlm?.maxIterations ?? 14,
|
|
3425
|
-
maxLlmCalls: config.dspyRlm?.maxLlmCalls ?? 8,
|
|
3426
|
-
maxToolCalls: config.dspyRlm?.maxToolCalls ?? 80,
|
|
3427
|
-
maxOutputChars: config.dspyRlm?.maxOutputChars ?? 8e3
|
|
3428
|
-
};
|
|
3429
|
-
const pricing = config.pricing ?? pricingForModel(config.model);
|
|
3430
|
-
const engine = createDspyRlmTraceEngine({
|
|
3431
|
-
baseUrl: config.baseUrl,
|
|
3432
|
-
apiKey: config.apiKey,
|
|
3433
|
-
model: config.model,
|
|
3434
|
-
maxOutputTokens: config.maxOutputTokens,
|
|
3435
|
-
timeoutMs: config.timeoutMs,
|
|
3436
|
-
maxCostUsd: config.maxCostUsdPerAnalysis ?? 1,
|
|
3437
|
-
pricing,
|
|
3438
|
-
...config.dspyRlm?.runner ? { runner: config.dspyRlm.runner } : {}
|
|
3439
|
-
});
|
|
3440
|
-
const definition = publicBenchmarkDefinition(dataset, limits);
|
|
3441
|
-
return {
|
|
3442
|
-
id: "dspy-rlm",
|
|
3443
|
-
async analyze(input, context) {
|
|
3444
|
-
const trajectoryId = trajectoryIdFromCaseId(dataset, context.caseId);
|
|
3445
|
-
const tags = {
|
|
3446
|
-
benchmarkCaseId: context.caseId,
|
|
3447
|
-
benchmarkRepetition: String(context.repetition)
|
|
3448
|
-
};
|
|
3449
|
-
let usage;
|
|
3450
|
-
let rawFindings = [];
|
|
3451
|
-
try {
|
|
3452
|
-
if (!input.traceStore) throw new Error(`${dataset} DSPy RLM runner requires a trace store`);
|
|
3453
|
-
const completed = await runTraceAnalyst({
|
|
3454
|
-
definition,
|
|
3455
|
-
engine,
|
|
3687
|
+
const converted = await publicBenchmarkPredictionsToFindings({
|
|
3688
|
+
dataset,
|
|
3689
|
+
trajectoryId,
|
|
3690
|
+
predictions: rawPredictions,
|
|
3456
3691
|
store: input.traceStore,
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
costPhase: "analyst.public-benchmark.dspy-rlm",
|
|
3462
|
-
tags,
|
|
3463
|
-
recordUsage: (receipt) => {
|
|
3464
|
-
usage = receipt;
|
|
3465
|
-
},
|
|
3466
|
-
signal: context.signal
|
|
3467
|
-
}
|
|
3692
|
+
analystId: "direct",
|
|
3693
|
+
providerModel,
|
|
3694
|
+
producedAt: requiredString(producedAt ?? "", "finding producedAt"),
|
|
3695
|
+
...context.signal ? { signal: context.signal } : {}
|
|
3468
3696
|
});
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
evidence_refs: evidenceRefsFromRawFinding(finding),
|
|
3479
|
-
recommended_action: finding.recommended_action,
|
|
3480
|
-
metadata: {
|
|
3481
|
-
analysis_mode: "recursive",
|
|
3482
|
-
engine: "dspy-rlm",
|
|
3483
|
-
model: config.model
|
|
3484
|
-
},
|
|
3485
|
-
produced_at: producedAt
|
|
3486
|
-
}));
|
|
3487
|
-
const adapted = await adaptPublicBenchmarkFindings({
|
|
3488
|
-
dataset,
|
|
3697
|
+
modelFindings = converted.findings;
|
|
3698
|
+
if (converted.diagnostics) modelMetadata = {
|
|
3699
|
+
...modelMetadata,
|
|
3700
|
+
blockDiagnostics: {
|
|
3701
|
+
...converted.diagnostics,
|
|
3702
|
+
rejectedBlocks
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
if (dataset === "codetracebench") await validateCodeTraceFindingEvidence({
|
|
3489
3706
|
trajectoryId,
|
|
3490
|
-
findings:
|
|
3491
|
-
analystId: "dspy-rlm",
|
|
3707
|
+
findings: modelFindings,
|
|
3492
3708
|
store: input.traceStore,
|
|
3493
3709
|
...context.signal ? { signal: context.signal } : {}
|
|
3494
3710
|
});
|
|
3495
3711
|
return {
|
|
3496
|
-
findings:
|
|
3497
|
-
usage,
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
...adapted.diagnostics ? { blockDiagnostics: adapted.diagnostics } : {},
|
|
3503
|
-
answer: completed.answer,
|
|
3504
|
-
trajectory: completed.trajectory,
|
|
3505
|
-
modelCalls: completed.modelCalls,
|
|
3506
|
-
toolCalls: completed.toolCalls,
|
|
3507
|
-
runtime: completed.runtime
|
|
3508
|
-
}
|
|
3712
|
+
findings: modelFindings,
|
|
3713
|
+
usage: usageReceiptFromCostLedger(costLedger, {
|
|
3714
|
+
channel: "analyst",
|
|
3715
|
+
tags: costTags
|
|
3716
|
+
}),
|
|
3717
|
+
metadata: modelMetadata
|
|
3509
3718
|
};
|
|
3510
3719
|
} catch (error) {
|
|
3511
3720
|
if (context.signal?.aborted) throw error;
|
|
3512
|
-
if (isPaidCallControlError(error)) throw error;
|
|
3721
|
+
if (isPaidCallControlError$1(error)) throw error;
|
|
3513
3722
|
return {
|
|
3514
3723
|
findings: [],
|
|
3515
|
-
usage,
|
|
3516
|
-
|
|
3724
|
+
usage: usageReceiptFromCostLedger(costLedger, {
|
|
3725
|
+
channel: "analyst",
|
|
3726
|
+
tags: costTags
|
|
3727
|
+
}),
|
|
3728
|
+
error: publicBenchmarkError(error, [apiKey]),
|
|
3517
3729
|
metadata: {
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3730
|
+
...modelMetadata,
|
|
3731
|
+
rawPredictions,
|
|
3732
|
+
acceptedFindings: modelFindings
|
|
3521
3733
|
}
|
|
3522
3734
|
};
|
|
3523
3735
|
}
|
|
3524
3736
|
}
|
|
3525
3737
|
};
|
|
3526
3738
|
}
|
|
3527
|
-
function
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
toolGroup: "singleTrace",
|
|
3536
|
-
limits
|
|
3537
|
-
};
|
|
3739
|
+
function settleCachedResponse(costLedger, cached) {
|
|
3740
|
+
const settled = costLedger.list().find((receipt) => receipt.callId === cached.callId);
|
|
3741
|
+
const pending = costLedger.listPending?.().find((record) => record.callId === cached.callId);
|
|
3742
|
+
if (settled && pending) throw new CostCallConflictError(`benchmark response '${cached.callId}' is both pending and settled`, { callId: cached.callId });
|
|
3743
|
+
const receipt = pending ? costLedger.reconcile(cached.callId, cached.receipt, { ...cached.status === "failed" ? { failed: true } : {} }) : settled;
|
|
3744
|
+
if (!receipt) throw new CostCallConflictError(`benchmark response cache '${cached.callId}' has no matching cost record`, { callId: cached.callId });
|
|
3745
|
+
assertCacheReceiptMatches(cached, receipt);
|
|
3746
|
+
return receipt;
|
|
3538
3747
|
}
|
|
3539
|
-
function
|
|
3540
|
-
|
|
3541
|
-
if (
|
|
3542
|
-
return {
|
|
3543
|
-
inputUsdPerMillion: pricing.input * 1e3,
|
|
3544
|
-
outputUsdPerMillion: pricing.output * 1e3
|
|
3545
|
-
};
|
|
3748
|
+
function assertNoSettledResponseWithoutCache(costLedger, callId) {
|
|
3749
|
+
if (!callId) return;
|
|
3750
|
+
if (costLedger.list().some((receipt) => receipt.callId === callId)) throw new CostCallConflictError(`settled benchmark call '${callId}' has no durable response cache`, { callId });
|
|
3546
3751
|
}
|
|
3547
|
-
function
|
|
3548
|
-
const
|
|
3549
|
-
if (
|
|
3550
|
-
|
|
3752
|
+
function assertCacheReceiptMatches(cached, receipt) {
|
|
3753
|
+
const expected = cached.receipt;
|
|
3754
|
+
if (receipt.callId !== cached.callId || receipt.model !== expected.model || receipt.inputTokens !== expected.inputTokens || receipt.outputTokens !== expected.outputTokens || (receipt.reasoningTokens ?? 0) !== (expected.reasoningTokens ?? 0) || (receipt.cachedTokens ?? 0) !== (expected.cachedTokens ?? 0) || (receipt.cacheWriteTokens ?? 0) !== (expected.cacheWriteTokens ?? 0) || expected.actualCostUsd !== void 0 && receipt.actualCostUsd !== expected.actualCostUsd || expected.estimatedCostUsd !== void 0 && receipt.estimatedCostUsd !== expected.estimatedCostUsd || expected.costUnknown === true && !receipt.costUnknown || expected.usageUnknown === true && !receipt.usageUnknown || cached.status === "succeeded" && receipt.error !== void 0 || cached.status === "failed" && receipt.error === void 0) throw new CostCallConflictError(`benchmark response cache receipt does not match cost record '${cached.callId}'`, {
|
|
3755
|
+
callId: cached.callId,
|
|
3756
|
+
receipt
|
|
3757
|
+
});
|
|
3551
3758
|
}
|
|
3552
|
-
function isPaidCallControlError(error) {
|
|
3759
|
+
function isPaidCallControlError$1(error) {
|
|
3553
3760
|
return error instanceof CostAccountingIncompleteError || error instanceof CostCallConflictError || error instanceof CostCeilingReachedError || error instanceof CostLedgerPersistenceError || error instanceof CostReceiptCaptureError || error instanceof CostReservationExceededError;
|
|
3554
3761
|
}
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
let outputStat;
|
|
3562
|
-
try {
|
|
3563
|
-
outputStat = await lstat(directory);
|
|
3564
|
-
} catch (error) {
|
|
3565
|
-
if (isNodeError(error, "ENOENT")) throw new Error(`cannot resume missing benchmark output directory: ${directory}`);
|
|
3566
|
-
throw error;
|
|
3567
|
-
}
|
|
3568
|
-
if (!outputStat.isDirectory() || outputStat.isSymbolicLink()) throw new Error(`benchmark output must be a real directory: ${directory}`);
|
|
3569
|
-
} else {
|
|
3570
|
-
await mkdir(dirname(directory), { recursive: true });
|
|
3571
|
-
try {
|
|
3572
|
-
await mkdir(directory);
|
|
3573
|
-
} catch (error) {
|
|
3574
|
-
if (isNodeError(error, "EEXIST")) throw new Error(`refusing to use existing benchmark output directory: ${directory}`);
|
|
3575
|
-
throw error;
|
|
3576
|
-
}
|
|
3577
|
-
await syncDirectory(dirname(directory));
|
|
3578
|
-
}
|
|
3579
|
-
return {
|
|
3580
|
-
directory,
|
|
3581
|
-
initializationComplete: resolve(directory, ANALYST_BENCHMARK_INITIALIZATION_COMPLETE_FILE),
|
|
3582
|
-
manifest: resolve(directory, ANALYST_BENCHMARK_MANIFEST_FILE),
|
|
3583
|
-
observations: resolve(directory, ANALYST_BENCHMARK_OBSERVATIONS_FILE),
|
|
3584
|
-
costLedger: resolve(directory, ANALYST_BENCHMARK_COST_LEDGER_FILE),
|
|
3585
|
-
modelResponses: resolve(directory, "model-responses"),
|
|
3586
|
-
localReceipt: resolve(directory, ANALYST_BENCHMARK_LOCAL_RECEIPT_FILE),
|
|
3587
|
-
result: resolve(directory, "result.json"),
|
|
3588
|
-
report: resolve(directory, "report.md")
|
|
3589
|
-
};
|
|
3590
|
-
}
|
|
3591
|
-
async function prepareOutputLockPath(outDir) {
|
|
3592
|
-
const directory = resolve(outDir);
|
|
3593
|
-
await mkdir(dirname(directory), { recursive: true });
|
|
3594
|
-
return `${directory}.lock`;
|
|
3595
|
-
}
|
|
3596
|
-
function createRunIdentity(config, prepared) {
|
|
3597
|
-
const caseDefinitions = prepared.cases.map((testCase) => ({
|
|
3598
|
-
id: testCase.id,
|
|
3599
|
-
clusterId: testCase.clusterId,
|
|
3600
|
-
labelState: testCase.labelState,
|
|
3601
|
-
expectedIssues: testCase.expectedIssues,
|
|
3602
|
-
labeledEvidence: testCase.labeledEvidence ?? [],
|
|
3603
|
-
tags: testCase.tags ?? [],
|
|
3604
|
-
metadata: testCase.metadata ?? {}
|
|
3605
|
-
}));
|
|
3762
|
+
function receiptForProviderFailure(error, completedResult, model) {
|
|
3763
|
+
if (completedResult) return costReceiptFromLlm(completedResult);
|
|
3764
|
+
if (error instanceof Error) {
|
|
3765
|
+
const captured = costReceiptFromLlmError(error);
|
|
3766
|
+
if (captured) return captured;
|
|
3767
|
+
}
|
|
3606
3768
|
return {
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
id: config.model.model,
|
|
3613
|
-
maxOutputTokens: config.model.maxOutputTokens,
|
|
3614
|
-
timeoutMs: config.model.timeoutMs
|
|
3615
|
-
},
|
|
3616
|
-
limit: config.limit,
|
|
3617
|
-
seed: config.seed,
|
|
3618
|
-
concurrency: config.concurrency,
|
|
3619
|
-
repetitions: config.repetitions,
|
|
3620
|
-
maxCostUsd: config.maxCostUsd,
|
|
3621
|
-
maxArtifactBytes: config.maxArtifactBytes,
|
|
3622
|
-
analystProtocolSha256: publicBenchmarkProtocolSha256(config.dataset),
|
|
3623
|
-
implementationSha256: ANALYST_BENCHMARK_IMPLEMENTATION_SHA256,
|
|
3624
|
-
dependencyLockSha256: ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256,
|
|
3625
|
-
runnerIds: ["empty", config.analyst]
|
|
3626
|
-
},
|
|
3627
|
-
inputs: {
|
|
3628
|
-
labelsSha256: prepared.labelsSha256,
|
|
3629
|
-
sourceRowCount: prepared.sourceRowCount,
|
|
3630
|
-
selectedCaseIds: [...prepared.selectedCaseIds],
|
|
3631
|
-
traceFiles: prepared.traceFiles.map((traceFile) => ({ ...traceFile })),
|
|
3632
|
-
verificationArtifactsSha256: digestCanonical(prepared.verificationArtifacts),
|
|
3633
|
-
caseDefinitionsSha256: digestCanonical(caseDefinitions)
|
|
3634
|
-
}
|
|
3769
|
+
model,
|
|
3770
|
+
inputTokens: 0,
|
|
3771
|
+
outputTokens: 0,
|
|
3772
|
+
costUnknown: true,
|
|
3773
|
+
usageUnknown: true
|
|
3635
3774
|
};
|
|
3636
3775
|
}
|
|
3637
|
-
function
|
|
3776
|
+
function costReceiptMetadata(receipt) {
|
|
3777
|
+
if (receipt.actualCostUsd !== void 0) return {
|
|
3778
|
+
source: "provider",
|
|
3779
|
+
actualCostUsd: receipt.actualCostUsd
|
|
3780
|
+
};
|
|
3781
|
+
if (receipt.estimatedCostUsd !== void 0) return {
|
|
3782
|
+
source: "external-estimate",
|
|
3783
|
+
estimatedCostUsd: receipt.estimatedCostUsd
|
|
3784
|
+
};
|
|
3785
|
+
if (receipt.pricing) return {
|
|
3786
|
+
source: "agent-eval-model-pricing",
|
|
3787
|
+
estimatedCostUsd: receipt.costUsd,
|
|
3788
|
+
ratesPerThousandTokens: receipt.pricing
|
|
3789
|
+
};
|
|
3638
3790
|
return {
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
labelsPath: resolve(config.labelsPath),
|
|
3642
|
-
traceDir: resolve(config.traceDir),
|
|
3643
|
-
...config.artifactDir ? { artifactDir: resolve(config.artifactDir) } : {},
|
|
3644
|
-
outputDir: paths.directory,
|
|
3645
|
-
baseUrl: config.model.baseUrl,
|
|
3646
|
-
apiKeyEnvironment: config.apiKeyEnv
|
|
3647
|
-
},
|
|
3648
|
-
command: config.command,
|
|
3649
|
-
environment: {
|
|
3650
|
-
node: process.version,
|
|
3651
|
-
platform: platform(),
|
|
3652
|
-
arch: arch()
|
|
3653
|
-
},
|
|
3654
|
-
files: {
|
|
3655
|
-
manifest: paths.manifest,
|
|
3656
|
-
observations: paths.observations,
|
|
3657
|
-
costLedger: paths.costLedger,
|
|
3658
|
-
modelResponses: paths.modelResponses,
|
|
3659
|
-
result: paths.result,
|
|
3660
|
-
report: paths.report
|
|
3661
|
-
}
|
|
3791
|
+
source: "unknown",
|
|
3792
|
+
estimatedCostUsd: null
|
|
3662
3793
|
};
|
|
3663
3794
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3795
|
+
const ModelSeveritySchema = z.enum([
|
|
3796
|
+
"critical",
|
|
3797
|
+
"high",
|
|
3798
|
+
"medium",
|
|
3799
|
+
"low",
|
|
3800
|
+
"info"
|
|
3801
|
+
]);
|
|
3802
|
+
const AgentRxPredictionSchema = z.object({
|
|
3803
|
+
step: z.number().int().positive(),
|
|
3804
|
+
severity: ModelSeveritySchema,
|
|
3805
|
+
claim: z.string().min(1),
|
|
3806
|
+
confidence: z.number().min(0).max(1),
|
|
3807
|
+
rationale: z.string().min(1).optional(),
|
|
3808
|
+
recommended_action: z.string().min(1).optional()
|
|
3809
|
+
}).strict();
|
|
3810
|
+
const CodeTraceBlockPredictionSchema = z.object({
|
|
3811
|
+
first_step: z.number().int().positive(),
|
|
3812
|
+
last_step: z.number().int().positive(),
|
|
3813
|
+
consequence_step: z.number().int().positive(),
|
|
3814
|
+
escape_status: z.enum(["escaped", "unescaped"]),
|
|
3815
|
+
severity: ModelSeveritySchema,
|
|
3816
|
+
claim: z.string().min(1),
|
|
3817
|
+
confidence: z.number().min(0).max(1),
|
|
3818
|
+
rationale: z.string().min(1).optional(),
|
|
3819
|
+
recommended_action: z.string().min(1).optional()
|
|
3820
|
+
}).strict().superRefine((block, ctx) => {
|
|
3821
|
+
if (block.last_step < block.first_step) {
|
|
3822
|
+
ctx.addIssue({
|
|
3823
|
+
code: z.ZodIssueCode.custom,
|
|
3824
|
+
message: `failure block last_step ${block.last_step} precedes first_step ${block.first_step}`
|
|
3825
|
+
});
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3828
|
+
const length = block.last_step - block.first_step + 1;
|
|
3829
|
+
if (length > 12) ctx.addIssue({
|
|
3830
|
+
code: z.ZodIssueCode.custom,
|
|
3831
|
+
message: `failure block spans ${length} steps; the maximum is 12`
|
|
3832
|
+
});
|
|
3833
|
+
if (block.consequence_step < block.first_step) ctx.addIssue({
|
|
3834
|
+
code: z.ZodIssueCode.custom,
|
|
3835
|
+
message: `failure block consequence_step ${block.consequence_step} precedes first_step ${block.first_step}`
|
|
3836
|
+
});
|
|
3837
|
+
});
|
|
3838
|
+
const AgentRxCategorySchema = z.enum([
|
|
3839
|
+
"instruction-plan-adherence-failure",
|
|
3840
|
+
"invention-of-new-information",
|
|
3841
|
+
"invalid-invocation",
|
|
3842
|
+
"misinterpretation-of-tool-output-handoff-failure",
|
|
3843
|
+
"intent-plan-misalignment",
|
|
3844
|
+
"underspecified-user-intent",
|
|
3845
|
+
"intent-not-supported",
|
|
3846
|
+
"guardrails-triggered",
|
|
3847
|
+
"system-failure",
|
|
3848
|
+
"inconclusive"
|
|
3849
|
+
]);
|
|
3850
|
+
const CodeTraceModelResponseEnvelopeSchema = z.object({
|
|
3851
|
+
report: z.string().min(1).max(4e3),
|
|
3852
|
+
findings: z.array(z.unknown()).max(16)
|
|
3853
|
+
}).strict();
|
|
3854
|
+
const AgentRxModelResponseSchema = z.object({
|
|
3855
|
+
report: z.string().min(1).max(4e3),
|
|
3856
|
+
findings: z.array(AgentRxPredictionSchema.extend({ category: AgentRxCategorySchema }).strict()).max(1)
|
|
3857
|
+
}).strict();
|
|
3858
|
+
function parsePublicBenchmarkModelResponse(dataset, value) {
|
|
3859
|
+
if (dataset === "agentrx") return {
|
|
3860
|
+
...AgentRxModelResponseSchema.parse(value),
|
|
3861
|
+
rejectedBlocks: []
|
|
3672
3862
|
};
|
|
3673
|
-
const
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3863
|
+
const envelope = CodeTraceModelResponseEnvelopeSchema.parse(value);
|
|
3864
|
+
const findings = [];
|
|
3865
|
+
const rejectedBlocks = [];
|
|
3866
|
+
for (const [index, block] of envelope.findings.entries()) {
|
|
3867
|
+
const parsed = CodeTraceBlockPredictionSchema.safeParse(block);
|
|
3868
|
+
if (parsed.success) {
|
|
3869
|
+
findings.push(parsed.data);
|
|
3870
|
+
continue;
|
|
3871
|
+
}
|
|
3872
|
+
rejectedBlocks.push(`block ${index}: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"} ${issue.message}`).join("; ")}`);
|
|
3873
|
+
}
|
|
3874
|
+
if (findings.length === 0 && envelope.findings.length > 0) throw new ValidationError(`every reported failure block was malformed: ${rejectedBlocks.join(" | ")}`);
|
|
3875
|
+
return {
|
|
3876
|
+
report: envelope.report,
|
|
3877
|
+
findings,
|
|
3878
|
+
rejectedBlocks
|
|
3677
3879
|
};
|
|
3678
|
-
const manifestContent = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
3679
|
-
const localReceiptContent = `${JSON.stringify(localReceipt, null, 2)}\n`;
|
|
3680
|
-
const initializationCompleteContent = renderInitializationComplete(manifest);
|
|
3681
|
-
await assertAbsentOrExact(paths.observations, "", "benchmark observation log");
|
|
3682
|
-
await assertAbsentOrExact(paths.localReceipt, localReceiptContent, "benchmark local run receipt");
|
|
3683
|
-
await assertAbsentOrExact(paths.manifest, manifestContent, "benchmark run manifest");
|
|
3684
|
-
for (const path of [
|
|
3685
|
-
paths.costLedger,
|
|
3686
|
-
paths.modelResponses,
|
|
3687
|
-
paths.result,
|
|
3688
|
-
paths.report
|
|
3689
|
-
]) if (await regularFileExists(path)) throw new Error(`benchmark initialization marker is missing but later run artifact exists: ${path}`);
|
|
3690
|
-
if (await regularFileExists(paths.initializationComplete)) throw new Error(`benchmark initialization marker already exists during partial initialization: ${paths.initializationComplete}`);
|
|
3691
|
-
await writeExclusiveOrVerify(paths.observations, "");
|
|
3692
|
-
await writeExclusiveOrVerify(paths.localReceipt, localReceiptContent);
|
|
3693
|
-
await writeExclusiveOrVerify(paths.manifest, manifestContent);
|
|
3694
|
-
await writeExclusiveOrVerify(paths.initializationComplete, initializationCompleteContent);
|
|
3695
|
-
return manifest;
|
|
3696
3880
|
}
|
|
3697
|
-
async function
|
|
3698
|
-
if (
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
if (
|
|
3703
|
-
|
|
3704
|
-
"
|
|
3705
|
-
"
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3881
|
+
async function publicBenchmarkPredictionsToFindings(options) {
|
|
3882
|
+
if (options.predictions.length === 0 && options.dataset === "agentrx") return {
|
|
3883
|
+
findings: [],
|
|
3884
|
+
diagnostics: void 0
|
|
3885
|
+
};
|
|
3886
|
+
if (options.dataset === "agentrx") {
|
|
3887
|
+
const prediction = options.predictions[0];
|
|
3888
|
+
if (!("step" in prediction)) throw new Error("AgentRx model output must name a single root-cause step");
|
|
3889
|
+
if (!prediction.category) throw new Error("AgentRx model output is missing its failure category");
|
|
3890
|
+
const evidenceByStep = await resolveAssistantStepEvidence({
|
|
3891
|
+
trajectoryId: options.trajectoryId,
|
|
3892
|
+
steps: [prediction.step],
|
|
3893
|
+
store: options.store,
|
|
3894
|
+
...options.signal ? { signal: options.signal } : {}
|
|
3895
|
+
});
|
|
3896
|
+
const [finding] = agentRxPredictionsToFindings(options.trajectoryId, [{
|
|
3897
|
+
failure_case: prediction.category,
|
|
3898
|
+
step_number: prediction.step,
|
|
3899
|
+
description: prediction.rationale ?? prediction.claim
|
|
3900
|
+
}], {
|
|
3901
|
+
analystId: options.analystId,
|
|
3902
|
+
producedAt: options.producedAt,
|
|
3903
|
+
confidence: prediction.confidence
|
|
3904
|
+
});
|
|
3905
|
+
if (!finding) throw new Error("AgentRx output adapter produced no root-cause finding");
|
|
3906
|
+
return {
|
|
3907
|
+
findings: [{
|
|
3908
|
+
...finding,
|
|
3909
|
+
evidence_refs: [evidenceByStep.get(prediction.step)],
|
|
3910
|
+
metadata: {
|
|
3911
|
+
...finding.metadata,
|
|
3912
|
+
model: options.providerModel
|
|
3913
|
+
}
|
|
3914
|
+
}],
|
|
3915
|
+
diagnostics: void 0
|
|
3916
|
+
};
|
|
3917
|
+
}
|
|
3918
|
+
const blocks = options.predictions.map((prediction) => {
|
|
3919
|
+
if (!("first_step" in prediction)) throw new Error("CodeTraceBench model output must report first_step/last_step failure blocks");
|
|
3920
|
+
return {
|
|
3921
|
+
firstStep: prediction.first_step,
|
|
3922
|
+
lastStep: prediction.last_step,
|
|
3923
|
+
consequenceStep: prediction.consequence_step,
|
|
3924
|
+
escapeStatus: prediction.escape_status,
|
|
3925
|
+
severity: prediction.severity,
|
|
3926
|
+
claim: prediction.claim,
|
|
3927
|
+
confidence: prediction.confidence,
|
|
3928
|
+
...prediction.rationale === void 0 ? {} : { rationale: prediction.rationale },
|
|
3929
|
+
...prediction.recommended_action === void 0 ? {} : { recommendedAction: prediction.recommended_action },
|
|
3930
|
+
metadata: {
|
|
3931
|
+
analysis_mode: "direct-baseline",
|
|
3932
|
+
model: options.providerModel
|
|
3933
|
+
}
|
|
3934
|
+
};
|
|
3935
|
+
});
|
|
3936
|
+
return expandCodeTraceFailureBlocks({
|
|
3937
|
+
trajectoryId: options.trajectoryId,
|
|
3938
|
+
blocks,
|
|
3939
|
+
store: options.store,
|
|
3940
|
+
analystId: options.analystId,
|
|
3941
|
+
producedAt: options.producedAt,
|
|
3942
|
+
...options.signal ? { signal: options.signal } : {}
|
|
3943
|
+
});
|
|
3944
|
+
}
|
|
3945
|
+
async function prepareSingleTraceContext(store, context) {
|
|
3946
|
+
const storeContext = context.signal ? { signal: context.signal } : void 0;
|
|
3947
|
+
const overview = await store.getOverview(void 0, storeContext);
|
|
3948
|
+
if (overview.total_traces !== 1 || overview.sample_trace_ids.length !== 1) throw new Error(`public model benchmark requires exactly one trace, received ${overview.total_traces}`);
|
|
3949
|
+
const traceId = overview.sample_trace_ids[0];
|
|
3950
|
+
for (const perAttributeByteCap of TRACE_PROJECTION_ATTRIBUTE_BYTE_CAPS) {
|
|
3951
|
+
const viewed = await store.viewTrace({
|
|
3952
|
+
trace_id: traceId,
|
|
3953
|
+
per_attribute_byte_cap: perAttributeByteCap
|
|
3954
|
+
}, storeContext);
|
|
3955
|
+
if (!viewed.spans) continue;
|
|
3956
|
+
return JSON.stringify({
|
|
3957
|
+
trace_id: traceId,
|
|
3958
|
+
per_attribute_byte_cap: perAttributeByteCap,
|
|
3959
|
+
spans: viewed.spans
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3722
3962
|
}
|
|
3723
|
-
|
|
3724
|
-
const
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
return manifest;
|
|
3963
|
+
function trajectoryIdFromCaseId$1(dataset, caseId) {
|
|
3964
|
+
const prefix = dataset === "agentrx" ? "agentrx:" : "codetrace:";
|
|
3965
|
+
if (!caseId.startsWith(prefix) || caseId.length === prefix.length) throw new Error(`unexpected ${dataset} benchmark case id '${caseId}'`);
|
|
3966
|
+
return caseId.slice(prefix.length);
|
|
3728
3967
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3968
|
+
//#endregion
|
|
3969
|
+
//#region src/analyst/benchmark-public-consensus.ts
|
|
3970
|
+
/**
|
|
3971
|
+
* Step-level majority vote across independent analyst samples.
|
|
3972
|
+
*
|
|
3973
|
+
* Each sample's accepted, evidence-resolved steps count as one vote per step.
|
|
3974
|
+
* Steps present in at least ceil(k/2) samples survive; surviving steps are
|
|
3975
|
+
* reassembled into contiguous consensus blocks. Each consensus block borrows
|
|
3976
|
+
* its metadata (consequence step, escape status, claim, severity, rationale)
|
|
3977
|
+
* from the contributing sample block with the largest step overlap — ties go
|
|
3978
|
+
* to the higher-confidence block, then to the earlier sample — while its
|
|
3979
|
+
* confidence is the mean across every contributor. The returned blocks still
|
|
3980
|
+
* pass through the shared expansion, so width, count, and evidence rules are
|
|
3981
|
+
* enforced there, never assumed here.
|
|
3982
|
+
*/
|
|
3983
|
+
function consensusCodeTraceBlocks(sampleAssignments) {
|
|
3984
|
+
const samples = sampleAssignments.length;
|
|
3985
|
+
if (samples < 2) throw new RangeError("step-level consensus requires at least two samples");
|
|
3986
|
+
const threshold = Math.ceil(samples / 2);
|
|
3987
|
+
const votesByStep = /* @__PURE__ */ new Map();
|
|
3988
|
+
sampleAssignments.forEach((assignments, sample) => {
|
|
3989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3990
|
+
for (const { step } of assignments) {
|
|
3991
|
+
if (!Number.isSafeInteger(step) || step < 0) throw new RangeError(`sample ${sample} assigned a non-step value: ${step}`);
|
|
3992
|
+
if (seen.has(step)) throw new Error(`sample ${sample} assigned step ${step} to more than one block`);
|
|
3993
|
+
seen.add(step);
|
|
3994
|
+
votesByStep.set(step, (votesByStep.get(step) ?? 0) + 1);
|
|
3995
|
+
}
|
|
3996
|
+
});
|
|
3997
|
+
const stepVotes = [...votesByStep].sort(([left], [right]) => left - right).map(([step, votes]) => ({
|
|
3998
|
+
step,
|
|
3999
|
+
votes,
|
|
4000
|
+
kept: votes >= threshold
|
|
4001
|
+
}));
|
|
4002
|
+
const keptSteps = stepVotes.filter((entry) => entry.kept).map((entry) => entry.step);
|
|
4003
|
+
const blocks = [];
|
|
4004
|
+
const blockDecisions = [];
|
|
4005
|
+
for (const segment of contiguousSegments(keptSteps)) {
|
|
4006
|
+
const contributors = segmentContributors(sampleAssignments, segment);
|
|
4007
|
+
const donor = contributors.reduce(betterDonor);
|
|
4008
|
+
const confidence = contributors.reduce((sum, contributor) => sum + contributor.block.confidence, 0) / contributors.length;
|
|
4009
|
+
blocks.push({
|
|
4010
|
+
firstStep: segment.firstStep,
|
|
4011
|
+
lastStep: segment.lastStep,
|
|
4012
|
+
consequenceStep: donor.block.consequenceStep,
|
|
4013
|
+
escapeStatus: donor.block.escapeStatus,
|
|
4014
|
+
severity: donor.block.severity,
|
|
4015
|
+
claim: donor.block.claim,
|
|
4016
|
+
confidence,
|
|
4017
|
+
...donor.block.rationale === void 0 ? {} : { rationale: donor.block.rationale },
|
|
4018
|
+
...donor.block.recommendedAction === void 0 ? {} : { recommendedAction: donor.block.recommendedAction },
|
|
4019
|
+
metadata: {
|
|
4020
|
+
...donor.block.metadata,
|
|
4021
|
+
consensus_samples: samples,
|
|
4022
|
+
consensus_threshold: threshold,
|
|
4023
|
+
consensus_contributors: contributors.length,
|
|
4024
|
+
consensus_donor_sample: donor.sample
|
|
4025
|
+
}
|
|
4026
|
+
});
|
|
4027
|
+
blockDecisions.push({
|
|
4028
|
+
firstStep: segment.firstStep,
|
|
4029
|
+
lastStep: segment.lastStep,
|
|
4030
|
+
consequenceStep: donor.block.consequenceStep,
|
|
4031
|
+
escapeStatus: donor.block.escapeStatus,
|
|
4032
|
+
confidence,
|
|
4033
|
+
donor: publicContributor(donor),
|
|
4034
|
+
contributors: contributors.map(publicContributor)
|
|
4035
|
+
});
|
|
4036
|
+
}
|
|
3744
4037
|
return {
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
4038
|
+
blocks,
|
|
4039
|
+
decision: {
|
|
4040
|
+
samples,
|
|
4041
|
+
threshold,
|
|
4042
|
+
stepVotes,
|
|
4043
|
+
blocks: blockDecisions
|
|
4044
|
+
}
|
|
3750
4045
|
};
|
|
3751
4046
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
4047
|
+
/**
|
|
4048
|
+
* All (sample, block) pairs whose accepted steps intersect the segment,
|
|
4049
|
+
* ordered by sample then by first overlapping step — the deterministic
|
|
4050
|
+
* tie-break order for donor selection.
|
|
4051
|
+
*/
|
|
4052
|
+
function segmentContributors(sampleAssignments, segment) {
|
|
4053
|
+
const contributors = [];
|
|
4054
|
+
sampleAssignments.forEach((assignments, sample) => {
|
|
4055
|
+
const overlapByBlock = /* @__PURE__ */ new Map();
|
|
4056
|
+
for (const { step, block } of assignments) {
|
|
4057
|
+
if (step < segment.firstStep || step > segment.lastStep) continue;
|
|
4058
|
+
overlapByBlock.set(block, (overlapByBlock.get(block) ?? 0) + 1);
|
|
4059
|
+
}
|
|
4060
|
+
for (const [block, overlapSteps] of overlapByBlock) contributors.push({
|
|
4061
|
+
sample,
|
|
4062
|
+
block,
|
|
4063
|
+
overlapSteps
|
|
4064
|
+
});
|
|
4065
|
+
});
|
|
4066
|
+
return contributors;
|
|
3759
4067
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
if (
|
|
4068
|
+
function betterDonor(left, right) {
|
|
4069
|
+
if (right.overlapSteps !== left.overlapSteps) return right.overlapSteps > left.overlapSteps ? right : left;
|
|
4070
|
+
if (right.block.confidence !== left.block.confidence) return right.block.confidence > left.block.confidence ? right : left;
|
|
4071
|
+
return left;
|
|
3763
4072
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
4073
|
+
function publicContributor(contributor) {
|
|
4074
|
+
return {
|
|
4075
|
+
sample: contributor.sample,
|
|
4076
|
+
firstStep: contributor.block.firstStep,
|
|
4077
|
+
lastStep: contributor.block.lastStep,
|
|
4078
|
+
consequenceStep: contributor.block.consequenceStep,
|
|
4079
|
+
confidence: contributor.block.confidence,
|
|
4080
|
+
overlapSteps: contributor.overlapSteps
|
|
4081
|
+
};
|
|
3767
4082
|
}
|
|
3768
|
-
function
|
|
3769
|
-
|
|
3770
|
-
const
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
previousRowSha256: progress.previousRowSha256,
|
|
3780
|
-
observation
|
|
3781
|
-
};
|
|
3782
|
-
const row = {
|
|
3783
|
-
...rowWithoutDigest,
|
|
3784
|
-
rowSha256: digestCanonical(rowWithoutDigest)
|
|
3785
|
-
};
|
|
3786
|
-
await appendDurable(path, `${JSON.stringify(row)}\n`);
|
|
3787
|
-
progress.nextSequence += 1;
|
|
3788
|
-
progress.previousRowSha256 = row.rowSha256;
|
|
3789
|
-
progress.observations.push(observation);
|
|
3790
|
-
seen.add(key);
|
|
4083
|
+
function contiguousSegments(sortedSteps) {
|
|
4084
|
+
const segments = [];
|
|
4085
|
+
for (const step of sortedSteps) {
|
|
4086
|
+
const current = segments[segments.length - 1];
|
|
4087
|
+
if (current && step === current.lastStep + 1) {
|
|
4088
|
+
current.lastStep = step;
|
|
4089
|
+
continue;
|
|
4090
|
+
}
|
|
4091
|
+
segments.push({
|
|
4092
|
+
firstStep: step,
|
|
4093
|
+
lastStep: step
|
|
3791
4094
|
});
|
|
3792
|
-
|
|
3793
|
-
|
|
4095
|
+
}
|
|
4096
|
+
return segments;
|
|
4097
|
+
}
|
|
4098
|
+
//#endregion
|
|
4099
|
+
//#region src/analyst/benchmark-public-rlm.ts
|
|
4100
|
+
/** Public benchmark candidate that runs the actual recursive trace analyst. */
|
|
4101
|
+
function createPublicBenchmarkRlmRunner(dataset, config) {
|
|
4102
|
+
const costLedger = config.costLedger ?? new CostLedger();
|
|
4103
|
+
const samples = config.dspyRlm?.samples ?? 1;
|
|
4104
|
+
if (!Number.isSafeInteger(samples) || samples < 1) throw new RangeError("dspyRlm.samples must be a positive safe integer");
|
|
4105
|
+
if (samples > 1 && dataset !== "codetracebench") throw new Error("dspyRlm.samples > 1 requires the codetracebench dataset; step-level consensus is defined on its block grammar");
|
|
4106
|
+
const limits = {
|
|
4107
|
+
maxIterations: config.dspyRlm?.maxIterations ?? 14,
|
|
4108
|
+
maxLlmCalls: config.dspyRlm?.maxLlmCalls ?? 8,
|
|
4109
|
+
maxToolCalls: config.dspyRlm?.maxToolCalls ?? 80,
|
|
4110
|
+
maxOutputChars: config.dspyRlm?.maxOutputChars ?? 8e3
|
|
4111
|
+
};
|
|
4112
|
+
const pricing = config.pricing ?? pricingForModel(config.model);
|
|
4113
|
+
const engine = createDspyRlmTraceEngine({
|
|
4114
|
+
baseUrl: config.baseUrl,
|
|
4115
|
+
apiKey: config.apiKey,
|
|
4116
|
+
model: config.model,
|
|
4117
|
+
maxOutputTokens: config.maxOutputTokens,
|
|
4118
|
+
timeoutMs: config.timeoutMs,
|
|
4119
|
+
maxCostUsd: config.maxCostUsdPerAnalysis ?? 1,
|
|
4120
|
+
pricing,
|
|
4121
|
+
...config.dspyRlm?.runner ? { runner: config.dspyRlm.runner } : {}
|
|
4122
|
+
});
|
|
4123
|
+
const instructions = config.instructionsOverride?.text ?? publicBenchmarkRlmInstructions(dataset);
|
|
4124
|
+
const protocolSha256 = effectiveAnalystProtocolSha256(dataset, config.instructionsOverride);
|
|
4125
|
+
const definition = publicBenchmarkDefinition(dataset, limits, instructions);
|
|
4126
|
+
const { instructionsOverride: _rlmOnlyOverride, ...directConfig } = config;
|
|
4127
|
+
const abstentionFallbackRunner = createPublicBenchmarkDirectRunner(dataset, {
|
|
4128
|
+
...directConfig,
|
|
4129
|
+
costLedger
|
|
4130
|
+
});
|
|
4131
|
+
return {
|
|
4132
|
+
id: "dspy-rlm",
|
|
4133
|
+
async analyze(input, context) {
|
|
4134
|
+
const trajectoryId = trajectoryIdFromCaseId(dataset, context.caseId);
|
|
4135
|
+
const tags = {
|
|
4136
|
+
benchmarkCaseId: context.caseId,
|
|
4137
|
+
benchmarkRepetition: String(context.repetition)
|
|
4138
|
+
};
|
|
4139
|
+
let usage;
|
|
4140
|
+
let rawFindings = [];
|
|
4141
|
+
try {
|
|
4142
|
+
if (!input.traceStore) throw new Error(`${dataset} DSPy RLM runner requires a trace store`);
|
|
4143
|
+
if (samples > 1) {
|
|
4144
|
+
const store = input.traceStore;
|
|
4145
|
+
const caseUsageFilter = {
|
|
4146
|
+
channel: "analyst",
|
|
4147
|
+
tags
|
|
4148
|
+
};
|
|
4149
|
+
const sampleRuns = [];
|
|
4150
|
+
const sampleAssignments = [];
|
|
4151
|
+
let totalModelCalls = 0;
|
|
4152
|
+
let totalToolCalls = 0;
|
|
4153
|
+
for (let sample = 0; sample < samples; sample += 1) {
|
|
4154
|
+
let sampleUsage;
|
|
4155
|
+
const completed = await runTraceAnalyst({
|
|
4156
|
+
definition,
|
|
4157
|
+
engine,
|
|
4158
|
+
store,
|
|
4159
|
+
context: {
|
|
4160
|
+
runId: context.caseId,
|
|
4161
|
+
correlationId: `${context.caseId}:${context.repetition}:sample-${sample}`,
|
|
4162
|
+
costLedger,
|
|
4163
|
+
costPhase: "analyst.public-benchmark.dspy-rlm",
|
|
4164
|
+
tags,
|
|
4165
|
+
recordUsage: (receipt) => {
|
|
4166
|
+
sampleUsage = receipt;
|
|
4167
|
+
usage = usageReceiptFromCostLedger(costLedger, caseUsageFilter);
|
|
4168
|
+
},
|
|
4169
|
+
signal: context.signal
|
|
4170
|
+
}
|
|
4171
|
+
});
|
|
4172
|
+
const producedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4173
|
+
const sampleFindings = completed.findings.map((finding) => makeFinding({
|
|
4174
|
+
analyst_id: "dspy-rlm",
|
|
4175
|
+
area: "incorrect",
|
|
4176
|
+
subject: finding.subject,
|
|
4177
|
+
claim: finding.claim,
|
|
4178
|
+
rationale: finding.rationale,
|
|
4179
|
+
severity: finding.severity,
|
|
4180
|
+
confidence: finding.confidence,
|
|
4181
|
+
evidence_refs: evidenceRefsFromRawFinding(finding),
|
|
4182
|
+
recommended_action: finding.recommended_action,
|
|
4183
|
+
metadata: {
|
|
4184
|
+
analysis_mode: "recursive",
|
|
4185
|
+
engine: "dspy-rlm",
|
|
4186
|
+
model: config.model,
|
|
4187
|
+
sample,
|
|
4188
|
+
...codeTraceBlockMetadataFromSubject(finding.subject) ?? {}
|
|
4189
|
+
},
|
|
4190
|
+
produced_at: producedAt
|
|
4191
|
+
}));
|
|
4192
|
+
rawFindings = [...rawFindings, ...sampleFindings];
|
|
4193
|
+
const adapted = await adaptPublicBenchmarkFindings({
|
|
4194
|
+
dataset,
|
|
4195
|
+
trajectoryId,
|
|
4196
|
+
findings: sampleFindings,
|
|
4197
|
+
analystId: "dspy-rlm",
|
|
4198
|
+
store,
|
|
4199
|
+
...context.signal ? { signal: context.signal } : {}
|
|
4200
|
+
});
|
|
4201
|
+
const assignments = adapted.stepBlocks ?? [];
|
|
4202
|
+
sampleAssignments.push(assignments);
|
|
4203
|
+
totalModelCalls += completed.modelCalls;
|
|
4204
|
+
totalToolCalls += completed.toolCalls;
|
|
4205
|
+
sampleRuns.push({
|
|
4206
|
+
sample,
|
|
4207
|
+
answer: completed.answer,
|
|
4208
|
+
trajectory: completed.trajectory,
|
|
4209
|
+
modelCalls: completed.modelCalls,
|
|
4210
|
+
toolCalls: completed.toolCalls,
|
|
4211
|
+
runtime: completed.runtime,
|
|
4212
|
+
blocks: sampleBlockRecords(assignments),
|
|
4213
|
+
steps: assignments.map((assignment) => assignment.step),
|
|
4214
|
+
...adapted.diagnostics ? { blockDiagnostics: adapted.diagnostics } : {},
|
|
4215
|
+
...sampleUsage ? { usage: sampleUsage } : {}
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
const consensus = consensusCodeTraceBlocks(sampleAssignments);
|
|
4219
|
+
const expanded = await expandCodeTraceFailureBlocks({
|
|
4220
|
+
trajectoryId,
|
|
4221
|
+
blocks: consensus.blocks,
|
|
4222
|
+
store,
|
|
4223
|
+
analystId: "dspy-rlm",
|
|
4224
|
+
producedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4225
|
+
...context.signal ? { signal: context.signal } : {}
|
|
4226
|
+
});
|
|
4227
|
+
let fallback;
|
|
4228
|
+
if (consensus.blocks.length === 0) fallback = await abstentionFallbackRunner.analyze(input, context);
|
|
4229
|
+
usage = usageReceiptFromCostLedger(costLedger, caseUsageFilter);
|
|
4230
|
+
return {
|
|
4231
|
+
findings: fallback && !fallback.error ? fallback.findings : expanded.findings,
|
|
4232
|
+
usage,
|
|
4233
|
+
metadata: {
|
|
4234
|
+
analysisMode: "recursive",
|
|
4235
|
+
engine: "dspy-rlm",
|
|
4236
|
+
protocolSha256,
|
|
4237
|
+
samples,
|
|
4238
|
+
sampleRuns,
|
|
4239
|
+
consensus: consensus.decision,
|
|
4240
|
+
blockDiagnostics: expanded.diagnostics,
|
|
4241
|
+
modelCalls: totalModelCalls,
|
|
4242
|
+
toolCalls: totalToolCalls,
|
|
4243
|
+
...fallback ? {
|
|
4244
|
+
abstentionFallback: "direct",
|
|
4245
|
+
...fallback.metadata ? { abstentionFallbackMetadata: fallback.metadata } : {},
|
|
4246
|
+
...fallback.error ? { abstentionFallbackError: fallback.error } : {}
|
|
4247
|
+
} : {}
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
const completed = await runTraceAnalyst({
|
|
4252
|
+
definition,
|
|
4253
|
+
engine,
|
|
4254
|
+
store: input.traceStore,
|
|
4255
|
+
context: {
|
|
4256
|
+
runId: context.caseId,
|
|
4257
|
+
correlationId: `${context.caseId}:${context.repetition}`,
|
|
4258
|
+
costLedger,
|
|
4259
|
+
costPhase: "analyst.public-benchmark.dspy-rlm",
|
|
4260
|
+
tags,
|
|
4261
|
+
recordUsage: (receipt) => {
|
|
4262
|
+
usage = receipt;
|
|
4263
|
+
},
|
|
4264
|
+
signal: context.signal
|
|
4265
|
+
}
|
|
4266
|
+
});
|
|
4267
|
+
const producedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4268
|
+
rawFindings = completed.findings.map((finding) => makeFinding({
|
|
4269
|
+
analyst_id: "dspy-rlm",
|
|
4270
|
+
area: dataset === "agentrx" ? "root-cause" : "incorrect",
|
|
4271
|
+
subject: finding.subject,
|
|
4272
|
+
claim: finding.claim,
|
|
4273
|
+
rationale: finding.rationale,
|
|
4274
|
+
severity: finding.severity,
|
|
4275
|
+
confidence: finding.confidence,
|
|
4276
|
+
evidence_refs: evidenceRefsFromRawFinding(finding),
|
|
4277
|
+
recommended_action: finding.recommended_action,
|
|
4278
|
+
metadata: {
|
|
4279
|
+
analysis_mode: "recursive",
|
|
4280
|
+
engine: "dspy-rlm",
|
|
4281
|
+
model: config.model,
|
|
4282
|
+
...dataset === "codetracebench" ? codeTraceBlockMetadataFromSubject(finding.subject) : {}
|
|
4283
|
+
},
|
|
4284
|
+
produced_at: producedAt
|
|
4285
|
+
}));
|
|
4286
|
+
const adapted = await adaptPublicBenchmarkFindings({
|
|
4287
|
+
dataset,
|
|
4288
|
+
trajectoryId,
|
|
4289
|
+
findings: rawFindings,
|
|
4290
|
+
analystId: "dspy-rlm",
|
|
4291
|
+
store: input.traceStore,
|
|
4292
|
+
...context.signal ? { signal: context.signal } : {}
|
|
4293
|
+
});
|
|
4294
|
+
let fallback;
|
|
4295
|
+
if (completed.findings.length === 0) {
|
|
4296
|
+
fallback = await abstentionFallbackRunner.analyze(input, context);
|
|
4297
|
+
usage = usageReceiptFromCostLedger(costLedger, {
|
|
4298
|
+
channel: "analyst",
|
|
4299
|
+
tags: {
|
|
4300
|
+
benchmarkCaseId: context.caseId,
|
|
4301
|
+
benchmarkRepetition: String(context.repetition)
|
|
4302
|
+
}
|
|
4303
|
+
});
|
|
4304
|
+
}
|
|
4305
|
+
return {
|
|
4306
|
+
findings: fallback && !fallback.error ? fallback.findings : adapted.findings,
|
|
4307
|
+
usage,
|
|
4308
|
+
metadata: {
|
|
4309
|
+
analysisMode: "recursive",
|
|
4310
|
+
engine: "dspy-rlm",
|
|
4311
|
+
protocolSha256,
|
|
4312
|
+
...adapted.diagnostics ? { blockDiagnostics: adapted.diagnostics } : {},
|
|
4313
|
+
answer: completed.answer,
|
|
4314
|
+
trajectory: completed.trajectory,
|
|
4315
|
+
modelCalls: completed.modelCalls,
|
|
4316
|
+
toolCalls: completed.toolCalls,
|
|
4317
|
+
runtime: completed.runtime,
|
|
4318
|
+
...fallback ? {
|
|
4319
|
+
abstentionFallback: "direct",
|
|
4320
|
+
...fallback.metadata ? { abstentionFallbackMetadata: fallback.metadata } : {},
|
|
4321
|
+
...fallback.error ? { abstentionFallbackError: fallback.error } : {}
|
|
4322
|
+
} : {}
|
|
4323
|
+
}
|
|
4324
|
+
};
|
|
4325
|
+
} catch (error) {
|
|
4326
|
+
if (context.signal?.aborted) throw error;
|
|
4327
|
+
if (isPaidCallControlError(error)) throw error;
|
|
4328
|
+
return {
|
|
4329
|
+
findings: [],
|
|
4330
|
+
usage,
|
|
4331
|
+
error: publicBenchmarkError(error, [config.apiKey]),
|
|
4332
|
+
metadata: {
|
|
4333
|
+
analysisMode: "recursive",
|
|
4334
|
+
engine: "dspy-rlm",
|
|
4335
|
+
...samples > 1 ? { samples } : {},
|
|
4336
|
+
rawFindings
|
|
4337
|
+
}
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
3794
4341
|
};
|
|
3795
4342
|
}
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
const
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
const allowedCases = new Set(caseIds);
|
|
3804
|
-
const plannedObservationCount = caseIds.length * 2 * repetitions;
|
|
3805
|
-
for (const [index, line] of rawLines.entries()) {
|
|
3806
|
-
if (!line.trim()) throw new Error(`benchmark observation log contains an empty row at line ${index + 1}`);
|
|
3807
|
-
const parsed = parseJson(line, `${path}:${index + 1}`);
|
|
3808
|
-
if (!isRecord$1(parsed)) throw new TypeError(`benchmark observation row ${index + 1} must be an object`);
|
|
3809
|
-
assertExactKeys(parsed, [
|
|
3810
|
-
"sequence",
|
|
3811
|
-
"runIdentitySha256",
|
|
3812
|
-
"previousRowSha256",
|
|
3813
|
-
"observation",
|
|
3814
|
-
"rowSha256"
|
|
3815
|
-
], `benchmark observation row ${index + 1}`);
|
|
3816
|
-
assertAnalystBenchmarkObservation(parsed.observation, `benchmark observation row ${index + 1}.observation`);
|
|
3817
|
-
const observation = parsed.observation;
|
|
3818
|
-
const key = observationKey(observation);
|
|
3819
|
-
if (seen.has(key)) throw new Error(`duplicate benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' at line ${index + 1}`);
|
|
3820
|
-
if (parsed.sequence !== index) throw new Error(`benchmark observation row ${index + 1} has sequence ${String(parsed.sequence)}; expected ${index}`);
|
|
3821
|
-
if (parsed.runIdentitySha256 !== runIdentitySha256) throw new Error(`benchmark observation row ${index + 1} belongs to another run`);
|
|
3822
|
-
if (parsed.previousRowSha256 !== previousRowSha256) throw new Error(`benchmark observation row ${index + 1} breaks the digest chain`);
|
|
3823
|
-
if (!isSha256(parsed.rowSha256)) throw new TypeError(`benchmark observation row ${index + 1} has an invalid digest`);
|
|
3824
|
-
if (digestCanonical({
|
|
3825
|
-
sequence: parsed.sequence,
|
|
3826
|
-
runIdentitySha256: parsed.runIdentitySha256,
|
|
3827
|
-
previousRowSha256: parsed.previousRowSha256,
|
|
3828
|
-
observation
|
|
3829
|
-
}) !== parsed.rowSha256) throw new Error(`benchmark observation row ${index + 1} digest does not match its contents`);
|
|
3830
|
-
if (!allowedCases.has(observation.caseId) || observation.runnerId !== "empty" && observation.runnerId !== analystRunnerId || observation.repetition >= repetitions || observation.executionIndex >= plannedObservationCount) throw new Error(`benchmark observation row ${index + 1} does not match a planned case, runner, and repetition`);
|
|
3831
|
-
if (executionIndexes.has(observation.executionIndex)) throw new Error(`duplicate benchmark executionIndex ${observation.executionIndex} at line ${index + 1}`);
|
|
3832
|
-
observations.push(observation);
|
|
3833
|
-
seen.add(key);
|
|
3834
|
-
executionIndexes.add(observation.executionIndex);
|
|
3835
|
-
previousRowSha256 = parsed.rowSha256;
|
|
4343
|
+
/** Per-sample accepted blocks with the exact steps the expansion kept for each. */
|
|
4344
|
+
function sampleBlockRecords(assignments) {
|
|
4345
|
+
const stepsByBlock = /* @__PURE__ */ new Map();
|
|
4346
|
+
for (const { step, block } of assignments) {
|
|
4347
|
+
const steps = stepsByBlock.get(block);
|
|
4348
|
+
if (steps) steps.push(step);
|
|
4349
|
+
else stepsByBlock.set(block, [step]);
|
|
3836
4350
|
}
|
|
4351
|
+
return [...stepsByBlock].map(([block, acceptedSteps]) => ({
|
|
4352
|
+
firstStep: block.firstStep,
|
|
4353
|
+
lastStep: block.lastStep,
|
|
4354
|
+
consequenceStep: block.consequenceStep,
|
|
4355
|
+
escapeStatus: block.escapeStatus,
|
|
4356
|
+
severity: block.severity,
|
|
4357
|
+
confidence: block.confidence,
|
|
4358
|
+
claim: block.claim,
|
|
4359
|
+
acceptedSteps
|
|
4360
|
+
}));
|
|
4361
|
+
}
|
|
4362
|
+
function publicBenchmarkDefinition(dataset, limits, instructions) {
|
|
3837
4363
|
return {
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4364
|
+
id: dataset === "agentrx" ? "agentrx-dspy-rlm" : "codetracebench-dspy-rlm",
|
|
4365
|
+
description: dataset === "agentrx" ? "Localizes the first unrecoverable root-cause step." : "Localizes every incorrect state-changing assistant step.",
|
|
4366
|
+
area: dataset === "agentrx" ? "root-cause" : "incorrect",
|
|
4367
|
+
version: "1.0.0",
|
|
4368
|
+
question: dataset === "agentrx" ? "What is the first unrecoverable root cause in this failed trajectory?" : "Which assistant steps are incorrect under the CodeTraceBench definition?",
|
|
4369
|
+
instructions,
|
|
4370
|
+
toolGroup: "singleTrace",
|
|
4371
|
+
limits
|
|
3841
4372
|
};
|
|
3842
4373
|
}
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
}
|
|
4374
|
+
function pricingForModel(model) {
|
|
4375
|
+
const pricing = resolveModelPricing(model);
|
|
4376
|
+
if (!pricing) throw new Error(`no pricing is configured for '${model}'; provide PublicAnalystBenchmarkModelConfig.pricing`);
|
|
4377
|
+
return {
|
|
4378
|
+
inputUsdPerMillion: pricing.input * 1e3,
|
|
4379
|
+
outputUsdPerMillion: pricing.output * 1e3
|
|
4380
|
+
};
|
|
3850
4381
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
return true;
|
|
3856
|
-
} catch (error) {
|
|
3857
|
-
if (isNodeError(error, "ENOENT")) return false;
|
|
3858
|
-
throw error;
|
|
3859
|
-
}
|
|
4382
|
+
function trajectoryIdFromCaseId(dataset, caseId) {
|
|
4383
|
+
const prefix = dataset === "agentrx" ? "agentrx:" : "codetrace:";
|
|
4384
|
+
if (!caseId.startsWith(prefix) || caseId.length === prefix.length) throw new Error(`unexpected ${dataset} benchmark case id '${caseId}'`);
|
|
4385
|
+
return caseId.slice(prefix.length);
|
|
3860
4386
|
}
|
|
3861
|
-
|
|
3862
|
-
|
|
4387
|
+
function isPaidCallControlError(error) {
|
|
4388
|
+
return error instanceof CostAccountingIncompleteError || error instanceof CostCallConflictError || error instanceof CostCeilingReachedError || error instanceof CostLedgerPersistenceError || error instanceof CostReceiptCaptureError || error instanceof CostReservationExceededError;
|
|
4389
|
+
}
|
|
4390
|
+
//#endregion
|
|
4391
|
+
//#region src/analyst/benchmark-public-data.ts
|
|
4392
|
+
const DEFAULT_MAX_PUBLIC_BENCHMARK_LABEL_BYTES = 256 * 1024 * 1024;
|
|
4393
|
+
const INPUT_OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0);
|
|
4394
|
+
async function loadPublicBenchmarkRows(path) {
|
|
4395
|
+
return parsePublicBenchmarkRows((await readImmutableInputSnapshot(resolve(path), DEFAULT_MAX_PUBLIC_BENCHMARK_LABEL_BYTES)).text, path);
|
|
4396
|
+
}
|
|
4397
|
+
function parsePublicBenchmarkRows(text, path) {
|
|
4398
|
+
const trimmed = text.trim();
|
|
4399
|
+
if (!trimmed) throw new Error(`public analyst benchmark dataset is empty: ${path}`);
|
|
4400
|
+
let parsed;
|
|
3863
4401
|
try {
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
await handle.close();
|
|
4402
|
+
parsed = JSON.parse(trimmed);
|
|
4403
|
+
} catch {
|
|
4404
|
+
return parseJsonl(trimmed, path);
|
|
3868
4405
|
}
|
|
4406
|
+
if (Array.isArray(parsed)) return records(parsed, path);
|
|
4407
|
+
if (isRecord(parsed) && Array.isArray(parsed.data)) return records(parsed.data, `${path}.data`);
|
|
4408
|
+
if (isRecord(parsed) && Array.isArray(parsed.cases)) return records(parsed.cases, `${path}.cases`);
|
|
4409
|
+
if (isRecord(parsed)) return [parsed];
|
|
4410
|
+
throw new TypeError(`public analyst benchmark dataset must contain JSON objects: ${path}`);
|
|
3869
4411
|
}
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
await link(temporary, path);
|
|
3880
|
-
await syncDirectory(dirname(path));
|
|
3881
|
-
} finally {
|
|
3882
|
-
await handle?.close().catch(() => void 0);
|
|
3883
|
-
await unlink(temporary).catch(() => void 0);
|
|
4412
|
+
function selectPublicBenchmarkRows(dataset, rows, options) {
|
|
4413
|
+
positiveSafeInteger(options.limit, "limit");
|
|
4414
|
+
safeInteger(options.seed, "seed");
|
|
4415
|
+
if (rows.length === 0) throw new Error("public analyst benchmark dataset has no rows");
|
|
4416
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4417
|
+
for (const row of rows) {
|
|
4418
|
+
const id = publicBenchmarkRowId(dataset, row);
|
|
4419
|
+
if (byId.has(id)) throw new Error(`public analyst benchmark dataset repeats trajectory id '${id}'`);
|
|
4420
|
+
byId.set(id, row);
|
|
3884
4421
|
}
|
|
4422
|
+
return [...byId].sort(([left], [right]) => selectionKey(options.seed, left).localeCompare(selectionKey(options.seed, right)) || left.localeCompare(right)).slice(0, Math.min(options.limit, byId.size)).map(([, row]) => row);
|
|
3885
4423
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
4424
|
+
function publicBenchmarkDistributions(dataset, rows) {
|
|
4425
|
+
const values = {
|
|
4426
|
+
class: [],
|
|
4427
|
+
agent: [],
|
|
4428
|
+
model: [],
|
|
4429
|
+
difficulty: [],
|
|
4430
|
+
solved: []
|
|
4431
|
+
};
|
|
4432
|
+
for (const row of rows) {
|
|
4433
|
+
const benchmarkCase = dataset === "agentrx" ? agentRxBenchmarkCase(row, void 0) : codeTraceBenchCase(row, void 0);
|
|
4434
|
+
values.class.push(dataset === "codetracebench" ? benchmarkCase.expectedIssues.length > 0 ? "positive" : row.solved === true ? "trusted-negative" : row.solved === false ? "unlabeled-failure" : "unlabeled-unknown" : benchmarkCase.expectedIssues[0]?.areas?.[0]);
|
|
4435
|
+
values.agent.push(scalarDistributionValue(row.agent) ?? (dataset === "agentrx" ? rootAgent(row) : void 0));
|
|
4436
|
+
values.model.push(scalarDistributionValue(row.model));
|
|
4437
|
+
values.difficulty.push(scalarDistributionValue(row.difficulty));
|
|
4438
|
+
values.solved.push(scalarDistributionValue(row.solved));
|
|
3893
4439
|
}
|
|
3894
|
-
|
|
3895
|
-
|
|
4440
|
+
return {
|
|
4441
|
+
class: valueDistribution(values.class),
|
|
4442
|
+
agent: valueDistribution(values.agent),
|
|
4443
|
+
model: valueDistribution(values.model),
|
|
4444
|
+
difficulty: valueDistribution(values.difficulty),
|
|
4445
|
+
solved: valueDistribution(values.solved)
|
|
4446
|
+
};
|
|
3896
4447
|
}
|
|
3897
|
-
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
4448
|
+
function publicBenchmarkSelectionReport(dataset, source, selected, seed) {
|
|
4449
|
+
const census = source.length === selected.length;
|
|
4450
|
+
return {
|
|
4451
|
+
method: census ? "census" : "deterministic-hash",
|
|
4452
|
+
seed,
|
|
4453
|
+
sourceCount: source.length,
|
|
4454
|
+
selectedCount: selected.length,
|
|
4455
|
+
stratified: false,
|
|
4456
|
+
representativeOfInput: census,
|
|
4457
|
+
source: publicBenchmarkDistributions(dataset, source),
|
|
4458
|
+
selected: publicBenchmarkDistributions(dataset, selected)
|
|
4459
|
+
};
|
|
4460
|
+
}
|
|
4461
|
+
async function preparePublicAnalystBenchmark(options) {
|
|
4462
|
+
const labelsPath = resolve(options.labelsPath);
|
|
4463
|
+
const traceRoot = resolve(options.traceDir);
|
|
4464
|
+
const artifactRoot = options.artifactDir ? resolve(options.artifactDir) : void 0;
|
|
4465
|
+
const labelSnapshot = await readImmutableInputSnapshot(labelsPath, DEFAULT_MAX_PUBLIC_BENCHMARK_LABEL_BYTES);
|
|
4466
|
+
const rows = parsePublicBenchmarkRows(labelSnapshot.text, labelsPath);
|
|
4467
|
+
const selected = selectPublicBenchmarkRows(options.dataset, rows, {
|
|
4468
|
+
limit: options.limit,
|
|
4469
|
+
seed: options.seed
|
|
4470
|
+
});
|
|
4471
|
+
const stores = await indexSelectedSingleTraceFiles(traceRoot, new Set(selected.map((row) => publicBenchmarkRowId(options.dataset, row))));
|
|
4472
|
+
const resolver = traceStoreEvidenceResolver((input) => {
|
|
4473
|
+
if (!input.traceStore) throw new Error("prepared benchmark case has no trace store");
|
|
4474
|
+
return input.traceStore;
|
|
4475
|
+
});
|
|
4476
|
+
const traceFiles = [];
|
|
4477
|
+
const verificationArtifacts = [];
|
|
4478
|
+
const cases = [];
|
|
4479
|
+
for (const row of selected) {
|
|
4480
|
+
const trajectoryId = publicBenchmarkRowId(options.dataset, row);
|
|
4481
|
+
const indexed = stores.get(trajectoryId);
|
|
4482
|
+
if (!indexed) throw new Error(`public analyst benchmark trace directory has no single-trace OTLP JSONL for '${trajectoryId}'`);
|
|
4483
|
+
let modelVisibleOtlp = indexed.text;
|
|
4484
|
+
let traceStore = indexed.store;
|
|
4485
|
+
let artifactDir;
|
|
4486
|
+
let verificationManifest;
|
|
4487
|
+
if (options.dataset === "codetracebench") {
|
|
4488
|
+
if (!options.artifactDir?.trim()) throw new Error("--artifact-dir is required for CodeTraceBench so final verification evidence is not omitted");
|
|
4489
|
+
const artifacts = await loadCodeTraceVerificationArtifacts({
|
|
4490
|
+
artifactDir: options.artifactDir,
|
|
4491
|
+
row,
|
|
4492
|
+
maxBytes: options.maxArtifactBytes ?? 8388608
|
|
4493
|
+
});
|
|
4494
|
+
for (const artifact of artifacts.files) assertNoBenchmarkLabelsInArtifact({
|
|
4495
|
+
traceId: trajectoryId,
|
|
4496
|
+
relativePath: artifact.relativePath,
|
|
4497
|
+
content: artifact.content
|
|
4498
|
+
});
|
|
4499
|
+
verificationManifest = shareableVerificationManifest(artifacts.manifest, artifactRoot ?? resolve(options.artifactDir));
|
|
4500
|
+
const collisions = await indexed.store.hasSpans({
|
|
4501
|
+
trace_id: trajectoryId,
|
|
4502
|
+
span_ids: [...artifacts.manifest.files.map((file) => file.spanId), artifacts.manifest.outcomeSpanId]
|
|
4503
|
+
});
|
|
4504
|
+
if (collisions.length > 0) throw new Error(`CodeTraceBench '${trajectoryId}' trace already contains benchmark verification span '${collisions[0]}'`);
|
|
4505
|
+
modelVisibleOtlp = appendVerificationArtifactsToOtlp(indexed.text, trajectoryId, artifacts, indexed.latestTimestamp);
|
|
4506
|
+
traceStore = otlpTextToTraceAnalysisStore(modelVisibleOtlp);
|
|
4507
|
+
artifactDir = artifacts.manifest.status === "present" ? artifacts.manifest.caseDirectory : void 0;
|
|
4508
|
+
verificationArtifacts.push(verificationManifest);
|
|
4509
|
+
}
|
|
4510
|
+
const labelLeakScan = assertNoBenchmarkLabelsInTrace({
|
|
4511
|
+
traceId: trajectoryId,
|
|
4512
|
+
otlpText: modelVisibleOtlp
|
|
4513
|
+
});
|
|
4514
|
+
const input = {
|
|
4515
|
+
traceStore,
|
|
4516
|
+
artifactDir
|
|
4517
|
+
};
|
|
4518
|
+
const benchmarkCase = options.dataset === "agentrx" ? agentRxBenchmarkCase(row, input, { stepCount: indexed.stepCount }) : codeTraceBenchCase(row, input);
|
|
4519
|
+
for (const evidence of benchmarkCase.labeledEvidence ?? []) if (!await resolver({
|
|
4520
|
+
caseId: benchmarkCase.id,
|
|
4521
|
+
caseInput: input,
|
|
4522
|
+
evidence: {
|
|
4523
|
+
kind: evidence.kind ?? "span",
|
|
4524
|
+
uri: evidence.uri
|
|
4525
|
+
}
|
|
4526
|
+
})) throw new Error(`${benchmarkCase.id}: missing labeled span ${spanIdFromEvidence(evidence.uri) ?? evidence.uri} in ${indexed.path}`);
|
|
4527
|
+
cases.push({
|
|
4528
|
+
...benchmarkCase,
|
|
4529
|
+
metadata: {
|
|
4530
|
+
...benchmarkCase.metadata,
|
|
4531
|
+
traceFileRelativePath: slashRelative(traceRoot, indexed.path),
|
|
4532
|
+
traceFileSha256: indexed.sha256,
|
|
4533
|
+
labelLeakScan,
|
|
4534
|
+
...verificationManifest ? { verificationArtifacts: verificationManifest } : {}
|
|
4535
|
+
}
|
|
4536
|
+
});
|
|
4537
|
+
traceFiles.push({
|
|
4538
|
+
traceId: trajectoryId,
|
|
4539
|
+
relativePath: slashRelative(traceRoot, indexed.path),
|
|
4540
|
+
sha256: indexed.sha256
|
|
4541
|
+
});
|
|
3903
4542
|
}
|
|
3904
|
-
}
|
|
3905
|
-
function isNodeError(error, code) {
|
|
3906
|
-
return error instanceof Error && "code" in error && error.code === code;
|
|
3907
|
-
}
|
|
3908
|
-
//#endregion
|
|
3909
|
-
//#region src/analyst/benchmark-comparison.ts
|
|
3910
|
-
function compareAnalystRunners(result, options) {
|
|
3911
|
-
const confidence = options.confidence ?? .95;
|
|
3912
|
-
const resamples = options.resamples ?? 2e3;
|
|
3913
|
-
assertComparisonControls(confidence, resamples);
|
|
3914
|
-
const runnerIds = new Set(result.summaries.map((summary) => summary.runnerId));
|
|
3915
|
-
if (!runnerIds.has(options.baselineRunnerId)) throw new TypeError(`unknown baseline analyst runner '${options.baselineRunnerId}'`);
|
|
3916
|
-
if (!runnerIds.has(options.candidateRunnerId)) throw new TypeError(`unknown candidate analyst runner '${options.candidateRunnerId}'`);
|
|
3917
|
-
if (options.baselineRunnerId === options.candidateRunnerId) throw new TypeError("baseline and candidate analyst runners must be different");
|
|
3918
|
-
const baseline = observationsByCase(result.observations, options.baselineRunnerId);
|
|
3919
|
-
const candidate = observationsByCase(result.observations, options.candidateRunnerId);
|
|
3920
|
-
const populationRepresentativenessProven = result.provenance.metadata?.populationRepresentativenessProven === true;
|
|
3921
|
-
const metrics = METRICS.map((metric) => compareMetric({
|
|
3922
|
-
metric,
|
|
3923
|
-
baseline,
|
|
3924
|
-
candidate,
|
|
3925
|
-
confidence,
|
|
3926
|
-
resamples,
|
|
3927
|
-
seed: options.seed,
|
|
3928
|
-
populationRepresentativenessProven
|
|
3929
|
-
}));
|
|
3930
4543
|
return {
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
4544
|
+
cases,
|
|
4545
|
+
sourceRowCount: rows.length,
|
|
4546
|
+
selectedCaseIds: cases.map((testCase) => testCase.id),
|
|
4547
|
+
labelsSha256: labelSnapshot.sha256,
|
|
4548
|
+
traceFiles,
|
|
4549
|
+
verificationArtifacts,
|
|
4550
|
+
selection: publicBenchmarkSelectionReport(options.dataset, rows, selected, options.seed)
|
|
3934
4551
|
};
|
|
3935
4552
|
}
|
|
3936
|
-
function
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
const
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
eligibleObservations += 1;
|
|
3958
|
-
clusterId = identity.clusterId;
|
|
3959
|
-
const baselineValue = baselineObservation ? metricValue(baselineObservation, options.metric) : null;
|
|
3960
|
-
const candidateValue = candidateObservation ? metricValue(candidateObservation, options.metric) : null;
|
|
3961
|
-
const baselineMissing = baselineValue === null;
|
|
3962
|
-
const candidateMissing = candidateValue === null;
|
|
3963
|
-
if (baselineMissing) baselineMissingObservations += 1;
|
|
3964
|
-
if (candidateMissing) candidateMissingObservations += 1;
|
|
3965
|
-
if (baselineMissing !== candidateMissing) asymmetricMissingObservations += 1;
|
|
3966
|
-
if (baselineMissing || candidateMissing) continue;
|
|
3967
|
-
caseBefore.push(baselineValue);
|
|
3968
|
-
caseAfter.push(candidateValue);
|
|
3969
|
-
pairedObservations += 1;
|
|
3970
|
-
}
|
|
3971
|
-
if (caseBefore.length === 0 || !clusterId) continue;
|
|
3972
|
-
pairedCases.push({
|
|
3973
|
-
clusterId,
|
|
3974
|
-
baseline: mean$1(caseBefore),
|
|
3975
|
-
candidate: mean$1(caseAfter)
|
|
4553
|
+
async function indexSelectedSingleTraceFiles(traceDir, selectedTraceIds) {
|
|
4554
|
+
if (selectedTraceIds.size === 0) throw new Error("public analyst benchmark selected no trace ids");
|
|
4555
|
+
const files = (await readdir(traceDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => resolve(traceDir, entry.name)).sort();
|
|
4556
|
+
if (files.length === 0) throw new Error(`public analyst benchmark trace directory has no JSONL files: ${traceDir}`);
|
|
4557
|
+
const indexed = /* @__PURE__ */ new Map();
|
|
4558
|
+
for (const path of files) {
|
|
4559
|
+
const snapshot = await readImmutableInputSnapshot(path, DEFAULT_MAX_TRACE_FILE_BYTES);
|
|
4560
|
+
const store = createOtlpBufferTraceStore(snapshot.bytes);
|
|
4561
|
+
const overview = await store.getOverview();
|
|
4562
|
+
if (overview.total_traces !== 1 || overview.sample_trace_ids.length !== 1) throw new Error(`public analyst benchmark trace file must contain exactly one trace: ${path} contains ${overview.total_traces}`);
|
|
4563
|
+
if (!overview.time_range) throw new Error(`public analyst benchmark trace file has no valid timestamps: ${path}`);
|
|
4564
|
+
const traceId = overview.sample_trace_ids[0];
|
|
4565
|
+
if (!selectedTraceIds.has(traceId)) continue;
|
|
4566
|
+
if (indexed.has(traceId)) throw new Error(`public analyst benchmark trace id '${traceId}' appears in multiple files`);
|
|
4567
|
+
indexed.set(traceId, {
|
|
4568
|
+
path,
|
|
4569
|
+
sha256: snapshot.sha256,
|
|
4570
|
+
store,
|
|
4571
|
+
latestTimestamp: overview.time_range.latest,
|
|
4572
|
+
text: snapshot.text,
|
|
4573
|
+
stepCount: traceStepCount(snapshot.text, path)
|
|
3976
4574
|
});
|
|
3977
4575
|
}
|
|
3978
|
-
|
|
3979
|
-
for (const pairedCase of pairedCases) {
|
|
3980
|
-
const rows = byCluster.get(pairedCase.clusterId) ?? [];
|
|
3981
|
-
rows.push(pairedCase);
|
|
3982
|
-
byCluster.set(pairedCase.clusterId, rows);
|
|
3983
|
-
}
|
|
3984
|
-
const before = [...byCluster.values()].map((rows) => mean$1(rows.map((row) => row.baseline)));
|
|
3985
|
-
const after = [...byCluster.values()].map((rows) => mean$1(rows.map((row) => row.candidate)));
|
|
3986
|
-
const interval = before.length === 0 ? null : pairedBootstrap(before, after, {
|
|
3987
|
-
confidence: options.confidence,
|
|
3988
|
-
resamples: options.resamples,
|
|
3989
|
-
statistic: "mean",
|
|
3990
|
-
seed: options.seed
|
|
3991
|
-
});
|
|
3992
|
-
const survivorOnly = pairedObservations < eligibleObservations;
|
|
3993
|
-
const limitations = [];
|
|
3994
|
-
if (!interval?.gateEligible) limitations.push("fewer-than-20-independent-clusters");
|
|
3995
|
-
if (!options.populationRepresentativenessProven) limitations.push("population-representativeness-not-proven");
|
|
3996
|
-
if (survivorOnly) limitations.push("missing-observations");
|
|
3997
|
-
const comparison = {
|
|
3998
|
-
metric: options.metric,
|
|
3999
|
-
direction: LOWER_IS_BETTER.has(options.metric) ? "lower" : "higher",
|
|
4000
|
-
pairedCases: pairedCases.length,
|
|
4001
|
-
pairedClusters: before.length,
|
|
4002
|
-
eligibleObservations,
|
|
4003
|
-
pairedObservations,
|
|
4004
|
-
baselineMissingObservations,
|
|
4005
|
-
candidateMissingObservations,
|
|
4006
|
-
asymmetricMissingObservations,
|
|
4007
|
-
survivorOnly,
|
|
4008
|
-
baselineMean: before.length === 0 ? null : mean$1(before),
|
|
4009
|
-
candidateMean: after.length === 0 ? null : mean$1(after),
|
|
4010
|
-
meanDelta: interval?.mean ?? null,
|
|
4011
|
-
intervalLow: interval?.low ?? null,
|
|
4012
|
-
intervalHigh: interval?.high ?? null,
|
|
4013
|
-
confidence: options.confidence,
|
|
4014
|
-
resamples: options.resamples,
|
|
4015
|
-
minimumSampleMet: interval?.gateEligible ?? false,
|
|
4016
|
-
populationInferenceEligible: limitations.length === 0,
|
|
4017
|
-
inferenceLimitations: limitations
|
|
4018
|
-
};
|
|
4019
|
-
assertValidComparison(comparison);
|
|
4020
|
-
return comparison;
|
|
4576
|
+
return indexed;
|
|
4021
4577
|
}
|
|
4022
|
-
|
|
4023
|
-
"
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
"citationExcerptCoverage",
|
|
4030
|
-
"citationLabelAgreement",
|
|
4031
|
-
"citationResolution",
|
|
4032
|
-
"trustedNegativeAccuracy",
|
|
4033
|
-
"latencyMs",
|
|
4034
|
-
"calls",
|
|
4035
|
-
"inputTokens",
|
|
4036
|
-
"outputTokens",
|
|
4037
|
-
"reasoningTokens",
|
|
4038
|
-
"cachedTokens",
|
|
4039
|
-
"cacheWriteTokens",
|
|
4040
|
-
"costUsd"
|
|
4041
|
-
];
|
|
4042
|
-
const LOWER_IS_BETTER = /* @__PURE__ */ new Set([
|
|
4043
|
-
"latencyMs",
|
|
4044
|
-
"calls",
|
|
4045
|
-
"inputTokens",
|
|
4046
|
-
"outputTokens",
|
|
4047
|
-
"reasoningTokens",
|
|
4048
|
-
"cachedTokens",
|
|
4049
|
-
"cacheWriteTokens",
|
|
4050
|
-
"costUsd"
|
|
4051
|
-
]);
|
|
4052
|
-
function observationsByCase(observations, runnerId) {
|
|
4053
|
-
const byCase = /* @__PURE__ */ new Map();
|
|
4054
|
-
for (const observation of observations) {
|
|
4055
|
-
if (observation.runnerId !== runnerId) continue;
|
|
4056
|
-
const rows = byCase.get(observation.caseId) ?? [];
|
|
4057
|
-
rows.push(observation);
|
|
4058
|
-
byCase.set(observation.caseId, rows);
|
|
4578
|
+
async function readImmutableInputSnapshot(path, maxBytes) {
|
|
4579
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new RangeError("benchmark input maxBytes must be a positive safe integer");
|
|
4580
|
+
const handle = await open(path, INPUT_OPEN_FLAGS);
|
|
4581
|
+
try {
|
|
4582
|
+
return await readImmutableInputHandle(handle, path, maxBytes);
|
|
4583
|
+
} finally {
|
|
4584
|
+
await handle.close();
|
|
4059
4585
|
}
|
|
4060
|
-
return byCase;
|
|
4061
|
-
}
|
|
4062
|
-
function assertSameCaseIdentity(baseline, candidate) {
|
|
4063
|
-
if (baseline.clusterId !== candidate.clusterId || baseline.labelState !== candidate.labelState) throw new Error(`analyst comparison case identity differs for '${baseline.caseId}' repetition ${baseline.repetition}`);
|
|
4064
|
-
}
|
|
4065
|
-
function metricApplies(observation, metric) {
|
|
4066
|
-
if (metric === "trustedNegativeAccuracy") return observation.labelState === "trusted-negative";
|
|
4067
|
-
if (metric === "issueRecall" || metric === "findingPrecision" || metric === "f1") return observation.labelState === "positive";
|
|
4068
|
-
if (metric === "criticalStepAccuracy") return observation.labelState === "positive" && observation.score.criticalStepAccuracy !== null;
|
|
4069
|
-
return true;
|
|
4070
4586
|
}
|
|
4071
|
-
function
|
|
4072
|
-
|
|
4073
|
-
if (
|
|
4074
|
-
if (
|
|
4075
|
-
|
|
4076
|
-
|
|
4587
|
+
async function readImmutableInputHandle(handle, path, maxBytes) {
|
|
4588
|
+
const before = await handle.stat({ bigint: true });
|
|
4589
|
+
if (!before.isFile()) throw new TypeError(`public analyst benchmark input must be a regular file: ${path}`);
|
|
4590
|
+
if (before.size > BigInt(maxBytes)) throw new RangeError(`public analyst benchmark input exceeds ${maxBytes} bytes: ${path} has ${before.size}`);
|
|
4591
|
+
const size = Number(before.size);
|
|
4592
|
+
const bytes = Buffer.allocUnsafe(size);
|
|
4593
|
+
let offset = 0;
|
|
4594
|
+
while (offset < size) {
|
|
4595
|
+
const result = await handle.read(bytes, offset, size - offset, offset);
|
|
4596
|
+
if (result.bytesRead === 0) throw new Error(`public analyst benchmark input changed while being read: ${path}`);
|
|
4597
|
+
offset += result.bytesRead;
|
|
4077
4598
|
}
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
if (
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
if (metric === "cacheWriteTokens") return observation.usage?.tokens?.cacheWrite ?? null;
|
|
4094
|
-
if (observation.usage?.cost.kind === "uncaptured") return null;
|
|
4095
|
-
return observation.usage?.cost.usd ?? null;
|
|
4096
|
-
}
|
|
4097
|
-
function mean$1(values) {
|
|
4098
|
-
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
4099
|
-
}
|
|
4100
|
-
function assertComparisonControls(confidence, resamples) {
|
|
4101
|
-
if (!Number.isSafeInteger(resamples) || resamples <= 0 || resamples > 1e6) throw new Error(`compareAnalystRunners: resamples must be a positive safe integer no greater than 1000000, got ${String(resamples)}`);
|
|
4102
|
-
if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) throw new Error(`compareAnalystRunners: confidence must be a finite number in (0,1), got ${String(confidence)}`);
|
|
4599
|
+
const overflow = Buffer.allocUnsafe(1);
|
|
4600
|
+
const extra = await handle.read(overflow, 0, 1, size);
|
|
4601
|
+
const after = await handle.stat({ bigint: true });
|
|
4602
|
+
if (extra.bytesRead !== 0 || before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs) throw new Error(`public analyst benchmark input changed while being read: ${path}`);
|
|
4603
|
+
let text;
|
|
4604
|
+
try {
|
|
4605
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4606
|
+
} catch (error) {
|
|
4607
|
+
throw new TypeError(`public analyst benchmark input is not valid UTF-8: ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4608
|
+
}
|
|
4609
|
+
return Object.freeze({
|
|
4610
|
+
bytes,
|
|
4611
|
+
sha256: sha256Digest(bytes),
|
|
4612
|
+
text
|
|
4613
|
+
});
|
|
4103
4614
|
}
|
|
4104
|
-
function
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
"eligibleObservations",
|
|
4109
|
-
"pairedObservations",
|
|
4110
|
-
"baselineMissingObservations",
|
|
4111
|
-
"candidateMissingObservations",
|
|
4112
|
-
"asymmetricMissingObservations",
|
|
4113
|
-
"confidence",
|
|
4114
|
-
"resamples"
|
|
4115
|
-
].some((field) => !Number.isFinite(comparison[field])) || [
|
|
4116
|
-
"baselineMean",
|
|
4117
|
-
"candidateMean",
|
|
4118
|
-
"meanDelta",
|
|
4119
|
-
"intervalLow",
|
|
4120
|
-
"intervalHigh"
|
|
4121
|
-
].some((field) => comparison[field] !== null && !Number.isFinite(comparison[field]))) throw new Error(`compareAnalystRunners: ${comparison.metric} produced non-finite comparison output`);
|
|
4122
|
-
if (comparison.intervalLow !== null && comparison.intervalHigh !== null && comparison.intervalLow > comparison.intervalHigh) throw new Error(`compareAnalystRunners: ${comparison.metric} produced an invalid confidence interval`);
|
|
4615
|
+
function traceStepCount(text, path) {
|
|
4616
|
+
const steps = parseJsonl(text, path).map((row) => row.span_id).filter((spanId) => typeof spanId === "string").map((spanId) => /^step-(\d+)$/.exec(spanId)?.[1]).filter((step) => step !== void 0).map(Number).filter((step) => Number.isSafeInteger(step) && step > 0);
|
|
4617
|
+
if (steps.length === 0) throw new Error(`public analyst benchmark trace has no step-<n> spans: ${path}`);
|
|
4618
|
+
return Math.max(...steps);
|
|
4123
4619
|
}
|
|
4124
|
-
|
|
4125
|
-
//#region src/analyst/benchmark-public-calibration.ts
|
|
4126
|
-
function summarizeCodeTraceCalibration(result) {
|
|
4620
|
+
function shareableVerificationManifest(manifest, artifactRoot) {
|
|
4127
4621
|
return {
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4622
|
+
...manifest,
|
|
4623
|
+
caseDirectory: slashRelative(artifactRoot, manifest.caseDirectory),
|
|
4624
|
+
caseDirectoriesSearched: manifest.caseDirectoriesSearched.map((path) => slashRelative(artifactRoot, path)),
|
|
4625
|
+
files: manifest.files.map((file) => ({
|
|
4626
|
+
...file,
|
|
4627
|
+
path: slashRelative(artifactRoot, file.path)
|
|
4628
|
+
}))
|
|
4131
4629
|
};
|
|
4132
4630
|
}
|
|
4133
|
-
function
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
""
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
4141
|
-
...summary.runners.map((runner) => `| ${escapeCell$1(runner.runnerId)} | ${runner.completedRuns}/${runner.selectedRuns} | ${runner.failedRuns} | ${runner.positiveRuns} | ${runner.trustedNegativeRuns} | ${runner.unlabeledRuns} | ${runner.failedLabelEmptyRuns} | ${runner.unknownLabelEmptyRuns} | ${runner.matchedIncorrectSteps}/${runner.expectedIncorrectSteps} | ${runner.predictedIncorrectSteps} | ${rate$1(runner.precision)} | ${rate$1(runner.recall)} | ${rate$1(runner.f1)} | ${rate$1(runner.officialAllRowF1)} | ${runner.officialAllRowRuns} | ${rate$1(runner.trustedNegativeFalsePositiveRate)} | ${rate$1(runner.trustedNegativeFailureRate)} | ${rate$1(runner.unlabeledPredictionRate)} | ${rate$1(runner.unlabeledFailureRate)} |`)
|
|
4142
|
-
].join("\n");
|
|
4143
|
-
}
|
|
4144
|
-
function summarizeRunner(runnerId, observations) {
|
|
4145
|
-
const positive = observations.filter((observation) => observation.labelState === "positive");
|
|
4146
|
-
const trustedNegative = observations.filter((observation) => observation.labelState === "trusted-negative");
|
|
4147
|
-
const excluded = observations.filter((observation) => observation.labelState === "unlabeled");
|
|
4148
|
-
const selected = [...positive, ...trustedNegative];
|
|
4149
|
-
const expected = sum(positive.map((observation) => observation.score.expectedIssueCount));
|
|
4150
|
-
const predicted = sum(selected.map((observation) => observation.error ? 0 : observation.findings.length));
|
|
4151
|
-
const matched = sum(positive.map((observation) => observation.score.matchedIssueIds.length));
|
|
4152
|
-
const precision = predicted === 0 ? expected > 0 ? 0 : null : matched / predicted;
|
|
4153
|
-
const recall = ratio(matched, expected);
|
|
4154
|
-
const completedTrustedNegative = trustedNegative.filter((observation) => !observation.error);
|
|
4155
|
-
const completedExcluded = excluded.filter((observation) => !observation.error);
|
|
4156
|
-
const officialRows = observations.map(officialCodeTraceF1);
|
|
4157
|
-
return {
|
|
4158
|
-
runnerId,
|
|
4159
|
-
selectedRuns: selected.length,
|
|
4160
|
-
positiveRuns: positive.length,
|
|
4161
|
-
trustedNegativeRuns: trustedNegative.length,
|
|
4162
|
-
unlabeledRuns: excluded.length,
|
|
4163
|
-
failedLabelEmptyRuns: excluded.filter((observation) => observation.caseMetadata?.solved === false).length,
|
|
4164
|
-
unknownLabelEmptyRuns: excluded.filter((observation) => observation.caseMetadata?.solved !== false).length,
|
|
4165
|
-
completedRuns: selected.filter((observation) => !observation.error).length,
|
|
4166
|
-
failedRuns: selected.filter((observation) => observation.error).length,
|
|
4167
|
-
expectedIncorrectSteps: expected,
|
|
4168
|
-
predictedIncorrectSteps: predicted,
|
|
4169
|
-
matchedIncorrectSteps: matched,
|
|
4170
|
-
officialAllRowF1: mean(officialRows),
|
|
4171
|
-
officialAllRowRuns: officialRows.length,
|
|
4172
|
-
precision,
|
|
4173
|
-
recall,
|
|
4174
|
-
f1: harmonicMean(precision, recall),
|
|
4175
|
-
trustedNegativeFalsePositiveRate: ratio(completedTrustedNegative.filter((observation) => observation.score.predictionOnLabelEmptyCase).length, completedTrustedNegative.length),
|
|
4176
|
-
trustedNegativeFailureRate: ratio(trustedNegative.filter((observation) => observation.error).length, trustedNegative.length),
|
|
4177
|
-
unlabeledPredictionRate: ratio(completedExcluded.filter((observation) => observation.findings.length > 0).length, completedExcluded.length),
|
|
4178
|
-
unlabeledFailureRate: ratio(excluded.filter((observation) => Boolean(observation.error)).length, excluded.length)
|
|
4179
|
-
};
|
|
4631
|
+
function slashRelative(root, path) {
|
|
4632
|
+
const value = relative(root, path);
|
|
4633
|
+
if (!value || value === ".." || value.startsWith(`..${sep}`)) {
|
|
4634
|
+
if (!value) return ".";
|
|
4635
|
+
throw new Error(`benchmark artifact path escapes its declared root: ${path}`);
|
|
4636
|
+
}
|
|
4637
|
+
return value.replaceAll("\\", "/");
|
|
4180
4638
|
}
|
|
4181
|
-
function
|
|
4182
|
-
const
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
if (finding.area !== "incorrect") continue;
|
|
4192
|
-
for (const evidence of finding.evidence_refs) {
|
|
4193
|
-
const location = codeTraceStepFromEvidence(evidence.uri);
|
|
4194
|
-
if (!location || location.traceId !== trajectoryId) throw new TypeError(`${observation.caseId}: invalid CodeTraceBench prediction evidence '${evidence.uri}'`);
|
|
4195
|
-
predicted.add(location.step);
|
|
4639
|
+
function parseJsonl(text, path) {
|
|
4640
|
+
const rows = [];
|
|
4641
|
+
for (const [index, line] of text.split(/\r?\n/).entries()) {
|
|
4642
|
+
const trimmed = line.trim();
|
|
4643
|
+
if (!trimmed) continue;
|
|
4644
|
+
let parsed;
|
|
4645
|
+
try {
|
|
4646
|
+
parsed = JSON.parse(trimmed);
|
|
4647
|
+
} catch (error) {
|
|
4648
|
+
throw new Error(`${path}:${index + 1}: invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4196
4649
|
}
|
|
4650
|
+
if (!isRecord(parsed)) throw new TypeError(`${path}:${index + 1}: dataset row must be a JSON object`);
|
|
4651
|
+
rows.push(parsed);
|
|
4197
4652
|
}
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
return harmonicMean(predicted.size === 0 ? 0 : matched / predicted.size, expected.size === 0 ? 0 : matched / expected.size) ?? 0;
|
|
4201
|
-
}
|
|
4202
|
-
function sum(values) {
|
|
4203
|
-
return values.reduce((total, value) => total + value, 0);
|
|
4204
|
-
}
|
|
4205
|
-
function ratio(numerator, denominator) {
|
|
4206
|
-
return denominator === 0 ? null : numerator / denominator;
|
|
4207
|
-
}
|
|
4208
|
-
function mean(values) {
|
|
4209
|
-
return values.length === 0 ? null : sum(values) / values.length;
|
|
4653
|
+
if (rows.length === 0) throw new Error(`public analyst benchmark dataset is empty: ${path}`);
|
|
4654
|
+
return rows;
|
|
4210
4655
|
}
|
|
4211
|
-
function
|
|
4212
|
-
|
|
4213
|
-
|
|
4656
|
+
function records(values, path) {
|
|
4657
|
+
return values.map((value, index) => {
|
|
4658
|
+
if (!isRecord(value)) throw new TypeError(`${path}[${index}] must be a JSON object`);
|
|
4659
|
+
return value;
|
|
4660
|
+
});
|
|
4214
4661
|
}
|
|
4215
|
-
function
|
|
4216
|
-
|
|
4662
|
+
function publicBenchmarkRowId(dataset, row) {
|
|
4663
|
+
const value = dataset === "agentrx" ? row.trajectory_id : row.traj_id;
|
|
4664
|
+
if (typeof value !== "string" && typeof value !== "number" || !String(value).trim()) throw new TypeError(`${dataset} dataset row requires a non-empty ${dataset === "agentrx" ? "trajectory_id" : "traj_id"}`);
|
|
4665
|
+
return String(value);
|
|
4217
4666
|
}
|
|
4218
|
-
function
|
|
4219
|
-
|
|
4667
|
+
function spanIdFromEvidence(uri) {
|
|
4668
|
+
const match = /\/span\/([^/]+)$/.exec(uri);
|
|
4669
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
4220
4670
|
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
async function readAnalystBenchmarkArtifact(path) {
|
|
4224
|
-
const value = parseJson(await readRegularFile(path, "analyst benchmark result"), path);
|
|
4225
|
-
assertAnalystBenchmarkArtifact(value, "analyst benchmark result");
|
|
4226
|
-
return value;
|
|
4671
|
+
function selectionKey(seed, id) {
|
|
4672
|
+
return sha256Digest(`${seed}\u0000${id}`);
|
|
4227
4673
|
}
|
|
4228
|
-
function
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
const
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
cases: prepared.verificationArtifacts.length,
|
|
4236
|
-
resultFilesPresent: prepared.verificationArtifacts.filter((artifact) => artifact.status === "present").length,
|
|
4237
|
-
resultFilesMissing: prepared.verificationArtifacts.filter((artifact) => artifact.status === "missing").length,
|
|
4238
|
-
outcomes: {
|
|
4239
|
-
passed: prepared.verificationArtifacts.filter((artifact) => artifact.outcome.status === "passed").length,
|
|
4240
|
-
failed: prepared.verificationArtifacts.filter((artifact) => artifact.outcome.status === "failed").length,
|
|
4241
|
-
unavailable: prepared.verificationArtifacts.filter((artifact) => artifact.outcome.status === "unavailable").length
|
|
4242
|
-
}
|
|
4243
|
-
};
|
|
4244
|
-
const expectedInputs = {
|
|
4245
|
-
dataset: config.dataset,
|
|
4246
|
-
datasetRevision: config.datasetRevision,
|
|
4247
|
-
datasetSplit: config.datasetSplit,
|
|
4248
|
-
labelsSha256: inputs.labelsSha256,
|
|
4249
|
-
sourceRowCount: inputs.sourceRowCount,
|
|
4250
|
-
traceFiles: inputs.traceFiles.map((traceFile) => ({ ...traceFile })),
|
|
4251
|
-
verificationArtifacts: prepared.verificationArtifacts,
|
|
4252
|
-
verificationAvailability,
|
|
4253
|
-
selection: {
|
|
4254
|
-
limit: config.limit,
|
|
4255
|
-
seed: config.seed,
|
|
4256
|
-
selectedCaseIds: [...inputs.selectedCaseIds],
|
|
4257
|
-
report: prepared.selection
|
|
4258
|
-
},
|
|
4259
|
-
execution: {
|
|
4260
|
-
repetitions: config.repetitions,
|
|
4261
|
-
concurrency: config.concurrency,
|
|
4262
|
-
model: config.model.id,
|
|
4263
|
-
maxOutputTokens: config.model.maxOutputTokens,
|
|
4264
|
-
timeoutMs: config.model.timeoutMs,
|
|
4265
|
-
maxCostUsd: config.maxCostUsd,
|
|
4266
|
-
maxArtifactBytes: config.maxArtifactBytes,
|
|
4267
|
-
analystProtocolSha256: config.analystProtocolSha256,
|
|
4268
|
-
implementationSha256: config.implementationSha256,
|
|
4269
|
-
dependencyLockSha256: config.dependencyLockSha256
|
|
4674
|
+
function valueDistribution(values) {
|
|
4675
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4676
|
+
let missing = 0;
|
|
4677
|
+
for (const value of values) {
|
|
4678
|
+
if (value === void 0) {
|
|
4679
|
+
missing += 1;
|
|
4680
|
+
continue;
|
|
4270
4681
|
}
|
|
4682
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
4683
|
+
}
|
|
4684
|
+
return {
|
|
4685
|
+
total: values.length,
|
|
4686
|
+
missing,
|
|
4687
|
+
counts: Object.fromEntries([...counts].sort(([left], [right]) => left.localeCompare(right)))
|
|
4271
4688
|
};
|
|
4272
|
-
if (canonicalJson(artifact.inputs) !== canonicalJson(expectedInputs)) throw new Error("completed benchmark result inputs do not match the run manifest");
|
|
4273
|
-
const provenance = artifact.result.provenance;
|
|
4274
|
-
const expectedDatasetId = config.dataset === "agentrx" ? "microsoft/AgentRx" : "NJU-LINK/CodeTraceBench";
|
|
4275
|
-
const expectedOutputAdapter = config.dataset === "agentrx" ? "agentrx-taxonomy-and-root-step" : "codetracebench-incorrect-block";
|
|
4276
|
-
if (provenance.id !== `${config.dataset}-real-model-analyst` || provenance.startedAt !== manifest.createdAt || !Number.isFinite(Date.parse(provenance.endedAt)) || Date.parse(provenance.endedAt) < Date.parse(provenance.startedAt) || canonicalJson(provenance.dataset) !== canonicalJson({
|
|
4277
|
-
id: expectedDatasetId,
|
|
4278
|
-
revision: config.datasetRevision,
|
|
4279
|
-
split: config.datasetSplit
|
|
4280
|
-
}) || provenance.caseCount !== inputs.selectedCaseIds.length || canonicalJson(provenance.runnerIds) !== canonicalJson(config.runnerIds) || provenance.repetitions !== config.repetitions || provenance.maxConcurrency !== Math.min(config.concurrency, expectedCount) || provenance.runnerOrderSeed !== config.seed || provenance.metadata?.model !== config.model.id || provenance.metadata?.outputAdapter !== expectedOutputAdapter || provenance.metadata?.caseSelection !== prepared.selection.method || provenance.metadata?.caseSelectionSeed !== config.seed || provenance.metadata?.selectionStratified !== prepared.selection.stratified || provenance.metadata?.protocolSha256 !== config.analystProtocolSha256 || provenance.metadata?.implementationSha256 !== config.implementationSha256 || provenance.metadata?.dependencyLockSha256 !== config.dependencyLockSha256 || provenance.metadata?.populationRepresentativenessProven !== false) throw new Error("completed benchmark result provenance does not match the run manifest");
|
|
4281
|
-
const expectedSummaries = config.runnerIds.map((runnerId) => summarizeAnalystBenchmarkRunner(runnerId, observations.filter((observation) => observation.runnerId === runnerId)));
|
|
4282
|
-
if (canonicalJson(artifact.result.summaries) !== canonicalJson(expectedSummaries)) throw new Error("completed benchmark summaries do not match durable observations");
|
|
4283
|
-
const expectedComparisons = [compareAnalystRunners(artifact.result, {
|
|
4284
|
-
baselineRunnerId: "empty",
|
|
4285
|
-
candidateRunnerId: config.runnerIds[1],
|
|
4286
|
-
seed: config.seed
|
|
4287
|
-
})];
|
|
4288
|
-
if (canonicalJson(artifact.comparisons) !== canonicalJson(expectedComparisons)) throw new Error("completed benchmark comparisons do not match durable observations");
|
|
4289
|
-
if (config.dataset === "codetracebench") {
|
|
4290
|
-
if (canonicalJson(artifact.codeTraceCalibration) !== canonicalJson(summarizeCodeTraceCalibration(artifact.result)) || artifact.agentRxCalibration !== void 0) throw new Error("completed CodeTraceBench calibration does not match durable observations");
|
|
4291
|
-
} else if (canonicalJson(artifact.agentRxCalibration) !== canonicalJson(summarizeAgentRxCalibration(artifact.result, "f228165bfec60a801fd5fedd9d8ffe0f9de0c69d")) || artifact.codeTraceCalibration !== void 0) throw new Error("completed AgentRx calibration does not match durable observations");
|
|
4292
4689
|
}
|
|
4293
|
-
function
|
|
4294
|
-
if (
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
}
|
|
4301
|
-
if (expectedByKey.size > 0) throw new Error("benchmark result is missing durable observations");
|
|
4690
|
+
function scalarDistributionValue(value) {
|
|
4691
|
+
if (typeof value === "string") return value.trim() || void 0;
|
|
4692
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
4693
|
+
}
|
|
4694
|
+
function rootAgent(row) {
|
|
4695
|
+
const rootCauseId = row.root_cause_failure_id ?? row.root_cause?.failure_id;
|
|
4696
|
+
return scalarDistributionValue(row.failures.find((failure) => String(failure.failure_id) === String(rootCauseId))?.failed_agent);
|
|
4302
4697
|
}
|
|
4303
4698
|
//#endregion
|
|
4304
4699
|
//#region src/analyst/benchmark-report.ts
|
|
@@ -4474,11 +4869,12 @@ async function executeAnalystBenchmarkCommand(config, dependencies) {
|
|
|
4474
4869
|
},
|
|
4475
4870
|
metadata: {
|
|
4476
4871
|
model: config.model.model,
|
|
4872
|
+
rlmSamples: config.rlmSamples,
|
|
4477
4873
|
outputAdapter: config.dataset === "agentrx" ? "agentrx-taxonomy-and-root-step" : "codetracebench-incorrect-block",
|
|
4478
4874
|
caseSelection: prepared.selection.method,
|
|
4479
4875
|
caseSelectionSeed: config.seed,
|
|
4480
4876
|
selectionStratified: prepared.selection.stratified,
|
|
4481
|
-
protocolSha256:
|
|
4877
|
+
protocolSha256: effectiveAnalystProtocolSha256(config.dataset, config.model.instructionsOverride),
|
|
4482
4878
|
implementationSha256: ANALYST_BENCHMARK_IMPLEMENTATION_SHA256,
|
|
4483
4879
|
dependencyLockSha256: ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256,
|
|
4484
4880
|
populationRepresentativenessProven: false
|
|
@@ -4522,12 +4918,14 @@ async function executeAnalystBenchmarkCommand(config, dependencies) {
|
|
|
4522
4918
|
execution: {
|
|
4523
4919
|
repetitions: config.repetitions,
|
|
4524
4920
|
concurrency: config.concurrency,
|
|
4921
|
+
rlmSamples: config.rlmSamples,
|
|
4525
4922
|
model: config.model.model,
|
|
4526
4923
|
maxOutputTokens: config.model.maxOutputTokens,
|
|
4527
4924
|
timeoutMs: config.model.timeoutMs,
|
|
4528
4925
|
maxCostUsd: config.maxCostUsd,
|
|
4529
4926
|
maxArtifactBytes: config.maxArtifactBytes,
|
|
4530
|
-
analystProtocolSha256:
|
|
4927
|
+
analystProtocolSha256: effectiveAnalystProtocolSha256(config.dataset, config.model.instructionsOverride),
|
|
4928
|
+
...config.model.instructionsOverride ? { instructionsOverrideSha256: config.model.instructionsOverride.sha256 } : {},
|
|
4531
4929
|
implementationSha256: ANALYST_BENCHMARK_IMPLEMENTATION_SHA256,
|
|
4532
4930
|
dependencyLockSha256: ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256
|
|
4533
4931
|
}
|
|
@@ -4608,6 +5006,14 @@ Controls:
|
|
|
4608
5006
|
--seed <integer> Case-selection and comparison seed. Default: 0
|
|
4609
5007
|
--concurrency <positive integer> Parallel benchmark jobs. Default: 1
|
|
4610
5008
|
--repetitions <positive integer> Runs per case and runner. Default: 1
|
|
5009
|
+
--rlm-samples <positive integer> Recursive-engine runs per case; above 1 the
|
|
5010
|
+
step-level majority consensus is scored
|
|
5011
|
+
(CodeTraceBench + dspy-rlm only). Default: 1
|
|
5012
|
+
--instructions-file <path> Replace the recursive analyst instructions
|
|
5013
|
+
with this file's text (dspy-rlm only). The
|
|
5014
|
+
recorded protocol digest binds the stock
|
|
5015
|
+
protocol to the override text, and
|
|
5016
|
+
result.json records instructionsOverrideSha256.
|
|
4611
5017
|
--max-output-tokens <positive> Model output limit per call. Default: 16384
|
|
4612
5018
|
--python <executable> Python with agent-eval-rpc[dspy]. Default: python
|
|
4613
5019
|
--timeout-ms <positive> Model analyst deadline per case. Default: 300000
|
|
@@ -4636,6 +5042,12 @@ function parseCommandConfig(argv, env) {
|
|
|
4636
5042
|
const python = flags.get("python")?.trim();
|
|
4637
5043
|
const analyst = flags.get("analyst")?.trim() ?? "dspy-rlm";
|
|
4638
5044
|
if (analyst !== "dspy-rlm" && analyst !== "direct") throw new Error("--analyst must be 'dspy-rlm' or 'direct'");
|
|
5045
|
+
const rlmSamples = positiveFlag(flags, "rlm-samples", 1);
|
|
5046
|
+
if (rlmSamples > 1 && analyst !== "dspy-rlm") throw new Error("--rlm-samples above 1 requires --analyst dspy-rlm");
|
|
5047
|
+
const instructionsFile = flags.get("instructions-file")?.trim();
|
|
5048
|
+
if (instructionsFile && analyst !== "dspy-rlm") throw new Error("--instructions-file requires --analyst dspy-rlm");
|
|
5049
|
+
const instructionsOverride = instructionsFile ? readAnalystInstructionsOverride(instructionsFile) : void 0;
|
|
5050
|
+
if (rlmSamples > 1 && dataset !== "codetracebench") throw new Error("--rlm-samples above 1 requires --dataset codetracebench; step-level consensus is defined on its block grammar");
|
|
4639
5051
|
return {
|
|
4640
5052
|
dataset,
|
|
4641
5053
|
analyst,
|
|
@@ -4652,12 +5064,17 @@ function parseCommandConfig(argv, env) {
|
|
|
4652
5064
|
maxOutputTokens: positiveFlag(flags, "max-output-tokens", 16384),
|
|
4653
5065
|
timeoutMs: positiveFlag(flags, "timeout-ms", 3e5),
|
|
4654
5066
|
maxCostUsdPerAnalysis: maxCostUsd,
|
|
4655
|
-
...
|
|
5067
|
+
...instructionsOverride ? { instructionsOverride } : {},
|
|
5068
|
+
dspyRlm: {
|
|
5069
|
+
...python ? { runner: { command: python } } : {},
|
|
5070
|
+
samples: rlmSamples
|
|
5071
|
+
}
|
|
4656
5072
|
},
|
|
4657
5073
|
limit: positiveFlag(flags, "limit"),
|
|
4658
5074
|
seed: integerFlag(flags, "seed", 0),
|
|
4659
5075
|
concurrency: positiveFlag(flags, "concurrency", 1),
|
|
4660
5076
|
repetitions: positiveFlag(flags, "repetitions", 1),
|
|
5077
|
+
rlmSamples,
|
|
4661
5078
|
maxCostUsd,
|
|
4662
5079
|
maxArtifactBytes: positiveFlag(flags, "max-artifact-bytes", DEFAULT_MAX_VERIFICATION_ARTIFACT_BYTES),
|
|
4663
5080
|
apiKeyEnv,
|
|
@@ -4703,6 +5120,8 @@ const KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
4703
5120
|
"seed",
|
|
4704
5121
|
"concurrency",
|
|
4705
5122
|
"repetitions",
|
|
5123
|
+
"rlm-samples",
|
|
5124
|
+
"instructions-file",
|
|
4706
5125
|
"max-output-tokens",
|
|
4707
5126
|
"python",
|
|
4708
5127
|
"timeout-ms",
|
|
@@ -4826,6 +5245,6 @@ function shellQuote(value) {
|
|
|
4826
5245
|
return /^[a-zA-Z0-9_./:@%+=,-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
4827
5246
|
}
|
|
4828
5247
|
//#endregion
|
|
4829
|
-
export {
|
|
5248
|
+
export { normalizeAgentRxCategory as $, parseVerificationOutcome as A, analystBenchmarkDependencyLockDigest as B, MAX_INCORRECT_BLOCK_STEPS as C, DEFAULT_MAX_VERIFICATION_ARTIFACT_BYTES as D, publicBenchmarkSystemPrompt as E, ANALYST_BENCHMARK_EVIDENCE_IMPLEMENTATION_SHA256 as F, ANALYST_BENCHMARK_OBSERVATIONS_FILE as G, ANALYST_BENCHMARK_COST_LEDGER_FILE as H, ANALYST_BENCHMARK_EVIDENCE_PACKAGE_VERSION as I, summarizeAgentRxCalibration as J, AGENT_RX_UPSTREAM_REVISION as K, ANALYST_BENCHMARK_IMPLEMENTATION_DIGEST_ALGORITHM as L, ANALYST_BENCHMARK_DEPENDENCY_LOCK_FILES as M, ANALYST_BENCHMARK_DEPENDENCY_LOCK_SHA256 as N, appendVerificationArtifactsToOtlp as O, ANALYST_BENCHMARK_EVIDENCE_DEPENDENCY_LOCK_SHA256 as P, agentRxPredictionsToFindings as Q, ANALYST_BENCHMARK_IMPLEMENTATION_FILES as R, MAX_INCORRECT_BLOCKS as S, publicBenchmarkRlmInstructions as T, ANALYST_BENCHMARK_LOCAL_RECEIPT_FILE as U, analystBenchmarkImplementationDigest as V, ANALYST_BENCHMARK_MANIFEST_FILE as W, codeTracerPredictionsToFindings as X, codeTraceBenchCase as Y, agentRxBenchmarkCase as Z, compareAnalystRunners as _, preparePublicAnalystBenchmark as a, readAnalystInstructionsOverride as b, selectPublicBenchmarkRows as c, adaptPublicBenchmarkFindings as d, roundAgentRxStep as et, emptyPublicBenchmarkRunner as f, summarizeCodeTraceCalibration as g, renderCodeTraceCalibrationMarkdown as h, loadPublicBenchmarkRows as i, ANALYST_BENCHMARK_DEPENDENCY_LOCK_DIGEST_ALGORITHM as j, loadCodeTraceVerificationArtifacts as k, createPublicBenchmarkRlmRunner as l, readAnalystBenchmarkArtifact as m, runAnalystBenchmarkCommand as n, publicBenchmarkDistributions as o, expandCodeTraceFailureBlocks as p, renderAgentRxCalibrationMarkdown as q, renderAnalystBenchmarkMarkdown as r, publicBenchmarkSelectionReport as s, ANALYST_BENCHMARK_HELP as t, normalizeBenchmarkLabel as tt, createPublicBenchmarkDirectRunner as u, analystInstructionsOverrideFromText as v, publicBenchmarkProtocolSha256 as w, CODE_TRACE_BENCH_ANALYST_PROMPT as x, effectiveAnalystProtocolSha256 as y, ANALYST_BENCHMARK_IMPLEMENTATION_SHA256 as z };
|
|
4830
5249
|
|
|
4831
|
-
//# sourceMappingURL=benchmark-command-
|
|
5250
|
+
//# sourceMappingURL=benchmark-command-D8xXNVVh.js.map
|