@tangle-network/agent-knowledge 3.2.1 → 4.0.1
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 +49 -0
- package/README.md +306 -40
- package/dist/benchmarks/index.d.ts +51 -4
- package/dist/benchmarks/index.js +1 -4
- package/dist/chunk-3CAAGJCE.js +4784 -0
- package/dist/chunk-3CAAGJCE.js.map +1 -0
- package/dist/chunk-BBCIB4UL.js +3321 -0
- package/dist/chunk-BBCIB4UL.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +44 -8
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.ts +618 -5
- package/dist/memory/index.js +44 -3
- package/dist/types-ZzY_x0r7.d.ts +661 -0
- package/package.json +7 -5
- package/dist/chunk-RIHIYCX2.js +0 -1853
- package/dist/chunk-RIHIYCX2.js.map +0 -1
- package/dist/chunk-VN2OGUUP.js +0 -851
- package/dist/chunk-VN2OGUUP.js.map +0 -1
- package/dist/chunk-XVU5FFQA.js +0 -49
- package/dist/chunk-XVU5FFQA.js.map +0 -1
- package/dist/index-BHQk7jOT.d.ts +0 -429
- package/dist/types-BFRyr390.d.ts +0 -319
package/dist/chunk-RIHIYCX2.js
DELETED
|
@@ -1,1853 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
searchKnowledge
|
|
3
|
-
} from "./chunk-DQ3PDMDP.js";
|
|
4
|
-
import {
|
|
5
|
-
memoryHitToSourceRecord,
|
|
6
|
-
memoryWriteResultToSourceRecord
|
|
7
|
-
} from "./chunk-XVU5FFQA.js";
|
|
8
|
-
|
|
9
|
-
// src/benchmarks/index.ts
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
import {
|
|
12
|
-
fsCampaignStorage,
|
|
13
|
-
runCampaign
|
|
14
|
-
} from "@tangle-network/agent-eval/campaign";
|
|
15
|
-
|
|
16
|
-
// src/retrieval-eval.ts
|
|
17
|
-
import {
|
|
18
|
-
heldOutGate,
|
|
19
|
-
parameterSweepProposer,
|
|
20
|
-
runImprovementLoop
|
|
21
|
-
} from "@tangle-network/agent-eval/campaign";
|
|
22
|
-
function retrievalConfigSurface(config) {
|
|
23
|
-
return canonicalJson(config);
|
|
24
|
-
}
|
|
25
|
-
function retrievalConfigFromSurface(surface) {
|
|
26
|
-
if (typeof surface !== "string") {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`retrievalConfigFromSurface expected a JSON string surface, got ${typeof surface}`
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
let parsed;
|
|
32
|
-
try {
|
|
33
|
-
parsed = JSON.parse(surface);
|
|
34
|
-
} catch (error) {
|
|
35
|
-
throw new Error(`retrievalConfigFromSurface could not parse JSON: ${error.message}`);
|
|
36
|
-
}
|
|
37
|
-
if (!isJsonObject(parsed)) {
|
|
38
|
-
throw new Error("retrievalConfigFromSurface expected a JSON object");
|
|
39
|
-
}
|
|
40
|
-
return parsed;
|
|
41
|
-
}
|
|
42
|
-
function buildRetrievalEvalDispatch(options) {
|
|
43
|
-
if (!options.retrieve && !options.index) {
|
|
44
|
-
throw new Error("buildRetrievalEvalDispatch requires either index or retrieve");
|
|
45
|
-
}
|
|
46
|
-
const defaultK = options.defaultK ?? 5;
|
|
47
|
-
const retrieve = options.retrieve ?? defaultSearchRetriever;
|
|
48
|
-
return async (surface, scenario, context) => {
|
|
49
|
-
const config = retrievalConfigFromSurface(surface);
|
|
50
|
-
const k = retrievalK(config, scenario, defaultK);
|
|
51
|
-
const startedAt = Date.now();
|
|
52
|
-
const result = await retrieve({
|
|
53
|
-
index: options.index,
|
|
54
|
-
config,
|
|
55
|
-
scenario,
|
|
56
|
-
k,
|
|
57
|
-
signal: context.signal,
|
|
58
|
-
context
|
|
59
|
-
});
|
|
60
|
-
const normalized = normalizeRetrieverResult(result);
|
|
61
|
-
return {
|
|
62
|
-
config,
|
|
63
|
-
query: scenario.query,
|
|
64
|
-
requestedK: k,
|
|
65
|
-
hits: normalized.hits,
|
|
66
|
-
durationMs: Date.now() - startedAt,
|
|
67
|
-
costUsd: normalized.costUsd,
|
|
68
|
-
metadata: normalized.metadata
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function retrievalRecallJudge(options = {}) {
|
|
73
|
-
const weights = normalizeWeights(options.weights ?? { recall: 1 });
|
|
74
|
-
return {
|
|
75
|
-
name: options.name ?? "retrieval-recall",
|
|
76
|
-
dimensions: [
|
|
77
|
-
{ key: "recall", description: "fraction of expected knowledge targets retrieved" },
|
|
78
|
-
{ key: "mrr", description: "reciprocal rank of the first matching hit" },
|
|
79
|
-
{ key: "ndcg", description: "rank-aware gain for newly matched targets" },
|
|
80
|
-
{
|
|
81
|
-
key: "precision_at_k",
|
|
82
|
-
description: "share of returned hits that match at least one target"
|
|
83
|
-
}
|
|
84
|
-
],
|
|
85
|
-
appliesTo: (scenario) => scenario.kind === "retrieval-eval",
|
|
86
|
-
async score({ artifact, scenario }) {
|
|
87
|
-
const metrics = scoreRetrievalArtifact(artifact, scenario);
|
|
88
|
-
const composite = (metrics.recall * weights.recall + metrics.mrr * weights.mrr + metrics.ndcg * weights.ndcg + metrics.precisionAtK * weights.precisionAtK) / (weights.recall + weights.mrr + weights.ndcg + weights.precisionAtK);
|
|
89
|
-
return {
|
|
90
|
-
dimensions: {
|
|
91
|
-
recall: metrics.recall,
|
|
92
|
-
mrr: metrics.mrr,
|
|
93
|
-
ndcg: metrics.ndcg,
|
|
94
|
-
precision_at_k: metrics.precisionAtK
|
|
95
|
-
},
|
|
96
|
-
composite,
|
|
97
|
-
notes: `matched ${metrics.matchedCount}/${metrics.expectedCount}; first_hit_rank=${metrics.firstHitRank ?? "none"}`
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function scoreRetrievalArtifact(artifact, scenario) {
|
|
103
|
-
const targets = normalizeTargets(scenario.expected);
|
|
104
|
-
if (targets.length === 0) {
|
|
105
|
-
throw new Error(`retrieval eval scenario ${scenario.id} has no expected targets`);
|
|
106
|
-
}
|
|
107
|
-
const matchedTargetIds = /* @__PURE__ */ new Set();
|
|
108
|
-
let firstHitRank = null;
|
|
109
|
-
let relevantHitCount = 0;
|
|
110
|
-
let dcg = 0;
|
|
111
|
-
for (const hit of artifact.hits) {
|
|
112
|
-
const newlyMatched = targets.filter((target) => {
|
|
113
|
-
const targetId = retrievalTargetId(target);
|
|
114
|
-
return !matchedTargetIds.has(targetId) && hitMatchesTarget(hit, target);
|
|
115
|
-
});
|
|
116
|
-
if (newlyMatched.length === 0) {
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
relevantHitCount += 1;
|
|
120
|
-
if (firstHitRank === null) {
|
|
121
|
-
firstHitRank = hit.rank;
|
|
122
|
-
}
|
|
123
|
-
dcg += 1 / Math.log2(hit.rank + 1);
|
|
124
|
-
for (const target of newlyMatched) {
|
|
125
|
-
matchedTargetIds.add(retrievalTargetId(target));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const expectedCount = targets.length;
|
|
129
|
-
const matchedCount = matchedTargetIds.size;
|
|
130
|
-
const idealRankCount = Math.min(expectedCount, Math.max(1, artifact.requestedK));
|
|
131
|
-
const idcg = idealDcg(idealRankCount);
|
|
132
|
-
return {
|
|
133
|
-
recall: matchedCount / expectedCount,
|
|
134
|
-
mrr: firstHitRank === null ? 0 : 1 / firstHitRank,
|
|
135
|
-
ndcg: idcg === 0 ? 0 : dcg / idcg,
|
|
136
|
-
precisionAtK: artifact.hits.length === 0 ? 0 : relevantHitCount / artifact.hits.length,
|
|
137
|
-
expectedCount,
|
|
138
|
-
matchedCount,
|
|
139
|
-
relevantHitCount,
|
|
140
|
-
firstHitRank,
|
|
141
|
-
matchedTargetIds: [...matchedTargetIds].sort()
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function buildRetrievalParameterCandidates(searchSpace, options = {}) {
|
|
145
|
-
const candidates = [];
|
|
146
|
-
for (const [path, values] of Object.entries(searchSpace).sort(([a], [b]) => a.localeCompare(b))) {
|
|
147
|
-
for (const value of values) {
|
|
148
|
-
if (options.baseline && canonicalJson(getConfigPath(options.baseline, path)) === canonicalJson(value)) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
candidates.push({
|
|
152
|
-
label: `${path}=${formatCandidateValue(value)}`,
|
|
153
|
-
rationale: `Set retrieval config ${path} to ${formatCandidateValue(value)}`,
|
|
154
|
-
changes: [{ path, value }]
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return candidates;
|
|
159
|
-
}
|
|
160
|
-
function retrievalParameterSweepProposer(options) {
|
|
161
|
-
const candidates = options.candidates ?? (options.searchSpace ? buildRetrievalParameterCandidates(options.searchSpace, { baseline: options.baseline }) : []);
|
|
162
|
-
if (candidates.length === 0) {
|
|
163
|
-
throw new Error("retrievalParameterSweepProposer requires at least one candidate");
|
|
164
|
-
}
|
|
165
|
-
return parameterSweepProposer({ candidates });
|
|
166
|
-
}
|
|
167
|
-
async function runRetrievalImprovementLoop(options) {
|
|
168
|
-
const split = splitRetrievalScenarios(options);
|
|
169
|
-
const candidates = resolveRetrievalCandidates(options);
|
|
170
|
-
const populationSize = options.populationSize ?? Math.max(1, Math.min(4, candidates.length));
|
|
171
|
-
const maxGenerations = options.maxGenerations ?? Math.max(1, Math.ceil(candidates.length / populationSize));
|
|
172
|
-
const proposer = withTargetRecallStop(
|
|
173
|
-
retrievalParameterSweepProposer({ candidates }),
|
|
174
|
-
options.targetRecall
|
|
175
|
-
);
|
|
176
|
-
const gate = options.gate ?? heldOutGate({
|
|
177
|
-
scenarios: split.holdoutScenarios,
|
|
178
|
-
deltaThreshold: options.deltaThreshold ?? 0.02
|
|
179
|
-
});
|
|
180
|
-
const result = await runImprovementLoop({
|
|
181
|
-
baselineSurface: retrievalConfigSurface(options.baseline),
|
|
182
|
-
scenarios: split.trainScenarios,
|
|
183
|
-
holdoutScenarios: split.holdoutScenarios,
|
|
184
|
-
dispatchWithSurface: buildRetrievalEvalDispatch({
|
|
185
|
-
index: options.index,
|
|
186
|
-
defaultK: options.defaultK,
|
|
187
|
-
retrieve: options.retrieve
|
|
188
|
-
}),
|
|
189
|
-
judges: [...options.judges ?? [retrievalRecallJudge({ weights: options.metricWeights })]],
|
|
190
|
-
proposer,
|
|
191
|
-
gate,
|
|
192
|
-
autoOnPromote: "none",
|
|
193
|
-
runDir: options.runDir ?? ".agent-knowledge/retrieval-improvement",
|
|
194
|
-
seed: options.seed,
|
|
195
|
-
reps: options.reps,
|
|
196
|
-
resumable: options.resumable,
|
|
197
|
-
costCeiling: options.costCeiling,
|
|
198
|
-
maxConcurrency: options.maxConcurrency,
|
|
199
|
-
dispatchTimeoutMs: options.dispatchTimeoutMs,
|
|
200
|
-
expectUsage: options.expectUsage ?? "off",
|
|
201
|
-
tracing: options.tracing,
|
|
202
|
-
storage: options.storage,
|
|
203
|
-
populationSize,
|
|
204
|
-
maxGenerations,
|
|
205
|
-
promoteTopK: options.promoteTopK,
|
|
206
|
-
maxImprovementShots: options.maxImprovementShots,
|
|
207
|
-
report: options.report,
|
|
208
|
-
findings: options.findings,
|
|
209
|
-
now: options.now
|
|
210
|
-
});
|
|
211
|
-
return {
|
|
212
|
-
...result,
|
|
213
|
-
baselineConfig: options.baseline,
|
|
214
|
-
winnerConfig: retrievalConfigFromSurface(result.winnerSurface),
|
|
215
|
-
trainScenarios: split.trainScenarios,
|
|
216
|
-
holdoutScenarios: split.holdoutScenarios,
|
|
217
|
-
candidates,
|
|
218
|
-
targetRecall: options.targetRecall
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
function splitRetrievalScenarios(options) {
|
|
222
|
-
const scenarios = [...options.scenarios];
|
|
223
|
-
if (scenarios.length === 0) {
|
|
224
|
-
throw new Error("runRetrievalImprovementLoop requires at least one training scenario");
|
|
225
|
-
}
|
|
226
|
-
if (options.holdoutScenarios) {
|
|
227
|
-
const holdoutScenarios = [...options.holdoutScenarios];
|
|
228
|
-
if (holdoutScenarios.length === 0) {
|
|
229
|
-
throw new Error("runRetrievalImprovementLoop holdoutScenarios must not be empty");
|
|
230
|
-
}
|
|
231
|
-
return { trainScenarios: scenarios, holdoutScenarios };
|
|
232
|
-
}
|
|
233
|
-
if (scenarios.length < 2) {
|
|
234
|
-
throw new Error(
|
|
235
|
-
"runRetrievalImprovementLoop requires at least 2 scenarios when holdoutScenarios are not provided"
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
const holdoutFraction = options.holdoutFraction ?? 0.3;
|
|
239
|
-
if (!Number.isFinite(holdoutFraction) || holdoutFraction <= 0 || holdoutFraction >= 1) {
|
|
240
|
-
throw new Error(
|
|
241
|
-
`runRetrievalImprovementLoop holdoutFraction must be > 0 and < 1, got ${String(holdoutFraction)}`
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
const shuffled = seededShuffle(scenarios, options.splitSeed ?? options.seed ?? 42);
|
|
245
|
-
const holdoutCount = Math.min(
|
|
246
|
-
shuffled.length - 1,
|
|
247
|
-
Math.max(1, Math.round(shuffled.length * holdoutFraction))
|
|
248
|
-
);
|
|
249
|
-
const splitIndex = shuffled.length - holdoutCount;
|
|
250
|
-
return {
|
|
251
|
-
trainScenarios: shuffled.slice(0, splitIndex),
|
|
252
|
-
holdoutScenarios: shuffled.slice(splitIndex)
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function resolveRetrievalCandidates(options) {
|
|
256
|
-
const candidates = options.candidates ? [...options.candidates] : options.searchSpace ? buildRetrievalParameterCandidates(options.searchSpace, { baseline: options.baseline }) : [];
|
|
257
|
-
if (candidates.length === 0) {
|
|
258
|
-
throw new Error("runRetrievalImprovementLoop requires candidates or searchSpace");
|
|
259
|
-
}
|
|
260
|
-
return candidates;
|
|
261
|
-
}
|
|
262
|
-
function withTargetRecallStop(proposer, targetRecall) {
|
|
263
|
-
if (targetRecall === void 0) {
|
|
264
|
-
return proposer;
|
|
265
|
-
}
|
|
266
|
-
if (!Number.isFinite(targetRecall) || targetRecall < 0 || targetRecall > 1) {
|
|
267
|
-
throw new Error(`targetRecall must be between 0 and 1, got ${String(targetRecall)}`);
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
kind: `${proposer.kind}:target-recall`,
|
|
271
|
-
propose: (context) => proposer.propose(context),
|
|
272
|
-
decide(args) {
|
|
273
|
-
const baseDecision = proposer.decide?.(args);
|
|
274
|
-
if (baseDecision?.stop) {
|
|
275
|
-
return baseDecision;
|
|
276
|
-
}
|
|
277
|
-
const bestRecall = bestObservedRecall(args.history);
|
|
278
|
-
if (bestRecall >= targetRecall) {
|
|
279
|
-
return {
|
|
280
|
-
stop: true,
|
|
281
|
-
reason: `target recall ${targetRecall} reached with train recall ${bestRecall}`
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
return { stop: false };
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
function bestObservedRecall(history) {
|
|
289
|
-
let best = Number.NEGATIVE_INFINITY;
|
|
290
|
-
for (const generation of history) {
|
|
291
|
-
for (const candidate of generation.candidates) {
|
|
292
|
-
const recall = candidate.dimensions.recall;
|
|
293
|
-
if (recall !== void 0 && Number.isFinite(recall) && recall > best) {
|
|
294
|
-
best = recall;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return best;
|
|
299
|
-
}
|
|
300
|
-
function retrievalK(config, scenario, defaultK) {
|
|
301
|
-
const rawK = config.k ?? config.topK ?? scenario.k ?? defaultK;
|
|
302
|
-
if (typeof rawK !== "number" || !Number.isFinite(rawK) || rawK <= 0) {
|
|
303
|
-
throw new Error(`retrieval k must be a positive number, got ${String(rawK)}`);
|
|
304
|
-
}
|
|
305
|
-
return Math.floor(rawK);
|
|
306
|
-
}
|
|
307
|
-
async function defaultSearchRetriever(input) {
|
|
308
|
-
if (!input.index) {
|
|
309
|
-
throw new Error("default retrieval eval search requires an index");
|
|
310
|
-
}
|
|
311
|
-
const results = searchKnowledge(input.index, input.scenario.query, input.k);
|
|
312
|
-
return { hits: results.map(hitFromSearchResult) };
|
|
313
|
-
}
|
|
314
|
-
function hitFromSearchResult(result) {
|
|
315
|
-
return {
|
|
316
|
-
pageId: result.page.id,
|
|
317
|
-
path: result.page.path,
|
|
318
|
-
title: result.page.title,
|
|
319
|
-
rank: result.rank,
|
|
320
|
-
score: result.score,
|
|
321
|
-
normalizedScore: result.normalizedScore,
|
|
322
|
-
sourceIds: result.page.sourceIds,
|
|
323
|
-
snippet: result.snippet
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function normalizeRetrieverResult(result) {
|
|
327
|
-
if (isHitArray(result)) {
|
|
328
|
-
return { hits: result };
|
|
329
|
-
}
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
332
|
-
function normalizeTargets(expected) {
|
|
333
|
-
return isTargetArray(expected) ? [...expected] : [expected];
|
|
334
|
-
}
|
|
335
|
-
function isHitArray(result) {
|
|
336
|
-
return Array.isArray(result);
|
|
337
|
-
}
|
|
338
|
-
function isTargetArray(expected) {
|
|
339
|
-
return Array.isArray(expected);
|
|
340
|
-
}
|
|
341
|
-
function hitMatchesTarget(hit, target) {
|
|
342
|
-
switch (target.kind) {
|
|
343
|
-
case "page":
|
|
344
|
-
return hit.pageId === target.pageId;
|
|
345
|
-
case "page-path":
|
|
346
|
-
return hit.path === target.path;
|
|
347
|
-
case "source":
|
|
348
|
-
return Boolean(hit.sourceIds?.includes(target.sourceId));
|
|
349
|
-
case "source-anchor":
|
|
350
|
-
return Boolean(
|
|
351
|
-
hit.sourceSpans?.some(
|
|
352
|
-
(span) => span.sourceId === target.sourceId && span.anchorId === target.anchorId
|
|
353
|
-
)
|
|
354
|
-
);
|
|
355
|
-
case "source-span":
|
|
356
|
-
return Boolean(
|
|
357
|
-
hit.sourceSpans?.some(
|
|
358
|
-
(span) => span.sourceId === target.sourceId && spanOverlaps(span, target)
|
|
359
|
-
)
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function spanOverlaps(hit, target) {
|
|
364
|
-
if (typeof hit.charStart !== "number" || typeof hit.charEnd !== "number") {
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
return hit.charStart < target.charEnd && target.charStart < hit.charEnd;
|
|
368
|
-
}
|
|
369
|
-
function retrievalTargetId(target) {
|
|
370
|
-
switch (target.kind) {
|
|
371
|
-
case "page":
|
|
372
|
-
return `page:${target.pageId}`;
|
|
373
|
-
case "page-path":
|
|
374
|
-
return `page-path:${target.path}`;
|
|
375
|
-
case "source":
|
|
376
|
-
return `source:${target.sourceId}`;
|
|
377
|
-
case "source-anchor":
|
|
378
|
-
return `source-anchor:${target.sourceId}:${target.anchorId}`;
|
|
379
|
-
case "source-span":
|
|
380
|
-
return `source-span:${target.sourceId}:${target.charStart}:${target.charEnd}`;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
function idealDcg(count) {
|
|
384
|
-
let score = 0;
|
|
385
|
-
for (let i = 1; i <= count; i += 1) {
|
|
386
|
-
score += 1 / Math.log2(i + 1);
|
|
387
|
-
}
|
|
388
|
-
return score;
|
|
389
|
-
}
|
|
390
|
-
function normalizeWeights(weights) {
|
|
391
|
-
const normalized = {
|
|
392
|
-
recall: normalizeWeight(weights.recall),
|
|
393
|
-
mrr: normalizeWeight(weights.mrr),
|
|
394
|
-
ndcg: normalizeWeight(weights.ndcg),
|
|
395
|
-
precisionAtK: normalizeWeight(weights.precisionAtK)
|
|
396
|
-
};
|
|
397
|
-
const total = normalized.recall + normalized.mrr + normalized.ndcg + normalized.precisionAtK;
|
|
398
|
-
if (total <= 0) {
|
|
399
|
-
throw new Error("retrievalRecallJudge requires at least one positive metric weight");
|
|
400
|
-
}
|
|
401
|
-
return normalized;
|
|
402
|
-
}
|
|
403
|
-
function normalizeWeight(value) {
|
|
404
|
-
if (value === void 0) {
|
|
405
|
-
return 0;
|
|
406
|
-
}
|
|
407
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
408
|
-
throw new Error(
|
|
409
|
-
`retrievalRecallJudge metric weights must be non-negative finite numbers, got ${String(value)}`
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
return value;
|
|
413
|
-
}
|
|
414
|
-
function getConfigPath(config, path) {
|
|
415
|
-
let current = config;
|
|
416
|
-
for (const part of path.split(".")) {
|
|
417
|
-
if (!isJsonObject(current)) {
|
|
418
|
-
return void 0;
|
|
419
|
-
}
|
|
420
|
-
current = current[part];
|
|
421
|
-
}
|
|
422
|
-
return current;
|
|
423
|
-
}
|
|
424
|
-
function formatCandidateValue(value) {
|
|
425
|
-
if (typeof value === "string") {
|
|
426
|
-
return value;
|
|
427
|
-
}
|
|
428
|
-
return canonicalJson(value);
|
|
429
|
-
}
|
|
430
|
-
function canonicalJson(value) {
|
|
431
|
-
if (value === void 0) {
|
|
432
|
-
return "undefined";
|
|
433
|
-
}
|
|
434
|
-
if (Array.isArray(value)) {
|
|
435
|
-
return `[${value.map(canonicalJson).join(",")}]`;
|
|
436
|
-
}
|
|
437
|
-
if (isJsonObject(value)) {
|
|
438
|
-
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => `${JSON.stringify(key)}:${canonicalJson(child)}`);
|
|
439
|
-
return `{${entries.join(",")}}`;
|
|
440
|
-
}
|
|
441
|
-
return JSON.stringify(value);
|
|
442
|
-
}
|
|
443
|
-
function isJsonObject(value) {
|
|
444
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
445
|
-
}
|
|
446
|
-
function seededShuffle(items, seed) {
|
|
447
|
-
const out = [...items];
|
|
448
|
-
let state = seed >>> 0;
|
|
449
|
-
for (let i = out.length - 1; i > 0; i -= 1) {
|
|
450
|
-
state = state * 1664525 + 1013904223 >>> 0;
|
|
451
|
-
const j = state % (i + 1);
|
|
452
|
-
[out[i], out[j]] = [out[j], out[i]];
|
|
453
|
-
}
|
|
454
|
-
return out;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// src/benchmarks/index.ts
|
|
458
|
-
var INDUSTRY_RAG_BENCHMARKS = [
|
|
459
|
-
{
|
|
460
|
-
id: "beir",
|
|
461
|
-
family: "beir",
|
|
462
|
-
taskKind: "retrieval",
|
|
463
|
-
primaryMetrics: ["nDCG@10", "Recall@100", "MRR@10"],
|
|
464
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
465
|
-
notes: "Classic zero-shot retrieval suites using query/corpus/qrels files."
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
id: "mteb-retrieval",
|
|
469
|
-
family: "mteb-retrieval",
|
|
470
|
-
taskKind: "retrieval",
|
|
471
|
-
primaryMetrics: ["nDCG@10", "Recall@100"],
|
|
472
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
473
|
-
notes: "MTEB retrieval task shape; same qrels bridge, different dataset provenance."
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
id: "msmarco",
|
|
477
|
-
family: "msmarco",
|
|
478
|
-
taskKind: "retrieval",
|
|
479
|
-
primaryMetrics: ["MRR@10", "Recall@100"],
|
|
480
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
481
|
-
notes: "Passage retrieval and reranking smoke for web-style questions."
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
id: "trec-dl",
|
|
485
|
-
family: "trec-dl",
|
|
486
|
-
taskKind: "retrieval",
|
|
487
|
-
primaryMetrics: ["nDCG@10", "MAP", "Recall@100"],
|
|
488
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
489
|
-
notes: "Deep Learning Track judgments over MS MARCO-derived corpora."
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
id: "miracl",
|
|
493
|
-
family: "miracl",
|
|
494
|
-
taskKind: "retrieval",
|
|
495
|
-
primaryMetrics: ["nDCG@10", "Recall@100"],
|
|
496
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
497
|
-
notes: "Multilingual retrieval; use language tags on cases."
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
id: "lotte",
|
|
501
|
-
family: "lotte",
|
|
502
|
-
taskKind: "retrieval",
|
|
503
|
-
primaryMetrics: ["Success@5", "Recall@100"],
|
|
504
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
505
|
-
notes: "Long-tail search tasks; map collection/domain into tags."
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
id: "bright",
|
|
509
|
-
family: "bright",
|
|
510
|
-
taskKind: "retrieval",
|
|
511
|
-
primaryMetrics: ["nDCG@10", "Recall@100"],
|
|
512
|
-
adapter: "buildRetrievalBenchmarkCasesFromQrels",
|
|
513
|
-
notes: "Reasoning-heavy retrieval; preserve domain tags for slice reporting."
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
id: "crag",
|
|
517
|
-
family: "crag",
|
|
518
|
-
taskKind: "rag-answer",
|
|
519
|
-
primaryMetrics: ["claim_recall", "citation_recall", "hallucination_safe"],
|
|
520
|
-
adapter: "KnowledgeAnswerBenchmarkCase",
|
|
521
|
-
notes: "Answer quality and freshness cases; use required/forbidden claims plus citations."
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
id: "hotpotqa",
|
|
525
|
-
family: "hotpotqa",
|
|
526
|
-
taskKind: "rag-answer",
|
|
527
|
-
primaryMetrics: ["claim_recall", "citation_recall"],
|
|
528
|
-
adapter: "KnowledgeAnswerBenchmarkCase",
|
|
529
|
-
notes: "Multihop QA; encode each supporting fact as a required claim."
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
id: "kilt",
|
|
533
|
-
family: "kilt",
|
|
534
|
-
taskKind: "rag-answer",
|
|
535
|
-
primaryMetrics: ["claim_recall", "citation_recall"],
|
|
536
|
-
adapter: "KnowledgeAnswerBenchmarkCase",
|
|
537
|
-
notes: "Knowledge-intensive generation with provenance; encode expected pages/sources."
|
|
538
|
-
},
|
|
539
|
-
{
|
|
540
|
-
id: "ragtruth",
|
|
541
|
-
family: "ragtruth",
|
|
542
|
-
taskKind: "hallucination",
|
|
543
|
-
primaryMetrics: ["hallucination_safe", "forbidden_claim_rate"],
|
|
544
|
-
adapter: "KnowledgeAnswerBenchmarkCase",
|
|
545
|
-
notes: "Hallucination detection; encode hallucinated spans as forbidden claims."
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
id: "faithbench",
|
|
549
|
-
family: "faithbench",
|
|
550
|
-
taskKind: "hallucination",
|
|
551
|
-
primaryMetrics: ["hallucination_safe", "forbidden_claim_rate"],
|
|
552
|
-
adapter: "KnowledgeAnswerBenchmarkCase",
|
|
553
|
-
notes: "Faithfulness benchmark; score unsupported claims as forbidden claims."
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
id: "first-party/kb-improvement",
|
|
557
|
-
family: "first-party",
|
|
558
|
-
taskKind: "kb-improvement",
|
|
559
|
-
primaryMetrics: ["claim_recall", "hallucination_safe", "score"],
|
|
560
|
-
adapter: "KnowledgeAnswerBenchmarkCase",
|
|
561
|
-
notes: "Project-owned candidate-KB validation; grade the produced KB text or answer bundle."
|
|
562
|
-
}
|
|
563
|
-
];
|
|
564
|
-
var INDUSTRY_MEMORY_BENCHMARKS = [
|
|
565
|
-
{
|
|
566
|
-
id: "locomo/qa",
|
|
567
|
-
family: "locomo",
|
|
568
|
-
taskKind: "memory-recall",
|
|
569
|
-
primaryMetrics: ["memory_fact_recall", "memory_event_recall"],
|
|
570
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
571
|
-
notes: "Long-term conversational QA over multi-session histories."
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
id: "locomo/event-summary",
|
|
575
|
-
family: "locomo",
|
|
576
|
-
taskKind: "memory-summarization",
|
|
577
|
-
primaryMetrics: ["memory_fact_recall", "memory_event_recall"],
|
|
578
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
579
|
-
notes: "Event summarization over long conversational histories."
|
|
580
|
-
},
|
|
581
|
-
{
|
|
582
|
-
id: "longmemeval",
|
|
583
|
-
family: "longmemeval",
|
|
584
|
-
taskKind: "memory-temporal",
|
|
585
|
-
primaryMetrics: ["memory_fact_recall", "memory_stale_safe", "memory_event_recall"],
|
|
586
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
587
|
-
notes: "Long-term assistant memory with temporal and update-sensitive probes."
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
id: "longmemeval-v2",
|
|
591
|
-
family: "longmemeval-v2",
|
|
592
|
-
taskKind: "memory-reasoning",
|
|
593
|
-
primaryMetrics: ["memory_fact_recall", "memory_event_recall", "score"],
|
|
594
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
595
|
-
notes: "Experience reuse from long agent histories; track accuracy and latency."
|
|
596
|
-
},
|
|
597
|
-
{
|
|
598
|
-
id: "memora",
|
|
599
|
-
family: "memora",
|
|
600
|
-
taskKind: "memory-update",
|
|
601
|
-
primaryMetrics: ["memory_fact_recall", "memory_stale_safe", "memory_stale_rate"],
|
|
602
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
603
|
-
notes: "Forgetting-aware memory accuracy: reward current facts and penalize obsolete ones."
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
id: "memoryagentbench",
|
|
607
|
-
family: "memoryagentbench",
|
|
608
|
-
taskKind: "memory-ingest",
|
|
609
|
-
primaryMetrics: ["memory_fact_recall", "memory_event_recall"],
|
|
610
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
611
|
-
notes: "Incremental multi-turn information intake before later recall."
|
|
612
|
-
},
|
|
613
|
-
{
|
|
614
|
-
id: "memorybank",
|
|
615
|
-
family: "memorybank",
|
|
616
|
-
taskKind: "memory-recommendation",
|
|
617
|
-
primaryMetrics: ["memory_fact_recall", "memory_stale_safe"],
|
|
618
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
619
|
-
notes: "Personalized memory use for preference-aware downstream choices."
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
id: "groupmembench",
|
|
623
|
-
family: "groupmembench",
|
|
624
|
-
taskKind: "memory-multiparty",
|
|
625
|
-
primaryMetrics: ["memory_fact_recall", "memory_actor_recall", "memory_stale_safe"],
|
|
626
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
627
|
-
notes: "Multi-party memory with speaker attribution, update, and term ambiguity pressure."
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
id: "first-party/memory-lifecycle",
|
|
631
|
-
family: "first-party",
|
|
632
|
-
taskKind: "memory-forgetting",
|
|
633
|
-
primaryMetrics: ["memory_fact_recall", "memory_stale_safe", "memory_event_recall"],
|
|
634
|
-
adapter: "KnowledgeMemoryBenchmarkCase",
|
|
635
|
-
notes: "Project-owned lifecycle pack for ingest, recall, update, forgetting, and ambiguity."
|
|
636
|
-
}
|
|
637
|
-
];
|
|
638
|
-
function buildIndustryRagBenchmarkSmokeCases(specs = INDUSTRY_RAG_BENCHMARKS) {
|
|
639
|
-
return specs.map((spec) => {
|
|
640
|
-
const source = {
|
|
641
|
-
name: spec.id,
|
|
642
|
-
version: "smoke"
|
|
643
|
-
};
|
|
644
|
-
const split = spec.taskKind === "retrieval" ? "search" : "holdout";
|
|
645
|
-
const tags = unique(["industry-smoke", spec.id, spec.family, spec.taskKind]);
|
|
646
|
-
if (spec.taskKind === "retrieval") {
|
|
647
|
-
return {
|
|
648
|
-
id: `${spec.id}/smoke:q1`,
|
|
649
|
-
family: spec.family,
|
|
650
|
-
taskKind: "retrieval",
|
|
651
|
-
split,
|
|
652
|
-
tags,
|
|
653
|
-
source,
|
|
654
|
-
query: `${spec.id} smoke retrieval query`,
|
|
655
|
-
expected: [{ kind: "page", pageId: `${spec.id}:doc-1` }],
|
|
656
|
-
k: 5,
|
|
657
|
-
metadata: {
|
|
658
|
-
adapter: spec.adapter,
|
|
659
|
-
primaryMetrics: spec.primaryMetrics
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
return {
|
|
664
|
-
id: `${spec.id}/smoke:q1`,
|
|
665
|
-
family: spec.family,
|
|
666
|
-
taskKind: spec.taskKind,
|
|
667
|
-
split,
|
|
668
|
-
tags,
|
|
669
|
-
source,
|
|
670
|
-
prompt: `${spec.id} smoke benchmark prompt`,
|
|
671
|
-
requiredClaims: [
|
|
672
|
-
{
|
|
673
|
-
id: `${spec.id}:required`,
|
|
674
|
-
anyOf: [`${spec.id} supported answer`]
|
|
675
|
-
}
|
|
676
|
-
],
|
|
677
|
-
forbiddenClaims: [
|
|
678
|
-
{
|
|
679
|
-
id: `${spec.id}:unsupported`,
|
|
680
|
-
anyOf: [`${spec.id} unsupported claim`]
|
|
681
|
-
}
|
|
682
|
-
],
|
|
683
|
-
expectedSourceIds: [`${spec.id}:source-1`],
|
|
684
|
-
referenceAnswer: `${spec.id} supported answer`,
|
|
685
|
-
metadata: {
|
|
686
|
-
adapter: spec.adapter,
|
|
687
|
-
primaryMetrics: spec.primaryMetrics
|
|
688
|
-
}
|
|
689
|
-
};
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
function respondToIndustryRagBenchmarkSmokeCase(input) {
|
|
693
|
-
const testCase = input.case;
|
|
694
|
-
if (isKnowledgeMemoryBenchmarkCase(testCase)) {
|
|
695
|
-
return respondToIndustryMemoryBenchmarkSmokeCase({ case: testCase });
|
|
696
|
-
}
|
|
697
|
-
if (testCase.taskKind === "retrieval") {
|
|
698
|
-
const expected = Array.isArray(testCase.expected) ? testCase.expected[0] : testCase.expected;
|
|
699
|
-
const hit = hitForExpectedTarget(expected, testCase.id);
|
|
700
|
-
return {
|
|
701
|
-
hits: [hit],
|
|
702
|
-
durationMs: 1,
|
|
703
|
-
metadata: {
|
|
704
|
-
smoke: true
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
answer: (testCase.requiredClaims ?? []).map((claim) => claim.anyOf[0]).filter((fragment) => Boolean(fragment)).join(" "),
|
|
710
|
-
citedSourceIds: testCase.expectedSourceIds ?? [],
|
|
711
|
-
durationMs: 1,
|
|
712
|
-
metadata: {
|
|
713
|
-
smoke: true
|
|
714
|
-
}
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
function buildIndustryMemoryBenchmarkSmokeCases(specs = INDUSTRY_MEMORY_BENCHMARKS) {
|
|
718
|
-
return specs.map((spec) => {
|
|
719
|
-
const currentEventId = `${spec.id}:event-current`;
|
|
720
|
-
const staleEventId = `${spec.id}:event-stale`;
|
|
721
|
-
const actorId = spec.taskKind === "memory-multiparty" ? "teammate-ada" : "user";
|
|
722
|
-
const currentFact = `${spec.id} current memory`;
|
|
723
|
-
const staleFact = `${spec.id} stale memory`;
|
|
724
|
-
return {
|
|
725
|
-
id: `${spec.id}/smoke:q1`,
|
|
726
|
-
family: spec.family,
|
|
727
|
-
taskKind: spec.taskKind,
|
|
728
|
-
split: spec.taskKind === "memory-forgetting" ? "holdout" : "dev",
|
|
729
|
-
tags: unique(["memory-smoke", spec.id, spec.family, spec.taskKind]),
|
|
730
|
-
source: {
|
|
731
|
-
name: spec.id,
|
|
732
|
-
version: "smoke"
|
|
733
|
-
},
|
|
734
|
-
events: [
|
|
735
|
-
{
|
|
736
|
-
id: staleEventId,
|
|
737
|
-
actorId,
|
|
738
|
-
sessionId: `${spec.id}:session-1`,
|
|
739
|
-
timestamp: "2026-01-01T00:00:00.000Z",
|
|
740
|
-
text: `${actorId} once had this obsolete fact: ${staleFact}.`
|
|
741
|
-
},
|
|
742
|
-
{
|
|
743
|
-
id: currentEventId,
|
|
744
|
-
actorId,
|
|
745
|
-
sessionId: `${spec.id}:session-2`,
|
|
746
|
-
timestamp: "2026-02-01T00:00:00.000Z",
|
|
747
|
-
text: `${actorId} updated the durable fact to: ${currentFact}.`
|
|
748
|
-
}
|
|
749
|
-
],
|
|
750
|
-
prompt: `Use memory to answer the ${spec.id} smoke probe.`,
|
|
751
|
-
requiredFacts: [
|
|
752
|
-
{
|
|
753
|
-
id: `${spec.id}:current`,
|
|
754
|
-
anyOf: [currentFact],
|
|
755
|
-
sourceEventIds: [currentEventId]
|
|
756
|
-
}
|
|
757
|
-
],
|
|
758
|
-
forbiddenFacts: [
|
|
759
|
-
{
|
|
760
|
-
id: `${spec.id}:stale`,
|
|
761
|
-
anyOf: [staleFact],
|
|
762
|
-
sourceEventIds: [staleEventId],
|
|
763
|
-
obsolete: true
|
|
764
|
-
}
|
|
765
|
-
],
|
|
766
|
-
expectedEventIds: [currentEventId],
|
|
767
|
-
expectedActorIds: [actorId],
|
|
768
|
-
referenceAnswer: currentFact,
|
|
769
|
-
metadata: {
|
|
770
|
-
adapter: spec.adapter,
|
|
771
|
-
primaryMetrics: spec.primaryMetrics
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
function respondToIndustryMemoryBenchmarkSmokeCase(input) {
|
|
777
|
-
const testCase = input.case;
|
|
778
|
-
const facts = testCase.requiredFacts?.map((fact) => fact.anyOf[0]).filter((fragment) => Boolean(fragment));
|
|
779
|
-
return {
|
|
780
|
-
answer: facts?.join(" ") ?? "",
|
|
781
|
-
rememberedFacts: facts ?? [],
|
|
782
|
-
citedEventIds: testCase.expectedEventIds ?? [],
|
|
783
|
-
actorIds: testCase.expectedActorIds ?? [],
|
|
784
|
-
durationMs: 1,
|
|
785
|
-
metadata: {
|
|
786
|
-
smoke: true
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
function buildFirstPartyMemoryLifecycleBenchmarkCases() {
|
|
791
|
-
const base = {
|
|
792
|
-
family: "first-party",
|
|
793
|
-
source: {
|
|
794
|
-
name: "first-party/memory-lifecycle",
|
|
795
|
-
version: "real-v1"
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
return [
|
|
799
|
-
memoryLifecycleCase({
|
|
800
|
-
...base,
|
|
801
|
-
id: "first-party/memory-lifecycle:allergy",
|
|
802
|
-
taskKind: "memory-ingest",
|
|
803
|
-
split: "dev",
|
|
804
|
-
actorId: "user",
|
|
805
|
-
prompt: "What food restriction should catering remember for this user?",
|
|
806
|
-
staleText: "The user said they had no food allergies on the first onboarding form.",
|
|
807
|
-
currentText: "The user later corrected the profile: they have a severe peanut allergy.",
|
|
808
|
-
required: "severe peanut allergy",
|
|
809
|
-
forbidden: "no food allergies"
|
|
810
|
-
}),
|
|
811
|
-
memoryLifecycleCase({
|
|
812
|
-
...base,
|
|
813
|
-
id: "first-party/memory-lifecycle:account-tier",
|
|
814
|
-
taskKind: "memory-recall",
|
|
815
|
-
split: "dev",
|
|
816
|
-
actorId: "sales-ops",
|
|
817
|
-
prompt: "What is the customer account tier now?",
|
|
818
|
-
staleText: "Sales ops originally marked the customer as starter tier.",
|
|
819
|
-
currentText: "Sales ops updated the customer to enterprise tier after procurement approval.",
|
|
820
|
-
required: "enterprise tier",
|
|
821
|
-
forbidden: "starter tier"
|
|
822
|
-
}),
|
|
823
|
-
memoryLifecycleCase({
|
|
824
|
-
...base,
|
|
825
|
-
id: "first-party/memory-lifecycle:launch-date",
|
|
826
|
-
taskKind: "memory-temporal",
|
|
827
|
-
split: "holdout",
|
|
828
|
-
actorId: "pm",
|
|
829
|
-
prompt: "What launch date should the agent use?",
|
|
830
|
-
staleText: "The PM first planned the launch for April 3.",
|
|
831
|
-
currentText: "The PM moved the launch date to April 17 after legal review.",
|
|
832
|
-
required: "April 17",
|
|
833
|
-
forbidden: "April 3"
|
|
834
|
-
}),
|
|
835
|
-
memoryLifecycleCase({
|
|
836
|
-
...base,
|
|
837
|
-
id: "first-party/memory-lifecycle:briefing-channel",
|
|
838
|
-
taskKind: "memory-update",
|
|
839
|
-
split: "holdout",
|
|
840
|
-
actorId: "user",
|
|
841
|
-
prompt: "How should daily briefings be delivered now?",
|
|
842
|
-
staleText: "The user used to want daily briefings by SMS.",
|
|
843
|
-
currentText: "The user changed daily briefings to email only.",
|
|
844
|
-
required: "email only",
|
|
845
|
-
forbidden: "SMS"
|
|
846
|
-
}),
|
|
847
|
-
memoryLifecycleCase({
|
|
848
|
-
...base,
|
|
849
|
-
id: "first-party/memory-lifecycle:shipping-address",
|
|
850
|
-
taskKind: "memory-forgetting",
|
|
851
|
-
split: "holdout",
|
|
852
|
-
actorId: "user",
|
|
853
|
-
prompt: "What shipping address is current?",
|
|
854
|
-
staleText: "The old shipping address was 14 Pine Street, Apartment 2.",
|
|
855
|
-
currentText: "The current shipping address is 88 Cedar Avenue, Suite 9.",
|
|
856
|
-
required: "88 Cedar Avenue, Suite 9",
|
|
857
|
-
forbidden: "14 Pine Street"
|
|
858
|
-
}),
|
|
859
|
-
memoryLifecycleCase({
|
|
860
|
-
...base,
|
|
861
|
-
id: "first-party/memory-lifecycle:approval-owner",
|
|
862
|
-
taskKind: "memory-reasoning",
|
|
863
|
-
split: "holdout",
|
|
864
|
-
actorId: "finance",
|
|
865
|
-
prompt: "Who owns travel approval now?",
|
|
866
|
-
staleText: "Finance said Liam owned travel approvals last quarter.",
|
|
867
|
-
currentText: "Finance reassigned travel approvals to Maya this quarter.",
|
|
868
|
-
required: "Maya",
|
|
869
|
-
forbidden: "Liam"
|
|
870
|
-
}),
|
|
871
|
-
memoryLifecycleCase({
|
|
872
|
-
...base,
|
|
873
|
-
id: "first-party/memory-lifecycle:project-summary",
|
|
874
|
-
taskKind: "memory-summarization",
|
|
875
|
-
split: "dev",
|
|
876
|
-
actorId: "pm",
|
|
877
|
-
prompt: "Summarize the current Project Orion risks.",
|
|
878
|
-
staleText: "The old Project Orion risk was logo color churn.",
|
|
879
|
-
currentText: "The current Project Orion risks are vendor delay and a QA staffing gap.",
|
|
880
|
-
required: "vendor delay",
|
|
881
|
-
extraRequired: "QA staffing gap",
|
|
882
|
-
forbidden: "logo color churn"
|
|
883
|
-
}),
|
|
884
|
-
memoryLifecycleCase({
|
|
885
|
-
...base,
|
|
886
|
-
id: "first-party/memory-lifecycle:meeting-format",
|
|
887
|
-
taskKind: "memory-recommendation",
|
|
888
|
-
split: "holdout",
|
|
889
|
-
actorId: "user",
|
|
890
|
-
prompt: "What meeting format should the agent recommend?",
|
|
891
|
-
staleText: "The user previously preferred long video calls for planning.",
|
|
892
|
-
currentText: "The user now prefers async docs for planning instead of video calls.",
|
|
893
|
-
required: "async docs",
|
|
894
|
-
forbidden: "long video calls"
|
|
895
|
-
}),
|
|
896
|
-
memoryLifecycleCase({
|
|
897
|
-
...base,
|
|
898
|
-
id: "first-party/memory-lifecycle:multiparty-ada",
|
|
899
|
-
taskKind: "memory-multiparty",
|
|
900
|
-
split: "holdout",
|
|
901
|
-
actorId: "ada",
|
|
902
|
-
prompt: "Which SDK language did Ada ask for?",
|
|
903
|
-
staleText: "Ben asked for a Python notebook example.",
|
|
904
|
-
currentText: "Ada asked for a Rust SDK example.",
|
|
905
|
-
required: "Rust SDK",
|
|
906
|
-
forbidden: "Python notebook"
|
|
907
|
-
}),
|
|
908
|
-
memoryLifecycleCase({
|
|
909
|
-
...base,
|
|
910
|
-
id: "first-party/memory-lifecycle:timezone",
|
|
911
|
-
taskKind: "memory-recall",
|
|
912
|
-
split: "dev",
|
|
913
|
-
actorId: "user",
|
|
914
|
-
prompt: "What timezone should scheduling use for this user?",
|
|
915
|
-
staleText: "The user profile originally listed Pacific time.",
|
|
916
|
-
currentText: "The user corrected scheduling to America/Denver time.",
|
|
917
|
-
required: "America/Denver",
|
|
918
|
-
forbidden: "Pacific time"
|
|
919
|
-
}),
|
|
920
|
-
memoryLifecycleCase({
|
|
921
|
-
...base,
|
|
922
|
-
id: "first-party/memory-lifecycle:dinner-preference",
|
|
923
|
-
taskKind: "memory-update",
|
|
924
|
-
split: "holdout",
|
|
925
|
-
actorId: "user",
|
|
926
|
-
prompt: "What dinner preference should the assistant use?",
|
|
927
|
-
staleText: "The user was previously vegetarian for team dinners.",
|
|
928
|
-
currentText: "The user updated dinner restrictions to no shellfish.",
|
|
929
|
-
required: "no shellfish",
|
|
930
|
-
forbidden: "vegetarian"
|
|
931
|
-
}),
|
|
932
|
-
memoryLifecycleCase({
|
|
933
|
-
...base,
|
|
934
|
-
id: "first-party/memory-lifecycle:support-sla",
|
|
935
|
-
taskKind: "memory-temporal",
|
|
936
|
-
split: "holdout",
|
|
937
|
-
actorId: "support-lead",
|
|
938
|
-
prompt: "What support SLA is current?",
|
|
939
|
-
staleText: "Support used to promise a 24 hour response SLA.",
|
|
940
|
-
currentText: "Support changed the current response SLA to 2 business hours.",
|
|
941
|
-
required: "2 business hours",
|
|
942
|
-
forbidden: "24 hour"
|
|
943
|
-
})
|
|
944
|
-
];
|
|
945
|
-
}
|
|
946
|
-
function createMemoryAdapterBenchmarkResponder(options) {
|
|
947
|
-
return async ({ case: testCase, context: dispatchContext }) => {
|
|
948
|
-
if (!isKnowledgeMemoryBenchmarkCase(testCase)) {
|
|
949
|
-
return { answer: "", metadata: { candidateId: options.candidateId, skipped: true } };
|
|
950
|
-
}
|
|
951
|
-
const costUsd = options.costUsdPerCase ?? 0;
|
|
952
|
-
if (!Number.isFinite(costUsd) || costUsd < 0) {
|
|
953
|
-
throw new Error(`memory adapter costUsdPerCase must be non-negative finite, got ${costUsd}`);
|
|
954
|
-
}
|
|
955
|
-
const execute = async () => {
|
|
956
|
-
const startedAt = Date.now();
|
|
957
|
-
const scope = benchmarkMemoryScope(options.candidateId, testCase, options.scope);
|
|
958
|
-
for (const event of testCase.events) {
|
|
959
|
-
await options.adapter.write({
|
|
960
|
-
id: event.id,
|
|
961
|
-
kind: "message",
|
|
962
|
-
text: event.text,
|
|
963
|
-
role: event.actorId === "user" ? "user" : "assistant",
|
|
964
|
-
title: `${testCase.id}:${event.id}`,
|
|
965
|
-
scope,
|
|
966
|
-
metadata: compactObject({
|
|
967
|
-
benchmarkCaseId: testCase.id,
|
|
968
|
-
eventId: event.id,
|
|
969
|
-
actorId: event.actorId,
|
|
970
|
-
sessionId: event.sessionId,
|
|
971
|
-
timestamp: event.timestamp,
|
|
972
|
-
...event.metadata
|
|
973
|
-
})
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
const adapterContext = await options.adapter.getContext(testCase.prompt, {
|
|
977
|
-
scope,
|
|
978
|
-
limit: options.searchLimit ?? 1,
|
|
979
|
-
metadata: {
|
|
980
|
-
benchmarkCaseId: testCase.id,
|
|
981
|
-
candidateId: options.candidateId
|
|
982
|
-
}
|
|
983
|
-
});
|
|
984
|
-
const hits = adapterContext.hits;
|
|
985
|
-
return {
|
|
986
|
-
answer: adapterContext.text,
|
|
987
|
-
rememberedFacts: hits.map((hit) => hit.text),
|
|
988
|
-
citedEventIds: unique(hits.map(memoryEventId).filter((id) => Boolean(id))),
|
|
989
|
-
usedMemoryIds: hits.map((hit) => hit.id),
|
|
990
|
-
actorIds: unique(hits.map(memoryActorId).filter((id) => Boolean(id))),
|
|
991
|
-
costUsd,
|
|
992
|
-
durationMs: Math.max(0, Date.now() - startedAt),
|
|
993
|
-
metadata: {
|
|
994
|
-
candidateId: options.candidateId,
|
|
995
|
-
adapterId: options.adapter.id,
|
|
996
|
-
hitCount: hits.length
|
|
997
|
-
}
|
|
998
|
-
};
|
|
999
|
-
};
|
|
1000
|
-
if (costUsd === 0) return execute();
|
|
1001
|
-
const receipt = {
|
|
1002
|
-
model: options.adapter.id,
|
|
1003
|
-
inputTokens: 0,
|
|
1004
|
-
outputTokens: 0,
|
|
1005
|
-
usageUnknown: true,
|
|
1006
|
-
actualCostUsd: costUsd
|
|
1007
|
-
};
|
|
1008
|
-
const paid = await dispatchContext.cost.runPaidCall({
|
|
1009
|
-
actor: `agent-knowledge:memory-adapter:${options.adapter.id}`,
|
|
1010
|
-
model: options.adapter.id,
|
|
1011
|
-
maximumCharge: { externallyEnforcedMaximumUsd: costUsd },
|
|
1012
|
-
execute,
|
|
1013
|
-
receipt: () => receipt,
|
|
1014
|
-
receiptFromError: () => receipt
|
|
1015
|
-
});
|
|
1016
|
-
if (!paid.succeeded) throw paid.error;
|
|
1017
|
-
return paid.value;
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
async function runMemoryAdapterBenchmark(options) {
|
|
1021
|
-
const storage = options.storage ?? fsCampaignStorage();
|
|
1022
|
-
const rows = [];
|
|
1023
|
-
for (const candidate of options.candidates) {
|
|
1024
|
-
const adapter = await candidate.createAdapter();
|
|
1025
|
-
const run = await runKnowledgeBenchmarkSuite({
|
|
1026
|
-
cases: options.cases,
|
|
1027
|
-
respond: createMemoryAdapterBenchmarkResponder({
|
|
1028
|
-
adapter,
|
|
1029
|
-
candidateId: candidate.id,
|
|
1030
|
-
searchLimit: candidate.searchLimit,
|
|
1031
|
-
scope: candidate.scope,
|
|
1032
|
-
costUsdPerCase: candidate.costUsdPerCase,
|
|
1033
|
-
now: options.now
|
|
1034
|
-
}),
|
|
1035
|
-
runDir: join(options.runDir, candidate.id),
|
|
1036
|
-
storage,
|
|
1037
|
-
repo: options.repo,
|
|
1038
|
-
seed: options.seed,
|
|
1039
|
-
reps: options.reps,
|
|
1040
|
-
resumable: options.resumable,
|
|
1041
|
-
costCeiling: options.costCeiling,
|
|
1042
|
-
maxConcurrency: options.maxConcurrency,
|
|
1043
|
-
dispatchTimeoutMs: options.dispatchTimeoutMs,
|
|
1044
|
-
expectUsage: options.expectUsage ?? "off",
|
|
1045
|
-
now: options.now
|
|
1046
|
-
});
|
|
1047
|
-
rows.push({
|
|
1048
|
-
rank: 0,
|
|
1049
|
-
candidateId: candidate.id,
|
|
1050
|
-
label: candidate.label ?? candidate.id,
|
|
1051
|
-
adapterId: adapter.id,
|
|
1052
|
-
scoreMean: run.report.score.mean,
|
|
1053
|
-
passRate: run.report.dimensions.passed?.mean ?? 0,
|
|
1054
|
-
totalCases: run.report.totalCases,
|
|
1055
|
-
totalCells: run.report.totalCells,
|
|
1056
|
-
cellsFailed: run.report.cellsFailed,
|
|
1057
|
-
totalCostUsd: run.report.totalCostUsd,
|
|
1058
|
-
reportJsonPath: run.reportJsonPath,
|
|
1059
|
-
reportMarkdownPath: run.reportMarkdownPath,
|
|
1060
|
-
report: run.report
|
|
1061
|
-
});
|
|
1062
|
-
await adapter.flush?.();
|
|
1063
|
-
}
|
|
1064
|
-
const ranked = rows.sort((a, b) => b.scoreMean - a.scoreMean || a.totalCostUsd - b.totalCostUsd).map((row, index) => ({ ...row, rank: index + 1 }));
|
|
1065
|
-
const rankingJsonPath = join(options.runDir, "memory-adapter-ranking.json");
|
|
1066
|
-
const rankingMarkdownPath = join(options.runDir, "memory-adapter-ranking.md");
|
|
1067
|
-
storage.write(rankingJsonPath, `${JSON.stringify({ rows: ranked }, null, 2)}
|
|
1068
|
-
`);
|
|
1069
|
-
storage.write(rankingMarkdownPath, renderMemoryAdapterRankingMarkdown(ranked));
|
|
1070
|
-
return {
|
|
1071
|
-
rows: ranked,
|
|
1072
|
-
rankingJsonPath,
|
|
1073
|
-
rankingMarkdownPath
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
function createNoopMemoryBenchmarkAdapter(id = "no-memory") {
|
|
1077
|
-
return {
|
|
1078
|
-
id,
|
|
1079
|
-
async search() {
|
|
1080
|
-
return [];
|
|
1081
|
-
},
|
|
1082
|
-
async getContext(query) {
|
|
1083
|
-
return { query, text: "", hits: [], sourceRecords: [] };
|
|
1084
|
-
},
|
|
1085
|
-
async write(input) {
|
|
1086
|
-
return {
|
|
1087
|
-
accepted: false,
|
|
1088
|
-
id: input.id ?? `${id}:ignored`,
|
|
1089
|
-
uri: `memory://${id}/ignored`,
|
|
1090
|
-
kind: input.kind
|
|
1091
|
-
};
|
|
1092
|
-
},
|
|
1093
|
-
async flush() {
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
function createInMemoryBenchmarkAdapter(options = {}) {
|
|
1098
|
-
const id = options.id ?? "in-memory";
|
|
1099
|
-
const rows = [];
|
|
1100
|
-
let seq = 0;
|
|
1101
|
-
const adapter = {
|
|
1102
|
-
id,
|
|
1103
|
-
async search(query, searchOptions = {}) {
|
|
1104
|
-
const scored = rows.filter((row) => memoryScopeMatches(row.input.scope, searchOptions.scope)).filter((row) => !searchOptions.kinds?.length || searchOptions.kinds.includes(row.hit.kind)).map((row) => {
|
|
1105
|
-
const lexical = tokenOverlap(query, row.hit.text);
|
|
1106
|
-
const recency = row.seq / Math.max(1, seq);
|
|
1107
|
-
return {
|
|
1108
|
-
...row.hit,
|
|
1109
|
-
score: lexical + recency * 0.01,
|
|
1110
|
-
normalizedScore: lexical
|
|
1111
|
-
};
|
|
1112
|
-
}).filter(
|
|
1113
|
-
(hit) => searchOptions.minScore === void 0 ? true : hit.score >= searchOptions.minScore
|
|
1114
|
-
).sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
1115
|
-
return scored.slice(0, searchOptions.limit ?? 5);
|
|
1116
|
-
},
|
|
1117
|
-
async getContext(query, searchOptions = {}) {
|
|
1118
|
-
const hits = await adapter.search(query, searchOptions);
|
|
1119
|
-
return {
|
|
1120
|
-
query,
|
|
1121
|
-
hits,
|
|
1122
|
-
sourceRecords: hits.map(
|
|
1123
|
-
(hit) => memoryHitToSourceRecord(hit, { scope: searchOptions.scope })
|
|
1124
|
-
),
|
|
1125
|
-
text: renderMemoryHits(hits)
|
|
1126
|
-
};
|
|
1127
|
-
},
|
|
1128
|
-
async write(input) {
|
|
1129
|
-
seq += 1;
|
|
1130
|
-
const memoryId = input.id ?? `${id}:${seq}`;
|
|
1131
|
-
const hit = {
|
|
1132
|
-
id: memoryId,
|
|
1133
|
-
uri: `memory://${id}/${encodeURIComponent(memoryId)}`,
|
|
1134
|
-
kind: input.kind,
|
|
1135
|
-
text: input.text,
|
|
1136
|
-
title: input.title,
|
|
1137
|
-
score: 1,
|
|
1138
|
-
normalizedScore: 1,
|
|
1139
|
-
createdAt: input.metadata?.timestamp,
|
|
1140
|
-
metadata: {
|
|
1141
|
-
...input.metadata ?? {},
|
|
1142
|
-
scope: input.scope
|
|
1143
|
-
}
|
|
1144
|
-
};
|
|
1145
|
-
rows.push({ seq, input, hit });
|
|
1146
|
-
return {
|
|
1147
|
-
accepted: true,
|
|
1148
|
-
id: memoryId,
|
|
1149
|
-
uri: hit.uri,
|
|
1150
|
-
kind: input.kind,
|
|
1151
|
-
sourceRecord: memoryWriteResultToSourceRecord(
|
|
1152
|
-
{
|
|
1153
|
-
accepted: true,
|
|
1154
|
-
id: memoryId,
|
|
1155
|
-
uri: hit.uri,
|
|
1156
|
-
kind: input.kind,
|
|
1157
|
-
metadata: hit.metadata
|
|
1158
|
-
},
|
|
1159
|
-
input.text,
|
|
1160
|
-
{ scope: input.scope }
|
|
1161
|
-
),
|
|
1162
|
-
metadata: hit.metadata
|
|
1163
|
-
};
|
|
1164
|
-
},
|
|
1165
|
-
async flush() {
|
|
1166
|
-
rows.length = 0;
|
|
1167
|
-
seq = 0;
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
return adapter;
|
|
1171
|
-
}
|
|
1172
|
-
function parseKnowledgeBenchmarkJsonl(text) {
|
|
1173
|
-
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line, index) => {
|
|
1174
|
-
try {
|
|
1175
|
-
return JSON.parse(line);
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
throw new Error(`invalid JSONL row ${index + 1}: ${error.message}`);
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
function parseKnowledgeBenchmarkQrels(text) {
|
|
1182
|
-
return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).flatMap((line, index) => {
|
|
1183
|
-
const parts = line.split(/\t|\s+/);
|
|
1184
|
-
if (parts.length < 3) return [];
|
|
1185
|
-
const [queryId, maybeZeroOrDocId, maybeDocIdOrScore, maybeScore] = parts;
|
|
1186
|
-
if (!queryId || !maybeZeroOrDocId || !maybeDocIdOrScore) return [];
|
|
1187
|
-
if (queryId.toLowerCase() === "qid" || queryId.toLowerCase() === "query-id") return [];
|
|
1188
|
-
const documentId = maybeScore === void 0 ? maybeZeroOrDocId : maybeDocIdOrScore;
|
|
1189
|
-
const scoreText = maybeScore === void 0 ? maybeDocIdOrScore : maybeScore;
|
|
1190
|
-
const score = Number(scoreText);
|
|
1191
|
-
if (!documentId || !Number.isFinite(score)) {
|
|
1192
|
-
throw new Error(`invalid qrels row ${index + 1}: expected query id, doc id, score`);
|
|
1193
|
-
}
|
|
1194
|
-
return [{ queryId, documentId, score }];
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
function buildRetrievalBenchmarkCasesFromQrels(options) {
|
|
1198
|
-
const qrelsByQuery = /* @__PURE__ */ new Map();
|
|
1199
|
-
for (const qrel of options.qrels) {
|
|
1200
|
-
if (qrel.score <= 0) continue;
|
|
1201
|
-
const list = qrelsByQuery.get(qrel.queryId) ?? [];
|
|
1202
|
-
list.push(qrel);
|
|
1203
|
-
qrelsByQuery.set(qrel.queryId, list);
|
|
1204
|
-
}
|
|
1205
|
-
return options.queries.flatMap((query) => {
|
|
1206
|
-
const qrels = qrelsByQuery.get(query.id) ?? [];
|
|
1207
|
-
if (qrels.length === 0) return [];
|
|
1208
|
-
const split = query.split ?? options.splitOf?.(query.id);
|
|
1209
|
-
const expected = qrels.map(
|
|
1210
|
-
(qrel) => options.documentTarget ? options.documentTarget(qrel.documentId, qrel) : defaultDocumentTarget(qrel.documentId, options.targetKind ?? "page")
|
|
1211
|
-
);
|
|
1212
|
-
return [
|
|
1213
|
-
compactObject({
|
|
1214
|
-
id: `${options.benchmarkId}:${query.id}`,
|
|
1215
|
-
family: options.family,
|
|
1216
|
-
taskKind: "retrieval",
|
|
1217
|
-
query: query.text,
|
|
1218
|
-
expected,
|
|
1219
|
-
k: options.k,
|
|
1220
|
-
split,
|
|
1221
|
-
tags: unique([...options.tags ?? [], ...query.tags ?? [], ...split ? [split] : []]),
|
|
1222
|
-
source: options.source,
|
|
1223
|
-
metadata: query.metadata
|
|
1224
|
-
})
|
|
1225
|
-
];
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
async function runKnowledgeBenchmarkSuite(options) {
|
|
1229
|
-
const storage = options.storage ?? fsCampaignStorage();
|
|
1230
|
-
const scenarios = buildKnowledgeBenchmarkScenarios(options.cases, options.splits);
|
|
1231
|
-
const dispatch = async (scenario, context) => {
|
|
1232
|
-
const artifact = await options.respond({ case: scenario.case, scenario, context });
|
|
1233
|
-
return artifact;
|
|
1234
|
-
};
|
|
1235
|
-
const campaign = await runCampaign({
|
|
1236
|
-
scenarios,
|
|
1237
|
-
dispatch,
|
|
1238
|
-
dispatchRef: "agent-knowledge:benchmark-suite",
|
|
1239
|
-
judges: [knowledgeBenchmarkJudge()],
|
|
1240
|
-
runDir: options.runDir,
|
|
1241
|
-
repo: options.repo,
|
|
1242
|
-
seed: options.seed,
|
|
1243
|
-
reps: options.reps,
|
|
1244
|
-
resumable: options.resumable,
|
|
1245
|
-
costCeiling: options.costCeiling,
|
|
1246
|
-
maxConcurrency: options.maxConcurrency,
|
|
1247
|
-
dispatchTimeoutMs: options.dispatchTimeoutMs,
|
|
1248
|
-
expectUsage: options.expectUsage ?? "off",
|
|
1249
|
-
storage,
|
|
1250
|
-
now: options.now
|
|
1251
|
-
});
|
|
1252
|
-
const report = summarizeKnowledgeBenchmarkCampaign({ scenarios, campaign });
|
|
1253
|
-
const reportJsonPath = join(campaign.runDir, "knowledge-benchmark-report.json");
|
|
1254
|
-
const reportMarkdownPath = join(campaign.runDir, "knowledge-benchmark-report.md");
|
|
1255
|
-
storage.write(reportJsonPath, `${JSON.stringify(report, null, 2)}
|
|
1256
|
-
`);
|
|
1257
|
-
storage.write(reportMarkdownPath, renderKnowledgeBenchmarkReportMarkdown(report));
|
|
1258
|
-
return {
|
|
1259
|
-
scenarios,
|
|
1260
|
-
campaign,
|
|
1261
|
-
report,
|
|
1262
|
-
reportJsonPath,
|
|
1263
|
-
reportMarkdownPath
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
function renderKnowledgeBenchmarkReportMarkdown(report) {
|
|
1267
|
-
return [
|
|
1268
|
-
"# Knowledge Benchmark Report",
|
|
1269
|
-
"",
|
|
1270
|
-
`- cases: ${report.totalCases}`,
|
|
1271
|
-
`- cells: ${report.totalCells} total, ${report.cellsFailed} failed, ${report.cellsCached} cached`,
|
|
1272
|
-
`- cost: $${formatNumber(report.totalCostUsd)}`,
|
|
1273
|
-
`- score: mean ${formatNumber(report.score.mean)}, median ${formatNumber(report.score.median)}, p90 ${formatNumber(report.score.p90)}, n=${report.score.n}`,
|
|
1274
|
-
"",
|
|
1275
|
-
"## Task Kinds",
|
|
1276
|
-
"",
|
|
1277
|
-
renderSliceTable(report.byTaskKind),
|
|
1278
|
-
"",
|
|
1279
|
-
"## Splits",
|
|
1280
|
-
"",
|
|
1281
|
-
renderSliceTable(report.bySplit),
|
|
1282
|
-
"",
|
|
1283
|
-
"## Dimensions",
|
|
1284
|
-
"",
|
|
1285
|
-
"| dimension | n | mean | p90 |",
|
|
1286
|
-
"| --- | ---: | ---: | ---: |",
|
|
1287
|
-
...Object.entries(report.dimensions).sort(([a], [b]) => a.localeCompare(b)).map(
|
|
1288
|
-
([key, dist]) => `| ${key} | ${dist.n} | ${formatNumber(dist.mean)} | ${formatNumber(dist.p90)} |`
|
|
1289
|
-
),
|
|
1290
|
-
""
|
|
1291
|
-
].join("\n");
|
|
1292
|
-
}
|
|
1293
|
-
function buildKnowledgeBenchmarkScenarios(cases, splits) {
|
|
1294
|
-
const splitSet = splits ? new Set(splits) : null;
|
|
1295
|
-
return cases.flatMap((testCase) => {
|
|
1296
|
-
const splitTag = testCase.split ?? "dev";
|
|
1297
|
-
if (splitSet && !splitSet.has(splitTag)) return [];
|
|
1298
|
-
return [
|
|
1299
|
-
compactObject({
|
|
1300
|
-
id: testCase.id,
|
|
1301
|
-
kind: "knowledge-benchmark",
|
|
1302
|
-
family: testCase.family,
|
|
1303
|
-
taskKind: testCase.taskKind,
|
|
1304
|
-
splitTag,
|
|
1305
|
-
tags: unique([splitTag, ...testCase.tags ?? []]),
|
|
1306
|
-
case: compactObject(testCase)
|
|
1307
|
-
})
|
|
1308
|
-
];
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
|
-
function knowledgeBenchmarkJudge() {
|
|
1312
|
-
return {
|
|
1313
|
-
name: "knowledge-benchmark",
|
|
1314
|
-
dimensions: [
|
|
1315
|
-
{ key: "score", description: "primary knowledge benchmark score" },
|
|
1316
|
-
{ key: "passed", description: "1 when the benchmark case passes" },
|
|
1317
|
-
{ key: "claim_recall", description: "required claim coverage" },
|
|
1318
|
-
{ key: "citation_recall", description: "expected citation/source coverage" },
|
|
1319
|
-
{ key: "hallucination_safe", description: "1 when no forbidden claim appears" },
|
|
1320
|
-
{ key: "memory_fact_recall", description: "current memory fact coverage" },
|
|
1321
|
-
{ key: "memory_event_recall", description: "expected memory event/source coverage" },
|
|
1322
|
-
{ key: "memory_stale_safe", description: "1 when obsolete memory is not reused" },
|
|
1323
|
-
{ key: "memory_actor_recall", description: "expected speaker/user attribution coverage" }
|
|
1324
|
-
],
|
|
1325
|
-
appliesTo: (scenario) => scenario.kind === "knowledge-benchmark",
|
|
1326
|
-
score({ artifact, scenario }) {
|
|
1327
|
-
const evaluation = scoreKnowledgeBenchmarkArtifact(scenario.case, artifact);
|
|
1328
|
-
return {
|
|
1329
|
-
dimensions: {
|
|
1330
|
-
score: evaluation.score,
|
|
1331
|
-
passed: evaluation.passed ? 1 : 0,
|
|
1332
|
-
...evaluation.dimensions
|
|
1333
|
-
},
|
|
1334
|
-
composite: evaluation.score,
|
|
1335
|
-
notes: evaluation.notes
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
function scoreKnowledgeBenchmarkArtifact(testCase, artifact) {
|
|
1341
|
-
if (testCase.taskKind === "retrieval") {
|
|
1342
|
-
const retrievalArtifact = normalizeRetrievalArtifact(testCase, artifact);
|
|
1343
|
-
const metrics = scoreRetrievalArtifact(retrievalArtifact, retrievalScenarioForCase(testCase));
|
|
1344
|
-
return {
|
|
1345
|
-
score: metrics.recall,
|
|
1346
|
-
passed: metrics.recall >= 1,
|
|
1347
|
-
dimensions: {
|
|
1348
|
-
recall: metrics.recall,
|
|
1349
|
-
mrr: metrics.mrr,
|
|
1350
|
-
ndcg: metrics.ndcg,
|
|
1351
|
-
precision_at_k: metrics.precisionAtK,
|
|
1352
|
-
expected_count: metrics.expectedCount,
|
|
1353
|
-
matched_count: metrics.matchedCount
|
|
1354
|
-
},
|
|
1355
|
-
notes: `matched ${metrics.matchedCount}/${metrics.expectedCount}; first_hit_rank=${metrics.firstHitRank ?? "none"}`,
|
|
1356
|
-
raw: { matchedTargetIds: metrics.matchedTargetIds }
|
|
1357
|
-
};
|
|
1358
|
-
}
|
|
1359
|
-
if (isKnowledgeMemoryBenchmarkCase(testCase)) {
|
|
1360
|
-
return scoreMemoryBenchmarkArtifact(testCase, artifact);
|
|
1361
|
-
}
|
|
1362
|
-
const answerArtifact = artifact;
|
|
1363
|
-
const text = answerArtifact.text ?? answerArtifact.answer ?? "";
|
|
1364
|
-
const required = scoreClaims(text, testCase.requiredClaims ?? []);
|
|
1365
|
-
const forbidden = scoreForbiddenClaims(text, testCase.forbiddenClaims ?? []);
|
|
1366
|
-
const citation = scoreCitationRecall(
|
|
1367
|
-
answerArtifact.citedSourceIds ?? [],
|
|
1368
|
-
testCase.expectedSourceIds ?? []
|
|
1369
|
-
);
|
|
1370
|
-
const components = [
|
|
1371
|
-
required.totalWeight > 0 ? required.recall : void 0,
|
|
1372
|
-
testCase.expectedSourceIds && testCase.expectedSourceIds.length > 0 ? citation : void 0,
|
|
1373
|
-
forbidden.safe
|
|
1374
|
-
].filter((value) => value !== void 0);
|
|
1375
|
-
const score = mean(components);
|
|
1376
|
-
return {
|
|
1377
|
-
score,
|
|
1378
|
-
passed: score >= 1,
|
|
1379
|
-
dimensions: {
|
|
1380
|
-
claim_recall: required.recall,
|
|
1381
|
-
citation_recall: citation,
|
|
1382
|
-
hallucination_safe: forbidden.safe,
|
|
1383
|
-
forbidden_claim_rate: forbidden.rate,
|
|
1384
|
-
required_claim_count: required.total,
|
|
1385
|
-
matched_claim_count: required.matched,
|
|
1386
|
-
forbidden_claim_count: forbidden.total,
|
|
1387
|
-
matched_forbidden_claim_count: forbidden.matched
|
|
1388
|
-
},
|
|
1389
|
-
notes: `required=${required.matched}/${required.total}; forbidden=${forbidden.matched}/${forbidden.total}; citation_recall=${citation.toFixed(3)}`,
|
|
1390
|
-
raw: {
|
|
1391
|
-
matchedRequiredClaimIds: required.matchedIds,
|
|
1392
|
-
matchedForbiddenClaimIds: forbidden.matchedIds
|
|
1393
|
-
}
|
|
1394
|
-
};
|
|
1395
|
-
}
|
|
1396
|
-
function summarizeKnowledgeBenchmarkCampaign(input) {
|
|
1397
|
-
const scenariosById = new Map(input.scenarios.map((scenario) => [scenario.id, scenario]));
|
|
1398
|
-
const rows = input.campaign.cells.map((cell) => {
|
|
1399
|
-
const score = Object.values(cell.judgeScores)[0];
|
|
1400
|
-
const scenario = scenariosById.get(cell.scenarioId);
|
|
1401
|
-
return {
|
|
1402
|
-
cell,
|
|
1403
|
-
scenario,
|
|
1404
|
-
composite: score?.composite ?? 0,
|
|
1405
|
-
passed: (score?.dimensions.passed ?? 0) >= 1,
|
|
1406
|
-
dimensions: score?.dimensions ?? {}
|
|
1407
|
-
};
|
|
1408
|
-
});
|
|
1409
|
-
const successful = rows.filter((row) => !row.cell.error);
|
|
1410
|
-
return {
|
|
1411
|
-
totalCases: input.scenarios.length,
|
|
1412
|
-
totalCells: input.campaign.cells.length,
|
|
1413
|
-
cellsFailed: input.campaign.aggregates.cellsFailed,
|
|
1414
|
-
cellsCached: input.campaign.aggregates.cellsCached,
|
|
1415
|
-
totalCostUsd: input.campaign.aggregates.totalCostUsd,
|
|
1416
|
-
bySplit: summarizeSlices(successful, (row) => row.scenario?.splitTag ?? "unknown"),
|
|
1417
|
-
byFamily: summarizeSlices(successful, (row) => row.scenario?.family ?? "unknown"),
|
|
1418
|
-
byTaskKind: summarizeSlices(successful, (row) => row.scenario?.taskKind ?? "unknown"),
|
|
1419
|
-
dimensions: summarizeDimensions(successful.map((row) => row.dimensions)),
|
|
1420
|
-
score: distribution(successful.map((row) => row.composite))
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1423
|
-
function scoreMemoryBenchmarkArtifact(testCase, artifact) {
|
|
1424
|
-
const memoryArtifact = artifact;
|
|
1425
|
-
const text = [
|
|
1426
|
-
memoryArtifact.text,
|
|
1427
|
-
memoryArtifact.answer,
|
|
1428
|
-
...memoryArtifact.rememberedFacts ?? []
|
|
1429
|
-
].filter((part) => typeof part === "string" && part.length > 0).join("\n");
|
|
1430
|
-
const required = scoreClaims(text, testCase.requiredFacts ?? []);
|
|
1431
|
-
const forbidden = scoreForbiddenClaims(text, testCase.forbiddenFacts ?? []);
|
|
1432
|
-
const eventIds = unique([
|
|
1433
|
-
...memoryArtifact.citedEventIds ?? [],
|
|
1434
|
-
...memoryArtifact.usedMemoryIds ?? []
|
|
1435
|
-
]);
|
|
1436
|
-
const eventRecall = scoreCitationRecall(eventIds, testCase.expectedEventIds ?? []);
|
|
1437
|
-
const actorRecall = scoreCitationRecall(
|
|
1438
|
-
memoryArtifact.actorIds ?? [],
|
|
1439
|
-
testCase.expectedActorIds ?? []
|
|
1440
|
-
);
|
|
1441
|
-
const components = [
|
|
1442
|
-
required.totalWeight > 0 ? required.recall : void 0,
|
|
1443
|
-
testCase.expectedEventIds && testCase.expectedEventIds.length > 0 ? eventRecall : void 0,
|
|
1444
|
-
testCase.expectedActorIds && testCase.expectedActorIds.length > 0 ? actorRecall : void 0,
|
|
1445
|
-
forbidden.safe
|
|
1446
|
-
].filter((value) => value !== void 0);
|
|
1447
|
-
const score = mean(components);
|
|
1448
|
-
return {
|
|
1449
|
-
score,
|
|
1450
|
-
passed: score >= 1,
|
|
1451
|
-
dimensions: {
|
|
1452
|
-
memory_fact_recall: required.recall,
|
|
1453
|
-
memory_event_recall: eventRecall,
|
|
1454
|
-
memory_actor_recall: actorRecall,
|
|
1455
|
-
memory_stale_safe: forbidden.safe,
|
|
1456
|
-
memory_stale_rate: forbidden.rate,
|
|
1457
|
-
memory_required_fact_count: required.total,
|
|
1458
|
-
memory_matched_fact_count: required.matched,
|
|
1459
|
-
memory_forbidden_fact_count: forbidden.total,
|
|
1460
|
-
memory_matched_forbidden_fact_count: forbidden.matched
|
|
1461
|
-
},
|
|
1462
|
-
notes: `memory required=${required.matched}/${required.total}; stale=${forbidden.matched}/${forbidden.total}; event_recall=${eventRecall.toFixed(3)}; actor_recall=${actorRecall.toFixed(3)}`,
|
|
1463
|
-
raw: {
|
|
1464
|
-
matchedRequiredFactIds: required.matchedIds,
|
|
1465
|
-
matchedForbiddenFactIds: forbidden.matchedIds,
|
|
1466
|
-
citedEventIds: eventIds,
|
|
1467
|
-
actorIds: memoryArtifact.actorIds ?? []
|
|
1468
|
-
}
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
function retrievalScenarioForCase(testCase) {
|
|
1472
|
-
return {
|
|
1473
|
-
id: testCase.id,
|
|
1474
|
-
kind: "retrieval-eval",
|
|
1475
|
-
query: testCase.query,
|
|
1476
|
-
expected: testCase.expected,
|
|
1477
|
-
...testCase.k !== void 0 ? { k: testCase.k } : {}
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
function normalizeRetrievalArtifact(testCase, artifact) {
|
|
1481
|
-
const maybe = artifact;
|
|
1482
|
-
const hits = maybe.hits ?? [];
|
|
1483
|
-
if (Array.isArray(maybe.hits) && maybe.query && maybe.requestedK !== void 0) {
|
|
1484
|
-
return maybe;
|
|
1485
|
-
}
|
|
1486
|
-
return {
|
|
1487
|
-
config: {},
|
|
1488
|
-
query: testCase.query,
|
|
1489
|
-
requestedK: testCase.k ?? Math.max(1, hits.length),
|
|
1490
|
-
hits,
|
|
1491
|
-
durationMs: maybe.durationMs ?? 0,
|
|
1492
|
-
...maybe.costUsd !== void 0 ? { costUsd: maybe.costUsd } : {},
|
|
1493
|
-
...maybe.metadata ? { metadata: maybe.metadata } : {}
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
function defaultDocumentTarget(documentId, targetKind) {
|
|
1497
|
-
switch (targetKind) {
|
|
1498
|
-
case "page":
|
|
1499
|
-
return { kind: "page", pageId: documentId };
|
|
1500
|
-
case "page-path":
|
|
1501
|
-
return { kind: "page-path", path: documentId };
|
|
1502
|
-
case "source":
|
|
1503
|
-
return { kind: "source", sourceId: documentId };
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
function hitForExpectedTarget(expected, fallbackId) {
|
|
1507
|
-
if (!expected) {
|
|
1508
|
-
return {
|
|
1509
|
-
pageId: fallbackId,
|
|
1510
|
-
path: `${fallbackId}.md`,
|
|
1511
|
-
rank: 1
|
|
1512
|
-
};
|
|
1513
|
-
}
|
|
1514
|
-
switch (expected.kind) {
|
|
1515
|
-
case "page":
|
|
1516
|
-
return {
|
|
1517
|
-
pageId: expected.pageId,
|
|
1518
|
-
path: `${expected.pageId}.md`,
|
|
1519
|
-
rank: 1
|
|
1520
|
-
};
|
|
1521
|
-
case "page-path":
|
|
1522
|
-
return {
|
|
1523
|
-
pageId: expected.path,
|
|
1524
|
-
path: expected.path,
|
|
1525
|
-
rank: 1
|
|
1526
|
-
};
|
|
1527
|
-
case "source":
|
|
1528
|
-
return {
|
|
1529
|
-
pageId: expected.sourceId,
|
|
1530
|
-
path: `${expected.sourceId}.md`,
|
|
1531
|
-
sourceIds: [expected.sourceId],
|
|
1532
|
-
rank: 1
|
|
1533
|
-
};
|
|
1534
|
-
case "source-anchor":
|
|
1535
|
-
return {
|
|
1536
|
-
pageId: expected.sourceId,
|
|
1537
|
-
path: `${expected.sourceId}.md`,
|
|
1538
|
-
sourceIds: [expected.sourceId],
|
|
1539
|
-
sourceSpans: [{ sourceId: expected.sourceId, anchorId: expected.anchorId }],
|
|
1540
|
-
rank: 1
|
|
1541
|
-
};
|
|
1542
|
-
case "source-span":
|
|
1543
|
-
return {
|
|
1544
|
-
pageId: expected.sourceId,
|
|
1545
|
-
path: `${expected.sourceId}.md`,
|
|
1546
|
-
sourceIds: [expected.sourceId],
|
|
1547
|
-
sourceSpans: [
|
|
1548
|
-
{
|
|
1549
|
-
sourceId: expected.sourceId,
|
|
1550
|
-
charStart: expected.charStart,
|
|
1551
|
-
charEnd: expected.charEnd
|
|
1552
|
-
}
|
|
1553
|
-
],
|
|
1554
|
-
rank: 1
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
function memoryLifecycleCase(input) {
|
|
1559
|
-
const staleEventId = `${input.id}:stale`;
|
|
1560
|
-
const currentEventId = `${input.id}:current`;
|
|
1561
|
-
return {
|
|
1562
|
-
id: input.id,
|
|
1563
|
-
family: input.family,
|
|
1564
|
-
taskKind: input.taskKind,
|
|
1565
|
-
split: input.split,
|
|
1566
|
-
tags: unique(["first-party-memory-lifecycle", input.taskKind, input.split]),
|
|
1567
|
-
source: input.source,
|
|
1568
|
-
events: [
|
|
1569
|
-
{
|
|
1570
|
-
id: staleEventId,
|
|
1571
|
-
actorId: input.actorId,
|
|
1572
|
-
sessionId: `${input.id}:session-1`,
|
|
1573
|
-
timestamp: "2026-01-01T00:00:00.000Z",
|
|
1574
|
-
text: input.staleText
|
|
1575
|
-
},
|
|
1576
|
-
{
|
|
1577
|
-
id: currentEventId,
|
|
1578
|
-
actorId: input.actorId,
|
|
1579
|
-
sessionId: `${input.id}:session-2`,
|
|
1580
|
-
timestamp: "2026-02-01T00:00:00.000Z",
|
|
1581
|
-
text: input.currentText
|
|
1582
|
-
}
|
|
1583
|
-
],
|
|
1584
|
-
prompt: input.prompt,
|
|
1585
|
-
requiredFacts: [
|
|
1586
|
-
{
|
|
1587
|
-
id: `${input.id}:required-1`,
|
|
1588
|
-
anyOf: [input.required],
|
|
1589
|
-
sourceEventIds: [currentEventId]
|
|
1590
|
-
},
|
|
1591
|
-
...input.extraRequired ? [
|
|
1592
|
-
{
|
|
1593
|
-
id: `${input.id}:required-2`,
|
|
1594
|
-
anyOf: [input.extraRequired],
|
|
1595
|
-
sourceEventIds: [currentEventId]
|
|
1596
|
-
}
|
|
1597
|
-
] : []
|
|
1598
|
-
],
|
|
1599
|
-
forbiddenFacts: [
|
|
1600
|
-
{
|
|
1601
|
-
id: `${input.id}:stale`,
|
|
1602
|
-
anyOf: [input.forbidden],
|
|
1603
|
-
sourceEventIds: [staleEventId],
|
|
1604
|
-
obsolete: true
|
|
1605
|
-
}
|
|
1606
|
-
],
|
|
1607
|
-
expectedEventIds: [currentEventId],
|
|
1608
|
-
expectedActorIds: [input.actorId],
|
|
1609
|
-
referenceAnswer: input.required
|
|
1610
|
-
};
|
|
1611
|
-
}
|
|
1612
|
-
function renderMemoryAdapterRankingMarkdown(rows) {
|
|
1613
|
-
return [
|
|
1614
|
-
"# Memory Adapter Ranking",
|
|
1615
|
-
"",
|
|
1616
|
-
"| rank | candidate | adapter | cases | cells | failed | mean score | pass rate | cost |",
|
|
1617
|
-
"| ---: | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
1618
|
-
...rows.map(
|
|
1619
|
-
(row) => `| ${row.rank} | ${row.label} | ${row.adapterId} | ${row.totalCases} | ${row.totalCells} | ${row.cellsFailed} | ${formatNumber(row.scoreMean)} | ${formatNumber(row.passRate)} | $${formatNumber(row.totalCostUsd)} |`
|
|
1620
|
-
),
|
|
1621
|
-
""
|
|
1622
|
-
].join("\n");
|
|
1623
|
-
}
|
|
1624
|
-
function benchmarkMemoryScope(candidateId, testCase, scope = {}) {
|
|
1625
|
-
return {
|
|
1626
|
-
...scope,
|
|
1627
|
-
namespace: scope.namespace ?? "agent-knowledge-memory-benchmark",
|
|
1628
|
-
tags: {
|
|
1629
|
-
...scope.tags ?? {},
|
|
1630
|
-
benchmarkCandidateId: candidateId,
|
|
1631
|
-
benchmarkCaseId: testCase.id
|
|
1632
|
-
}
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
function memoryEventId(hit) {
|
|
1636
|
-
const eventId = hit.metadata?.eventId;
|
|
1637
|
-
return typeof eventId === "string" ? eventId : void 0;
|
|
1638
|
-
}
|
|
1639
|
-
function memoryActorId(hit) {
|
|
1640
|
-
const actorId = hit.metadata?.actorId;
|
|
1641
|
-
return typeof actorId === "string" ? actorId : void 0;
|
|
1642
|
-
}
|
|
1643
|
-
function memoryScopeMatches(stored, requested) {
|
|
1644
|
-
if (!requested) return true;
|
|
1645
|
-
if (requested.tenantId !== void 0 && stored?.tenantId !== requested.tenantId) return false;
|
|
1646
|
-
if (requested.userId !== void 0 && stored?.userId !== requested.userId) return false;
|
|
1647
|
-
if (requested.sessionId !== void 0 && stored?.sessionId !== requested.sessionId) return false;
|
|
1648
|
-
if (requested.namespace !== void 0 && stored?.namespace !== requested.namespace) return false;
|
|
1649
|
-
for (const [key, value] of Object.entries(requested.tags ?? {})) {
|
|
1650
|
-
if (stored?.tags?.[key] !== value) return false;
|
|
1651
|
-
}
|
|
1652
|
-
return true;
|
|
1653
|
-
}
|
|
1654
|
-
function tokenOverlap(query, text) {
|
|
1655
|
-
const queryTokens = new Set(tokenize(query));
|
|
1656
|
-
if (queryTokens.size === 0) return 0;
|
|
1657
|
-
const textTokens = new Set(tokenize(text));
|
|
1658
|
-
let matched = 0;
|
|
1659
|
-
for (const token of queryTokens) {
|
|
1660
|
-
if (textTokens.has(token)) matched += 1;
|
|
1661
|
-
}
|
|
1662
|
-
return matched / queryTokens.size;
|
|
1663
|
-
}
|
|
1664
|
-
function tokenize(text) {
|
|
1665
|
-
const stop = /* @__PURE__ */ new Set([
|
|
1666
|
-
"the",
|
|
1667
|
-
"and",
|
|
1668
|
-
"for",
|
|
1669
|
-
"this",
|
|
1670
|
-
"that",
|
|
1671
|
-
"with",
|
|
1672
|
-
"what",
|
|
1673
|
-
"should",
|
|
1674
|
-
"agent",
|
|
1675
|
-
"user",
|
|
1676
|
-
"current",
|
|
1677
|
-
"now",
|
|
1678
|
-
"use"
|
|
1679
|
-
]);
|
|
1680
|
-
return text.toLowerCase().split(/[^a-z0-9/]+/).filter((token) => token.length > 2 && !stop.has(token));
|
|
1681
|
-
}
|
|
1682
|
-
function renderMemoryHits(hits) {
|
|
1683
|
-
return hits.map((hit, index) => {
|
|
1684
|
-
const eventId = memoryEventId(hit);
|
|
1685
|
-
const actorId = memoryActorId(hit);
|
|
1686
|
-
return [
|
|
1687
|
-
`[${index + 1}] ${hit.title ?? hit.id}`,
|
|
1688
|
-
eventId ? `event=${eventId}` : "",
|
|
1689
|
-
actorId ? `actor=${actorId}` : "",
|
|
1690
|
-
hit.text
|
|
1691
|
-
].filter(Boolean).join("\n");
|
|
1692
|
-
}).join("\n\n");
|
|
1693
|
-
}
|
|
1694
|
-
function isKnowledgeMemoryBenchmarkCase(testCase) {
|
|
1695
|
-
return testCase.taskKind.startsWith("memory-");
|
|
1696
|
-
}
|
|
1697
|
-
function scoreClaims(text, claims) {
|
|
1698
|
-
let matched = 0;
|
|
1699
|
-
let matchedWeight = 0;
|
|
1700
|
-
let totalWeight = 0;
|
|
1701
|
-
const matchedIds = [];
|
|
1702
|
-
const haystack = text.toLowerCase();
|
|
1703
|
-
for (const claim of claims) {
|
|
1704
|
-
const weight = claim.weight ?? 1;
|
|
1705
|
-
totalWeight += weight;
|
|
1706
|
-
if (claim.anyOf.some((fragment) => haystack.includes(fragment.toLowerCase()))) {
|
|
1707
|
-
matched += 1;
|
|
1708
|
-
matchedWeight += weight;
|
|
1709
|
-
matchedIds.push(claim.id);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
return {
|
|
1713
|
-
total: claims.length,
|
|
1714
|
-
matched,
|
|
1715
|
-
totalWeight,
|
|
1716
|
-
recall: totalWeight === 0 ? 1 : matchedWeight / totalWeight,
|
|
1717
|
-
matchedIds
|
|
1718
|
-
};
|
|
1719
|
-
}
|
|
1720
|
-
function scoreForbiddenClaims(text, claims) {
|
|
1721
|
-
const matched = scoreClaims(text, claims);
|
|
1722
|
-
return {
|
|
1723
|
-
total: claims.length,
|
|
1724
|
-
matched: matched.matched,
|
|
1725
|
-
matchedIds: matched.matchedIds,
|
|
1726
|
-
rate: claims.length === 0 ? 0 : matched.matched / claims.length,
|
|
1727
|
-
safe: matched.matched === 0 ? 1 : 0
|
|
1728
|
-
};
|
|
1729
|
-
}
|
|
1730
|
-
function scoreCitationRecall(citedSourceIds, expectedSourceIds) {
|
|
1731
|
-
if (expectedSourceIds.length === 0) return 1;
|
|
1732
|
-
const cited = new Set(citedSourceIds);
|
|
1733
|
-
const matched = expectedSourceIds.filter((sourceId) => cited.has(sourceId)).length;
|
|
1734
|
-
return matched / expectedSourceIds.length;
|
|
1735
|
-
}
|
|
1736
|
-
function summarizeDimensions(rows) {
|
|
1737
|
-
const values = /* @__PURE__ */ new Map();
|
|
1738
|
-
for (const row of rows) {
|
|
1739
|
-
for (const [key, value] of Object.entries(row)) {
|
|
1740
|
-
if (!Number.isFinite(value)) continue;
|
|
1741
|
-
const list = values.get(key) ?? [];
|
|
1742
|
-
list.push(value);
|
|
1743
|
-
values.set(key, list);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
return Object.fromEntries([...values.entries()].map(([key, vals]) => [key, distribution(vals)]));
|
|
1747
|
-
}
|
|
1748
|
-
function summarizeSlices(rows, keyOf) {
|
|
1749
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
1750
|
-
for (const row of rows) {
|
|
1751
|
-
const key = keyOf(row);
|
|
1752
|
-
const list = grouped.get(key) ?? [];
|
|
1753
|
-
list.push(row);
|
|
1754
|
-
grouped.set(key, list);
|
|
1755
|
-
}
|
|
1756
|
-
return Object.fromEntries(
|
|
1757
|
-
[...grouped.entries()].map(([key, list]) => {
|
|
1758
|
-
const withShape = list;
|
|
1759
|
-
return [
|
|
1760
|
-
key,
|
|
1761
|
-
{
|
|
1762
|
-
n: list.length,
|
|
1763
|
-
meanScore: mean(withShape.map((row) => row.composite)),
|
|
1764
|
-
passRate: mean(withShape.map((row) => row.passed ? 1 : 0)),
|
|
1765
|
-
score: distribution(withShape.map((row) => row.composite))
|
|
1766
|
-
}
|
|
1767
|
-
];
|
|
1768
|
-
})
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1771
|
-
function distribution(values) {
|
|
1772
|
-
const finite = [...values].filter(Number.isFinite).sort((a, b) => a - b);
|
|
1773
|
-
if (finite.length === 0) return { n: 0, min: 0, mean: 0, median: 0, p90: 0, max: 0 };
|
|
1774
|
-
return {
|
|
1775
|
-
n: finite.length,
|
|
1776
|
-
min: finite[0],
|
|
1777
|
-
mean: mean(finite),
|
|
1778
|
-
median: percentile(finite, 0.5),
|
|
1779
|
-
p90: percentile(finite, 0.9),
|
|
1780
|
-
max: finite[finite.length - 1]
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
function percentile(sortedValues, p) {
|
|
1784
|
-
if (sortedValues.length === 0) return 0;
|
|
1785
|
-
const index = Math.min(
|
|
1786
|
-
sortedValues.length - 1,
|
|
1787
|
-
Math.max(0, Math.ceil(p * sortedValues.length) - 1)
|
|
1788
|
-
);
|
|
1789
|
-
return sortedValues[index];
|
|
1790
|
-
}
|
|
1791
|
-
function mean(values) {
|
|
1792
|
-
const finite = values.filter(Number.isFinite);
|
|
1793
|
-
if (finite.length === 0) return 0;
|
|
1794
|
-
return finite.reduce((sum, value) => sum + value, 0) / finite.length;
|
|
1795
|
-
}
|
|
1796
|
-
function unique(values) {
|
|
1797
|
-
return [...new Set(values.filter(Boolean))];
|
|
1798
|
-
}
|
|
1799
|
-
function renderSliceTable(slices) {
|
|
1800
|
-
const rows = Object.entries(slices).map(
|
|
1801
|
-
([key, slice]) => `| ${key} | ${slice.n} | ${formatNumber(slice.meanScore)} | ${formatNumber(slice.passRate)} | ${formatNumber(slice.score.p90)} |`
|
|
1802
|
-
);
|
|
1803
|
-
return [
|
|
1804
|
-
"| slice | n | mean score | pass rate | score p90 |",
|
|
1805
|
-
"| --- | ---: | ---: | ---: | ---: |",
|
|
1806
|
-
...rows.length ? rows : ["| none | 0 | 0 | 0 | 0 |"]
|
|
1807
|
-
].join("\n");
|
|
1808
|
-
}
|
|
1809
|
-
function formatNumber(value) {
|
|
1810
|
-
if (!Number.isFinite(value)) return "0";
|
|
1811
|
-
return value.toFixed(value === 0 || Math.abs(value) >= 10 ? 0 : 3);
|
|
1812
|
-
}
|
|
1813
|
-
function compactObject(value) {
|
|
1814
|
-
if (Array.isArray(value)) return value.map(compactObject);
|
|
1815
|
-
if (!value || typeof value !== "object") return value;
|
|
1816
|
-
return Object.fromEntries(
|
|
1817
|
-
Object.entries(value).filter(([, entry]) => entry !== void 0).map(([key, entry]) => [key, compactObject(entry)])
|
|
1818
|
-
);
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
export {
|
|
1822
|
-
retrievalConfigSurface,
|
|
1823
|
-
retrievalConfigFromSurface,
|
|
1824
|
-
buildRetrievalEvalDispatch,
|
|
1825
|
-
retrievalRecallJudge,
|
|
1826
|
-
scoreRetrievalArtifact,
|
|
1827
|
-
buildRetrievalParameterCandidates,
|
|
1828
|
-
retrievalParameterSweepProposer,
|
|
1829
|
-
runRetrievalImprovementLoop,
|
|
1830
|
-
INDUSTRY_RAG_BENCHMARKS,
|
|
1831
|
-
INDUSTRY_MEMORY_BENCHMARKS,
|
|
1832
|
-
buildIndustryRagBenchmarkSmokeCases,
|
|
1833
|
-
respondToIndustryRagBenchmarkSmokeCase,
|
|
1834
|
-
buildIndustryMemoryBenchmarkSmokeCases,
|
|
1835
|
-
respondToIndustryMemoryBenchmarkSmokeCase,
|
|
1836
|
-
buildFirstPartyMemoryLifecycleBenchmarkCases,
|
|
1837
|
-
createMemoryAdapterBenchmarkResponder,
|
|
1838
|
-
runMemoryAdapterBenchmark,
|
|
1839
|
-
createNoopMemoryBenchmarkAdapter,
|
|
1840
|
-
createInMemoryBenchmarkAdapter,
|
|
1841
|
-
parseKnowledgeBenchmarkJsonl,
|
|
1842
|
-
parseKnowledgeBenchmarkQrels,
|
|
1843
|
-
buildRetrievalBenchmarkCasesFromQrels,
|
|
1844
|
-
runKnowledgeBenchmarkSuite,
|
|
1845
|
-
renderKnowledgeBenchmarkReportMarkdown,
|
|
1846
|
-
buildKnowledgeBenchmarkScenarios,
|
|
1847
|
-
knowledgeBenchmarkJudge,
|
|
1848
|
-
scoreKnowledgeBenchmarkArtifact,
|
|
1849
|
-
summarizeKnowledgeBenchmarkCampaign,
|
|
1850
|
-
scoreMemoryBenchmarkArtifact,
|
|
1851
|
-
isKnowledgeMemoryBenchmarkCase
|
|
1852
|
-
};
|
|
1853
|
-
//# sourceMappingURL=chunk-RIHIYCX2.js.map
|