@b4run/evals 0.8.28
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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/define-eval.d.ts +3 -0
- package/dist/define-eval.d.ts.map +1 -0
- package/dist/define-eval.js +15 -0
- package/dist/gate.d.ts +13 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +64 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/llm-judge.d.ts +16 -0
- package/dist/llm-judge.d.ts.map +1 -0
- package/dist/llm-judge.js +57 -0
- package/dist/regex-safety.d.ts +2 -0
- package/dist/regex-safety.d.ts.map +1 -0
- package/dist/regex-safety.js +66 -0
- package/dist/resolve-dataset.d.ts +3 -0
- package/dist/resolve-dataset.d.ts.map +1 -0
- package/dist/resolve-dataset.js +38 -0
- package/dist/run-eval.d.ts +10 -0
- package/dist/run-eval.d.ts.map +1 -0
- package/dist/run-eval.js +56 -0
- package/dist/score.d.ts +8 -0
- package/dist/score.d.ts.map +1 -0
- package/dist/score.js +24 -0
- package/dist/scorers.d.ts +50 -0
- package/dist/scorers.d.ts.map +1 -0
- package/dist/scorers.js +141 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +55 -0
package/dist/scorers.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createSafeRegexTester } from "./regex-safety.js";
|
|
2
|
+
function deepEqual(a, b) {
|
|
3
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
4
|
+
}
|
|
5
|
+
/** finalMessage === case.expected (string compare). */
|
|
6
|
+
export function exactMatch(opts) {
|
|
7
|
+
return {
|
|
8
|
+
name: "exactMatch",
|
|
9
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
10
|
+
score: (run, c) => (run.finalMessage === String(c.expected ?? "") ? 1 : 0),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function contains(substring, opts) {
|
|
14
|
+
return {
|
|
15
|
+
name: `contains(${substring})`,
|
|
16
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
17
|
+
score: (run) => (run.finalMessage.includes(substring) ? 1 : 0),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function regex(re, opts) {
|
|
21
|
+
const test = createSafeRegexTester(re);
|
|
22
|
+
return {
|
|
23
|
+
name: `regex(${re.source})`,
|
|
24
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
25
|
+
score: (run) => (test(run.finalMessage) ? 1 : 0),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Deep-equals case.expected against parsed finalMessage (default) or a selector. */
|
|
29
|
+
export function jsonEquals(opts) {
|
|
30
|
+
return {
|
|
31
|
+
name: "jsonEquals",
|
|
32
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
33
|
+
score: (run, c) => {
|
|
34
|
+
let actual;
|
|
35
|
+
if (opts?.select) {
|
|
36
|
+
actual = opts.select(run);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
try {
|
|
40
|
+
actual = JSON.parse(run.finalMessage);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return deepEqual(actual, c.expected) ? 1 : 0;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function toolCalled(name, opts) {
|
|
51
|
+
return {
|
|
52
|
+
name: `toolCalled(${name})`,
|
|
53
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
54
|
+
score: (run) => {
|
|
55
|
+
const calls = run.toolCalls.filter((t) => t.name === name);
|
|
56
|
+
if (calls.length === 0)
|
|
57
|
+
return 0;
|
|
58
|
+
if (!opts?.withArgs)
|
|
59
|
+
return 1;
|
|
60
|
+
const want = opts.withArgs;
|
|
61
|
+
const hit = calls.some((call) => {
|
|
62
|
+
const args = (call.args ?? {});
|
|
63
|
+
return Object.entries(want).every(([k, v]) => deepEqual(args[k], v));
|
|
64
|
+
});
|
|
65
|
+
return hit ? 1 : 0;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function tokensUnder(budget, opts) {
|
|
70
|
+
return {
|
|
71
|
+
name: `tokensUnder(${budget})`,
|
|
72
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
73
|
+
score: (run) => (run.tokens.length < budget ? 1 : 0),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function custom(fn, opts) {
|
|
77
|
+
return {
|
|
78
|
+
name: opts?.name ?? "custom",
|
|
79
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
80
|
+
score: fn,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Memory scorers
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/** Returns the string content of all tool results whose name matches `toolName`. */
|
|
87
|
+
function recallOutputs(run, toolName) {
|
|
88
|
+
return run.toolResults
|
|
89
|
+
.filter((r) => r.name === toolName)
|
|
90
|
+
.map((r) => (typeof r.content === "string" ? r.content : JSON.stringify(r.content)));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Score 1 if every id in `expectedIds` appears in at least one `recall` tool
|
|
94
|
+
* result string; score 0 with a reason listing missing ids.
|
|
95
|
+
*/
|
|
96
|
+
export function memoryRecalled(expectedIds, opts) {
|
|
97
|
+
return {
|
|
98
|
+
name: `memoryRecalled(${expectedIds.join(",")})`,
|
|
99
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
100
|
+
score: (run) => {
|
|
101
|
+
const outputs = recallOutputs(run, "recall");
|
|
102
|
+
const combined = outputs.join("\n");
|
|
103
|
+
const missing = expectedIds.filter((id) => !combined.includes(id));
|
|
104
|
+
if (missing.length === 0)
|
|
105
|
+
return 1;
|
|
106
|
+
return { score: 0, reason: `Missing memory ids: ${missing.join(", ")}` };
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Score 1 if `run.finalMessage` contains `expectedValue` (freshness check —
|
|
112
|
+
* the newer value surfaced in the response); else 0.
|
|
113
|
+
*/
|
|
114
|
+
export function memoryFresh(expectedValue, opts) {
|
|
115
|
+
return {
|
|
116
|
+
name: `memoryFresh(${expectedValue})`,
|
|
117
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
118
|
+
score: (run) => run.finalMessage.includes(expectedValue)
|
|
119
|
+
? 1
|
|
120
|
+
: { score: 0, reason: `Expected "${expectedValue}" in finalMessage` },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Score 1 if `forbidden` does NOT appear in any recall tool output or
|
|
125
|
+
* finalMessage (no cross-namespace leak); score 0 if it leaks.
|
|
126
|
+
*/
|
|
127
|
+
export function memoryIsolated(forbidden, opts) {
|
|
128
|
+
return {
|
|
129
|
+
name: `memoryIsolated(${forbidden})`,
|
|
130
|
+
...(opts?.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
131
|
+
score: (run) => {
|
|
132
|
+
const outputs = recallOutputs(run, "recall");
|
|
133
|
+
const leaksInRecall = outputs.some((o) => o.includes(forbidden));
|
|
134
|
+
const leaksInMessage = run.finalMessage.includes(forbidden);
|
|
135
|
+
if (leaksInRecall || leaksInMessage) {
|
|
136
|
+
return { score: 0, reason: `Cross-namespace leak: "${forbidden}" found in output` };
|
|
137
|
+
}
|
|
138
|
+
return 1;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|