vitest-evals 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -172
- package/dist/index.d.mts +2 -98
- package/dist/index.d.ts +2 -98
- package/dist/index.js +270 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +269 -11
- package/dist/index.mjs.map +1 -1
- package/dist/scorers/index.d.mts +2 -0
- package/dist/scorers/index.d.ts +2 -0
- package/dist/scorers/index.js +282 -0
- package/dist/scorers/index.js.map +1 -0
- package/dist/scorers/index.mjs +256 -0
- package/dist/scorers/index.mjs.map +1 -0
- package/dist/scorers/toolCallScorer.d.mts +240 -0
- package/dist/scorers/toolCallScorer.d.ts +240 -0
- package/dist/scorers/toolCallScorer.js +280 -0
- package/dist/scorers/toolCallScorer.js.map +1 -0
- package/dist/scorers/toolCallScorer.mjs +256 -0
- package/dist/scorers/toolCallScorer.mjs.map +1 -0
- package/package.json +16 -4
- package/dist/compatibility.test.d.mts +0 -2
- package/dist/compatibility.test.d.ts +0 -2
- package/dist/compatibility.test.js +0 -45009
- package/dist/compatibility.test.js.map +0 -1
- package/dist/compatibility.test.mjs +0 -45864
- package/dist/compatibility.test.mjs.map +0 -1
- package/dist/formatScores.test.d.mts +0 -2
- package/dist/formatScores.test.d.ts +0 -2
- package/dist/formatScores.test.js +0 -195
- package/dist/formatScores.test.js.map +0 -1
- package/dist/formatScores.test.mjs +0 -194
- package/dist/formatScores.test.mjs.map +0 -1
- package/dist/wrapText.test.d.mts +0 -2
- package/dist/wrapText.test.d.ts +0 -2
- package/dist/wrapText.test.js +0 -162
- package/dist/wrapText.test.js.map +0 -1
- package/dist/wrapText.test.mjs +0 -161
- package/dist/wrapText.test.mjs.map +0 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var __async = (__this, __arguments, generator) => {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
var fulfilled = (value) => {
|
|
22
|
+
try {
|
|
23
|
+
step(generator.next(value));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
reject(e);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var rejected = (value) => {
|
|
29
|
+
try {
|
|
30
|
+
step(generator.throw(value));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
reject(e);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
36
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/scorers/index.ts
|
|
41
|
+
var scorers_exports = {};
|
|
42
|
+
__export(scorers_exports, {
|
|
43
|
+
ToolCallScorer: () => ToolCallScorer
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(scorers_exports);
|
|
46
|
+
|
|
47
|
+
// src/scorers/toolCallScorer.ts
|
|
48
|
+
function fuzzyMatch(expected, actual) {
|
|
49
|
+
if (expected == null || actual == null) {
|
|
50
|
+
return expected === actual;
|
|
51
|
+
}
|
|
52
|
+
if (typeof expected === "object" && typeof actual === "object" && !Array.isArray(expected)) {
|
|
53
|
+
return Object.entries(expected).every(
|
|
54
|
+
([key, value]) => key in actual && fuzzyMatch(value, actual[key])
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
58
|
+
return actual.toLowerCase().includes(expected.toLowerCase());
|
|
59
|
+
}
|
|
60
|
+
if (typeof expected === "number" && typeof actual === "number") {
|
|
61
|
+
const tolerance = Math.max(Math.abs(expected) * 1e-3, 1e-3);
|
|
62
|
+
return Math.abs(expected - actual) <= tolerance;
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
65
|
+
return expected.every(
|
|
66
|
+
(expItem) => actual.some((actItem) => fuzzyMatch(expItem, actItem))
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return expected === actual;
|
|
70
|
+
}
|
|
71
|
+
function strictEquals(expected, actual) {
|
|
72
|
+
if (expected === actual) return true;
|
|
73
|
+
if (expected == null || actual == null) return false;
|
|
74
|
+
if (typeof expected !== typeof actual) return false;
|
|
75
|
+
if (Array.isArray(expected)) {
|
|
76
|
+
if (!Array.isArray(actual)) return false;
|
|
77
|
+
if (expected.length !== actual.length) return false;
|
|
78
|
+
return expected.every((item, i) => strictEquals(item, actual[i]));
|
|
79
|
+
}
|
|
80
|
+
if (typeof expected === "object") {
|
|
81
|
+
const expectedKeys = Object.keys(expected).sort();
|
|
82
|
+
const actualKeys = Object.keys(actual).sort();
|
|
83
|
+
if (expectedKeys.length !== actualKeys.length) return false;
|
|
84
|
+
if (!expectedKeys.every((key, i) => key === actualKeys[i])) return false;
|
|
85
|
+
return expectedKeys.every(
|
|
86
|
+
(key) => strictEquals(expected[key], actual[key])
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return expected === actual;
|
|
90
|
+
}
|
|
91
|
+
function ToolCallScorer(config = {}) {
|
|
92
|
+
const {
|
|
93
|
+
ordered = false,
|
|
94
|
+
requireAll = true,
|
|
95
|
+
allowExtras = true,
|
|
96
|
+
params = "strict"
|
|
97
|
+
} = config;
|
|
98
|
+
const argMatcher = typeof params === "function" ? params : params === "strict" ? strictEquals : fuzzyMatch;
|
|
99
|
+
return (opts) => __async(null, null, function* () {
|
|
100
|
+
const expectedTools = opts.expectedTools || [];
|
|
101
|
+
const actualCalls = opts.toolCalls || [];
|
|
102
|
+
if (expectedTools.length === 0) {
|
|
103
|
+
return {
|
|
104
|
+
score: 1,
|
|
105
|
+
metadata: {
|
|
106
|
+
rationale: "No tool calls expected"
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (actualCalls.length === 0) {
|
|
111
|
+
return {
|
|
112
|
+
score: 0,
|
|
113
|
+
metadata: {
|
|
114
|
+
rationale: `Expected ${expectedTools.length} tool(s) but none were called`
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (ordered) {
|
|
119
|
+
return evaluateOrderedTools(expectedTools, actualCalls, {
|
|
120
|
+
argMatcher,
|
|
121
|
+
allowExtras,
|
|
122
|
+
requireAllTools: requireAll
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return evaluateUnorderedTools(expectedTools, actualCalls, {
|
|
126
|
+
argMatcher,
|
|
127
|
+
requireAllTools: requireAll,
|
|
128
|
+
allowExtras
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function evaluateOrderedTools(expected, actual, options) {
|
|
133
|
+
let expectedIndex = 0;
|
|
134
|
+
let actualIndex = 0;
|
|
135
|
+
while (expectedIndex < expected.length && actualIndex < actual.length) {
|
|
136
|
+
const exp = expected[expectedIndex];
|
|
137
|
+
const act = actual[actualIndex];
|
|
138
|
+
if (exp.name === act.name) {
|
|
139
|
+
if (exp.arguments !== void 0) {
|
|
140
|
+
const argsMatch = options.argMatcher(
|
|
141
|
+
exp.arguments,
|
|
142
|
+
act.arguments || {}
|
|
143
|
+
);
|
|
144
|
+
if (!argsMatch) {
|
|
145
|
+
return {
|
|
146
|
+
score: 0.5,
|
|
147
|
+
metadata: {
|
|
148
|
+
rationale: `Tool '${exp.name}' called with incorrect arguments at position ${expectedIndex + 1}`,
|
|
149
|
+
expected: exp.arguments,
|
|
150
|
+
actual: act.arguments
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
expectedIndex++;
|
|
156
|
+
actualIndex++;
|
|
157
|
+
} else if (options.allowExtras) {
|
|
158
|
+
actualIndex++;
|
|
159
|
+
} else {
|
|
160
|
+
return {
|
|
161
|
+
score: 0,
|
|
162
|
+
metadata: {
|
|
163
|
+
rationale: `Expected '${exp.name}' at position ${expectedIndex + 1} but found '${act.name}'`
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (expectedIndex < expected.length) {
|
|
169
|
+
const missing = expected.slice(expectedIndex).map((t) => t.name);
|
|
170
|
+
if (options.requireAllTools) {
|
|
171
|
+
return {
|
|
172
|
+
score: 0,
|
|
173
|
+
metadata: {
|
|
174
|
+
rationale: `Missing required tools in sequence: ${missing.join(", ")}`
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const matchedCount = expectedIndex;
|
|
179
|
+
const totalCount = expected.length;
|
|
180
|
+
const score = totalCount > 0 ? matchedCount / totalCount : 1;
|
|
181
|
+
return {
|
|
182
|
+
score,
|
|
183
|
+
metadata: {
|
|
184
|
+
rationale: `Partial match: ${matchedCount}/${totalCount} tools called in order (missing: ${missing.join(", ")})`,
|
|
185
|
+
matched: matchedCount,
|
|
186
|
+
total: totalCount
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (!options.allowExtras && actualIndex < actual.length) {
|
|
191
|
+
const extra = actual.slice(actualIndex).map((t) => t.name);
|
|
192
|
+
return {
|
|
193
|
+
score: 0,
|
|
194
|
+
metadata: {
|
|
195
|
+
rationale: `Unexpected extra tools: ${extra.join(", ")}`
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
score: 1,
|
|
201
|
+
metadata: {
|
|
202
|
+
rationale: "All tools called in expected order with correct arguments"
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function evaluateUnorderedTools(expected, actual, options) {
|
|
207
|
+
const matchedExpected = /* @__PURE__ */ new Set();
|
|
208
|
+
const matchedActual = /* @__PURE__ */ new Set();
|
|
209
|
+
const issues = [];
|
|
210
|
+
for (let i = 0; i < expected.length; i++) {
|
|
211
|
+
const exp = expected[i];
|
|
212
|
+
let found = false;
|
|
213
|
+
for (let j = 0; j < actual.length; j++) {
|
|
214
|
+
if (matchedActual.has(j)) continue;
|
|
215
|
+
const act = actual[j];
|
|
216
|
+
if (exp.name === act.name) {
|
|
217
|
+
if (exp.arguments !== void 0) {
|
|
218
|
+
const argsMatch = options.argMatcher(
|
|
219
|
+
exp.arguments,
|
|
220
|
+
act.arguments || {}
|
|
221
|
+
);
|
|
222
|
+
if (!argsMatch) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
matchedExpected.add(i);
|
|
227
|
+
matchedActual.add(j);
|
|
228
|
+
found = true;
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!found) {
|
|
233
|
+
if (exp.arguments !== void 0) {
|
|
234
|
+
const wrongArgsCalls = actual.filter((a) => a.name === exp.name);
|
|
235
|
+
if (wrongArgsCalls.length > 0) {
|
|
236
|
+
issues.push(`Tool '${exp.name}' called but with incorrect arguments`);
|
|
237
|
+
} else {
|
|
238
|
+
issues.push(`Missing required tool: ${exp.name}`);
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
issues.push(`Missing required tool: ${exp.name}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const extraTools = actual.filter((_, i) => !matchedActual.has(i)).map((t) => t.name);
|
|
246
|
+
if (!options.allowExtras && extraTools.length > 0) {
|
|
247
|
+
issues.push(`Unexpected extra tools: ${extraTools.join(", ")}`);
|
|
248
|
+
}
|
|
249
|
+
const expectedMatched = matchedExpected.size;
|
|
250
|
+
const expectedTotal = expected.length;
|
|
251
|
+
if (issues.length > 0 && (options.requireAllTools || !options.allowExtras)) {
|
|
252
|
+
return {
|
|
253
|
+
score: 0,
|
|
254
|
+
metadata: {
|
|
255
|
+
rationale: issues.join("; ")
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const score = expectedTotal > 0 ? expectedMatched / expectedTotal : 1;
|
|
260
|
+
if (score === 1) {
|
|
261
|
+
const extraInfo = extraTools.length > 0 ? ` (plus extra: ${extraTools.join(", ")})` : "";
|
|
262
|
+
return {
|
|
263
|
+
score: 1,
|
|
264
|
+
metadata: {
|
|
265
|
+
rationale: `All expected tools were called${extraInfo}`
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
score,
|
|
271
|
+
metadata: {
|
|
272
|
+
rationale: issues.join("; "),
|
|
273
|
+
matched: expectedMatched,
|
|
274
|
+
total: expectedTotal
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
279
|
+
0 && (module.exports = {
|
|
280
|
+
ToolCallScorer
|
|
281
|
+
});
|
|
282
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/scorers/index.ts","../../src/scorers/toolCallScorer.ts"],"sourcesContent":["export {\n ToolCallScorer,\n type ToolCallScorerOptions,\n type ToolCallScorerConfig,\n} from \"./toolCallScorer\";\n","import type { ScoreFn, BaseScorerOptions, ToolCall } from \"../index\";\n\nexport interface ToolCallScorerOptions extends BaseScorerOptions {\n // Expected tools are now defined in the test data\n expectedTools?: Array<{\n name: string;\n arguments?: any;\n }>;\n}\n\nexport interface ToolCallScorerConfig {\n /**\n * Whether tools must be called in the exact order specified\n * @default false\n */\n ordered?: boolean;\n\n /**\n * Whether all expected tools must be called for a passing score\n * When false: gives partial credit based on tools matched\n * @default true\n */\n requireAll?: boolean;\n\n /**\n * Whether to allow additional tool calls beyond those expected\n * @default true\n */\n allowExtras?: boolean;\n\n /**\n * How to match tool arguments/parameters\n * - \"strict\": Exact equality required (default)\n * - \"fuzzy\": Case-insensitive, subset matching, numeric tolerance\n * - Custom function: Your own comparison logic\n * @default \"strict\"\n */\n params?: \"strict\" | \"fuzzy\" | ((expected: any, actual: any) => boolean);\n}\n\n/**\n * Default fuzzy matching for arguments\n */\nfunction fuzzyMatch(expected: any, actual: any): boolean {\n // Null/undefined handling\n if (expected == null || actual == null) {\n return expected === actual;\n }\n\n // For objects, check if actual has all expected properties\n if (\n typeof expected === \"object\" &&\n typeof actual === \"object\" &&\n !Array.isArray(expected)\n ) {\n return Object.entries(expected).every(\n ([key, value]) => key in actual && fuzzyMatch(value, actual[key]),\n );\n }\n\n // For strings, case-insensitive substring match\n if (typeof expected === \"string\" && typeof actual === \"string\") {\n return actual.toLowerCase().includes(expected.toLowerCase());\n }\n\n // For numbers, allow small differences (0.1% or 0.001, whichever is larger)\n if (typeof expected === \"number\" && typeof actual === \"number\") {\n const tolerance = Math.max(Math.abs(expected) * 0.001, 0.001);\n return Math.abs(expected - actual) <= tolerance;\n }\n\n // For arrays, check if all expected items exist in actual (order doesn't matter in fuzzy mode)\n if (Array.isArray(expected) && Array.isArray(actual)) {\n return expected.every((expItem) =>\n actual.some((actItem) => fuzzyMatch(expItem, actItem)),\n );\n }\n\n // Otherwise strict equality\n return expected === actual;\n}\n\n/**\n * Strict equality comparison (deep equals)\n */\nfunction strictEquals(expected: any, actual: any): boolean {\n // Handle primitive types and null/undefined\n if (expected === actual) return true;\n if (expected == null || actual == null) return false;\n\n // Must be same type\n if (typeof expected !== typeof actual) return false;\n\n // Handle arrays\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) return false;\n if (expected.length !== actual.length) return false;\n return expected.every((item, i) => strictEquals(item, actual[i]));\n }\n\n // Handle objects\n if (typeof expected === \"object\") {\n const expectedKeys = Object.keys(expected).sort();\n const actualKeys = Object.keys(actual).sort();\n\n // Must have same keys\n if (expectedKeys.length !== actualKeys.length) return false;\n if (!expectedKeys.every((key, i) => key === actualKeys[i])) return false;\n\n // All values must match\n return expectedKeys.every((key) =>\n strictEquals(expected[key], actual[key]),\n );\n }\n\n // Primitive types\n return expected === actual;\n}\n\n/**\n * A configurable scorer for evaluating tool usage in LLM responses.\n *\n * The test data defines WHAT tools/arguments are expected,\n * while this scorer defines HOW to evaluate them.\n *\n * @param config - Configuration options for the scorer\n * @param config.ordered - Require exact order of tool calls\n * @param config.requireAll - Require all expected tools (vs partial credit)\n * @param config.allowExtras - Allow additional tool calls\n * @param config.params - How to match parameters: \"strict\", \"fuzzy\", or custom function\n *\n * @example\n * // Default: strict params, any order\n * describeEval(\"search test\", {\n * data: async () => [{\n * input: \"Find restaurants\",\n * expectedTools: [\n * { name: \"search\", arguments: { type: \"restaurant\" } },\n * { name: \"filter\" }\n * ]\n * }],\n * task: myTask,\n * scorers: [ToolCallScorer()]\n * });\n *\n * @example\n * // Strict order and parameters\n * describeEval(\"payment flow\", {\n * data: async () => [{\n * input: \"Process payment\",\n * expectedTools: [\n * { name: \"validate\", arguments: { amount: 100 } },\n * { name: \"charge\", arguments: { amount: 100, method: \"card\" } }\n * ]\n * }],\n * task: myTask,\n * scorers: [ToolCallScorer({ ordered: true, params: \"strict\" })]\n * });\n */\nexport function ToolCallScorer(\n config: ToolCallScorerConfig = {},\n): ScoreFn<ToolCallScorerOptions> {\n const {\n ordered = false,\n requireAll = true,\n allowExtras = true,\n params = \"strict\",\n } = config;\n\n // Determine the argument matcher\n const argMatcher =\n typeof params === \"function\"\n ? params\n : params === \"strict\"\n ? strictEquals\n : fuzzyMatch;\n\n return async (opts) => {\n const expectedTools = opts.expectedTools || [];\n const actualCalls = opts.toolCalls || [];\n\n // No expectations means pass\n if (expectedTools.length === 0) {\n return {\n score: 1.0,\n metadata: {\n rationale: \"No tool calls expected\",\n },\n };\n }\n\n // No actual calls when we expected some\n if (actualCalls.length === 0) {\n return {\n score: 0.0,\n metadata: {\n rationale: `Expected ${expectedTools.length} tool(s) but none were called`,\n },\n };\n }\n\n if (ordered) {\n return evaluateOrderedTools(expectedTools, actualCalls, {\n argMatcher,\n allowExtras,\n requireAllTools: requireAll,\n });\n }\n\n return evaluateUnorderedTools(expectedTools, actualCalls, {\n argMatcher,\n requireAllTools: requireAll,\n allowExtras,\n });\n };\n}\n\n/**\n * Evaluate tools that must be called in a specific order\n */\nfunction evaluateOrderedTools(\n expected: Array<{ name: string; arguments?: any }>,\n actual: ToolCall[],\n options: {\n argMatcher: (expected: any, actual: any) => boolean;\n allowExtras: boolean;\n requireAllTools: boolean;\n },\n) {\n let expectedIndex = 0;\n let actualIndex = 0;\n\n // Match expected tools in order\n while (expectedIndex < expected.length && actualIndex < actual.length) {\n const exp = expected[expectedIndex];\n const act = actual[actualIndex];\n\n if (exp.name === act.name) {\n // Check arguments if specified\n if (exp.arguments !== undefined) {\n const argsMatch = options.argMatcher(\n exp.arguments,\n act.arguments || {},\n );\n if (!argsMatch) {\n return {\n score: 0.5,\n metadata: {\n rationale: `Tool '${exp.name}' called with incorrect arguments at position ${expectedIndex + 1}`,\n expected: exp.arguments,\n actual: act.arguments,\n },\n };\n }\n }\n expectedIndex++;\n actualIndex++;\n } else if (options.allowExtras) {\n // Skip extra tool\n actualIndex++;\n } else {\n // Wrong tool in sequence when extra tools not allowed\n return {\n score: 0.0,\n metadata: {\n rationale: `Expected '${exp.name}' at position ${expectedIndex + 1} but found '${act.name}'`,\n },\n };\n }\n }\n\n // Check if all expected tools were matched\n if (expectedIndex < expected.length) {\n const missing = expected.slice(expectedIndex).map((t) => t.name);\n\n if (options.requireAllTools) {\n return {\n score: 0.0,\n metadata: {\n rationale: `Missing required tools in sequence: ${missing.join(\", \")}`,\n },\n };\n }\n\n // Partial credit when requireAllTools is false\n const matchedCount = expectedIndex;\n const totalCount = expected.length;\n const score = totalCount > 0 ? matchedCount / totalCount : 1.0;\n\n return {\n score,\n metadata: {\n rationale: `Partial match: ${matchedCount}/${totalCount} tools called in order (missing: ${missing.join(\", \")})`,\n matched: matchedCount,\n total: totalCount,\n },\n };\n }\n\n // Check for extra tools at the end if not allowed\n if (!options.allowExtras && actualIndex < actual.length) {\n const extra = actual.slice(actualIndex).map((t) => t.name);\n return {\n score: 0.0,\n metadata: {\n rationale: `Unexpected extra tools: ${extra.join(\", \")}`,\n },\n };\n }\n\n return {\n score: 1.0,\n metadata: {\n rationale: \"All tools called in expected order with correct arguments\",\n },\n };\n}\n\n/**\n * Evaluate tools that can be called in any order\n */\nfunction evaluateUnorderedTools(\n expected: Array<{ name: string; arguments?: any }>,\n actual: ToolCall[],\n options: {\n argMatcher: (expected: any, actual: any) => boolean;\n requireAllTools: boolean;\n allowExtras: boolean;\n },\n) {\n const matchedExpected = new Set<number>();\n const matchedActual = new Set<number>();\n const issues: string[] = [];\n\n // Try to match each expected tool\n for (let i = 0; i < expected.length; i++) {\n const exp = expected[i];\n let found = false;\n\n // Look for a matching actual tool call\n for (let j = 0; j < actual.length; j++) {\n if (matchedActual.has(j)) continue;\n\n const act = actual[j];\n if (exp.name === act.name) {\n // Check arguments if specified\n if (exp.arguments !== undefined) {\n const argsMatch = options.argMatcher(\n exp.arguments,\n act.arguments || {},\n );\n if (!argsMatch) {\n continue; // Try to find another call with matching args\n }\n }\n\n // Found a match\n matchedExpected.add(i);\n matchedActual.add(j);\n found = true;\n break;\n }\n }\n\n if (!found) {\n if (exp.arguments !== undefined) {\n // Check if tool was called but with wrong args\n const wrongArgsCalls = actual.filter((a) => a.name === exp.name);\n if (wrongArgsCalls.length > 0) {\n issues.push(`Tool '${exp.name}' called but with incorrect arguments`);\n } else {\n issues.push(`Missing required tool: ${exp.name}`);\n }\n } else {\n issues.push(`Missing required tool: ${exp.name}`);\n }\n }\n }\n\n // Check for extra tools\n const extraTools = actual\n .filter((_, i) => !matchedActual.has(i))\n .map((t) => t.name);\n\n if (!options.allowExtras && extraTools.length > 0) {\n issues.push(`Unexpected extra tools: ${extraTools.join(\", \")}`);\n }\n\n // Calculate score\n const expectedMatched = matchedExpected.size;\n const expectedTotal = expected.length;\n\n // If we have any critical issues (wrong tools, missing tools when required, or extra tools when not allowed)\n if (issues.length > 0 && (options.requireAllTools || !options.allowExtras)) {\n return {\n score: 0.0,\n metadata: {\n rationale: issues.join(\"; \"),\n },\n };\n }\n\n // Partial credit when not all required\n const score = expectedTotal > 0 ? expectedMatched / expectedTotal : 1.0;\n\n if (score === 1.0) {\n const extraInfo =\n extraTools.length > 0 ? ` (plus extra: ${extraTools.join(\", \")})` : \"\";\n return {\n score: 1.0,\n metadata: {\n rationale: `All expected tools were called${extraInfo}`,\n },\n };\n }\n\n return {\n score,\n metadata: {\n rationale: issues.join(\"; \"),\n matched: expectedMatched,\n total: expectedTotal,\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
var __async = (__this, __arguments, generator) => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
var fulfilled = (value) => {
|
|
4
|
+
try {
|
|
5
|
+
step(generator.next(value));
|
|
6
|
+
} catch (e) {
|
|
7
|
+
reject(e);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var rejected = (value) => {
|
|
11
|
+
try {
|
|
12
|
+
step(generator.throw(value));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
reject(e);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
18
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/scorers/toolCallScorer.ts
|
|
23
|
+
function fuzzyMatch(expected, actual) {
|
|
24
|
+
if (expected == null || actual == null) {
|
|
25
|
+
return expected === actual;
|
|
26
|
+
}
|
|
27
|
+
if (typeof expected === "object" && typeof actual === "object" && !Array.isArray(expected)) {
|
|
28
|
+
return Object.entries(expected).every(
|
|
29
|
+
([key, value]) => key in actual && fuzzyMatch(value, actual[key])
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
33
|
+
return actual.toLowerCase().includes(expected.toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
if (typeof expected === "number" && typeof actual === "number") {
|
|
36
|
+
const tolerance = Math.max(Math.abs(expected) * 1e-3, 1e-3);
|
|
37
|
+
return Math.abs(expected - actual) <= tolerance;
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(expected) && Array.isArray(actual)) {
|
|
40
|
+
return expected.every(
|
|
41
|
+
(expItem) => actual.some((actItem) => fuzzyMatch(expItem, actItem))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return expected === actual;
|
|
45
|
+
}
|
|
46
|
+
function strictEquals(expected, actual) {
|
|
47
|
+
if (expected === actual) return true;
|
|
48
|
+
if (expected == null || actual == null) return false;
|
|
49
|
+
if (typeof expected !== typeof actual) return false;
|
|
50
|
+
if (Array.isArray(expected)) {
|
|
51
|
+
if (!Array.isArray(actual)) return false;
|
|
52
|
+
if (expected.length !== actual.length) return false;
|
|
53
|
+
return expected.every((item, i) => strictEquals(item, actual[i]));
|
|
54
|
+
}
|
|
55
|
+
if (typeof expected === "object") {
|
|
56
|
+
const expectedKeys = Object.keys(expected).sort();
|
|
57
|
+
const actualKeys = Object.keys(actual).sort();
|
|
58
|
+
if (expectedKeys.length !== actualKeys.length) return false;
|
|
59
|
+
if (!expectedKeys.every((key, i) => key === actualKeys[i])) return false;
|
|
60
|
+
return expectedKeys.every(
|
|
61
|
+
(key) => strictEquals(expected[key], actual[key])
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return expected === actual;
|
|
65
|
+
}
|
|
66
|
+
function ToolCallScorer(config = {}) {
|
|
67
|
+
const {
|
|
68
|
+
ordered = false,
|
|
69
|
+
requireAll = true,
|
|
70
|
+
allowExtras = true,
|
|
71
|
+
params = "strict"
|
|
72
|
+
} = config;
|
|
73
|
+
const argMatcher = typeof params === "function" ? params : params === "strict" ? strictEquals : fuzzyMatch;
|
|
74
|
+
return (opts) => __async(null, null, function* () {
|
|
75
|
+
const expectedTools = opts.expectedTools || [];
|
|
76
|
+
const actualCalls = opts.toolCalls || [];
|
|
77
|
+
if (expectedTools.length === 0) {
|
|
78
|
+
return {
|
|
79
|
+
score: 1,
|
|
80
|
+
metadata: {
|
|
81
|
+
rationale: "No tool calls expected"
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (actualCalls.length === 0) {
|
|
86
|
+
return {
|
|
87
|
+
score: 0,
|
|
88
|
+
metadata: {
|
|
89
|
+
rationale: `Expected ${expectedTools.length} tool(s) but none were called`
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (ordered) {
|
|
94
|
+
return evaluateOrderedTools(expectedTools, actualCalls, {
|
|
95
|
+
argMatcher,
|
|
96
|
+
allowExtras,
|
|
97
|
+
requireAllTools: requireAll
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return evaluateUnorderedTools(expectedTools, actualCalls, {
|
|
101
|
+
argMatcher,
|
|
102
|
+
requireAllTools: requireAll,
|
|
103
|
+
allowExtras
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function evaluateOrderedTools(expected, actual, options) {
|
|
108
|
+
let expectedIndex = 0;
|
|
109
|
+
let actualIndex = 0;
|
|
110
|
+
while (expectedIndex < expected.length && actualIndex < actual.length) {
|
|
111
|
+
const exp = expected[expectedIndex];
|
|
112
|
+
const act = actual[actualIndex];
|
|
113
|
+
if (exp.name === act.name) {
|
|
114
|
+
if (exp.arguments !== void 0) {
|
|
115
|
+
const argsMatch = options.argMatcher(
|
|
116
|
+
exp.arguments,
|
|
117
|
+
act.arguments || {}
|
|
118
|
+
);
|
|
119
|
+
if (!argsMatch) {
|
|
120
|
+
return {
|
|
121
|
+
score: 0.5,
|
|
122
|
+
metadata: {
|
|
123
|
+
rationale: `Tool '${exp.name}' called with incorrect arguments at position ${expectedIndex + 1}`,
|
|
124
|
+
expected: exp.arguments,
|
|
125
|
+
actual: act.arguments
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
expectedIndex++;
|
|
131
|
+
actualIndex++;
|
|
132
|
+
} else if (options.allowExtras) {
|
|
133
|
+
actualIndex++;
|
|
134
|
+
} else {
|
|
135
|
+
return {
|
|
136
|
+
score: 0,
|
|
137
|
+
metadata: {
|
|
138
|
+
rationale: `Expected '${exp.name}' at position ${expectedIndex + 1} but found '${act.name}'`
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (expectedIndex < expected.length) {
|
|
144
|
+
const missing = expected.slice(expectedIndex).map((t) => t.name);
|
|
145
|
+
if (options.requireAllTools) {
|
|
146
|
+
return {
|
|
147
|
+
score: 0,
|
|
148
|
+
metadata: {
|
|
149
|
+
rationale: `Missing required tools in sequence: ${missing.join(", ")}`
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const matchedCount = expectedIndex;
|
|
154
|
+
const totalCount = expected.length;
|
|
155
|
+
const score = totalCount > 0 ? matchedCount / totalCount : 1;
|
|
156
|
+
return {
|
|
157
|
+
score,
|
|
158
|
+
metadata: {
|
|
159
|
+
rationale: `Partial match: ${matchedCount}/${totalCount} tools called in order (missing: ${missing.join(", ")})`,
|
|
160
|
+
matched: matchedCount,
|
|
161
|
+
total: totalCount
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (!options.allowExtras && actualIndex < actual.length) {
|
|
166
|
+
const extra = actual.slice(actualIndex).map((t) => t.name);
|
|
167
|
+
return {
|
|
168
|
+
score: 0,
|
|
169
|
+
metadata: {
|
|
170
|
+
rationale: `Unexpected extra tools: ${extra.join(", ")}`
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
score: 1,
|
|
176
|
+
metadata: {
|
|
177
|
+
rationale: "All tools called in expected order with correct arguments"
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function evaluateUnorderedTools(expected, actual, options) {
|
|
182
|
+
const matchedExpected = /* @__PURE__ */ new Set();
|
|
183
|
+
const matchedActual = /* @__PURE__ */ new Set();
|
|
184
|
+
const issues = [];
|
|
185
|
+
for (let i = 0; i < expected.length; i++) {
|
|
186
|
+
const exp = expected[i];
|
|
187
|
+
let found = false;
|
|
188
|
+
for (let j = 0; j < actual.length; j++) {
|
|
189
|
+
if (matchedActual.has(j)) continue;
|
|
190
|
+
const act = actual[j];
|
|
191
|
+
if (exp.name === act.name) {
|
|
192
|
+
if (exp.arguments !== void 0) {
|
|
193
|
+
const argsMatch = options.argMatcher(
|
|
194
|
+
exp.arguments,
|
|
195
|
+
act.arguments || {}
|
|
196
|
+
);
|
|
197
|
+
if (!argsMatch) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
matchedExpected.add(i);
|
|
202
|
+
matchedActual.add(j);
|
|
203
|
+
found = true;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (!found) {
|
|
208
|
+
if (exp.arguments !== void 0) {
|
|
209
|
+
const wrongArgsCalls = actual.filter((a) => a.name === exp.name);
|
|
210
|
+
if (wrongArgsCalls.length > 0) {
|
|
211
|
+
issues.push(`Tool '${exp.name}' called but with incorrect arguments`);
|
|
212
|
+
} else {
|
|
213
|
+
issues.push(`Missing required tool: ${exp.name}`);
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
issues.push(`Missing required tool: ${exp.name}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const extraTools = actual.filter((_, i) => !matchedActual.has(i)).map((t) => t.name);
|
|
221
|
+
if (!options.allowExtras && extraTools.length > 0) {
|
|
222
|
+
issues.push(`Unexpected extra tools: ${extraTools.join(", ")}`);
|
|
223
|
+
}
|
|
224
|
+
const expectedMatched = matchedExpected.size;
|
|
225
|
+
const expectedTotal = expected.length;
|
|
226
|
+
if (issues.length > 0 && (options.requireAllTools || !options.allowExtras)) {
|
|
227
|
+
return {
|
|
228
|
+
score: 0,
|
|
229
|
+
metadata: {
|
|
230
|
+
rationale: issues.join("; ")
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const score = expectedTotal > 0 ? expectedMatched / expectedTotal : 1;
|
|
235
|
+
if (score === 1) {
|
|
236
|
+
const extraInfo = extraTools.length > 0 ? ` (plus extra: ${extraTools.join(", ")})` : "";
|
|
237
|
+
return {
|
|
238
|
+
score: 1,
|
|
239
|
+
metadata: {
|
|
240
|
+
rationale: `All expected tools were called${extraInfo}`
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
score,
|
|
246
|
+
metadata: {
|
|
247
|
+
rationale: issues.join("; "),
|
|
248
|
+
matched: expectedMatched,
|
|
249
|
+
total: expectedTotal
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
export {
|
|
254
|
+
ToolCallScorer
|
|
255
|
+
};
|
|
256
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/scorers/toolCallScorer.ts"],"sourcesContent":["import type { ScoreFn, BaseScorerOptions, ToolCall } from \"../index\";\n\nexport interface ToolCallScorerOptions extends BaseScorerOptions {\n // Expected tools are now defined in the test data\n expectedTools?: Array<{\n name: string;\n arguments?: any;\n }>;\n}\n\nexport interface ToolCallScorerConfig {\n /**\n * Whether tools must be called in the exact order specified\n * @default false\n */\n ordered?: boolean;\n\n /**\n * Whether all expected tools must be called for a passing score\n * When false: gives partial credit based on tools matched\n * @default true\n */\n requireAll?: boolean;\n\n /**\n * Whether to allow additional tool calls beyond those expected\n * @default true\n */\n allowExtras?: boolean;\n\n /**\n * How to match tool arguments/parameters\n * - \"strict\": Exact equality required (default)\n * - \"fuzzy\": Case-insensitive, subset matching, numeric tolerance\n * - Custom function: Your own comparison logic\n * @default \"strict\"\n */\n params?: \"strict\" | \"fuzzy\" | ((expected: any, actual: any) => boolean);\n}\n\n/**\n * Default fuzzy matching for arguments\n */\nfunction fuzzyMatch(expected: any, actual: any): boolean {\n // Null/undefined handling\n if (expected == null || actual == null) {\n return expected === actual;\n }\n\n // For objects, check if actual has all expected properties\n if (\n typeof expected === \"object\" &&\n typeof actual === \"object\" &&\n !Array.isArray(expected)\n ) {\n return Object.entries(expected).every(\n ([key, value]) => key in actual && fuzzyMatch(value, actual[key]),\n );\n }\n\n // For strings, case-insensitive substring match\n if (typeof expected === \"string\" && typeof actual === \"string\") {\n return actual.toLowerCase().includes(expected.toLowerCase());\n }\n\n // For numbers, allow small differences (0.1% or 0.001, whichever is larger)\n if (typeof expected === \"number\" && typeof actual === \"number\") {\n const tolerance = Math.max(Math.abs(expected) * 0.001, 0.001);\n return Math.abs(expected - actual) <= tolerance;\n }\n\n // For arrays, check if all expected items exist in actual (order doesn't matter in fuzzy mode)\n if (Array.isArray(expected) && Array.isArray(actual)) {\n return expected.every((expItem) =>\n actual.some((actItem) => fuzzyMatch(expItem, actItem)),\n );\n }\n\n // Otherwise strict equality\n return expected === actual;\n}\n\n/**\n * Strict equality comparison (deep equals)\n */\nfunction strictEquals(expected: any, actual: any): boolean {\n // Handle primitive types and null/undefined\n if (expected === actual) return true;\n if (expected == null || actual == null) return false;\n\n // Must be same type\n if (typeof expected !== typeof actual) return false;\n\n // Handle arrays\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) return false;\n if (expected.length !== actual.length) return false;\n return expected.every((item, i) => strictEquals(item, actual[i]));\n }\n\n // Handle objects\n if (typeof expected === \"object\") {\n const expectedKeys = Object.keys(expected).sort();\n const actualKeys = Object.keys(actual).sort();\n\n // Must have same keys\n if (expectedKeys.length !== actualKeys.length) return false;\n if (!expectedKeys.every((key, i) => key === actualKeys[i])) return false;\n\n // All values must match\n return expectedKeys.every((key) =>\n strictEquals(expected[key], actual[key]),\n );\n }\n\n // Primitive types\n return expected === actual;\n}\n\n/**\n * A configurable scorer for evaluating tool usage in LLM responses.\n *\n * The test data defines WHAT tools/arguments are expected,\n * while this scorer defines HOW to evaluate them.\n *\n * @param config - Configuration options for the scorer\n * @param config.ordered - Require exact order of tool calls\n * @param config.requireAll - Require all expected tools (vs partial credit)\n * @param config.allowExtras - Allow additional tool calls\n * @param config.params - How to match parameters: \"strict\", \"fuzzy\", or custom function\n *\n * @example\n * // Default: strict params, any order\n * describeEval(\"search test\", {\n * data: async () => [{\n * input: \"Find restaurants\",\n * expectedTools: [\n * { name: \"search\", arguments: { type: \"restaurant\" } },\n * { name: \"filter\" }\n * ]\n * }],\n * task: myTask,\n * scorers: [ToolCallScorer()]\n * });\n *\n * @example\n * // Strict order and parameters\n * describeEval(\"payment flow\", {\n * data: async () => [{\n * input: \"Process payment\",\n * expectedTools: [\n * { name: \"validate\", arguments: { amount: 100 } },\n * { name: \"charge\", arguments: { amount: 100, method: \"card\" } }\n * ]\n * }],\n * task: myTask,\n * scorers: [ToolCallScorer({ ordered: true, params: \"strict\" })]\n * });\n */\nexport function ToolCallScorer(\n config: ToolCallScorerConfig = {},\n): ScoreFn<ToolCallScorerOptions> {\n const {\n ordered = false,\n requireAll = true,\n allowExtras = true,\n params = \"strict\",\n } = config;\n\n // Determine the argument matcher\n const argMatcher =\n typeof params === \"function\"\n ? params\n : params === \"strict\"\n ? strictEquals\n : fuzzyMatch;\n\n return async (opts) => {\n const expectedTools = opts.expectedTools || [];\n const actualCalls = opts.toolCalls || [];\n\n // No expectations means pass\n if (expectedTools.length === 0) {\n return {\n score: 1.0,\n metadata: {\n rationale: \"No tool calls expected\",\n },\n };\n }\n\n // No actual calls when we expected some\n if (actualCalls.length === 0) {\n return {\n score: 0.0,\n metadata: {\n rationale: `Expected ${expectedTools.length} tool(s) but none were called`,\n },\n };\n }\n\n if (ordered) {\n return evaluateOrderedTools(expectedTools, actualCalls, {\n argMatcher,\n allowExtras,\n requireAllTools: requireAll,\n });\n }\n\n return evaluateUnorderedTools(expectedTools, actualCalls, {\n argMatcher,\n requireAllTools: requireAll,\n allowExtras,\n });\n };\n}\n\n/**\n * Evaluate tools that must be called in a specific order\n */\nfunction evaluateOrderedTools(\n expected: Array<{ name: string; arguments?: any }>,\n actual: ToolCall[],\n options: {\n argMatcher: (expected: any, actual: any) => boolean;\n allowExtras: boolean;\n requireAllTools: boolean;\n },\n) {\n let expectedIndex = 0;\n let actualIndex = 0;\n\n // Match expected tools in order\n while (expectedIndex < expected.length && actualIndex < actual.length) {\n const exp = expected[expectedIndex];\n const act = actual[actualIndex];\n\n if (exp.name === act.name) {\n // Check arguments if specified\n if (exp.arguments !== undefined) {\n const argsMatch = options.argMatcher(\n exp.arguments,\n act.arguments || {},\n );\n if (!argsMatch) {\n return {\n score: 0.5,\n metadata: {\n rationale: `Tool '${exp.name}' called with incorrect arguments at position ${expectedIndex + 1}`,\n expected: exp.arguments,\n actual: act.arguments,\n },\n };\n }\n }\n expectedIndex++;\n actualIndex++;\n } else if (options.allowExtras) {\n // Skip extra tool\n actualIndex++;\n } else {\n // Wrong tool in sequence when extra tools not allowed\n return {\n score: 0.0,\n metadata: {\n rationale: `Expected '${exp.name}' at position ${expectedIndex + 1} but found '${act.name}'`,\n },\n };\n }\n }\n\n // Check if all expected tools were matched\n if (expectedIndex < expected.length) {\n const missing = expected.slice(expectedIndex).map((t) => t.name);\n\n if (options.requireAllTools) {\n return {\n score: 0.0,\n metadata: {\n rationale: `Missing required tools in sequence: ${missing.join(\", \")}`,\n },\n };\n }\n\n // Partial credit when requireAllTools is false\n const matchedCount = expectedIndex;\n const totalCount = expected.length;\n const score = totalCount > 0 ? matchedCount / totalCount : 1.0;\n\n return {\n score,\n metadata: {\n rationale: `Partial match: ${matchedCount}/${totalCount} tools called in order (missing: ${missing.join(\", \")})`,\n matched: matchedCount,\n total: totalCount,\n },\n };\n }\n\n // Check for extra tools at the end if not allowed\n if (!options.allowExtras && actualIndex < actual.length) {\n const extra = actual.slice(actualIndex).map((t) => t.name);\n return {\n score: 0.0,\n metadata: {\n rationale: `Unexpected extra tools: ${extra.join(\", \")}`,\n },\n };\n }\n\n return {\n score: 1.0,\n metadata: {\n rationale: \"All tools called in expected order with correct arguments\",\n },\n };\n}\n\n/**\n * Evaluate tools that can be called in any order\n */\nfunction evaluateUnorderedTools(\n expected: Array<{ name: string; arguments?: any }>,\n actual: ToolCall[],\n options: {\n argMatcher: (expected: any, actual: any) => boolean;\n requireAllTools: boolean;\n allowExtras: boolean;\n },\n) {\n const matchedExpected = new Set<number>();\n const matchedActual = new Set<number>();\n const issues: string[] = [];\n\n // Try to match each expected tool\n for (let i = 0; i < expected.length; i++) {\n const exp = expected[i];\n let found = false;\n\n // Look for a matching actual tool call\n for (let j = 0; j < actual.length; j++) {\n if (matchedActual.has(j)) continue;\n\n const act = actual[j];\n if (exp.name === act.name) {\n // Check arguments if specified\n if (exp.arguments !== undefined) {\n const argsMatch = options.argMatcher(\n exp.arguments,\n act.arguments || {},\n );\n if (!argsMatch) {\n continue; // Try to find another call with matching args\n }\n }\n\n // Found a match\n matchedExpected.add(i);\n matchedActual.add(j);\n found = true;\n break;\n }\n }\n\n if (!found) {\n if (exp.arguments !== undefined) {\n // Check if tool was called but with wrong args\n const wrongArgsCalls = actual.filter((a) => a.name === exp.name);\n if (wrongArgsCalls.length > 0) {\n issues.push(`Tool '${exp.name}' called but with incorrect arguments`);\n } else {\n issues.push(`Missing required tool: ${exp.name}`);\n }\n } else {\n issues.push(`Missing required tool: ${exp.name}`);\n }\n }\n }\n\n // Check for extra tools\n const extraTools = actual\n .filter((_, i) => !matchedActual.has(i))\n .map((t) => t.name);\n\n if (!options.allowExtras && extraTools.length > 0) {\n issues.push(`Unexpected extra tools: ${extraTools.join(\", \")}`);\n }\n\n // Calculate score\n const expectedMatched = matchedExpected.size;\n const expectedTotal = expected.length;\n\n // If we have any critical issues (wrong tools, missing tools when required, or extra tools when not allowed)\n if (issues.length > 0 && (options.requireAllTools || !options.allowExtras)) {\n return {\n score: 0.0,\n metadata: {\n rationale: issues.join(\"; \"),\n },\n };\n }\n\n // Partial credit when not all required\n const score = expectedTotal > 0 ? expectedMatched / expectedTotal : 1.0;\n\n if (score === 1.0) {\n const extraInfo =\n extraTools.length > 0 ? ` (plus extra: ${extraTools.join(\", \")})` : \"\";\n return {\n score: 1.0,\n metadata: {\n rationale: `All expected tools were called${extraInfo}`,\n },\n };\n }\n\n return {\n score,\n metadata: {\n rationale: issues.join(\"; \"),\n matched: expectedMatched,\n total: expectedTotal,\n },\n };\n}\n"],"mappings":"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","names":[]}
|