vitest-evals 0.9.0 → 0.10.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.
Files changed (68) hide show
  1. package/README.md +112 -66
  2. package/dist/harness.d.mts +12 -1
  3. package/dist/harness.d.ts +12 -1
  4. package/dist/harness.js +8 -0
  5. package/dist/harness.js.map +1 -1
  6. package/dist/harness.mjs +7 -0
  7. package/dist/harness.mjs.map +1 -1
  8. package/dist/index.d.mts +48 -20
  9. package/dist/index.d.ts +48 -20
  10. package/dist/index.js +308 -21
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +304 -21
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/internal/scoring.d.mts +3 -3
  15. package/dist/internal/scoring.d.ts +3 -3
  16. package/dist/internal/scoring.js.map +1 -1
  17. package/dist/internal/toolCallScorer.js +42 -2
  18. package/dist/internal/toolCallScorer.js.map +1 -1
  19. package/dist/internal/toolCallScorer.mjs +42 -2
  20. package/dist/internal/toolCallScorer.mjs.map +1 -1
  21. package/dist/judges/factualityJudge.d.mts +151 -0
  22. package/dist/judges/factualityJudge.d.ts +151 -0
  23. package/dist/judges/factualityJudge.js +235 -0
  24. package/dist/judges/factualityJudge.js.map +1 -0
  25. package/dist/judges/factualityJudge.mjs +208 -0
  26. package/dist/judges/factualityJudge.mjs.map +1 -0
  27. package/dist/judges/index.d.mts +3 -1
  28. package/dist/judges/index.d.ts +3 -1
  29. package/dist/judges/index.js +447 -7
  30. package/dist/judges/index.js.map +1 -1
  31. package/dist/judges/index.mjs +443 -6
  32. package/dist/judges/index.mjs.map +1 -1
  33. package/dist/judges/judgeHarness.d.mts +122 -0
  34. package/dist/judges/judgeHarness.d.ts +122 -0
  35. package/dist/judges/judgeHarness.js +303 -0
  36. package/dist/judges/judgeHarness.js.map +1 -0
  37. package/dist/judges/judgeHarness.mjs +274 -0
  38. package/dist/judges/judgeHarness.mjs.map +1 -0
  39. package/dist/judges/structuredOutputJudge.d.mts +1 -0
  40. package/dist/judges/structuredOutputJudge.d.ts +1 -0
  41. package/dist/judges/toolCallJudge.d.mts +1 -0
  42. package/dist/judges/toolCallJudge.d.ts +1 -0
  43. package/dist/judges/toolCallJudge.js +42 -2
  44. package/dist/judges/toolCallJudge.js.map +1 -1
  45. package/dist/judges/toolCallJudge.mjs +42 -2
  46. package/dist/judges/toolCallJudge.mjs.map +1 -1
  47. package/dist/judges/types.d.mts +33 -6
  48. package/dist/judges/types.d.ts +33 -6
  49. package/dist/judges/types.js.map +1 -1
  50. package/dist/legacy/scorers/index.js +42 -2
  51. package/dist/legacy/scorers/index.js.map +1 -1
  52. package/dist/legacy/scorers/index.mjs +42 -2
  53. package/dist/legacy/scorers/index.mjs.map +1 -1
  54. package/dist/legacy/scorers/toolCallScorer.js +42 -2
  55. package/dist/legacy/scorers/toolCallScorer.js.map +1 -1
  56. package/dist/legacy/scorers/toolCallScorer.mjs +42 -2
  57. package/dist/legacy/scorers/toolCallScorer.mjs.map +1 -1
  58. package/dist/legacy.js +56 -3
  59. package/dist/legacy.js.map +1 -1
  60. package/dist/legacy.mjs +56 -3
  61. package/dist/legacy.mjs.map +1 -1
  62. package/dist/replay.js +1 -1
  63. package/dist/replay.js.map +1 -1
  64. package/dist/replay.mjs +1 -1
  65. package/dist/replay.mjs.map +1 -1
  66. package/dist/reporter.js.map +1 -1
  67. package/dist/reporter.mjs.map +1 -1
  68. package/package.json +1 -1
@@ -20,11 +20,448 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/judges/index.ts
21
21
  var judges_exports = {};
22
22
  __export(judges_exports, {
23
+ FactualityJudge: () => FactualityJudge,
23
24
  StructuredOutputJudge: () => StructuredOutputJudge,
24
- ToolCallJudge: () => ToolCallJudge
25
+ ToolCallJudge: () => ToolCallJudge,
26
+ createJudgeHarness: () => createJudgeHarness,
27
+ runJudgeHarness: () => runJudgeHarness
25
28
  });
