@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.
@@ -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
+ }