jev-recipes 0.8.4 → 0.8.5
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 +14 -2
- package/dist/catalog/evidence.d.ts +4 -1
- package/dist/catalog/generated/metadata.js +171 -259
- package/dist/evaluation/archive.d.ts +9 -1
- package/dist/evaluation/archive.js +41 -7
- package/dist/evaluation/comparison.d.ts +8 -0
- package/dist/evaluation/comparison.js +83 -0
- package/dist/evaluation/dataset.js +5 -34
- package/dist/evaluation/decisions.d.ts +0 -2
- package/dist/evaluation/decisions.js +0 -22
- package/dist/evaluation/engine.js +4 -3
- package/dist/evaluation/report.d.ts +3 -0
- package/dist/evaluation/report.js +6 -4
- package/dist/src/batch.js +14 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -31,9 +31,21 @@ For example, give `route` a support message and descriptions of your teams. It r
|
|
|
31
31
|
|
|
32
32
|
Evaluate your own cases with the [installed evaluator](https://github.com/agencyenterprise/jev-recipes/blob/main/docs/evaluation.md), retain model responses, and replay confidence policies offline. The [agent workflow](https://github.com/agencyenterprise/jev-recipes/blob/main/examples/agent-loop/README.md) and [customer queue](https://github.com/agencyenterprise/jev-recipes/blob/main/examples/customer-queue/README.md) show how decisions fit into application code. [Direct and Gateway integrations](https://github.com/agencyenterprise/jev-recipes/blob/main/docs/integrations.md) use the same recipe interface.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
## Build a complete support flow
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
The [support conversation starter](https://github.com/agencyenterprise/jev-recipes/blob/main/examples/support-routing/README.md) checks for missing details, asks a configured question, and resumes with the customer's answer. It proposes a queue or requests human review when the conversation remains unresolved.
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
cd examples/support-routing
|
|
40
|
+
npm ci --ignore-scripts
|
|
41
|
+
npm run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Open `http://localhost:3000` and choose **Ask and continue**. This folder is portable and needs no repository build. Edit `config.mjs` to supply your queues, required information, questions, and review policy. Run `npm run demo` for the command-line version. Saved scenarios work without a key; the starter guide explains live calls and application integration.
|
|
45
|
+
|
|
46
|
+
The existing [rules/Jev/fallback comparison](https://github.com/agencyenterprise/jev-recipes/blob/main/evals/support-routing/README.md) covers single-turn routing, not this clarification loop. Its evidence remains experimental.
|
|
47
|
+
|
|
48
|
+
Build the searchable static catalog with `npm run site:build`, then preview it with `npm run site:preview`. It includes fixture exploration, related-recipe comparisons, and saved evaluation evidence. Current measurements and missing, unknown-origin, or older evidence are labeled explicitly. Use `npm run eval:audit` after building to check retained evidence offline; see [evaluation and archive format](https://github.com/agencyenterprise/jev-recipes/blob/main/docs/evaluation.md#archive-format-and-evidence-origin).
|
|
37
49
|
|
|
38
50
|
## Hear a real application: Jevthoven
|
|
39
51
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export interface RecipeEvidence {
|
|
2
|
-
kind: 'fixture' | 'earlier' | 'synthetic' | 'public-dataset' | 'human-reviewed' | 'mixed' | 'unspecified';
|
|
2
|
+
kind: 'fixture' | 'unknown' | 'earlier' | 'synthetic' | 'public-dataset' | 'human-reviewed' | 'mixed' | 'unspecified';
|
|
3
3
|
label: string;
|
|
4
4
|
experimental: boolean;
|
|
5
5
|
measurement: {
|
|
6
6
|
model: string;
|
|
7
7
|
date: string | null;
|
|
8
|
+
operation?: string | null;
|
|
9
|
+
replayedAt?: string | null;
|
|
10
|
+
scoringRevision?: number;
|
|
8
11
|
split: string | null;
|
|
9
12
|
cases: number;
|
|
10
13
|
ready: number | null;
|
|
@@ -45,12 +45,15 @@ export const recipeMetadata = [
|
|
|
45
45
|
},
|
|
46
46
|
],
|
|
47
47
|
evidence: {
|
|
48
|
-
kind: '
|
|
49
|
-
label: '
|
|
50
|
-
experimental:
|
|
48
|
+
kind: 'earlier',
|
|
49
|
+
label: 'Earlier-evaluator measurement',
|
|
50
|
+
experimental: true,
|
|
51
51
|
measurement: {
|
|
52
52
|
model: 'typesafe-ai/jev',
|
|
53
53
|
date: '2026-09-27T21:19:58.394Z',
|
|
54
|
+
operation: 'live',
|
|
55
|
+
replayedAt: null,
|
|
56
|
+
scoringRevision: 1,
|
|
54
57
|
split: 'held-out',
|
|
55
58
|
cases: 40,
|
|
56
59
|
ready: 20,
|
|
@@ -86,21 +89,10 @@ export const recipeMetadata = [
|
|
|
86
89
|
},
|
|
87
90
|
],
|
|
88
91
|
evidence: {
|
|
89
|
-
kind: '
|
|
90
|
-
label: '
|
|
92
|
+
kind: 'unknown',
|
|
93
|
+
label: 'Unknown response origin',
|
|
91
94
|
experimental: true,
|
|
92
|
-
measurement:
|
|
93
|
-
model: 'jev-1.13.0',
|
|
94
|
-
date: null,
|
|
95
|
-
split: null,
|
|
96
|
-
cases: 44,
|
|
97
|
-
ready: null,
|
|
98
|
-
reviewRate: null,
|
|
99
|
-
readyAccuracy: null,
|
|
100
|
-
failed: null,
|
|
101
|
-
provenance: [],
|
|
102
|
-
acceptanceMet: null,
|
|
103
|
-
},
|
|
95
|
+
measurement: null,
|
|
104
96
|
},
|
|
105
97
|
},
|
|
106
98
|
{
|
|
@@ -306,21 +298,10 @@ export const recipeMetadata = [
|
|
|
306
298
|
},
|
|
307
299
|
],
|
|
308
300
|
evidence: {
|
|
309
|
-
kind: '
|
|
310
|
-
label: '
|
|
301
|
+
kind: 'unknown',
|
|
302
|
+
label: 'Unknown response origin',
|
|
311
303
|
experimental: true,
|
|
312
|
-
measurement:
|
|
313
|
-
model: 'jev-1.13.0',
|
|
314
|
-
date: null,
|
|
315
|
-
split: null,
|
|
316
|
-
cases: 50,
|
|
317
|
-
ready: null,
|
|
318
|
-
reviewRate: null,
|
|
319
|
-
readyAccuracy: null,
|
|
320
|
-
failed: null,
|
|
321
|
-
provenance: [],
|
|
322
|
-
acceptanceMet: null,
|
|
323
|
-
},
|
|
304
|
+
measurement: null,
|
|
324
305
|
},
|
|
325
306
|
},
|
|
326
307
|
{
|
|
@@ -583,12 +564,15 @@ export const recipeMetadata = [
|
|
|
583
564
|
},
|
|
584
565
|
],
|
|
585
566
|
evidence: {
|
|
586
|
-
kind: '
|
|
587
|
-
label: '
|
|
588
|
-
experimental:
|
|
567
|
+
kind: 'earlier',
|
|
568
|
+
label: 'Earlier-evaluator measurement',
|
|
569
|
+
experimental: true,
|
|
589
570
|
measurement: {
|
|
590
571
|
model: 'jev-1.13.0',
|
|
591
572
|
date: '2026-09-27T07:56:26.559Z',
|
|
573
|
+
operation: 'live',
|
|
574
|
+
replayedAt: null,
|
|
575
|
+
scoringRevision: 1,
|
|
592
576
|
split: 'held-out',
|
|
593
577
|
cases: 25,
|
|
594
578
|
ready: 24,
|
|
@@ -648,12 +632,15 @@ export const recipeMetadata = [
|
|
|
648
632
|
{ id: 'followup-timing', reason: 'Use followup-timing for when another contact is wanted.' },
|
|
649
633
|
],
|
|
650
634
|
evidence: {
|
|
651
|
-
kind: '
|
|
652
|
-
label: '
|
|
653
|
-
experimental:
|
|
635
|
+
kind: 'earlier',
|
|
636
|
+
label: 'Earlier-evaluator measurement',
|
|
637
|
+
experimental: true,
|
|
654
638
|
measurement: {
|
|
655
639
|
model: 'jev-1.13.0',
|
|
656
640
|
date: '2026-09-27T07:56:29.897Z',
|
|
641
|
+
operation: 'live',
|
|
642
|
+
replayedAt: null,
|
|
643
|
+
scoringRevision: 1,
|
|
657
644
|
split: 'held-out',
|
|
658
645
|
cases: 40,
|
|
659
646
|
ready: 24,
|
|
@@ -906,21 +893,10 @@ export const recipeMetadata = [
|
|
|
906
893
|
},
|
|
907
894
|
],
|
|
908
895
|
evidence: {
|
|
909
|
-
kind: '
|
|
910
|
-
label: '
|
|
896
|
+
kind: 'unknown',
|
|
897
|
+
label: 'Unknown response origin',
|
|
911
898
|
experimental: true,
|
|
912
|
-
measurement:
|
|
913
|
-
model: 'jev-1.13.0',
|
|
914
|
-
date: null,
|
|
915
|
-
split: null,
|
|
916
|
-
cases: 31,
|
|
917
|
-
ready: null,
|
|
918
|
-
reviewRate: null,
|
|
919
|
-
readyAccuracy: null,
|
|
920
|
-
failed: null,
|
|
921
|
-
provenance: [],
|
|
922
|
-
acceptanceMet: null,
|
|
923
|
-
},
|
|
899
|
+
measurement: null,
|
|
924
900
|
},
|
|
925
901
|
},
|
|
926
902
|
{
|
|
@@ -963,21 +939,10 @@ export const recipeMetadata = [
|
|
|
963
939
|
},
|
|
964
940
|
],
|
|
965
941
|
evidence: {
|
|
966
|
-
kind: '
|
|
967
|
-
label: '
|
|
942
|
+
kind: 'unknown',
|
|
943
|
+
label: 'Unknown response origin',
|
|
968
944
|
experimental: true,
|
|
969
|
-
measurement:
|
|
970
|
-
model: 'jev-1.13.0',
|
|
971
|
-
date: null,
|
|
972
|
-
split: null,
|
|
973
|
-
cases: 32,
|
|
974
|
-
ready: null,
|
|
975
|
-
reviewRate: null,
|
|
976
|
-
readyAccuracy: null,
|
|
977
|
-
failed: null,
|
|
978
|
-
provenance: [],
|
|
979
|
-
acceptanceMet: null,
|
|
980
|
-
},
|
|
945
|
+
measurement: null,
|
|
981
946
|
},
|
|
982
947
|
},
|
|
983
948
|
{
|
|
@@ -1081,21 +1046,10 @@ export const recipeMetadata = [
|
|
|
1081
1046
|
},
|
|
1082
1047
|
],
|
|
1083
1048
|
evidence: {
|
|
1084
|
-
kind: '
|
|
1085
|
-
label: '
|
|
1049
|
+
kind: 'unknown',
|
|
1050
|
+
label: 'Unknown response origin',
|
|
1086
1051
|
experimental: true,
|
|
1087
|
-
measurement:
|
|
1088
|
-
model: 'jev-1.13.0',
|
|
1089
|
-
date: null,
|
|
1090
|
-
split: null,
|
|
1091
|
-
cases: 53,
|
|
1092
|
-
ready: null,
|
|
1093
|
-
reviewRate: null,
|
|
1094
|
-
readyAccuracy: null,
|
|
1095
|
-
failed: null,
|
|
1096
|
-
provenance: [],
|
|
1097
|
-
acceptanceMet: null,
|
|
1098
|
-
},
|
|
1052
|
+
measurement: null,
|
|
1099
1053
|
},
|
|
1100
1054
|
},
|
|
1101
1055
|
{
|
|
@@ -1116,21 +1070,10 @@ export const recipeMetadata = [
|
|
|
1116
1070
|
},
|
|
1117
1071
|
],
|
|
1118
1072
|
evidence: {
|
|
1119
|
-
kind: '
|
|
1120
|
-
label: '
|
|
1073
|
+
kind: 'unknown',
|
|
1074
|
+
label: 'Unknown response origin',
|
|
1121
1075
|
experimental: true,
|
|
1122
|
-
measurement:
|
|
1123
|
-
model: 'jev-1.13.0',
|
|
1124
|
-
date: null,
|
|
1125
|
-
split: null,
|
|
1126
|
-
cases: 50,
|
|
1127
|
-
ready: null,
|
|
1128
|
-
reviewRate: null,
|
|
1129
|
-
readyAccuracy: null,
|
|
1130
|
-
failed: null,
|
|
1131
|
-
provenance: [],
|
|
1132
|
-
acceptanceMet: null,
|
|
1133
|
-
},
|
|
1076
|
+
measurement: null,
|
|
1134
1077
|
},
|
|
1135
1078
|
},
|
|
1136
1079
|
{
|
|
@@ -1298,12 +1241,15 @@ export const recipeMetadata = [
|
|
|
1298
1241
|
},
|
|
1299
1242
|
],
|
|
1300
1243
|
evidence: {
|
|
1301
|
-
kind: '
|
|
1302
|
-
label: '
|
|
1303
|
-
experimental:
|
|
1244
|
+
kind: 'earlier',
|
|
1245
|
+
label: 'Earlier-evaluator measurement',
|
|
1246
|
+
experimental: true,
|
|
1304
1247
|
measurement: {
|
|
1305
1248
|
model: 'typesafe-ai/jev',
|
|
1306
1249
|
date: '2026-09-27T21:20:01.789Z',
|
|
1250
|
+
operation: 'live',
|
|
1251
|
+
replayedAt: null,
|
|
1252
|
+
scoringRevision: 1,
|
|
1307
1253
|
split: 'held-out',
|
|
1308
1254
|
cases: 40,
|
|
1309
1255
|
ready: 30,
|
|
@@ -1449,12 +1395,15 @@ export const recipeMetadata = [
|
|
|
1449
1395
|
},
|
|
1450
1396
|
],
|
|
1451
1397
|
evidence: {
|
|
1452
|
-
kind: '
|
|
1453
|
-
label: '
|
|
1454
|
-
experimental:
|
|
1398
|
+
kind: 'earlier',
|
|
1399
|
+
label: 'Earlier-evaluator measurement',
|
|
1400
|
+
experimental: true,
|
|
1455
1401
|
measurement: {
|
|
1456
1402
|
model: 'jev-1.13.0',
|
|
1457
1403
|
date: '2026-09-27T07:56:27.401Z',
|
|
1404
|
+
operation: 'live',
|
|
1405
|
+
replayedAt: null,
|
|
1406
|
+
scoringRevision: 1,
|
|
1458
1407
|
split: 'held-out',
|
|
1459
1408
|
cases: 40,
|
|
1460
1409
|
ready: 33,
|
|
@@ -1545,12 +1494,15 @@ export const recipeMetadata = [
|
|
|
1545
1494
|
},
|
|
1546
1495
|
],
|
|
1547
1496
|
evidence: {
|
|
1548
|
-
kind: '
|
|
1549
|
-
label: '
|
|
1497
|
+
kind: 'earlier',
|
|
1498
|
+
label: 'Earlier-evaluator measurement',
|
|
1550
1499
|
experimental: true,
|
|
1551
1500
|
measurement: {
|
|
1552
1501
|
model: 'typesafe-ai/jev',
|
|
1553
1502
|
date: '2026-09-27T21:21:18.930Z',
|
|
1503
|
+
operation: 'live',
|
|
1504
|
+
replayedAt: null,
|
|
1505
|
+
scoringRevision: 1,
|
|
1554
1506
|
split: 'held-out',
|
|
1555
1507
|
cases: 20,
|
|
1556
1508
|
ready: 11,
|
|
@@ -1778,21 +1730,10 @@ export const recipeMetadata = [
|
|
|
1778
1730
|
},
|
|
1779
1731
|
],
|
|
1780
1732
|
evidence: {
|
|
1781
|
-
kind: '
|
|
1782
|
-
label: '
|
|
1733
|
+
kind: 'unknown',
|
|
1734
|
+
label: 'Unknown response origin',
|
|
1783
1735
|
experimental: true,
|
|
1784
|
-
measurement:
|
|
1785
|
-
model: 'jev-1.13.0',
|
|
1786
|
-
date: null,
|
|
1787
|
-
split: null,
|
|
1788
|
-
cases: 43,
|
|
1789
|
-
ready: null,
|
|
1790
|
-
reviewRate: null,
|
|
1791
|
-
readyAccuracy: null,
|
|
1792
|
-
failed: null,
|
|
1793
|
-
provenance: [],
|
|
1794
|
-
acceptanceMet: null,
|
|
1795
|
-
},
|
|
1736
|
+
measurement: null,
|
|
1796
1737
|
},
|
|
1797
1738
|
},
|
|
1798
1739
|
{
|
|
@@ -1972,12 +1913,15 @@ export const recipeMetadata = [
|
|
|
1972
1913
|
},
|
|
1973
1914
|
],
|
|
1974
1915
|
evidence: {
|
|
1975
|
-
kind: '
|
|
1976
|
-
label: '
|
|
1977
|
-
experimental:
|
|
1916
|
+
kind: 'earlier',
|
|
1917
|
+
label: 'Earlier-evaluator measurement',
|
|
1918
|
+
experimental: true,
|
|
1978
1919
|
measurement: {
|
|
1979
1920
|
model: 'jev-1.13.0',
|
|
1980
1921
|
date: '2026-09-27T07:56:33.689Z',
|
|
1922
|
+
operation: 'live',
|
|
1923
|
+
replayedAt: null,
|
|
1924
|
+
scoringRevision: 1,
|
|
1981
1925
|
split: 'held-out',
|
|
1982
1926
|
cases: 40,
|
|
1983
1927
|
ready: 30,
|
|
@@ -2268,12 +2212,15 @@ export const recipeMetadata = [
|
|
|
2268
2212
|
},
|
|
2269
2213
|
],
|
|
2270
2214
|
evidence: {
|
|
2271
|
-
kind: '
|
|
2272
|
-
label: '
|
|
2273
|
-
experimental:
|
|
2215
|
+
kind: 'earlier',
|
|
2216
|
+
label: 'Earlier-evaluator measurement',
|
|
2217
|
+
experimental: true,
|
|
2274
2218
|
measurement: {
|
|
2275
2219
|
model: 'jev-1.13.0',
|
|
2276
2220
|
date: '2026-09-27T07:56:32.456Z',
|
|
2221
|
+
operation: 'live',
|
|
2222
|
+
replayedAt: null,
|
|
2223
|
+
scoringRevision: 1,
|
|
2277
2224
|
split: 'held-out',
|
|
2278
2225
|
cases: 40,
|
|
2279
2226
|
ready: 40,
|
|
@@ -2412,12 +2359,15 @@ export const recipeMetadata = [
|
|
|
2412
2359
|
},
|
|
2413
2360
|
],
|
|
2414
2361
|
evidence: {
|
|
2415
|
-
kind: '
|
|
2416
|
-
label: '
|
|
2417
|
-
experimental:
|
|
2362
|
+
kind: 'earlier',
|
|
2363
|
+
label: 'Earlier-evaluator measurement',
|
|
2364
|
+
experimental: true,
|
|
2418
2365
|
measurement: {
|
|
2419
2366
|
model: 'jev-1.13.0',
|
|
2420
2367
|
date: '2026-09-27T07:56:31.169Z',
|
|
2368
|
+
operation: 'live',
|
|
2369
|
+
replayedAt: null,
|
|
2370
|
+
scoringRevision: 1,
|
|
2421
2371
|
split: 'held-out',
|
|
2422
2372
|
cases: 40,
|
|
2423
2373
|
ready: 30,
|
|
@@ -2501,12 +2451,15 @@ export const recipeMetadata = [
|
|
|
2501
2451
|
},
|
|
2502
2452
|
],
|
|
2503
2453
|
evidence: {
|
|
2504
|
-
kind: '
|
|
2505
|
-
label: '
|
|
2506
|
-
experimental:
|
|
2454
|
+
kind: 'earlier',
|
|
2455
|
+
label: 'Earlier-evaluator measurement',
|
|
2456
|
+
experimental: true,
|
|
2507
2457
|
measurement: {
|
|
2508
2458
|
model: 'jev-1.13.0',
|
|
2509
2459
|
date: '2026-09-27T07:56:24.179Z',
|
|
2460
|
+
operation: 'live',
|
|
2461
|
+
replayedAt: null,
|
|
2462
|
+
scoringRevision: 1,
|
|
2510
2463
|
split: 'held-out',
|
|
2511
2464
|
cases: 40,
|
|
2512
2465
|
ready: 30,
|
|
@@ -2551,12 +2504,15 @@ export const recipeMetadata = [
|
|
|
2551
2504
|
},
|
|
2552
2505
|
],
|
|
2553
2506
|
evidence: {
|
|
2554
|
-
kind: '
|
|
2555
|
-
label: '
|
|
2556
|
-
experimental:
|
|
2507
|
+
kind: 'earlier',
|
|
2508
|
+
label: 'Earlier-evaluator measurement',
|
|
2509
|
+
experimental: true,
|
|
2557
2510
|
measurement: {
|
|
2558
2511
|
model: 'jev-1.13.0',
|
|
2559
2512
|
date: '2026-09-27T07:56:28.731Z',
|
|
2513
|
+
operation: 'live',
|
|
2514
|
+
replayedAt: null,
|
|
2515
|
+
scoringRevision: 1,
|
|
2560
2516
|
split: 'held-out',
|
|
2561
2517
|
cases: 40,
|
|
2562
2518
|
ready: 37,
|
|
@@ -2750,21 +2706,10 @@ export const recipeMetadata = [
|
|
|
2750
2706
|
},
|
|
2751
2707
|
],
|
|
2752
2708
|
evidence: {
|
|
2753
|
-
kind: '
|
|
2754
|
-
label: '
|
|
2709
|
+
kind: 'unknown',
|
|
2710
|
+
label: 'Unknown response origin',
|
|
2755
2711
|
experimental: true,
|
|
2756
|
-
measurement:
|
|
2757
|
-
model: 'jev-1.13.0',
|
|
2758
|
-
date: null,
|
|
2759
|
-
split: null,
|
|
2760
|
-
cases: 44,
|
|
2761
|
-
ready: null,
|
|
2762
|
-
reviewRate: null,
|
|
2763
|
-
readyAccuracy: null,
|
|
2764
|
-
failed: null,
|
|
2765
|
-
provenance: [],
|
|
2766
|
-
acceptanceMet: null,
|
|
2767
|
-
},
|
|
2712
|
+
measurement: null,
|
|
2768
2713
|
},
|
|
2769
2714
|
},
|
|
2770
2715
|
{
|
|
@@ -2828,12 +2773,15 @@ export const recipeMetadata = [
|
|
|
2828
2773
|
{ id: 'route', reason: 'Use route to choose a handler when escalation is not the decision.' },
|
|
2829
2774
|
],
|
|
2830
2775
|
evidence: {
|
|
2831
|
-
kind: '
|
|
2832
|
-
label: '
|
|
2833
|
-
experimental:
|
|
2776
|
+
kind: 'earlier',
|
|
2777
|
+
label: 'Earlier-evaluator measurement',
|
|
2778
|
+
experimental: true,
|
|
2834
2779
|
measurement: {
|
|
2835
2780
|
model: 'jev-1.13.0',
|
|
2836
2781
|
date: '2026-09-27T07:56:21.635Z',
|
|
2782
|
+
operation: 'live',
|
|
2783
|
+
replayedAt: null,
|
|
2784
|
+
scoringRevision: 1,
|
|
2837
2785
|
split: 'held-out',
|
|
2838
2786
|
cases: 40,
|
|
2839
2787
|
ready: 28,
|
|
@@ -2958,21 +2906,10 @@ export const recipeMetadata = [
|
|
|
2958
2906
|
},
|
|
2959
2907
|
],
|
|
2960
2908
|
evidence: {
|
|
2961
|
-
kind: '
|
|
2962
|
-
label: '
|
|
2909
|
+
kind: 'unknown',
|
|
2910
|
+
label: 'Unknown response origin',
|
|
2963
2911
|
experimental: true,
|
|
2964
|
-
measurement:
|
|
2965
|
-
model: 'jev-1.13.0',
|
|
2966
|
-
date: null,
|
|
2967
|
-
split: null,
|
|
2968
|
-
cases: 44,
|
|
2969
|
-
ready: null,
|
|
2970
|
-
reviewRate: null,
|
|
2971
|
-
readyAccuracy: null,
|
|
2972
|
-
failed: null,
|
|
2973
|
-
provenance: [],
|
|
2974
|
-
acceptanceMet: null,
|
|
2975
|
-
},
|
|
2912
|
+
measurement: null,
|
|
2976
2913
|
},
|
|
2977
2914
|
},
|
|
2978
2915
|
{
|
|
@@ -3686,12 +3623,15 @@ export const recipeMetadata = [
|
|
|
3686
3623
|
},
|
|
3687
3624
|
],
|
|
3688
3625
|
evidence: {
|
|
3689
|
-
kind: '
|
|
3690
|
-
label: '
|
|
3691
|
-
experimental:
|
|
3626
|
+
kind: 'earlier',
|
|
3627
|
+
label: 'Earlier-evaluator measurement',
|
|
3628
|
+
experimental: true,
|
|
3692
3629
|
measurement: {
|
|
3693
3630
|
model: 'jev-1.13.0',
|
|
3694
3631
|
date: '2026-09-27T07:56:10.135Z',
|
|
3632
|
+
operation: 'live',
|
|
3633
|
+
replayedAt: null,
|
|
3634
|
+
scoringRevision: 1,
|
|
3695
3635
|
split: 'held-out',
|
|
3696
3636
|
cases: 40,
|
|
3697
3637
|
ready: 26,
|
|
@@ -4118,12 +4058,15 @@ export const recipeMetadata = [
|
|
|
4118
4058
|
},
|
|
4119
4059
|
],
|
|
4120
4060
|
evidence: {
|
|
4121
|
-
kind: '
|
|
4122
|
-
label: '
|
|
4061
|
+
kind: 'earlier',
|
|
4062
|
+
label: 'Earlier-evaluator measurement',
|
|
4123
4063
|
experimental: true,
|
|
4124
4064
|
measurement: {
|
|
4125
4065
|
model: 'typesafe-ai/jev',
|
|
4126
4066
|
date: '2026-09-27T21:21:33.447Z',
|
|
4067
|
+
operation: 'live',
|
|
4068
|
+
replayedAt: null,
|
|
4069
|
+
scoringRevision: 1,
|
|
4127
4070
|
split: 'held-out',
|
|
4128
4071
|
cases: 167,
|
|
4129
4072
|
ready: 82,
|
|
@@ -4446,21 +4389,10 @@ export const recipeMetadata = [
|
|
|
4446
4389
|
},
|
|
4447
4390
|
],
|
|
4448
4391
|
evidence: {
|
|
4449
|
-
kind: '
|
|
4450
|
-
label: '
|
|
4392
|
+
kind: 'unknown',
|
|
4393
|
+
label: 'Unknown response origin',
|
|
4451
4394
|
experimental: true,
|
|
4452
|
-
measurement:
|
|
4453
|
-
model: 'jev-1.13.0',
|
|
4454
|
-
date: null,
|
|
4455
|
-
split: null,
|
|
4456
|
-
cases: 43,
|
|
4457
|
-
ready: null,
|
|
4458
|
-
reviewRate: null,
|
|
4459
|
-
readyAccuracy: null,
|
|
4460
|
-
failed: null,
|
|
4461
|
-
provenance: [],
|
|
4462
|
-
acceptanceMet: null,
|
|
4463
|
-
},
|
|
4395
|
+
measurement: null,
|
|
4464
4396
|
},
|
|
4465
4397
|
},
|
|
4466
4398
|
{
|
|
@@ -4693,12 +4625,15 @@ export const recipeMetadata = [
|
|
|
4693
4625
|
},
|
|
4694
4626
|
],
|
|
4695
4627
|
evidence: {
|
|
4696
|
-
kind: '
|
|
4697
|
-
label: '
|
|
4698
|
-
experimental:
|
|
4628
|
+
kind: 'earlier',
|
|
4629
|
+
label: 'Earlier-evaluator measurement',
|
|
4630
|
+
experimental: true,
|
|
4699
4631
|
measurement: {
|
|
4700
4632
|
model: 'jev-1.13.0',
|
|
4701
4633
|
date: '2026-09-27T07:56:14.022Z',
|
|
4634
|
+
operation: 'live',
|
|
4635
|
+
replayedAt: null,
|
|
4636
|
+
scoringRevision: 1,
|
|
4702
4637
|
split: 'held-out',
|
|
4703
4638
|
cases: 40,
|
|
4704
4639
|
ready: 40,
|
|
@@ -4733,12 +4668,15 @@ export const recipeMetadata = [
|
|
|
4733
4668
|
},
|
|
4734
4669
|
],
|
|
4735
4670
|
evidence: {
|
|
4736
|
-
kind: '
|
|
4737
|
-
label: '
|
|
4738
|
-
experimental:
|
|
4671
|
+
kind: 'earlier',
|
|
4672
|
+
label: 'Earlier-evaluator measurement',
|
|
4673
|
+
experimental: true,
|
|
4739
4674
|
measurement: {
|
|
4740
4675
|
model: 'jev-1.13.0',
|
|
4741
4676
|
date: '2026-09-27T07:56:25.450Z',
|
|
4677
|
+
operation: 'live',
|
|
4678
|
+
replayedAt: null,
|
|
4679
|
+
scoringRevision: 1,
|
|
4742
4680
|
split: 'held-out',
|
|
4743
4681
|
cases: 40,
|
|
4744
4682
|
ready: 40,
|
|
@@ -5117,21 +5055,10 @@ export const recipeMetadata = [
|
|
|
5117
5055
|
},
|
|
5118
5056
|
],
|
|
5119
5057
|
evidence: {
|
|
5120
|
-
kind: '
|
|
5121
|
-
label: '
|
|
5058
|
+
kind: 'unknown',
|
|
5059
|
+
label: 'Unknown response origin',
|
|
5122
5060
|
experimental: true,
|
|
5123
|
-
measurement:
|
|
5124
|
-
model: 'jev-1.13.0',
|
|
5125
|
-
date: null,
|
|
5126
|
-
split: null,
|
|
5127
|
-
cases: 57,
|
|
5128
|
-
ready: null,
|
|
5129
|
-
reviewRate: null,
|
|
5130
|
-
readyAccuracy: null,
|
|
5131
|
-
failed: null,
|
|
5132
|
-
provenance: [],
|
|
5133
|
-
acceptanceMet: null,
|
|
5134
|
-
},
|
|
5061
|
+
measurement: null,
|
|
5135
5062
|
},
|
|
5136
5063
|
},
|
|
5137
5064
|
{
|
|
@@ -5173,12 +5100,15 @@ export const recipeMetadata = [
|
|
|
5173
5100
|
},
|
|
5174
5101
|
],
|
|
5175
5102
|
evidence: {
|
|
5176
|
-
kind: '
|
|
5177
|
-
label: '
|
|
5178
|
-
experimental:
|
|
5103
|
+
kind: 'earlier',
|
|
5104
|
+
label: 'Earlier-evaluator measurement',
|
|
5105
|
+
experimental: true,
|
|
5179
5106
|
measurement: {
|
|
5180
5107
|
model: 'jev-1.13.0',
|
|
5181
5108
|
date: '2026-09-27T07:56:22.956Z',
|
|
5109
|
+
operation: 'live',
|
|
5110
|
+
replayedAt: null,
|
|
5111
|
+
scoringRevision: 1,
|
|
5182
5112
|
split: 'held-out',
|
|
5183
5113
|
cases: 40,
|
|
5184
5114
|
ready: 40,
|
|
@@ -5231,21 +5161,10 @@ export const recipeMetadata = [
|
|
|
5231
5161
|
},
|
|
5232
5162
|
],
|
|
5233
5163
|
evidence: {
|
|
5234
|
-
kind: '
|
|
5235
|
-
label: '
|
|
5164
|
+
kind: 'unknown',
|
|
5165
|
+
label: 'Unknown response origin',
|
|
5236
5166
|
experimental: true,
|
|
5237
|
-
measurement:
|
|
5238
|
-
model: 'jev-1.13.0',
|
|
5239
|
-
date: null,
|
|
5240
|
-
split: null,
|
|
5241
|
-
cases: 44,
|
|
5242
|
-
ready: null,
|
|
5243
|
-
reviewRate: null,
|
|
5244
|
-
readyAccuracy: null,
|
|
5245
|
-
failed: null,
|
|
5246
|
-
provenance: [],
|
|
5247
|
-
acceptanceMet: null,
|
|
5248
|
-
},
|
|
5167
|
+
measurement: null,
|
|
5249
5168
|
},
|
|
5250
5169
|
},
|
|
5251
5170
|
{
|
|
@@ -5347,12 +5266,15 @@ export const recipeMetadata = [
|
|
|
5347
5266
|
},
|
|
5348
5267
|
],
|
|
5349
5268
|
evidence: {
|
|
5350
|
-
kind: '
|
|
5351
|
-
label: '
|
|
5352
|
-
experimental:
|
|
5269
|
+
kind: 'earlier',
|
|
5270
|
+
label: 'Earlier-evaluator measurement',
|
|
5271
|
+
experimental: true,
|
|
5353
5272
|
measurement: {
|
|
5354
5273
|
model: 'jev-1.13.0',
|
|
5355
5274
|
date: '2026-09-27T07:56:15.276Z',
|
|
5275
|
+
operation: 'live',
|
|
5276
|
+
replayedAt: null,
|
|
5277
|
+
scoringRevision: 1,
|
|
5356
5278
|
split: 'held-out',
|
|
5357
5279
|
cases: 40,
|
|
5358
5280
|
ready: 39,
|
|
@@ -5535,12 +5457,15 @@ export const recipeMetadata = [
|
|
|
5535
5457
|
},
|
|
5536
5458
|
],
|
|
5537
5459
|
evidence: {
|
|
5538
|
-
kind: '
|
|
5539
|
-
label: '
|
|
5540
|
-
experimental:
|
|
5460
|
+
kind: 'earlier',
|
|
5461
|
+
label: 'Earlier-evaluator measurement',
|
|
5462
|
+
experimental: true,
|
|
5541
5463
|
measurement: {
|
|
5542
5464
|
model: 'jev-1.13.0',
|
|
5543
5465
|
date: '2026-09-27T07:56:19.034Z',
|
|
5466
|
+
operation: 'live',
|
|
5467
|
+
replayedAt: null,
|
|
5468
|
+
scoringRevision: 1,
|
|
5544
5469
|
split: 'held-out',
|
|
5545
5470
|
cases: 40,
|
|
5546
5471
|
ready: 24,
|
|
@@ -5580,12 +5505,15 @@ export const recipeMetadata = [
|
|
|
5580
5505
|
},
|
|
5581
5506
|
],
|
|
5582
5507
|
evidence: {
|
|
5583
|
-
kind: '
|
|
5584
|
-
label: '
|
|
5585
|
-
experimental:
|
|
5508
|
+
kind: 'earlier',
|
|
5509
|
+
label: 'Earlier-version measurement',
|
|
5510
|
+
experimental: true,
|
|
5586
5511
|
measurement: {
|
|
5587
5512
|
model: 'jev-1.13.0',
|
|
5588
5513
|
date: '2026-09-27T07:56:20.238Z',
|
|
5514
|
+
operation: 'live',
|
|
5515
|
+
replayedAt: null,
|
|
5516
|
+
scoringRevision: 1,
|
|
5589
5517
|
split: 'held-out',
|
|
5590
5518
|
cases: 40,
|
|
5591
5519
|
ready: 25,
|
|
@@ -6204,12 +6132,15 @@ export const recipeMetadata = [
|
|
|
6204
6132
|
},
|
|
6205
6133
|
],
|
|
6206
6134
|
evidence: {
|
|
6207
|
-
kind: '
|
|
6208
|
-
label: '
|
|
6135
|
+
kind: 'earlier',
|
|
6136
|
+
label: 'Earlier-evaluator measurement',
|
|
6209
6137
|
experimental: true,
|
|
6210
6138
|
measurement: {
|
|
6211
6139
|
model: 'typesafe-ai/jev',
|
|
6212
6140
|
date: '2026-09-27T21:21:20.879Z',
|
|
6141
|
+
operation: 'live',
|
|
6142
|
+
replayedAt: null,
|
|
6143
|
+
scoringRevision: 1,
|
|
6213
6144
|
split: 'held-out',
|
|
6214
6145
|
cases: 189,
|
|
6215
6146
|
ready: 136,
|
|
@@ -6404,12 +6335,15 @@ export const recipeMetadata = [
|
|
|
6404
6335
|
},
|
|
6405
6336
|
],
|
|
6406
6337
|
evidence: {
|
|
6407
|
-
kind: '
|
|
6408
|
-
label: '
|
|
6409
|
-
experimental:
|
|
6338
|
+
kind: 'earlier',
|
|
6339
|
+
label: 'Earlier-evaluator measurement',
|
|
6340
|
+
experimental: true,
|
|
6410
6341
|
measurement: {
|
|
6411
6342
|
model: 'jev-1.13.0',
|
|
6412
6343
|
date: '2026-09-27T07:56:16.449Z',
|
|
6344
|
+
operation: 'live',
|
|
6345
|
+
replayedAt: null,
|
|
6346
|
+
scoringRevision: 1,
|
|
6413
6347
|
split: 'held-out',
|
|
6414
6348
|
cases: 40,
|
|
6415
6349
|
ready: 31,
|
|
@@ -6618,21 +6552,10 @@ export const recipeMetadata = [
|
|
|
6618
6552
|
},
|
|
6619
6553
|
],
|
|
6620
6554
|
evidence: {
|
|
6621
|
-
kind: '
|
|
6622
|
-
label: '
|
|
6555
|
+
kind: 'unknown',
|
|
6556
|
+
label: 'Unknown response origin',
|
|
6623
6557
|
experimental: true,
|
|
6624
|
-
measurement:
|
|
6625
|
-
model: 'jev-1.13.0',
|
|
6626
|
-
date: null,
|
|
6627
|
-
split: null,
|
|
6628
|
-
cases: 50,
|
|
6629
|
-
ready: null,
|
|
6630
|
-
reviewRate: null,
|
|
6631
|
-
readyAccuracy: null,
|
|
6632
|
-
failed: null,
|
|
6633
|
-
provenance: [],
|
|
6634
|
-
acceptanceMet: null,
|
|
6635
|
-
},
|
|
6558
|
+
measurement: null,
|
|
6636
6559
|
},
|
|
6637
6560
|
},
|
|
6638
6561
|
{
|
|
@@ -6657,21 +6580,10 @@ export const recipeMetadata = [
|
|
|
6657
6580
|
},
|
|
6658
6581
|
],
|
|
6659
6582
|
evidence: {
|
|
6660
|
-
kind: '
|
|
6661
|
-
label: '
|
|
6583
|
+
kind: 'unknown',
|
|
6584
|
+
label: 'Unknown response origin',
|
|
6662
6585
|
experimental: true,
|
|
6663
|
-
measurement:
|
|
6664
|
-
model: 'jev-1.13.0',
|
|
6665
|
-
date: null,
|
|
6666
|
-
split: null,
|
|
6667
|
-
cases: 44,
|
|
6668
|
-
ready: null,
|
|
6669
|
-
reviewRate: null,
|
|
6670
|
-
readyAccuracy: null,
|
|
6671
|
-
failed: null,
|
|
6672
|
-
provenance: [],
|
|
6673
|
-
acceptanceMet: null,
|
|
6674
|
-
},
|
|
6586
|
+
measurement: null,
|
|
6675
6587
|
},
|
|
6676
6588
|
},
|
|
6677
6589
|
{
|
|
@@ -4,7 +4,13 @@ import type { EvaluationRow } from './engine.js';
|
|
|
4
4
|
import { buildReport } from './report.js';
|
|
5
5
|
import type { EvaluationOptions, EvaluationPolicy } from './schema.js';
|
|
6
6
|
declare const archiveSchema: z.ZodObject<{
|
|
7
|
-
format: z.ZodLiteral<
|
|
7
|
+
format: z.ZodLiteral<2>;
|
|
8
|
+
scoringRevision: z.ZodLiteral<2>;
|
|
9
|
+
sourceMode: z.ZodEnum<{
|
|
10
|
+
fixture: "fixture";
|
|
11
|
+
live: "live";
|
|
12
|
+
}>;
|
|
13
|
+
evaluatedAt: z.ZodISODateTime;
|
|
8
14
|
runId: z.ZodString;
|
|
9
15
|
createdAt: z.ZodISODateTime;
|
|
10
16
|
mode: z.ZodEnum<{
|
|
@@ -125,6 +131,7 @@ export declare function compare(baseline: EvaluationRun, candidate: EvaluationRu
|
|
|
125
131
|
} | undefined;
|
|
126
132
|
};
|
|
127
133
|
mode: "fixture" | "live" | "replay";
|
|
134
|
+
scoringRevision: 2;
|
|
128
135
|
report: {
|
|
129
136
|
contestedAccuracy: number | null;
|
|
130
137
|
adversarialAccuracy: number | null;
|
|
@@ -310,6 +317,7 @@ export declare function compare(baseline: EvaluationRun, candidate: EvaluationRu
|
|
|
310
317
|
} | undefined;
|
|
311
318
|
};
|
|
312
319
|
mode: "fixture" | "live" | "replay";
|
|
320
|
+
scoringRevision: 2;
|
|
313
321
|
report: {
|
|
314
322
|
contestedAccuracy: number | null;
|
|
315
323
|
adversarialAccuracy: number | null;
|
|
@@ -7,8 +7,13 @@ import { datasetFingerprints, fingerprint, loadEvaluationRecipe, recipeFingerpri
|
|
|
7
7
|
import { confidenceThresholds, evaluateCases } from './engine.js';
|
|
8
8
|
import { buildReport } from './report.js';
|
|
9
9
|
import { evaluationCaseSchema, evaluationOptionsSchema, evaluationPolicySchema, priceSchema, } from './schema.js';
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import { scoringRevision } from './comparison.js';
|
|
11
|
+
const archiveSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
format: z.literal(2),
|
|
14
|
+
scoringRevision: z.literal(scoringRevision),
|
|
15
|
+
sourceMode: z.enum(['live', 'fixture']),
|
|
16
|
+
evaluatedAt: z.iso.datetime(),
|
|
12
17
|
runId: z.string(),
|
|
13
18
|
createdAt: z.iso.datetime(),
|
|
14
19
|
mode: z.enum(['live', 'fixture', 'replay']),
|
|
@@ -53,6 +58,21 @@ const archiveSchema = z.object({
|
|
|
53
58
|
thresholds: z.array(z.number()),
|
|
54
59
|
sourceRun: z.string().optional(),
|
|
55
60
|
report: z.record(z.string(), z.unknown()),
|
|
61
|
+
})
|
|
62
|
+
.superRefine((run, context) => {
|
|
63
|
+
if (run.mode !== 'replay' && (run.sourceMode !== run.mode || run.evaluatedAt !== run.createdAt))
|
|
64
|
+
context.addIssue({
|
|
65
|
+
code: 'custom',
|
|
66
|
+
message: 'Direct runs must retain their own response mode and date.',
|
|
67
|
+
});
|
|
68
|
+
for (const [rowIndex, row] of run.rows.entries())
|
|
69
|
+
for (const [exchangeIndex, exchange] of row.exchanges.entries())
|
|
70
|
+
if (Object.hasOwn(exchange, 'response') === Object.hasOwn(exchange, 'error'))
|
|
71
|
+
context.addIssue({
|
|
72
|
+
code: 'custom',
|
|
73
|
+
path: ['rows', rowIndex, 'exchanges', exchangeIndex],
|
|
74
|
+
message: 'A completed exchange must contain exactly one response or error.',
|
|
75
|
+
});
|
|
56
76
|
});
|
|
57
77
|
export async function evaluate(name, values, options = {}) {
|
|
58
78
|
const settings = evaluationOptionsSchema.parse(options);
|
|
@@ -73,10 +93,14 @@ export async function evaluate(name, values, options = {}) {
|
|
|
73
93
|
throw new Error('The frozen policy belongs to a different recipe version or requested model.');
|
|
74
94
|
}
|
|
75
95
|
const manifest = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
96
|
+
const createdAt = new Date().toISOString();
|
|
76
97
|
const metadata = {
|
|
77
|
-
format:
|
|
98
|
+
format: 2,
|
|
99
|
+
scoringRevision,
|
|
100
|
+
sourceMode: options.mode ?? 'live',
|
|
101
|
+
evaluatedAt: createdAt,
|
|
78
102
|
runId: randomUUID(),
|
|
79
|
-
createdAt
|
|
103
|
+
createdAt,
|
|
80
104
|
mode: options.mode ?? 'live',
|
|
81
105
|
packageVersion: manifest.version,
|
|
82
106
|
recipe: recipe.id,
|
|
@@ -130,6 +154,8 @@ export async function replay(run, policy = run.policy, out) {
|
|
|
130
154
|
const cases = validateCases(recipe, run.cases);
|
|
131
155
|
const metadata = {
|
|
132
156
|
...run,
|
|
157
|
+
format: 2,
|
|
158
|
+
scoringRevision,
|
|
133
159
|
runId: randomUUID(),
|
|
134
160
|
createdAt: new Date().toISOString(),
|
|
135
161
|
mode: 'replay',
|
|
@@ -158,8 +184,10 @@ export async function readRun(directory) {
|
|
|
158
184
|
throw error;
|
|
159
185
|
return gunzipSync(await readFile(join(directory, 'run.json.gz'))).toString('utf8');
|
|
160
186
|
});
|
|
161
|
-
const
|
|
162
|
-
|
|
187
|
+
const raw = JSON.parse(content);
|
|
188
|
+
if (raw.format !== 2)
|
|
189
|
+
throw new Error('Unsupported archive format. This evaluator reads format 2; historical files remain available for direct inspection.');
|
|
190
|
+
const run = archiveSchema.parse(raw);
|
|
163
191
|
const hashes = datasetFingerprints(run.cases);
|
|
164
192
|
if (Object.entries(hashes).some(([key, hash]) => run[key] !== hash))
|
|
165
193
|
throw new Error('Archive dataset fingerprints do not match its cases.');
|
|
@@ -196,7 +224,10 @@ function reportEvidence(run) {
|
|
|
196
224
|
}
|
|
197
225
|
return {
|
|
198
226
|
runId: run.runId,
|
|
199
|
-
|
|
227
|
+
sourceMode: run.sourceMode,
|
|
228
|
+
evaluatedAt: run.evaluatedAt,
|
|
229
|
+
createdAt: run.createdAt,
|
|
230
|
+
scoringRevision: run.scoringRevision,
|
|
200
231
|
packageVersion: run.packageVersion,
|
|
201
232
|
recipeFingerprint: run.recipeFingerprint,
|
|
202
233
|
datasetFingerprint: run.datasetFingerprint,
|
|
@@ -207,6 +238,8 @@ function reportEvidence(run) {
|
|
|
207
238
|
};
|
|
208
239
|
}
|
|
209
240
|
export function compare(baseline, candidate) {
|
|
241
|
+
if (baseline.scoringRevision !== candidate.scoringRevision)
|
|
242
|
+
throw new Error('Comparison requires the same scoring revision. Replay compatible archives before comparing.');
|
|
210
243
|
if (baseline.recipe !== candidate.recipe ||
|
|
211
244
|
baseline.inputFingerprint !== candidate.inputFingerprint ||
|
|
212
245
|
baseline.answerKeyFingerprint !== candidate.answerKeyFingerprint)
|
|
@@ -220,6 +253,7 @@ export function compare(baseline, candidate) {
|
|
|
220
253
|
models: run.report.models,
|
|
221
254
|
policy: run.policy,
|
|
222
255
|
mode: run.mode,
|
|
256
|
+
scoringRevision: run.scoringRevision,
|
|
223
257
|
report: run.report,
|
|
224
258
|
});
|
|
225
259
|
return {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const scoringRevision: 2;
|
|
2
|
+
type Schema = Record<string, unknown>;
|
|
3
|
+
export declare function resultSchemaFor(recipe: string): Schema;
|
|
4
|
+
export declare function comparableDecision(value: unknown, recipe: string): unknown;
|
|
5
|
+
export declare function matchesExpected(actual: unknown, expected: unknown, recipe: string): boolean;
|
|
6
|
+
export declare function isMetadataPath(schema: Schema, segments: string[]): boolean;
|
|
7
|
+
export declare function schemaAtPath(schema: Schema, segments: string[]): Schema | undefined;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { describeRecipe } from '../catalog/index.js';
|
|
3
|
+
import { recipeNameSchema } from '../catalog/schema.js';
|
|
4
|
+
import { isRecord } from './decisions.js';
|
|
5
|
+
export const scoringRevision = 2;
|
|
6
|
+
const metadataFields = new Set([
|
|
7
|
+
'model',
|
|
8
|
+
'usage',
|
|
9
|
+
'confidence',
|
|
10
|
+
'probability',
|
|
11
|
+
'probabilities',
|
|
12
|
+
'score',
|
|
13
|
+
'status',
|
|
14
|
+
]);
|
|
15
|
+
const resultSchemas = new Map();
|
|
16
|
+
export function resultSchemaFor(recipe) {
|
|
17
|
+
const cached = resultSchemas.get(recipe);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
const schema = describeRecipe(recipeNameSchema.parse(recipe)).resultSchema;
|
|
21
|
+
resultSchemas.set(recipe, schema);
|
|
22
|
+
return schema;
|
|
23
|
+
}
|
|
24
|
+
// Expected keys are result paths. Below an unconstrained payload, every key is data.
|
|
25
|
+
export function comparableDecision(value, recipe) {
|
|
26
|
+
if (!isRecord(value))
|
|
27
|
+
return value;
|
|
28
|
+
const schema = resultSchemaFor(recipe);
|
|
29
|
+
return Object.fromEntries(Object.entries(value)
|
|
30
|
+
.filter(([path]) => !isMetadataPath(schema, path.split('.')))
|
|
31
|
+
.map(([path, field]) => [path, normalize(field, schemaAtPath(schema, path.split('.')))]));
|
|
32
|
+
}
|
|
33
|
+
export function matchesExpected(actual, expected, recipe) {
|
|
34
|
+
return isDeepStrictEqual(comparableDecision(actual, recipe), comparableDecision(expected, recipe));
|
|
35
|
+
}
|
|
36
|
+
function normalize(value, schema) {
|
|
37
|
+
if (!schema)
|
|
38
|
+
return value;
|
|
39
|
+
if (Array.isArray(value))
|
|
40
|
+
return value.map((item, index) => normalize(item, schemaAtPath(schema, [String(index)])));
|
|
41
|
+
if (!isRecord(value))
|
|
42
|
+
return value;
|
|
43
|
+
return Object.fromEntries(Object.entries(value)
|
|
44
|
+
.filter(([name]) => !isMetadataPath(schema, [name]))
|
|
45
|
+
.map(([name, field]) => [name, normalize(field, schemaAtPath(schema, [name]))]));
|
|
46
|
+
}
|
|
47
|
+
export function isMetadataPath(schema, segments) {
|
|
48
|
+
if (!segments.length)
|
|
49
|
+
return false;
|
|
50
|
+
if (Array.isArray(schema.anyOf))
|
|
51
|
+
return schema.anyOf.some((branch) => isRecord(branch) && isMetadataPath(branch, segments));
|
|
52
|
+
const [first, ...rest] = segments;
|
|
53
|
+
if (isRecord(schema.properties) &&
|
|
54
|
+
Object.hasOwn(schema.properties, first) &&
|
|
55
|
+
metadataFields.has(first))
|
|
56
|
+
return true;
|
|
57
|
+
const child = schemaAtPath(schema, [first]);
|
|
58
|
+
return child !== undefined && isMetadataPath(child, rest);
|
|
59
|
+
}
|
|
60
|
+
export function schemaAtPath(schema, segments) {
|
|
61
|
+
if (!segments.length)
|
|
62
|
+
return schema;
|
|
63
|
+
if (Array.isArray(schema.anyOf)) {
|
|
64
|
+
const candidates = schema.anyOf.flatMap((branch) => {
|
|
65
|
+
const field = isRecord(branch) ? schemaAtPath(branch, segments) : undefined;
|
|
66
|
+
return field ? [field] : [];
|
|
67
|
+
});
|
|
68
|
+
return candidates.length ? { anyOf: candidates } : undefined;
|
|
69
|
+
}
|
|
70
|
+
const [first, ...rest] = segments;
|
|
71
|
+
if (isRecord(schema.properties) &&
|
|
72
|
+
Object.hasOwn(schema.properties, first) &&
|
|
73
|
+
isRecord(schema.properties[first]))
|
|
74
|
+
return schemaAtPath(schema.properties[first], rest);
|
|
75
|
+
if (schema.type === 'array' && /^(0|[1-9]\d*)$/.test(first) && isRecord(schema.items))
|
|
76
|
+
return schemaAtPath(schema.items, rest);
|
|
77
|
+
if (isRecord(schema.additionalProperties))
|
|
78
|
+
return schemaAtPath(schema.additionalProperties, rest);
|
|
79
|
+
if (schema.additionalProperties === true ||
|
|
80
|
+
(schema.type === undefined && schema.enum === undefined && schema.const === undefined))
|
|
81
|
+
return {};
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { describeRecipe } from '../catalog/index.js';
|
|
5
4
|
import { recipeNameSchema } from '../catalog/schema.js';
|
|
6
|
-
import { isRecord
|
|
5
|
+
import { isRecord } from './decisions.js';
|
|
6
|
+
import { comparableDecision, isMetadataPath, schemaAtPath, resultSchemaFor } from './comparison.js';
|
|
7
7
|
import { evaluationCaseSchema } from './schema.js';
|
|
8
8
|
export async function loadEvaluationRecipe(name) {
|
|
9
9
|
const id = recipeNameSchema.parse(name);
|
|
@@ -30,7 +30,7 @@ export async function readEvaluationCases(path) {
|
|
|
30
30
|
export function validateCases(recipe, values) {
|
|
31
31
|
if (!values.length)
|
|
32
32
|
throw new Error('The case file is empty.');
|
|
33
|
-
const resultSchema =
|
|
33
|
+
const resultSchema = resultSchemaFor(recipe.id);
|
|
34
34
|
const cases = values.map((value) => evaluationCaseSchema.parse(value));
|
|
35
35
|
const ids = new Set();
|
|
36
36
|
const familySplits = new Map();
|
|
@@ -45,17 +45,7 @@ export function validateCases(recipe, values) {
|
|
|
45
45
|
}
|
|
46
46
|
recipe.inputSchema.parse(entry.input);
|
|
47
47
|
for (const [path, expected] of Object.entries(entry.expected)) {
|
|
48
|
-
if (path
|
|
49
|
-
.split('.')
|
|
50
|
-
.some((segment) => [
|
|
51
|
-
'status',
|
|
52
|
-
'confidence',
|
|
53
|
-
'probabilities',
|
|
54
|
-
'probability',
|
|
55
|
-
'model',
|
|
56
|
-
'usage',
|
|
57
|
-
'score',
|
|
58
|
-
].includes(segment)))
|
|
48
|
+
if (isMetadataPath(resultSchema, path.split('.')))
|
|
59
49
|
throw new Error(`${entry.id}: expected must name an ungated decision, not ${path}.`);
|
|
60
50
|
const field = schemaAtPath(resultSchema, path.split('.'));
|
|
61
51
|
if (!field)
|
|
@@ -63,30 +53,11 @@ export function validateCases(recipe, values) {
|
|
|
63
53
|
if (!acceptsValue(field, expected))
|
|
64
54
|
throw new Error(`${entry.id}: expected ${path} does not match the result schema.`);
|
|
65
55
|
}
|
|
66
|
-
if (isEmptyDecision(comparableDecision(entry.expected)))
|
|
56
|
+
if (isEmptyDecision(comparableDecision(entry.expected, recipe.id)))
|
|
67
57
|
throw new Error(`${entry.id}: expected must contain a decision to compare.`);
|
|
68
58
|
}
|
|
69
59
|
return cases;
|
|
70
60
|
}
|
|
71
|
-
function schemaAtPath(schema, segments) {
|
|
72
|
-
if (!segments.length)
|
|
73
|
-
return schema;
|
|
74
|
-
if (Array.isArray(schema.anyOf)) {
|
|
75
|
-
const candidates = schema.anyOf.flatMap((branch) => {
|
|
76
|
-
const field = isRecord(branch) ? schemaAtPath(branch, segments) : undefined;
|
|
77
|
-
return field ? [field] : [];
|
|
78
|
-
});
|
|
79
|
-
return candidates.length ? { anyOf: candidates } : undefined;
|
|
80
|
-
}
|
|
81
|
-
const [first, ...rest] = segments;
|
|
82
|
-
if (isRecord(schema.properties) && isRecord(schema.properties[first]))
|
|
83
|
-
return schemaAtPath(schema.properties[first], rest);
|
|
84
|
-
if (schema.type === 'array' && /^(0|[1-9]\d*)$/.test(first) && isRecord(schema.items))
|
|
85
|
-
return schemaAtPath(schema.items, rest);
|
|
86
|
-
if (isRecord(schema.additionalProperties))
|
|
87
|
-
return schemaAtPath(schema.additionalProperties, rest);
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
61
|
function acceptsValue(schema, value) {
|
|
91
62
|
if (Array.isArray(schema.anyOf))
|
|
92
63
|
return schema.anyOf.some((candidate) => isRecord(candidate) && acceptsValue(candidate, value));
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export declare function comparableDecision(value: unknown): unknown;
|
|
2
|
-
export declare function matchesExpected(actual: unknown, expected: unknown): boolean;
|
|
3
1
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
4
2
|
export declare function valueAtPath(record: unknown, path: string): unknown;
|
|
5
3
|
export declare function pickPaths(record: unknown, paths: string[]): Record<string, unknown>;
|
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
const metadataFields = new Set([
|
|
3
|
-
'model',
|
|
4
|
-
'usage',
|
|
5
|
-
'confidence',
|
|
6
|
-
'probability',
|
|
7
|
-
'probabilities',
|
|
8
|
-
'score',
|
|
9
|
-
'status',
|
|
10
|
-
]);
|
|
11
|
-
export function comparableDecision(value) {
|
|
12
|
-
if (Array.isArray(value))
|
|
13
|
-
return value.map(comparableDecision);
|
|
14
|
-
if (isRecord(value))
|
|
15
|
-
return Object.fromEntries(Object.entries(value)
|
|
16
|
-
.filter(([name]) => !metadataFields.has(name))
|
|
17
|
-
.map(([name, field]) => [name, comparableDecision(field)]));
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
export function matchesExpected(actual, expected) {
|
|
21
|
-
return isDeepStrictEqual(comparableDecision(actual), comparableDecision(expected));
|
|
22
|
-
}
|
|
23
1
|
export function isRecord(value) {
|
|
24
2
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
25
3
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
2
|
import { createClient } from '../src/client.js';
|
|
3
|
-
import { caseConfidence, isReady, mapWithConcurrencyLimit,
|
|
3
|
+
import { caseConfidence, isReady, mapWithConcurrencyLimit, pickPaths } from './decisions.js';
|
|
4
|
+
import { matchesExpected } from './comparison.js';
|
|
4
5
|
export const confidenceThresholds = [0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95];
|
|
5
6
|
export async function evaluateCases(recipe, cases, options = {}) {
|
|
6
7
|
let requests = 0;
|
|
@@ -50,7 +51,7 @@ async function evaluateCase(recipe, goldenCase, client, options) {
|
|
|
50
51
|
try {
|
|
51
52
|
row.result = await recipe.run(goldenCase.input, recipeOptions);
|
|
52
53
|
row.actual = pickPaths(row.result, Object.keys(goldenCase.expected));
|
|
53
|
-
row.correct = matchesExpected(row.actual, row.expected);
|
|
54
|
+
row.correct = matchesExpected(row.actual, row.expected, recipe.id);
|
|
54
55
|
row.ready = isReady(row.result);
|
|
55
56
|
row.confidence = caseConfidence(row.result, Object.keys(goldenCase.expected));
|
|
56
57
|
const model = pickPaths(row.result, ['model']).model;
|
|
@@ -72,7 +73,7 @@ async function evaluateCase(recipe, goldenCase, client, options) {
|
|
|
72
73
|
row.decisions.push({
|
|
73
74
|
minConfidence,
|
|
74
75
|
ready: isReady(result),
|
|
75
|
-
correct: matchesExpected(pickPaths(result, Object.keys(row.expected)), row.expected),
|
|
76
|
+
correct: matchesExpected(pickPaths(result, Object.keys(row.expected)), row.expected, recipe.id),
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
79
|
return row;
|
|
@@ -4,6 +4,9 @@ import type { EvaluationCase, EvaluationPolicy } from './schema.js';
|
|
|
4
4
|
export interface ReportEvidence {
|
|
5
5
|
runId: string;
|
|
6
6
|
evaluatedAt: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
sourceMode: 'live' | 'fixture';
|
|
9
|
+
scoringRevision: number;
|
|
7
10
|
packageVersion: string;
|
|
8
11
|
recipeFingerprint: string;
|
|
9
12
|
datasetFingerprint: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isRecord,
|
|
1
|
+
import { isRecord, roundedTo } from './decisions.js';
|
|
2
|
+
import { matchesExpected } from './comparison.js';
|
|
2
3
|
export function buildReport(id, rows, confidenceThresholds, price, evidence) {
|
|
3
4
|
const completed = rows.filter((row) => row.error === undefined);
|
|
4
5
|
const ready = completed.filter((row) => row.ready);
|
|
@@ -41,7 +42,7 @@ export function buildReport(id, rows, confidenceThresholds, price, evidence) {
|
|
|
41
42
|
reviewRate: completed.length
|
|
42
43
|
? roundedTo(3, (completed.length - ready.length) / completed.length)
|
|
43
44
|
: null,
|
|
44
|
-
...itemAccuracyOf(rows),
|
|
45
|
+
...itemAccuracyOf(id, rows),
|
|
45
46
|
contestedAccuracy: accuracyOf(rows.filter((row) => row.contested)),
|
|
46
47
|
adversarialAccuracy: accuracyOf(rows.filter((row) => row.adversarial)),
|
|
47
48
|
calibration: [0, 0.5, 0.6, 0.7, 0.8, 0.9].map((lower, index, edges) => {
|
|
@@ -90,14 +91,15 @@ export function buildReport(id, rows, confidenceThresholds, price, evidence) {
|
|
|
90
91
|
function accuracyOf(rows) {
|
|
91
92
|
return rows.length ? roundedTo(3, rows.filter((row) => row.correct).length / rows.length) : null;
|
|
92
93
|
}
|
|
93
|
-
function itemAccuracyOf(rows) {
|
|
94
|
+
function itemAccuracyOf(recipe, rows) {
|
|
94
95
|
const paths = rows.flatMap((row) => Object.entries(row.expected).map(([path, expected]) => ({
|
|
96
|
+
path,
|
|
95
97
|
expected,
|
|
96
98
|
actual: row.error === undefined ? row.actual?.[path] : undefined,
|
|
97
99
|
})));
|
|
98
100
|
if (paths.length <= rows.length)
|
|
99
101
|
return {};
|
|
100
|
-
const correct = paths.filter((
|
|
102
|
+
const correct = paths.filter(({ path, actual, expected }) => matchesExpected({ [path]: actual }, { [path]: expected }, recipe)).length;
|
|
101
103
|
return {
|
|
102
104
|
items: paths.length,
|
|
103
105
|
correctItems: correct,
|
package/dist/src/batch.js
CHANGED
|
@@ -11,7 +11,20 @@ export async function evaluateInBatches(items, batchSize, evaluate) {
|
|
|
11
11
|
const chunks = [];
|
|
12
12
|
for (let offset = 0; offset < items.length; offset += batchSize)
|
|
13
13
|
chunks.push({ chunk: items.slice(offset, offset + batchSize), offset });
|
|
14
|
-
|
|
14
|
+
let firstFailure;
|
|
15
|
+
const settled = await Promise.allSettled(chunks.map(async ({ chunk, offset }) => {
|
|
16
|
+
try {
|
|
17
|
+
return await evaluate(chunk, offset);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
firstFailure ??= { error };
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
24
|
+
// Let request recorders finish before a caller archives this failed batch.
|
|
25
|
+
if (firstFailure)
|
|
26
|
+
throw firstFailure.error;
|
|
27
|
+
const evaluations = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
|
|
15
28
|
return {
|
|
16
29
|
items: evaluations.flatMap((evaluation) => evaluation.items),
|
|
17
30
|
model: evaluations[0]?.model ?? 'none',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jev-recipes",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "Small, composable Jev decisions for retrieval, conversations, and agent workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1054,6 +1054,7 @@
|
|
|
1054
1054
|
"eval": "node --env-file-if-exists=.env evals/run.mjs",
|
|
1055
1055
|
"eval:smoke": "node --env-file-if-exists=.env evals/smoke.mjs",
|
|
1056
1056
|
"eval:validate": "node evals/validate.mjs",
|
|
1057
|
+
"eval:audit": "node scripts/audit-evidence.mjs",
|
|
1057
1058
|
"jev-recipes": "node --env-file-if-exists=.env dist/cli/index.js",
|
|
1058
1059
|
"ci": "node scripts/ci.mjs",
|
|
1059
1060
|
"pack:check": "node scripts/pack-check.mjs",
|