26
29
  module.exports = __toCommonJS(judges_exports);
27
30
 
31
+ // src/harness.ts
32
+ function isJsonPrimitive(value) {
33
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
34
+ }
35
+ function isJsonRecord(value) {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+ function normalizeJsonArray(value) {
39
+ return value.map((item) => {
40
+ const normalized = toJsonValue(item);
41
+ return normalized === void 0 ? null : normalized;
42
+ });
43
+ }
44
+ function normalizeJsonObject(value) {
45
+ const normalized = {};
46
+ for (const [key, entryValue] of Object.entries(value)) {
47
+ const entry = toJsonValue(entryValue);
48
+ if (entry !== void 0) {
49
+ normalized[key] = entry;
50
+ }
51
+ }
52
+ return normalized;
53
+ }
54
+ function toJsonValue(value) {
55
+ if (isJsonPrimitive(value)) {
56
+ return value;
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return normalizeJsonArray(value);
60
+ }
61
+ if (isJsonRecord(value)) {
62
+ return normalizeJsonObject(value);
63
+ }
64
+ return void 0;
65
+ }
66
+ function normalizeRecord(value) {
67
+ return normalizeJsonObject(value);
68
+ }
69
+ function normalizeMetadata(value) {
70
+ const normalized = normalizeRecord(value);
71
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
72
+ }
73
+ function normalizeContent(value) {
74
+ const normalized = toJsonValue(value);
75
+ return normalized !== void 0 ? normalized : String(value);
76
+ }
77
+ function createHarness(options) {
78
+ const harness = {
79
+ name: options.name,
80
+ run: async (input, context) => {
81
+ const result = await options.run({
82
+ input,
83
+ metadata: context.metadata,
84
+ signal: context.signal,
85
+ artifacts: context.artifacts,
86
+ setArtifact: context.setArtifact
87
+ });
88
+ return normalizeHarnessRun(input, result, context);
89
+ }
90
+ };
91
+ return harness;
92
+ }
93
+ function normalizeHarnessRun(input, result, context) {
94
+ if (isHarnessRun(result)) {
95
+ if (context && Object.keys(context.artifacts).length > 0 && !result.artifacts) {
96
+ return {
97
+ ...result,
98
+ artifacts: context.artifacts
99
+ };
100
+ }
101
+ return result;
102
+ }
103
+ const output = result.output;
104
+ const toolCalls = normalizeSimpleToolCalls(result.toolCalls);
105
+ const usage = result.usage ?? {};
106
+ const messages = result.messages ?? createDefaultSessionMessages({
107
+ input,
108
+ output,
109
+ toolCalls
110
+ });
111
+ const metadata = result.metadata ? normalizeMetadata(result.metadata) : void 0;
112
+ const artifacts = normalizeMergedArtifacts(
113
+ context?.artifacts,
114
+ result.artifacts
115
+ );
116
+ return {
117
+ session: {
118
+ messages,
119
+ ...usage.provider ? { provider: usage.provider } : {},
120
+ ...usage.model ? { model: usage.model } : {},
121
+ ...metadata ? { metadata } : {}
122
+ },
123
+ ...output !== void 0 ? { output } : {},
124
+ usage,
125
+ ...result.timings ? { timings: result.timings } : {},
126
+ ...artifacts ? { artifacts } : {},
127
+ errors: normalizeSimpleErrors(result.errors)
128
+ };
129
+ }
130
+ function createDefaultSessionMessages({
131
+ input,
132
+ output,
133
+ toolCalls: normalizedToolCalls
134
+ }) {
135
+ const messages = [
136
+ {
137
+ role: "user",
138
+ content: normalizeContent(input)
139
+ }
140
+ ];
141
+ if (output !== void 0 || normalizedToolCalls.length > 0) {
142
+ messages.push({
143
+ role: "assistant",
144
+ ...output !== void 0 ? { content: normalizeContent(output) } : {},
145
+ ...normalizedToolCalls.length > 0 ? { toolCalls: normalizedToolCalls } : {}
146
+ });
147
+ }
148
+ return messages;
149
+ }
150
+ function normalizeSimpleToolCalls(calls) {
151
+ return (calls ?? []).map((call) => {
152
+ const {
153
+ arguments: rawArguments,
154
+ result: rawResult,
155
+ error: rawError,
156
+ metadata: rawMetadata,
157
+ ...toolCall
158
+ } = call;
159
+ const args = normalizeToolCallArguments(rawArguments);
160
+ const result = toJsonValue(rawResult);
161
+ const error = normalizeToolCallError(rawError);
162
+ const metadata = rawMetadata ? normalizeMetadata(rawMetadata) : void 0;
163
+ return {
164
+ ...toolCall,
165
+ ...args ? { arguments: args } : {},
166
+ ...result !== void 0 ? { result } : {},
167
+ ...error ? { error } : {},
168
+ ...metadata ? { metadata } : {}
169
+ };
170
+ });
171
+ }
172
+ function normalizeToolCallArguments(value) {
173
+ if (value === void 0) {
174
+ return void 0;
175
+ }
176
+ const normalized = toJsonValue(value);
177
+ return normalized && typeof normalized === "object" && !Array.isArray(normalized) ? normalized : void 0;
178
+ }
179
+ function normalizeToolCallError(value) {
180
+ if (value === void 0) {
181
+ return void 0;
182
+ }
183
+ const serialized = serializeError(value);
184
+ const { message, type, ...details } = serialized;
185
+ return {
186
+ ...details,
187
+ message: typeof message === "string" ? message : String(message),
188
+ ...typeof type === "string" ? { type } : {}
189
+ };
190
+ }
191
+ function normalizeMergedArtifacts(contextArtifacts, resultArtifacts) {
192
+ const artifacts = {
193
+ ...contextArtifacts ?? {},
194
+ ...resultArtifacts ? normalizeRecord(resultArtifacts) : {}
195
+ };
196
+ return Object.keys(artifacts).length > 0 ? artifacts : void 0;
197
+ }
198
+ function normalizeSimpleErrors(errors) {
199
+ return (errors ?? []).map((error) => {
200
+ const normalized = toJsonValue(error);
201
+ if (normalized && typeof normalized === "object" && !Array.isArray(normalized) && Object.keys(normalized).length > 0) {
202
+ return normalized;
203
+ }
204
+ return serializeError(error);
205
+ });
206
+ }
207
+ function messagesByRole(session, role) {
208
+ return session.messages.filter((message) => message.role === role);
209
+ }
210
+ function hasNonEmptyMessageContent(message) {
211
+ return message.content !== void 0 && (typeof message.content !== "string" || message.content.trim().length > 0);
212
+ }
213
+ function assistantMessages(session) {
214
+ return messagesByRole(session, "assistant");
215
+ }
216
+ function latestAssistantMessageContent(session) {
217
+ return [...assistantMessages(session)].reverse().find(hasNonEmptyMessageContent)?.content;
218
+ }
219
+ function isHarnessRun(value) {
220
+ if (!value || typeof value !== "object") {
221
+ return false;
222
+ }
223
+ const candidate = value;
224
+ return isNormalizedSession(candidate.session) && Boolean(candidate.usage) && typeof candidate.usage === "object" && !Array.isArray(candidate.usage) && Array.isArray(candidate.errors);
225
+ }
226
+ function isNormalizedSession(value) {
227
+ return Boolean(value) && typeof value === "object" && value !== null && "messages" in value && Array.isArray(value.messages);
228
+ }
229
+ function serializeError(error) {
230
+ if (error instanceof Error) {
231
+ return {
232
+ type: error.name,
233
+ message: error.message
234
+ };
235
+ }
236
+ return {
237
+ type: "Error",
238
+ message: String(error)
239
+ };
240
+ }
241
+
242
+ // src/judges/judgeHarness.ts
243
+ function createJudgeHarness(options) {
244
+ return createHarness({
245
+ name: options.name ?? "judge-harness",
246
+ run: async ({ input, signal, metadata }) => {
247
+ return normalizeJudgeHarnessResult(
248
+ await options.run(input, { signal, metadata })
249
+ );
250
+ }
251
+ });
252
+ }
253
+ async function runJudgeHarness(judgeHarness, input, options = {}) {
254
+ const artifacts = {};
255
+ const run = await judgeHarness.run(input, {
256
+ metadata: options.metadata ?? {},
257
+ signal: options.signal,
258
+ artifacts,
259
+ setArtifact: (name, value) => {
260
+ artifacts[name] = value;
261
+ }
262
+ });
263
+ return run.output !== void 0 ? run.output : resolveJudgeHarnessAssistantOutput(run);
264
+ }
265
+ function createRunJudge(judgeHarness, signal) {
266
+ if (!judgeHarness) {
267
+ return void 0;
268
+ }
269
+ return (input, options) => runJudgeHarness(judgeHarness, input, {
270
+ metadata: options?.metadata,
271
+ signal
272
+ });
273
+ }
274
+ function normalizeJudgeHarnessResult(result) {
275
+ if (isHarnessRun(result)) {
276
+ return result;
277
+ }
278
+ if (hasOutputField(result)) {
279
+ return {
280
+ output: normalizeJudgeHarnessOutput(result.output)
281
+ };
282
+ }
283
+ return {
284
+ output: normalizeJudgeHarnessOutput(result)
285
+ };
286
+ }
287
+ function hasOutputField(value) {
288
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 1 && "output" in value;
289
+ }
290
+ function normalizeJudgeHarnessOutput(value) {
291
+ if (value === void 0) {
292
+ return void 0;
293
+ }
294
+ return normalizeContent(value);
295
+ }
296
+ function resolveJudgeHarnessAssistantOutput(run) {
297
+ return latestAssistantMessageContent(run.session) ?? "";
298
+ }
299
+
300
+ // src/judges/factualityJudge.ts
301
+ var FACTUALITY_CHOICE_SCORES = {
302
+ A: 0.4,
303
+ B: 0.6,
304
+ C: 1,
305
+ D: 0,
306
+ E: 1
307
+ };
308
+ var FACTUALITY_SYSTEM = "You are comparing factual content. Ignore differences in style, grammar, punctuation, and formatting.";
309
+ var FACTUALITY_RESPONSE_SCHEMA = {
310
+ type: "object",
311
+ additionalProperties: false,
312
+ required: ["choice", "rationale"],
313
+ properties: {
314
+ choice: {
315
+ enum: ["A", "B", "C", "D", "E"]
316
+ },
317
+ rationale: {
318
+ type: "string"
319
+ }
320
+ }
321
+ };
322
+ function FactualityJudge(config = {}) {
323
+ const judgeHarness = config.judgeHarness;
324
+ return {
325
+ name: config.name ?? "FactualityJudge",
326
+ judgeHarness,
327
+ assess: (opts) => assessFactuality(opts, judgeHarness)
328
+ };
329
+ }
330
+ async function assessFactuality(opts, configuredJudgeHarness) {
331
+ const metadata = opts.metadata;
332
+ const expected = opts.expected === void 0 ? metadata.expected : opts.expected;
333
+ if (isMissingExpectedAnswer(expected)) {
334
+ return {
335
+ score: 0,
336
+ metadata: {
337
+ rationale: "FactualityJudge requires a non-empty expert answer in `expected` or `metadata.expected`."
338
+ }
339
+ };
340
+ }
341
+ const runJudge = opts.runJudge ?? createRunJudge(
342
+ configuredJudgeHarness,
343
+ opts.signal
344
+ );
345
+ if (!runJudge) {
346
+ throw new Error(
347
+ "FactualityJudge requires a judgeHarness in FactualityJudge(...) config, describeEval(...) options, toSatisfyJudge(...) options, or JudgeContext.runJudge."
348
+ );
349
+ }
350
+ const verdict = await runJudge({
351
+ system: FACTUALITY_SYSTEM,
352
+ prompt: formatFactualityPrompt({
353
+ input: opts.input,
354
+ expected,
355
+ output: resolveJudgeOutput(opts)
356
+ }),
357
+ responseFormat: {
358
+ type: "json",
359
+ schema: FACTUALITY_RESPONSE_SCHEMA
360
+ }
361
+ });
362
+ return formatJudgeResult(parseFactualityJudgeVerdict(verdict));
363
+ }
364
+ function isMissingExpectedAnswer(value) {
365
+ return value == null || typeof value === "string" && value.trim().length === 0;
366
+ }
367
+ function resolveJudgeOutput(opts) {
368
+ if (opts.output !== void 0) {
369
+ return opts.output;
370
+ }
371
+ return latestAssistantMessageContent(opts.session) ?? "";
372
+ }
373
+ function parseFactualityJudgeVerdict(value) {
374
+ const parsed = typeof value === "string" ? parseJsonObject(value) : value;
375
+ if (!parsed || typeof parsed !== "object") {
376
+ throw new Error(
377
+ "FactualityJudge judgeHarness must return an object with `choice` and `rationale`."
378
+ );
379
+ }
380
+ const verdict = parsed;
381
+ if (!isFactualityChoice(verdict.choice)) {
382
+ throw new Error(
383
+ "FactualityJudge judgeHarness must return choice A, B, C, D, or E."
384
+ );
385
+ }
386
+ if (typeof verdict.rationale !== "string") {
387
+ throw new Error(
388
+ "FactualityJudge judgeHarness must return a string `rationale`."
389
+ );
390
+ }
391
+ return {
392
+ choice: verdict.choice,
393
+ rationale: verdict.rationale
394
+ };
395
+ }
396
+ function parseJsonObject(value) {
397
+ try {
398
+ return JSON.parse(value);
399
+ } catch {
400
+ const fencedJson = value.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
401
+ if (!fencedJson) {
402
+ throw new Error(
403
+ "FactualityJudge judgeHarness must return JSON with `choice` and `rationale`."
404
+ );
405
+ }
406
+ return JSON.parse(fencedJson[1]);
407
+ }
408
+ }
409
+ function isFactualityChoice(value) {
410
+ return value === "A" || value === "B" || value === "C" || value === "D" || value === "E";
411
+ }
412
+ function formatFactualityPrompt({
413
+ input,
414
+ expected,
415
+ output
416
+ }) {
417
+ const comparison = formatJudgeValue({
418
+ question: input ?? "",
419
+ expert_answer: expected,
420
+ submitted_answer: output ?? ""
421
+ });
422
+ return `Compare the submitted answer with the expert answer.
423
+
424
+ Comparison payload:
425
+ ${comparison}
426
+
427
+ Select exactly one option:
428
+ A: The submission is a fully consistent subset of the expert answer.
429
+ B: The submission is a fully consistent superset of the expert answer.
430
+ C: The submission contains the same factual details as the expert answer.
431
+ D: The submission disagrees with the expert answer.
432
+ E: The answers differ only in ways that do not affect factuality.
433
+
434
+ Return JSON with exactly these fields:
435
+ {
436
+ "choice": "C",
437
+ "rationale": "Brief explanation for the selected choice"
438
+ }
439
+
440
+ The choice value must be one of A, B, C, D, or E.`;
441
+ }
442
+ function formatJudgeValue(value) {
443
+ if (typeof value === "string") {
444
+ return value;
445
+ }
446
+ if (value === void 0) {
447
+ return "";
448
+ }
449
+ try {
450
+ return JSON.stringify(value, null, 2) ?? String(value);
451
+ } catch {
452
+ return String(value);
453
+ }
454
+ }
455
+ function formatJudgeResult(object) {
456
+ return {
457
+ score: FACTUALITY_CHOICE_SCORES[object.choice],
458
+ metadata: {
459
+ rationale: object.rationale,
460
+ choice: object.choice
461
+ }
462
+ };
463
+ }
464
+
28
465
  // src/internal/matchers.ts
29
466
  function isRecord(value) {
30
467
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -402,8 +839,8 @@ function evaluateOrderedTools(expected, actual, options) {
402
839
  score: expectedIndex / expected.length,
403
840
  metadata: {
404
841
  rationale: `Tool '${expectedTool.name}' called with incorrect arguments at position ${expectedIndex + 1} (${expectedIndex}/${expected.length} tools matched correctly)`,
405
- expected: expectedTool.arguments,
406
- actual: actualTool.arguments,
842
+ expected: normalizeContent(expectedTool.arguments),
843
+ actual: actualTool.arguments === void 0 ? void 0 : normalizeContent(actualTool.arguments),
407
844
  matched: expectedIndex,
408
845
  total: expected.length
409
846
  }
@@ -535,8 +972,8 @@ function ToolCallJudge(config = {}) {
535
972
  const metadata = opts.metadata;
536
973
  return scorer({
537
974
  ...opts,
538
- input: formatJudgeValue(opts.input),
539
- output: formatJudgeValue(opts.output),
975
+ input: formatJudgeValue2(opts.input),
976
+ output: formatJudgeValue2(opts.output),
540
977
  expectedTools: normalizeExpectedTools(
541
978
  opts.expectedTools ?? metadata.expectedTools
542
979
  )
@@ -549,7 +986,7 @@ function normalizeExpectedTools(expectedTools) {
549
986
  (tool) => typeof tool === "string" ? { name: tool } : tool
550
987
  );
551
988
  }
552
- function formatJudgeValue(value) {
989
+ function formatJudgeValue2(value) {
553
990
  if (typeof value === "string") {
554
991
  return value;
555
992
  }
@@ -564,7 +1001,10 @@ function formatJudgeValue(value) {
564
1001
  }
565
1002
  // Annotate the CommonJS export names for ESM import in node:
566
1003
  0 && (module.exports = {
1004
+ FactualityJudge,
567
1005
  StructuredOutputJudge,
568
- ToolCallJudge
1006
+ ToolCallJudge,
1007
+ createJudgeHarness,
1008
+ runJudgeHarness
569
1009
  });
570
1010
  //# sourceMappingURL=index.js.map