@tangle-network/agent-eval 0.137.0 → 0.139.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 +78 -0
- package/README.md +34 -0
- package/dist/analyst/index.d.ts +485 -104
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +10 -607
- package/dist/analyst/index.js.map +1 -1
- package/dist/{benchmark-YDrpumqB.js → benchmark-CYtcIF2V.js} +299 -159
- package/dist/benchmark-CYtcIF2V.js.map +1 -0
- package/dist/{benchmark-CHX4orG7.d.ts → benchmark-DDVdWcwA.d.ts} +67 -15
- package/dist/benchmark-DDVdWcwA.d.ts.map +1 -0
- package/dist/benchmark-command-BKfjOBJ5.js +4537 -0
- package/dist/benchmark-command-BKfjOBJ5.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-DCLkQOmc.js → benchmarks-zxhy1QV3.js} +4 -3
- package/dist/{benchmarks-DCLkQOmc.js.map → benchmarks-zxhy1QV3.js.map} +1 -1
- package/dist/campaign/index.d.ts +5 -5
- package/dist/campaign/index.js +4 -3
- package/dist/{campaign-lgObcHFC.js → campaign-DrS6_hLd.js} +19 -11
- package/dist/campaign-DrS6_hLd.js.map +1 -0
- package/dist/canonical-D011XM8r.js +86 -0
- package/dist/canonical-D011XM8r.js.map +1 -0
- package/dist/cli.js +10 -3
- package/dist/cli.js.map +1 -1
- package/dist/{client-C8L6h6Wf.d.ts → client-BohnDFBq.d.ts} +4 -4
- package/dist/{client-C8L6h6Wf.d.ts.map → client-BohnDFBq.d.ts.map} +1 -1
- package/dist/{completion-verifier-DSyRNVzU.d.ts → completion-verifier-IPoP4fQO.d.ts} +178 -4
- package/dist/completion-verifier-IPoP4fQO.d.ts.map +1 -0
- package/dist/contract/index.d.ts +10 -10
- package/dist/contract/index.js +9 -8
- package/dist/contract/index.js.map +1 -1
- package/dist/control.d.ts +2 -2
- package/dist/{cost-ledger-D-5_-dhi.js → cost-ledger-CZ9diLxY.js} +90 -45
- package/dist/cost-ledger-CZ9diLxY.js.map +1 -0
- package/dist/{cost-ledger-D2o6JOrL.d.ts → cost-ledger-DKgyIWRj.d.ts} +6 -2
- package/dist/cost-ledger-DKgyIWRj.d.ts.map +1 -0
- package/dist/default-registry-B8vf7Rmf.d.ts +118 -0
- package/dist/default-registry-B8vf7Rmf.d.ts.map +1 -0
- package/dist/default-registry-BgJJItGr.js +2364 -0
- package/dist/default-registry-BgJJItGr.js.map +1 -0
- package/dist/dspy-rlm-engine-DTkVyDX-.js +344 -0
- package/dist/dspy-rlm-engine-DTkVyDX-.js.map +1 -0
- package/dist/{eval-campaign-CHqfLnff.js → eval-campaign-BmptJj50.js} +2 -2
- package/dist/{eval-campaign-CHqfLnff.js.map → eval-campaign-BmptJj50.js.map} +1 -1
- package/dist/exact-types-MaaFcllV.d.ts +234 -0
- package/dist/exact-types-MaaFcllV.d.ts.map +1 -0
- package/dist/external-optimizer-contracts-BrxY2Sli.d.ts +32 -0
- package/dist/external-optimizer-contracts-BrxY2Sli.d.ts.map +1 -0
- package/dist/{extract-usage-p-56bh8q.js → extract-usage-DZs601Va.js} +2 -2
- package/dist/{extract-usage-p-56bh8q.js.map → extract-usage-DZs601Va.js.map} +1 -1
- package/dist/{feedback-trajectory-N_F0PwHz.d.ts → feedback-trajectory-BJUWOkJM.d.ts} +3 -2
- package/dist/feedback-trajectory-BJUWOkJM.d.ts.map +1 -0
- package/dist/fuzz.d.ts +1 -1
- package/dist/fuzz.js +1 -1
- package/dist/{hf-dataset-DBJXXoY1.js → hf-dataset-XggBupCr.js} +2 -2
- package/dist/{hf-dataset-DBJXXoY1.js.map → hf-dataset-XggBupCr.js.map} +1 -1
- package/dist/hosted/index.d.ts +3 -3
- package/dist/{index-C-Pr4OWg.d.ts → index-BTm_P9aC.d.ts} +12 -11
- package/dist/index-BTm_P9aC.d.ts.map +1 -0
- package/dist/{index-U3RHOShi.d.ts → index-CWOPCJiw.d.ts} +2 -2
- package/dist/{index-U3RHOShi.d.ts.map → index-CWOPCJiw.d.ts.map} +1 -1
- package/dist/{index-DRNl6g_N.d.ts → index-CtR1xh4V.d.ts} +3 -3
- package/dist/{index-DRNl6g_N.d.ts.map → index-CtR1xh4V.d.ts.map} +1 -1
- package/dist/index-DEb46kc6.d.ts.map +1 -1
- package/dist/{index-BnP1QJUv.d.ts → index-_66rVpwN.d.ts} +5 -5
- package/dist/{index-BnP1QJUv.d.ts.map → index-_66rVpwN.d.ts.map} +1 -1
- package/dist/index.d.ts +35 -55
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -514
- package/dist/index.js.map +1 -1
- package/dist/{insight-report-B9ooYH_g.d.ts → insight-report-Bu5Wi9tG.d.ts} +4 -4
- package/dist/{insight-report-B9ooYH_g.d.ts.map → insight-report-Bu5Wi9tG.d.ts.map} +1 -1
- package/dist/{integrity-CKxosZ5Z.d.ts → integrity-COTh3DTH.d.ts} +2 -2
- package/dist/{integrity-CKxosZ5Z.d.ts.map → integrity-COTh3DTH.d.ts.map} +1 -1
- package/dist/kind-factory-CFxA0JQX.js +2133 -0
- package/dist/kind-factory-CFxA0JQX.js.map +1 -0
- package/dist/ledger-core/index.js +2 -1
- package/dist/{ledger-core-t6sItivm.js → ledger-core-Dxz0Rkwa.js} +210 -99
- package/dist/ledger-core-Dxz0Rkwa.js.map +1 -0
- package/dist/{llm-client-DKB25jV8.js → llm-client-bkztEfIx.js} +5 -5
- package/dist/llm-client-bkztEfIx.js.map +1 -0
- package/dist/meta-eval/index.d.ts +2 -2
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/{proposal-findings-DCawte-y.js → proposal-findings-2GIUo1et.js} +2 -68
- package/dist/proposal-findings-2GIUo1et.js.map +1 -0
- package/dist/{release-report-CofgVNZt.d.ts → release-report-fZarvIm-.d.ts} +3 -3
- package/dist/{release-report-CofgVNZt.d.ts.map → release-report-fZarvIm-.d.ts.map} +1 -1
- package/dist/{replay-K8FaC0CB.d.ts → replay-DjG4IG60.d.ts} +34 -143
- package/dist/replay-DjG4IG60.d.ts.map +1 -0
- package/dist/{replay-Bju0T8Ls.js → replay-SA4OB7O7.js} +48 -136
- package/dist/replay-SA4OB7O7.js.map +1 -0
- package/dist/reporting.d.ts +4 -4
- package/dist/{researcher-Da0Wj-bt.d.ts → researcher-BxhtGfKa.d.ts} +5 -5
- package/dist/{researcher-Da0Wj-bt.d.ts.map → researcher-BxhtGfKa.d.ts.map} +1 -1
- package/dist/{reward-hacking-CQ3hTCO3.d.ts → reward-hacking-CqSLiV51.d.ts} +2 -2
- package/dist/{reward-hacking-CQ3hTCO3.d.ts.map → reward-hacking-CqSLiV51.d.ts.map} +1 -1
- package/dist/rl.d.ts +5 -5
- package/dist/rl.js +1 -1
- package/dist/rollout/index.d.ts +1 -1
- package/dist/rollout/index.js +2 -2
- package/dist/{rollout-DQFl0UXA.js → rollout-8nj3mYvx.js} +2 -2
- package/dist/{rollout-DQFl0UXA.js.map → rollout-8nj3mYvx.js.map} +1 -1
- package/dist/{rubric-predictive-validity-C4sztLR3.d.ts → rubric-predictive-validity-DQBQj6uV.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-C4sztLR3.d.ts.map → rubric-predictive-validity-DQBQj6uV.d.ts.map} +1 -1
- package/dist/{run-evidence-BDIircdA.d.ts → run-evidence-C4RcRQT5.d.ts} +3 -3
- package/dist/{run-evidence-BDIircdA.d.ts.map → run-evidence-C4RcRQT5.d.ts.map} +1 -1
- package/dist/{run-record-BPCa2rQ8.d.ts → run-record-CztDMXVF.d.ts} +2 -2
- package/dist/{run-record-BPCa2rQ8.d.ts.map → run-record-CztDMXVF.d.ts.map} +1 -1
- package/dist/{semantic-concept-judge-Bz64IckK.js → semantic-concept-judge-BuIJ9IfB.js} +49 -6
- package/dist/semantic-concept-judge-BuIJ9IfB.js.map +1 -0
- package/dist/{server-KjXZZUDX.js → server-DaCpLfi0.js} +3 -3
- package/dist/{server-KjXZZUDX.js.map → server-DaCpLfi0.js.map} +1 -1
- package/dist/single-run-lock-BTTtPZ9N.js +989 -0
- package/dist/single-run-lock-BTTtPZ9N.js.map +1 -0
- package/dist/{skill-usage-CFDLLlhF.d.ts → skill-usage-B-BFS8M2.d.ts} +65 -40
- package/dist/skill-usage-B-BFS8M2.d.ts.map +1 -0
- package/dist/{skillopt-optimization-method-f4o9sUT4.js → skillopt-optimization-method-BbGnCC53.js} +20 -979
- package/dist/skillopt-optimization-method-BbGnCC53.js.map +1 -0
- package/dist/{skillopt-optimization-method-BpbnlvAZ.d.ts → skillopt-optimization-method-_s0Tub7Y.d.ts} +11 -39
- package/dist/skillopt-optimization-method-_s0Tub7Y.d.ts.map +1 -0
- package/dist/{statistics-_7P642CN.d.ts → statistics-B5d0Zd-z.d.ts} +2 -2
- package/dist/{statistics-_7P642CN.d.ts.map → statistics-B5d0Zd-z.d.ts.map} +1 -1
- package/dist/store-otlp-DX4fGIcf.js +757 -0
- package/dist/store-otlp-DX4fGIcf.js.map +1 -0
- package/dist/{summary-report-DHipz9Kx.d.ts → summary-report-Cg7BifAM.d.ts} +3 -3
- package/dist/{summary-report-DHipz9Kx.d.ts.map → summary-report-Cg7BifAM.d.ts.map} +1 -1
- package/dist/tool-groups-CdYq22lX.d.ts +258 -0
- package/dist/tool-groups-CdYq22lX.d.ts.map +1 -0
- package/dist/traces.d.ts +7 -6
- package/dist/traces.js +5 -4
- package/dist/{types-CTGbIm57.d.ts → types-BBFNHxSK.d.ts} +5 -5
- package/dist/{types-CTGbIm57.d.ts.map → types-BBFNHxSK.d.ts.map} +1 -1
- package/dist/{types-CKswbJGO.d.ts → types-DoEYskCd.d.ts} +5 -5
- package/dist/{types-CKswbJGO.d.ts.map → types-DoEYskCd.d.ts.map} +1 -1
- package/dist/{types-CTvKfr5F.d.ts → types-uPrS6mD-.d.ts} +2 -2
- package/dist/{types-CTvKfr5F.d.ts.map → types-uPrS6mD-.d.ts.map} +1 -1
- package/dist/usage-receipt-CgxMEBZq.js +134 -0
- package/dist/usage-receipt-CgxMEBZq.js.map +1 -0
- package/dist/wire/index.d.ts +3 -3
- package/dist/wire/index.js +1 -1
- package/docs/trace-analysis.md +191 -385
- package/package.json +5 -4
- package/dist/analyze-runs-PVtnfjvA.d.ts +0 -72
- package/dist/analyze-runs-PVtnfjvA.d.ts.map +0 -1
- package/dist/benchmark-CHX4orG7.d.ts.map +0 -1
- package/dist/benchmark-YDrpumqB.js.map +0 -1
- package/dist/campaign-lgObcHFC.js.map +0 -1
- package/dist/completion-verifier-DSyRNVzU.d.ts.map +0 -1
- package/dist/concurrency-MUjT7VjM.js +0 -109
- package/dist/concurrency-MUjT7VjM.js.map +0 -1
- package/dist/cost-ledger-D-5_-dhi.js.map +0 -1
- package/dist/cost-ledger-D2o6JOrL.d.ts.map +0 -1
- package/dist/default-registry-CLXbRt0f.js +0 -2594
- package/dist/default-registry-CLXbRt0f.js.map +0 -1
- package/dist/default-registry-Dc5D_Loc.d.ts +0 -202
- package/dist/default-registry-Dc5D_Loc.d.ts.map +0 -1
- package/dist/feedback-trajectory-N_F0PwHz.d.ts.map +0 -1
- package/dist/index-C-Pr4OWg.d.ts.map +0 -1
- package/dist/ledger-core-t6sItivm.js.map +0 -1
- package/dist/llm-client-DKB25jV8.js.map +0 -1
- package/dist/proposal-findings-DCawte-y.js.map +0 -1
- package/dist/registry-BdM7SuTr.d.ts +0 -124
- package/dist/registry-BdM7SuTr.d.ts.map +0 -1
- package/dist/replay-Bju0T8Ls.js.map +0 -1
- package/dist/replay-K8FaC0CB.d.ts.map +0 -1
- package/dist/semantic-concept-judge-Bz64IckK.js.map +0 -1
- package/dist/skill-usage-CFDLLlhF.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-BpbnlvAZ.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-f4o9sUT4.js.map +0 -1
- package/dist/tools-DZk2Jn64.js +0 -1876
- package/dist/tools-DZk2Jn64.js.map +0 -1
|
@@ -1,24 +1,9 @@
|
|
|
1
|
+
import { t as assertValidAnalystUsageReceipt } from "./usage-receipt-CgxMEBZq.js";
|
|
1
2
|
import { performance } from "node:perf_hooks";
|
|
2
3
|
import { linearSumAssignment } from "linear-sum-assignment";
|
|
3
|
-
//#region src/analyst/benchmark.ts
|
|
4
|
-
/**
|
|
5
|
-
* Resolve canonical `trace://<trace>/span/<span>` evidence against a trace store.
|
|
6
|
-
* Other evidence kinds and URI schemes require a caller-supplied resolver.
|
|
7
|
-
*/
|
|
8
|
-
function traceStoreEvidenceResolver(getStore) {
|
|
9
|
-
return async ({ caseInput, evidence }) => {
|
|
10
|
-
if (evidence.kind !== "span") return false;
|
|
11
|
-
const location = parseTraceSpanUri(evidence.uri);
|
|
12
|
-
if (!location) return false;
|
|
13
|
-
const result = await getStore(caseInput).viewSpans({
|
|
14
|
-
trace_id: location.traceId,
|
|
15
|
-
span_ids: [location.spanId]
|
|
16
|
-
});
|
|
17
|
-
return result.trace_id === location.traceId && result.missing_span_ids.length === 0 && result.spans.some((span) => span.span_id === location.spanId);
|
|
18
|
-
};
|
|
19
|
-
}
|
|
4
|
+
//#region src/analyst/benchmark-scoring.ts
|
|
20
5
|
function scoreAnalystFindings(testCase, findings) {
|
|
21
|
-
|
|
6
|
+
assertValidAnalystScoringCase(testCase);
|
|
22
7
|
const matchedFindingByIssue = matchFindingsToIssues(testCase.expectedIssues, findings);
|
|
23
8
|
const matchedIssueIds = testCase.expectedIssues.filter((_, index) => matchedFindingByIssue.has(index)).map((issue) => issue.id);
|
|
24
9
|
const missedIssueIds = testCase.expectedIssues.filter((_, index) => !matchedFindingByIssue.has(index)).map((issue) => issue.id);
|
|
@@ -27,7 +12,7 @@ function scoreAnalystFindings(testCase, findings) {
|
|
|
27
12
|
const expectedIssueCount = testCase.expectedIssues.length;
|
|
28
13
|
const issueRecall = expectedIssueCount === 0 ? 1 : matchedIssueIds.length / expectedIssueCount;
|
|
29
14
|
const findingPrecision = findings.length === 0 ? expectedIssueCount === 0 ? 1 : 0 : supportedFindingIndexes.size / findings.length;
|
|
30
|
-
const f1 =
|
|
15
|
+
const f1 = harmonicMeanScore(findingPrecision, issueRecall);
|
|
31
16
|
const allEvidence = findings.flatMap((finding) => finding.evidence_refs);
|
|
32
17
|
const criticalIssues = testCase.expectedIssues.filter((issue) => (issue.criticalEvidence?.length ?? 0) > 0);
|
|
33
18
|
const criticalHits = testCase.expectedIssues.filter((issue) => {
|
|
@@ -48,10 +33,25 @@ function scoreAnalystFindings(testCase, findings) {
|
|
|
48
33
|
f1,
|
|
49
34
|
criticalStepAccuracy: criticalIssues.length === 0 ? null : criticalHits / criticalIssues.length,
|
|
50
35
|
citationCoverage: findings.length === 0 ? null : findingsWithEvidence / findings.length,
|
|
36
|
+
citationExcerptCoverage: allEvidence.length === 0 ? null : allEvidence.filter((evidence) => Boolean(evidence.excerpt?.trim())).length / allEvidence.length,
|
|
51
37
|
citationLabelAgreement: testCase.labeledEvidence === void 0 ? null : allEvidence.length === 0 ? findings.length === 0 ? null : 0 : (allEvidence.length - unlabeledEvidence.length) / allEvidence.length,
|
|
52
|
-
|
|
38
|
+
predictionOnLabelEmptyCase: expectedIssueCount === 0 && findings.length > 0
|
|
53
39
|
};
|
|
54
40
|
}
|
|
41
|
+
function assertValidAnalystScoringCase(testCase) {
|
|
42
|
+
if (!testCase.id.trim()) throw new TypeError("analyst benchmark case id must not be empty");
|
|
43
|
+
const ids = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const issue of testCase.expectedIssues) {
|
|
45
|
+
if (!issue.id.trim()) throw new TypeError(`${testCase.id}: expected issue id must not be empty`);
|
|
46
|
+
if (ids.has(issue.id)) throw new TypeError(`${testCase.id}: duplicate expected issue id '${issue.id}'`);
|
|
47
|
+
ids.add(issue.id);
|
|
48
|
+
if (!issue.findingIds?.length && !issue.areas?.length && !issue.subjects?.length && !issue.evidence?.length) throw new TypeError(`${testCase.id}/${issue.id}: expected issue must identify a finding by id, area, subject, or evidence`);
|
|
49
|
+
}
|
|
50
|
+
for (const ref of testCase.labeledEvidence ?? []) if (!ref.uri.trim()) throw new TypeError(`${testCase.id}: labeled evidence URI must not be empty`);
|
|
51
|
+
}
|
|
52
|
+
function harmonicMeanScore(a, b) {
|
|
53
|
+
return a + b === 0 ? 0 : 2 * a * b / (a + b);
|
|
54
|
+
}
|
|
55
55
|
function matchFindingsToIssues(issues, findings) {
|
|
56
56
|
if (issues.length === 0) return /* @__PURE__ */ new Map();
|
|
57
57
|
const cardinalityWeight = issues.length + 1;
|
|
@@ -68,23 +68,218 @@ function matchFindingsToIssues(issues, findings) {
|
|
|
68
68
|
}
|
|
69
69
|
return matches;
|
|
70
70
|
}
|
|
71
|
+
function findingMatchesIssue(finding, issue) {
|
|
72
|
+
if (issue.findingIds && !issue.findingIds.includes(finding.finding_id)) return false;
|
|
73
|
+
if (issue.areas && !issue.areas.includes(finding.area)) return false;
|
|
74
|
+
if (issue.subjects && (!finding.subject || !issue.subjects.includes(finding.subject))) return false;
|
|
75
|
+
if (issue.evidence && !matchesEvidence(finding.evidence_refs, issue.evidence, issue.evidenceMode ?? "any")) return false;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
function matchesEvidence(actual, expected, mode) {
|
|
79
|
+
if (expected.length === 0) return true;
|
|
80
|
+
const match = (target) => actual.some((ref) => evidenceMatches(ref, target));
|
|
81
|
+
return mode === "all" ? expected.every(match) : expected.some(match);
|
|
82
|
+
}
|
|
83
|
+
function evidenceMatches(actual, expected) {
|
|
84
|
+
return actual.uri === expected.uri && (expected.kind === void 0 || actual.kind === expected.kind);
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/analyst/benchmark-summary.ts
|
|
88
|
+
function summarizeAnalystBenchmarkRunner(runnerId, observations) {
|
|
89
|
+
const issueBearing = observations.filter((observation) => observation.labelState === "positive");
|
|
90
|
+
const completed = observations.filter((observation) => !observation.error);
|
|
91
|
+
const expectedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.expectedIssueCount, 0);
|
|
92
|
+
const matchedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.matchedIssueIds.length, 0);
|
|
93
|
+
const issueFindings = issueBearing.reduce((sum, observation) => sum + (observation.error ? 0 : observation.findings.length), 0);
|
|
94
|
+
const supportedFindings = issueBearing.reduce((sum, observation) => sum + observation.score.supportedFindingIndexes.length, 0);
|
|
95
|
+
const issueRecall = expectedIssues === 0 ? null : matchedIssues / expectedIssues;
|
|
96
|
+
const findingPrecision = expectedIssues === 0 ? null : issueFindings === 0 ? 0 : supportedFindings / issueFindings;
|
|
97
|
+
const macroIssueRecall = issueBearing.length === 0 ? null : mean(issueBearing.map((observation) => observation.score.issueRecall));
|
|
98
|
+
const macroFindingPrecision = issueBearing.length === 0 ? null : mean(issueBearing.map((observation) => observation.score.findingPrecision));
|
|
99
|
+
const macroF1 = issueBearing.length === 0 ? null : mean(issueBearing.map((observation) => observation.score.f1));
|
|
100
|
+
const critical = observations.map((observation) => observation.score.criticalStepAccuracy).filter((value) => value !== null);
|
|
101
|
+
const findingsWithEvidence = completed.reduce((sum, observation) => sum + observation.findings.filter((finding) => finding.evidence_refs.length > 0).length, 0);
|
|
102
|
+
const allFindings = completed.reduce((sum, observation) => sum + observation.findings.length, 0);
|
|
103
|
+
const allCitations = completed.flatMap((observation) => observation.findings.flatMap((finding) => finding.evidence_refs));
|
|
104
|
+
const citationObservations = completed.filter((observation) => observation.score.citationLabelAgreement !== null);
|
|
105
|
+
const citationCount = citationObservations.reduce((sum, observation) => sum + observation.findings.reduce((count, finding) => count + finding.evidence_refs.length, 0), 0);
|
|
106
|
+
const invalidCitationCount = citationObservations.reduce((sum, observation) => sum + observation.score.unlabeledEvidence.length, 0);
|
|
107
|
+
const trustedNegative = observations.filter((observation) => observation.labelState === "trusted-negative");
|
|
108
|
+
const completedTrustedNegative = trustedNegative.filter((observation) => !observation.error);
|
|
109
|
+
const unlabeled = observations.filter((observation) => observation.labelState === "unlabeled");
|
|
110
|
+
const completedUnlabeled = unlabeled.filter((observation) => !observation.error);
|
|
111
|
+
const resolvedCitations = completed.reduce((sum, observation) => sum + (observation.evidenceResolution?.resolved ?? 0), 0);
|
|
112
|
+
const unresolvedCitations = completed.reduce((sum, observation) => sum + (observation.evidenceResolution?.unresolvedEvidence.length ?? 0), 0);
|
|
113
|
+
const citationResolutionErrors = completed.reduce((sum, observation) => sum + (observation.evidenceResolution?.errors.length ?? 0), 0);
|
|
114
|
+
const resolutionAttempts = completed.filter((observation) => observation.findings.some((finding) => finding.evidence_refs.length > 0));
|
|
115
|
+
const citationResolutionUnknownRuns = resolutionAttempts.filter((observation) => !observation.evidenceResolution || observation.evidenceResolution.errors.length > 0).length;
|
|
116
|
+
const usages = observations.map((observation) => observation.usage);
|
|
117
|
+
const knownCostUsd = stableSum(usages.map((usage) => {
|
|
118
|
+
if (!usage) return 0;
|
|
119
|
+
return usage.cost.kind === "uncaptured" ? usage.knownCostUsd ?? 0 : usage.cost.usd;
|
|
120
|
+
}));
|
|
121
|
+
const predictionAgreement = repeatedObservationAgreement(observations, predictionSignature);
|
|
122
|
+
const matchedLabelAgreement = repeatedObservationAgreement(observations.filter((observation) => observation.labelState === "positive"), matchedLabelSignature);
|
|
123
|
+
return {
|
|
124
|
+
runnerId,
|
|
125
|
+
plannedRuns: observations.length,
|
|
126
|
+
completedRuns: observations.filter((observation) => !observation.error).length,
|
|
127
|
+
failedRuns: observations.filter((observation) => Boolean(observation.error)).length,
|
|
128
|
+
issueBearingRuns: issueBearing.length,
|
|
129
|
+
trustedNegativeRuns: trustedNegative.length,
|
|
130
|
+
unlabeledRuns: unlabeled.length,
|
|
131
|
+
issueRecall,
|
|
132
|
+
findingPrecision,
|
|
133
|
+
f1: findingPrecision === null || issueRecall === null ? null : harmonicMeanScore(findingPrecision, issueRecall),
|
|
134
|
+
macroIssueRecall,
|
|
135
|
+
macroFindingPrecision,
|
|
136
|
+
macroF1,
|
|
137
|
+
criticalStepAccuracy: critical.length === 0 ? null : mean(critical),
|
|
138
|
+
citationCoverage: allFindings === 0 ? null : findingsWithEvidence / allFindings,
|
|
139
|
+
citationExcerptCoverage: allCitations.length === 0 ? null : allCitations.filter((evidence) => Boolean(evidence.excerpt?.trim())).length / allCitations.length,
|
|
140
|
+
citationLabelAgreement: citationObservations.length === 0 ? null : citationCount === 0 ? 0 : (citationCount - invalidCitationCount) / citationCount,
|
|
141
|
+
citationResolution: resolutionAttempts.length === 0 || citationResolutionUnknownRuns > 0 || resolvedCitations + unresolvedCitations === 0 ? null : resolvedCitations / (resolvedCitations + unresolvedCitations),
|
|
142
|
+
citationResolutionUnknownRuns,
|
|
143
|
+
unresolvedCitations,
|
|
144
|
+
citationResolutionErrors,
|
|
145
|
+
trustedNegativeFalsePositiveRate: completedTrustedNegative.length === 0 ? null : completedTrustedNegative.filter((observation) => observation.score.predictionOnLabelEmptyCase).length / completedTrustedNegative.length,
|
|
146
|
+
trustedNegativeFailureRate: trustedNegative.length === 0 ? null : trustedNegative.filter((observation) => Boolean(observation.error)).length / trustedNegative.length,
|
|
147
|
+
unlabeledPredictionRate: completedUnlabeled.length === 0 ? null : completedUnlabeled.filter((observation) => observation.findings.length > 0).length / completedUnlabeled.length,
|
|
148
|
+
unlabeledFailureRate: unlabeled.length === 0 ? null : unlabeled.filter((observation) => Boolean(observation.error)).length / unlabeled.length,
|
|
149
|
+
predictionAgreement: predictionAgreement.value,
|
|
150
|
+
predictionAgreementCases: predictionAgreement.cases,
|
|
151
|
+
matchedLabelAgreement: matchedLabelAgreement.value,
|
|
152
|
+
matchedLabelAgreementCases: matchedLabelAgreement.cases,
|
|
153
|
+
latencyMs: latencyDistribution(observations.map((observation) => observation.latencyMs).filter((value) => value !== null)),
|
|
154
|
+
benchmarkClockLatencyRuns: observations.filter((observation) => observation.latencySource === "benchmark-clock").length,
|
|
155
|
+
runnerReportedLatencyRuns: observations.filter((observation) => observation.latencySource === "runner-reported").length,
|
|
156
|
+
latencyUnknownRuns: observations.filter((observation) => observation.latencySource === "uncaptured").length,
|
|
157
|
+
calls: usages.reduce((sum, usage) => sum + (usage?.calls ?? 0), 0),
|
|
158
|
+
callsUnknownRuns: usages.filter((usage) => !usage || usage.calls === null).length,
|
|
159
|
+
inputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.input ?? 0), 0),
|
|
160
|
+
outputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.output ?? 0), 0),
|
|
161
|
+
reasoningTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.reasoning ?? 0), 0),
|
|
162
|
+
cachedTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cached ?? 0), 0),
|
|
163
|
+
cacheWriteTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cacheWrite ?? 0), 0),
|
|
164
|
+
tokenUsageUnknownRuns: usages.filter((usage) => !usage?.tokens).length,
|
|
165
|
+
reasoningTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.reasoning === void 0).length,
|
|
166
|
+
cachedTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cached === void 0).length,
|
|
167
|
+
cacheWriteTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cacheWrite === void 0).length,
|
|
168
|
+
knownCostUsd,
|
|
169
|
+
costUnknownRuns: usages.filter((usage) => !usage || usage.cost.kind === "uncaptured").length
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function mean(values) {
|
|
173
|
+
return values.length === 0 ? 0 : stableSum(values) / values.length;
|
|
174
|
+
}
|
|
175
|
+
function stableSum(values) {
|
|
176
|
+
const ordered = [...values].sort((left, right) => Math.abs(left) - Math.abs(right) || left - right);
|
|
177
|
+
let sum = 0;
|
|
178
|
+
let correction = 0;
|
|
179
|
+
for (const value of ordered) {
|
|
180
|
+
const next = sum + value;
|
|
181
|
+
correction += Math.abs(sum) >= Math.abs(value) ? sum - next + value : value - next + sum;
|
|
182
|
+
sum = next;
|
|
183
|
+
}
|
|
184
|
+
return sum + correction;
|
|
185
|
+
}
|
|
186
|
+
function latencyDistribution(values) {
|
|
187
|
+
if (values.length === 0) return null;
|
|
188
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
189
|
+
return {
|
|
190
|
+
min: sorted[0],
|
|
191
|
+
mean: mean(sorted),
|
|
192
|
+
p50: percentile(sorted, .5),
|
|
193
|
+
p95: percentile(sorted, .95),
|
|
194
|
+
max: sorted.at(-1)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function percentile(sorted, quantile) {
|
|
198
|
+
if (sorted.length === 0) return 0;
|
|
199
|
+
return sorted[Math.ceil(quantile * sorted.length) - 1] ?? sorted.at(-1) ?? 0;
|
|
200
|
+
}
|
|
201
|
+
function repeatedObservationAgreement(observations, signature) {
|
|
202
|
+
const byCase = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const observation of observations) {
|
|
204
|
+
const rows = byCase.get(observation.caseId) ?? [];
|
|
205
|
+
rows.push(observation);
|
|
206
|
+
byCase.set(observation.caseId, rows);
|
|
207
|
+
}
|
|
208
|
+
const caseAgreements = [];
|
|
209
|
+
for (const rows of byCase.values()) {
|
|
210
|
+
const agreements = [];
|
|
211
|
+
for (let left = 0; left < rows.length; left++) for (let right = left + 1; right < rows.length; right++) agreements.push(jaccard(signature(rows[left]), signature(rows[right])));
|
|
212
|
+
if (agreements.length > 0) caseAgreements.push(mean(agreements));
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
value: caseAgreements.length === 0 ? null : mean(caseAgreements),
|
|
216
|
+
cases: caseAgreements.length
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function matchedLabelSignature(observation) {
|
|
220
|
+
if (observation.error) return [`error:${observation.error.class}`];
|
|
221
|
+
return observation.score.matchedIssueIds;
|
|
222
|
+
}
|
|
223
|
+
function predictionSignature(observation) {
|
|
224
|
+
if (observation.error) return [`error:${observation.error.class}`];
|
|
225
|
+
return observation.findings.map((finding) => JSON.stringify([finding.finding_id, finding.evidence_refs.map((evidence) => [
|
|
226
|
+
evidence.kind,
|
|
227
|
+
evidence.uri,
|
|
228
|
+
evidence.excerpt ?? null
|
|
229
|
+
]).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))])).sort();
|
|
230
|
+
}
|
|
231
|
+
function jaccard(left, right) {
|
|
232
|
+
const a = new Set(left);
|
|
233
|
+
const b = new Set(right);
|
|
234
|
+
const union = /* @__PURE__ */ new Set([...a, ...b]);
|
|
235
|
+
if (union.size === 0) return 1;
|
|
236
|
+
let intersection = 0;
|
|
237
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
238
|
+
return intersection / union.size;
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/analyst/benchmark.ts
|
|
242
|
+
/**
|
|
243
|
+
* Resolve canonical `trace://<trace>/span/<span>` evidence against a trace store.
|
|
244
|
+
* Other evidence kinds and URI schemes require a caller-supplied resolver.
|
|
245
|
+
*/
|
|
246
|
+
function traceStoreEvidenceResolver(getStore) {
|
|
247
|
+
return async ({ caseInput, evidence, signal }) => {
|
|
248
|
+
if (evidence.kind !== "span") return false;
|
|
249
|
+
const location = parseTraceSpanUri(evidence.uri);
|
|
250
|
+
if (!location) return false;
|
|
251
|
+
const result = await getStore(caseInput).viewSpans({
|
|
252
|
+
trace_id: location.traceId,
|
|
253
|
+
span_ids: [location.spanId]
|
|
254
|
+
}, signal ? { signal } : void 0);
|
|
255
|
+
return result.trace_id === location.traceId && result.missing_span_ids.length === 0 && result.spans.some((span) => span.span_id === location.spanId);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
71
258
|
async function runAnalystBenchmark(options) {
|
|
72
259
|
validateBenchmarkOptions(options);
|
|
73
260
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
74
261
|
const repetitions = options.repetitions ?? 1;
|
|
75
262
|
const runnerOrderSeed = options.runnerOrderSeed ?? 0;
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
263
|
+
const allJobs = benchmarkJobs(options.cases, options.runners, repetitions, runnerOrderSeed);
|
|
264
|
+
const maxConcurrency = Math.min(options.maxConcurrency ?? 1, allJobs.length);
|
|
265
|
+
const initialObservations = validateInitialObservations(options.initialObservations ?? [], allJobs);
|
|
266
|
+
const completed = new Set(initialObservations.map(observationKey));
|
|
267
|
+
const jobs = allJobs.filter((job) => !completed.has(jobKey(job)));
|
|
268
|
+
const observations = [...initialObservations];
|
|
79
269
|
let cursor = 0;
|
|
80
270
|
const worker = async () => {
|
|
81
271
|
while (cursor < jobs.length) {
|
|
82
272
|
options.signal?.throwIfAborted();
|
|
83
273
|
const job = jobs[cursor++];
|
|
84
|
-
|
|
274
|
+
const observation = await runBenchmarkJob(job, options.signal, options.resolveEvidence);
|
|
275
|
+
options.signal?.throwIfAborted();
|
|
276
|
+
await options.onObservation?.(observation);
|
|
277
|
+
options.signal?.throwIfAborted();
|
|
278
|
+
observations.push(observation);
|
|
85
279
|
}
|
|
86
280
|
};
|
|
87
|
-
await Promise.all(Array.from({ length: maxConcurrency }, worker));
|
|
281
|
+
await Promise.all(Array.from({ length: Math.min(maxConcurrency, jobs.length) }, worker));
|
|
282
|
+
options.signal?.throwIfAborted();
|
|
88
283
|
const runnerOrder = new Map(options.runners.map((runner, index) => [runner.id, index]));
|
|
89
284
|
const caseOrder = new Map(options.cases.map((testCase, index) => [testCase.id, index]));
|
|
90
285
|
observations.sort((a, b) => (runnerOrder.get(a.runnerId) ?? 0) - (runnerOrder.get(b.runnerId) ?? 0) || (caseOrder.get(a.caseId) ?? 0) - (caseOrder.get(b.caseId) ?? 0) || a.repetition - b.repetition);
|
|
@@ -100,7 +295,7 @@ async function runAnalystBenchmark(options) {
|
|
|
100
295
|
runnerOrderSeed
|
|
101
296
|
},
|
|
102
297
|
observations,
|
|
103
|
-
summaries: options.runners.map((runner) =>
|
|
298
|
+
summaries: options.runners.map((runner) => summarizeAnalystBenchmarkRunner(runner.id, observations.filter((observation) => observation.runnerId === runner.id)))
|
|
104
299
|
};
|
|
105
300
|
}
|
|
106
301
|
function registryBenchmarkRunner(options) {
|
|
@@ -114,7 +309,8 @@ function registryBenchmarkRunner(options) {
|
|
|
114
309
|
return {
|
|
115
310
|
findings: result.findings,
|
|
116
311
|
usage: mergeRegistryUsage(result),
|
|
117
|
-
metadata: { analystRun: result }
|
|
312
|
+
metadata: { analystRun: result },
|
|
313
|
+
...options.failOnAnalystFailure ? { error: registryRunFailure(result) } : {}
|
|
118
314
|
};
|
|
119
315
|
}
|
|
120
316
|
};
|
|
@@ -127,19 +323,27 @@ async function runBenchmarkJob(job, signal, resolveEvidence) {
|
|
|
127
323
|
repetition: job.repetition,
|
|
128
324
|
signal
|
|
129
325
|
});
|
|
326
|
+
if (output.usage) assertValidAnalystUsageReceipt(output.usage, "analyst benchmark usage");
|
|
327
|
+
const benchmarkLatencyMs = performance.now() - started;
|
|
328
|
+
const latency = resolveBenchmarkLatency(output.observedLatencyMs, benchmarkLatencyMs);
|
|
329
|
+
const scoredFindings = output.error ? [] : output.findings;
|
|
130
330
|
return {
|
|
131
331
|
runnerId: job.runner.id,
|
|
132
332
|
caseId: job.testCase.id,
|
|
333
|
+
clusterId: job.testCase.clusterId,
|
|
334
|
+
labelState: job.testCase.labelState,
|
|
133
335
|
repetition: job.repetition,
|
|
134
336
|
executionIndex: job.executionIndex,
|
|
135
|
-
latencyMs:
|
|
337
|
+
latencyMs: latency.value,
|
|
338
|
+
latencySource: latency.source,
|
|
136
339
|
findings: output.findings,
|
|
137
|
-
score: scoreAnalystFindings(job.testCase,
|
|
340
|
+
score: scoreAnalystFindings(job.testCase, scoredFindings),
|
|
138
341
|
evidenceResolution: resolveEvidence ? await resolveFindingEvidence(job.testCase, output.findings, resolveEvidence, signal) : void 0,
|
|
139
342
|
caseTags: [...job.testCase.tags ?? []],
|
|
140
343
|
caseMetadata: job.testCase.metadata,
|
|
141
344
|
usage: output.usage,
|
|
142
|
-
runnerMetadata: output.metadata
|
|
345
|
+
runnerMetadata: output.metadata,
|
|
346
|
+
...output.error ? { error: output.error } : {}
|
|
143
347
|
};
|
|
144
348
|
} catch (error) {
|
|
145
349
|
if (signal?.aborted) throw error;
|
|
@@ -147,9 +351,12 @@ async function runBenchmarkJob(job, signal, resolveEvidence) {
|
|
|
147
351
|
return {
|
|
148
352
|
runnerId: job.runner.id,
|
|
149
353
|
caseId: job.testCase.id,
|
|
354
|
+
clusterId: job.testCase.clusterId,
|
|
355
|
+
labelState: job.testCase.labelState,
|
|
150
356
|
repetition: job.repetition,
|
|
151
357
|
executionIndex: job.executionIndex,
|
|
152
358
|
latencyMs: performance.now() - started,
|
|
359
|
+
latencySource: "benchmark-clock",
|
|
153
360
|
findings,
|
|
154
361
|
score: scoreAnalystFindings(job.testCase, findings),
|
|
155
362
|
caseTags: [...job.testCase.tags ?? []],
|
|
@@ -161,6 +368,29 @@ async function runBenchmarkJob(job, signal, resolveEvidence) {
|
|
|
161
368
|
};
|
|
162
369
|
}
|
|
163
370
|
}
|
|
371
|
+
function resolveBenchmarkLatency(observedLatencyMs, fallbackMs) {
|
|
372
|
+
if (observedLatencyMs === void 0) return {
|
|
373
|
+
value: fallbackMs,
|
|
374
|
+
source: "benchmark-clock"
|
|
375
|
+
};
|
|
376
|
+
if (observedLatencyMs === null) return {
|
|
377
|
+
value: null,
|
|
378
|
+
source: "uncaptured"
|
|
379
|
+
};
|
|
380
|
+
if (!Number.isFinite(observedLatencyMs) || observedLatencyMs < 0) throw new RangeError("analyst benchmark observedLatencyMs must be finite and non-negative");
|
|
381
|
+
return {
|
|
382
|
+
value: observedLatencyMs,
|
|
383
|
+
source: "runner-reported"
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function registryRunFailure(result) {
|
|
387
|
+
const failed = result.per_analyst.filter((summary) => summary.status === "failed");
|
|
388
|
+
if (failed.length === 0) return void 0;
|
|
389
|
+
return {
|
|
390
|
+
class: "AnalystRunFailure",
|
|
391
|
+
message: failed.map((summary) => `${summary.analyst_id}: ${summary.error?.class ?? "Error"}: ${summary.error?.message ?? "analyst failed"}`).join("; ")
|
|
392
|
+
};
|
|
393
|
+
}
|
|
164
394
|
function benchmarkJobs(cases, runners, repetitions, runnerOrderSeed) {
|
|
165
395
|
const seededRunners = [...runners].sort((left, right) => stableHash(`${runnerOrderSeed}\u0000${left.id}`) - stableHash(`${runnerOrderSeed}\u0000${right.id}`) || left.id.localeCompare(right.id));
|
|
166
396
|
let executionIndex = 0;
|
|
@@ -174,6 +404,35 @@ function benchmarkJobs(cases, runners, repetitions, runnerOrderSeed) {
|
|
|
174
404
|
}));
|
|
175
405
|
}).flat());
|
|
176
406
|
}
|
|
407
|
+
function validateInitialObservations(observations, jobs) {
|
|
408
|
+
const expected = new Map(jobs.map((job) => [jobKey(job), job]));
|
|
409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
410
|
+
return observations.map((observation) => {
|
|
411
|
+
const key = observationKey(observation);
|
|
412
|
+
if (seen.has(key)) throw new TypeError(`duplicate initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}'`);
|
|
413
|
+
seen.add(key);
|
|
414
|
+
const job = expected.get(key);
|
|
415
|
+
if (!job) throw new TypeError(`initial analyst benchmark observation does not match a planned job: '${observation.runnerId}/${observation.caseId}/${observation.repetition}'`);
|
|
416
|
+
if (observation.executionIndex !== job.executionIndex) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' has executionIndex ${observation.executionIndex}; expected ${job.executionIndex}`);
|
|
417
|
+
if (observation.clusterId !== job.testCase.clusterId || observation.labelState !== job.testCase.labelState) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' does not match the current case labels`);
|
|
418
|
+
if (JSON.stringify(observation.caseTags) !== JSON.stringify(job.testCase.tags ?? []) || JSON.stringify(observation.caseMetadata) !== JSON.stringify(job.testCase.metadata)) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' does not match the current case metadata`);
|
|
419
|
+
const expectedScore = scoreAnalystFindings(job.testCase, observation.error ? [] : observation.findings);
|
|
420
|
+
if (JSON.stringify(observation.score) !== JSON.stringify(expectedScore)) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' has stale or invalid scores`);
|
|
421
|
+
if (observation.usage) assertValidAnalystUsageReceipt(observation.usage, `initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' usage`);
|
|
422
|
+
if (![
|
|
423
|
+
"benchmark-clock",
|
|
424
|
+
"runner-reported",
|
|
425
|
+
"uncaptured"
|
|
426
|
+
].includes(observation.latencySource) || observation.latencySource === "uncaptured" && observation.latencyMs !== null || observation.latencySource !== "uncaptured" && (observation.latencyMs === null || !Number.isFinite(observation.latencyMs) || observation.latencyMs < 0)) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' has invalid latency`);
|
|
427
|
+
return { ...observation };
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function jobKey(job) {
|
|
431
|
+
return `${job.runner.id}\u0000${job.testCase.id}\u0000${job.repetition}`;
|
|
432
|
+
}
|
|
433
|
+
function observationKey(observation) {
|
|
434
|
+
return `${observation.runnerId}\u0000${observation.caseId}\u0000${observation.repetition}`;
|
|
435
|
+
}
|
|
177
436
|
async function resolveFindingEvidence(testCase, findings, resolver, signal) {
|
|
178
437
|
const evidence = findings.flatMap((finding) => finding.evidence_refs);
|
|
179
438
|
const resolved = [];
|
|
@@ -206,81 +465,6 @@ async function resolveFindingEvidence(testCase, findings, resolver, signal) {
|
|
|
206
465
|
validity: evidence.length === 0 || errors.length > 0 ? null : resolved.length / evidence.length
|
|
207
466
|
};
|
|
208
467
|
}
|
|
209
|
-
function summarizeRunner(runnerId, observations) {
|
|
210
|
-
const issueBearing = observations.filter((observation) => observation.score.expectedIssueCount > 0);
|
|
211
|
-
const expectedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.expectedIssueCount, 0);
|
|
212
|
-
const matchedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.matchedIssueIds.length, 0);
|
|
213
|
-
const issueFindings = issueBearing.reduce((sum, observation) => sum + observation.findings.length, 0);
|
|
214
|
-
const supportedFindings = issueBearing.reduce((sum, observation) => sum + observation.score.supportedFindingIndexes.length, 0);
|
|
215
|
-
const issueRecall = expectedIssues === 0 ? null : matchedIssues / expectedIssues;
|
|
216
|
-
const findingPrecision = expectedIssues === 0 ? null : issueFindings === 0 ? 0 : supportedFindings / issueFindings;
|
|
217
|
-
const critical = observations.map((observation) => observation.score.criticalStepAccuracy).filter((value) => value !== null);
|
|
218
|
-
const findingsWithEvidence = observations.reduce((sum, observation) => sum + observation.findings.filter((finding) => finding.evidence_refs.length > 0).length, 0);
|
|
219
|
-
const allFindings = observations.reduce((sum, observation) => sum + observation.findings.length, 0);
|
|
220
|
-
const citationObservations = observations.filter((observation) => observation.score.citationLabelAgreement !== null);
|
|
221
|
-
const citationCount = citationObservations.reduce((sum, observation) => sum + observation.findings.reduce((count, finding) => count + finding.evidence_refs.length, 0), 0);
|
|
222
|
-
const invalidCitationCount = citationObservations.reduce((sum, observation) => sum + observation.score.unlabeledEvidence.length, 0);
|
|
223
|
-
const clean = observations.filter((observation) => observation.score.expectedIssueCount === 0);
|
|
224
|
-
const completedClean = clean.filter((observation) => !observation.error);
|
|
225
|
-
const resolvedCitations = observations.reduce((sum, observation) => sum + (observation.evidenceResolution?.resolved ?? 0), 0);
|
|
226
|
-
const unresolvedCitations = observations.reduce((sum, observation) => sum + (observation.evidenceResolution?.unresolvedEvidence.length ?? 0), 0);
|
|
227
|
-
const citationResolutionErrors = observations.reduce((sum, observation) => sum + (observation.evidenceResolution?.errors.length ?? 0), 0);
|
|
228
|
-
const resolutionAttempts = observations.filter((observation) => observation.findings.some((finding) => finding.evidence_refs.length > 0));
|
|
229
|
-
const citationResolutionUnknownRuns = resolutionAttempts.filter((observation) => !observation.evidenceResolution || observation.evidenceResolution.errors.length > 0).length;
|
|
230
|
-
const usages = observations.map((observation) => observation.usage);
|
|
231
|
-
const knownCostUsd = usages.reduce((sum, usage) => {
|
|
232
|
-
if (!usage) return sum;
|
|
233
|
-
return sum + (usage.cost.kind === "uncaptured" ? usage.knownCostUsd ?? 0 : usage.cost.usd);
|
|
234
|
-
}, 0);
|
|
235
|
-
return {
|
|
236
|
-
runnerId,
|
|
237
|
-
plannedRuns: observations.length,
|
|
238
|
-
completedRuns: observations.filter((observation) => !observation.error).length,
|
|
239
|
-
failedRuns: observations.filter((observation) => Boolean(observation.error)).length,
|
|
240
|
-
issueRecall,
|
|
241
|
-
findingPrecision,
|
|
242
|
-
f1: findingPrecision === null || issueRecall === null ? null : harmonicMean(findingPrecision, issueRecall),
|
|
243
|
-
criticalStepAccuracy: critical.length === 0 ? null : mean(critical),
|
|
244
|
-
citationCoverage: allFindings === 0 ? null : findingsWithEvidence / allFindings,
|
|
245
|
-
citationLabelAgreement: citationObservations.length === 0 ? null : citationCount === 0 ? 0 : (citationCount - invalidCitationCount) / citationCount,
|
|
246
|
-
citationResolution: resolutionAttempts.length === 0 || citationResolutionUnknownRuns > 0 || resolvedCitations + unresolvedCitations === 0 ? null : resolvedCitations / (resolvedCitations + unresolvedCitations),
|
|
247
|
-
citationResolutionUnknownRuns,
|
|
248
|
-
unresolvedCitations,
|
|
249
|
-
citationResolutionErrors,
|
|
250
|
-
cleanCaseFalsePositiveRate: completedClean.length === 0 ? null : completedClean.filter((observation) => observation.score.cleanFalsePositive).length / completedClean.length,
|
|
251
|
-
cleanCaseFailureRate: clean.length === 0 ? null : clean.filter((observation) => Boolean(observation.error)).length / clean.length,
|
|
252
|
-
runAgreement: issueAgreement(observations.filter((observation) => !observation.error)),
|
|
253
|
-
latencyMs: latencyDistribution(observations.map((observation) => observation.latencyMs)),
|
|
254
|
-
calls: usages.reduce((sum, usage) => sum + (usage?.calls ?? 0), 0),
|
|
255
|
-
callsUnknownRuns: usages.filter((usage) => !usage || usage.calls === null).length,
|
|
256
|
-
inputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.input ?? 0), 0),
|
|
257
|
-
outputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.output ?? 0), 0),
|
|
258
|
-
reasoningTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.reasoning ?? 0), 0),
|
|
259
|
-
cachedTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cached ?? 0), 0),
|
|
260
|
-
cacheWriteTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cacheWrite ?? 0), 0),
|
|
261
|
-
tokenUsageUnknownRuns: usages.filter((usage) => !usage?.tokens).length,
|
|
262
|
-
reasoningTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.reasoning === void 0).length,
|
|
263
|
-
cachedTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cached === void 0).length,
|
|
264
|
-
cacheWriteTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cacheWrite === void 0).length,
|
|
265
|
-
knownCostUsd,
|
|
266
|
-
costUnknownRuns: usages.filter((usage) => !usage || usage.cost.kind === "uncaptured").length
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
function findingMatchesIssue(finding, issue) {
|
|
270
|
-
if (issue.findingIds && !issue.findingIds.includes(finding.finding_id)) return false;
|
|
271
|
-
if (issue.areas && !issue.areas.includes(finding.area)) return false;
|
|
272
|
-
if (issue.subjects && (!finding.subject || !issue.subjects.includes(finding.subject))) return false;
|
|
273
|
-
if (issue.evidence && !matchesEvidence(finding.evidence_refs, issue.evidence, issue.evidenceMode ?? "any")) return false;
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
function matchesEvidence(actual, expected, mode) {
|
|
277
|
-
if (expected.length === 0) return true;
|
|
278
|
-
const match = (target) => actual.some((ref) => evidenceMatches(ref, target));
|
|
279
|
-
return mode === "all" ? expected.every(match) : expected.some(match);
|
|
280
|
-
}
|
|
281
|
-
function evidenceMatches(actual, expected) {
|
|
282
|
-
return actual.uri === expected.uri && (expected.kind === void 0 || actual.kind === expected.kind);
|
|
283
|
-
}
|
|
284
468
|
function parseTraceSpanUri(uri) {
|
|
285
469
|
const match = /^trace:\/\/([^/]+)\/span\/([^/]+)$/.exec(uri);
|
|
286
470
|
if (!match) return null;
|
|
@@ -295,17 +479,6 @@ function parseTraceSpanUri(uri) {
|
|
|
295
479
|
return null;
|
|
296
480
|
}
|
|
297
481
|
}
|
|
298
|
-
function validateCase(testCase) {
|
|
299
|
-
if (!testCase.id.trim()) throw new TypeError("analyst benchmark case id must not be empty");
|
|
300
|
-
const ids = /* @__PURE__ */ new Set();
|
|
301
|
-
for (const issue of testCase.expectedIssues) {
|
|
302
|
-
if (!issue.id.trim()) throw new TypeError(`${testCase.id}: expected issue id must not be empty`);
|
|
303
|
-
if (ids.has(issue.id)) throw new TypeError(`${testCase.id}: duplicate expected issue id '${issue.id}'`);
|
|
304
|
-
ids.add(issue.id);
|
|
305
|
-
if (!issue.findingIds?.length && !issue.areas?.length && !issue.subjects?.length && !issue.evidence?.length) throw new TypeError(`${testCase.id}/${issue.id}: expected issue must identify a finding by id, area, subject, or evidence`);
|
|
306
|
-
}
|
|
307
|
-
for (const ref of testCase.labeledEvidence ?? []) if (!ref.uri.trim()) throw new TypeError(`${testCase.id}: labeled evidence URI must not be empty`);
|
|
308
|
-
}
|
|
309
482
|
function validateBenchmarkOptions(options) {
|
|
310
483
|
if (options.cases.length === 0) throw new TypeError("runAnalystBenchmark requires cases");
|
|
311
484
|
if (options.runners.length === 0) throw new TypeError("runAnalystBenchmark requires runners");
|
|
@@ -316,7 +489,14 @@ function validateBenchmarkOptions(options) {
|
|
|
316
489
|
if (!Number.isSafeInteger(options.runnerOrderSeed ?? 0)) throw new RangeError("runAnalystBenchmark runnerOrderSeed must be a safe integer");
|
|
317
490
|
assertUniqueNonEmpty(options.cases.map((testCase) => testCase.id), "case");
|
|
318
491
|
assertUniqueNonEmpty(options.runners.map((runner) => runner.id), "runner");
|
|
319
|
-
for (const testCase of options.cases)
|
|
492
|
+
for (const testCase of options.cases) validateBenchmarkCase(testCase);
|
|
493
|
+
}
|
|
494
|
+
function validateBenchmarkCase(testCase) {
|
|
495
|
+
assertValidAnalystScoringCase(testCase);
|
|
496
|
+
if (!testCase.clusterId.trim()) throw new TypeError(`${testCase.id}: analyst benchmark clusterId must not be empty`);
|
|
497
|
+
if (testCase.labelState !== "positive" && testCase.labelState !== "trusted-negative" && testCase.labelState !== "unlabeled") throw new TypeError(`${testCase.id}: analyst benchmark labelState is invalid`);
|
|
498
|
+
if (testCase.labelState === "positive" && testCase.expectedIssues.length === 0) throw new TypeError(`${testCase.id}: positive case requires at least one expected issue`);
|
|
499
|
+
if (testCase.labelState !== "positive" && testCase.expectedIssues.length > 0) throw new TypeError(`${testCase.id}: ${testCase.labelState} case cannot contain expected issues`);
|
|
320
500
|
}
|
|
321
501
|
function assertUniqueNonEmpty(values, label) {
|
|
322
502
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -326,26 +506,6 @@ function assertUniqueNonEmpty(values, label) {
|
|
|
326
506
|
seen.add(value);
|
|
327
507
|
}
|
|
328
508
|
}
|
|
329
|
-
function harmonicMean(a, b) {
|
|
330
|
-
return a + b === 0 ? 0 : 2 * a * b / (a + b);
|
|
331
|
-
}
|
|
332
|
-
function mean(values) {
|
|
333
|
-
return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
334
|
-
}
|
|
335
|
-
function latencyDistribution(values) {
|
|
336
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
337
|
-
return {
|
|
338
|
-
min: sorted[0] ?? 0,
|
|
339
|
-
mean: mean(sorted),
|
|
340
|
-
p50: percentile(sorted, .5),
|
|
341
|
-
p95: percentile(sorted, .95),
|
|
342
|
-
max: sorted.at(-1) ?? 0
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
function percentile(sorted, quantile) {
|
|
346
|
-
if (sorted.length === 0) return 0;
|
|
347
|
-
return sorted[Math.ceil(quantile * sorted.length) - 1] ?? sorted.at(-1) ?? 0;
|
|
348
|
-
}
|
|
349
509
|
function stableHash(value) {
|
|
350
510
|
let hash = 2166136261;
|
|
351
511
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -354,26 +514,6 @@ function stableHash(value) {
|
|
|
354
514
|
}
|
|
355
515
|
return hash >>> 0;
|
|
356
516
|
}
|
|
357
|
-
function issueAgreement(observations) {
|
|
358
|
-
const byCase = /* @__PURE__ */ new Map();
|
|
359
|
-
for (const observation of observations) {
|
|
360
|
-
const rows = byCase.get(observation.caseId) ?? [];
|
|
361
|
-
rows.push(observation);
|
|
362
|
-
byCase.set(observation.caseId, rows);
|
|
363
|
-
}
|
|
364
|
-
const agreements = [];
|
|
365
|
-
for (const rows of byCase.values()) for (let left = 0; left < rows.length; left++) for (let right = left + 1; right < rows.length; right++) agreements.push(jaccard(rows[left].score.matchedIssueIds, rows[right].score.matchedIssueIds));
|
|
366
|
-
return agreements.length === 0 ? null : mean(agreements);
|
|
367
|
-
}
|
|
368
|
-
function jaccard(left, right) {
|
|
369
|
-
const a = new Set(left);
|
|
370
|
-
const b = new Set(right);
|
|
371
|
-
const union = /* @__PURE__ */ new Set([...a, ...b]);
|
|
372
|
-
if (union.size === 0) return 1;
|
|
373
|
-
let intersection = 0;
|
|
374
|
-
for (const value of a) if (b.has(value)) intersection += 1;
|
|
375
|
-
return intersection / union.size;
|
|
376
|
-
}
|
|
377
517
|
function mergeRegistryUsage(result) {
|
|
378
518
|
const usages = result.per_analyst.map((summary) => summary.usage);
|
|
379
519
|
const calls = usages.every((usage) => usage.calls !== null) ? usages.reduce((sum, usage) => sum + (usage.calls ?? 0), 0) : null;
|
|
@@ -409,6 +549,6 @@ function mergeRegistryUsage(result) {
|
|
|
409
549
|
};
|
|
410
550
|
}
|
|
411
551
|
//#endregion
|
|
412
|
-
export {
|
|
552
|
+
export { scoreAnalystFindings as a, summarizeAnalystBenchmarkRunner as i, runAnalystBenchmark as n, traceStoreEvidenceResolver as r, registryBenchmarkRunner as t };
|
|
413
553
|
|
|
414
|
-
//# sourceMappingURL=benchmark-
|
|
554
|
+
//# sourceMappingURL=benchmark-CYtcIF2V.js.map
|