jev-recipes 0.7.0 → 0.8.1

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 (77) hide show
  1. package/README.md +5 -1
  2. package/dist/catalog/generated/details/callback-responsibility.json +45 -0
  3. package/dist/catalog/generated/details/contact-opt-out.json +45 -0
  4. package/dist/catalog/generated/details/followup-timing.json +45 -0
  5. package/dist/catalog/generated/details/game-action.json +8 -109
  6. package/dist/catalog/generated/loaders.js +3 -0
  7. package/dist/catalog/generated/metadata.js +78 -0
  8. package/dist/catalog/generated/names.d.ts +1 -1
  9. package/dist/catalog/generated/names.js +3 -0
  10. package/dist/catalog/schema.d.ts +3 -0
  11. package/dist/cli/evaluation.d.ts +1 -0
  12. package/dist/cli/evaluation.js +82 -0
  13. package/dist/cli/index.js +13 -0
  14. package/dist/cli/schema.d.ts +9 -0
  15. package/dist/evaluation/archive.d.ts +501 -0
  16. package/dist/evaluation/archive.js +276 -0
  17. package/dist/evaluation/dataset.d.ts +12 -0
  18. package/dist/evaluation/dataset.js +162 -0
  19. package/dist/evaluation/decisions.d.ts +10 -0
  20. package/dist/evaluation/decisions.js +87 -0
  21. package/dist/evaluation/engine.d.ts +50 -0
  22. package/dist/evaluation/engine.js +130 -0
  23. package/dist/evaluation/index.d.ts +5 -0
  24. package/dist/evaluation/index.js +3 -0
  25. package/dist/evaluation/report.d.ts +186 -0
  26. package/dist/evaluation/report.js +177 -0
  27. package/dist/evaluation/schema.d.ts +82 -0
  28. package/dist/evaluation/schema.js +50 -0
  29. package/dist/recipes/callback-responsibility/demo.json +25 -0
  30. package/dist/recipes/callback-responsibility/index.d.ts +5 -0
  31. package/dist/recipes/callback-responsibility/index.js +17 -0
  32. package/dist/recipes/callback-responsibility/schema.d.ts +42 -0
  33. package/dist/recipes/callback-responsibility/schema.js +20 -0
  34. package/dist/recipes/checkers-move/schema.d.ts +1 -1
  35. package/dist/recipes/choose-action/schema.d.ts +1 -1
  36. package/dist/recipes/contact-opt-out/demo.json +24 -0
  37. package/dist/recipes/contact-opt-out/index.d.ts +5 -0
  38. package/dist/recipes/contact-opt-out/index.js +17 -0
  39. package/dist/recipes/contact-opt-out/schema.d.ts +42 -0
  40. package/dist/recipes/contact-opt-out/schema.js +20 -0
  41. package/dist/recipes/correction-target/schema.d.ts +1 -1
  42. package/dist/recipes/exercise-select/schema.d.ts +1 -1
  43. package/dist/recipes/failure-kind/schema.d.ts +1 -1
  44. package/dist/recipes/field-select/schema.d.ts +1 -1
  45. package/dist/recipes/finger-actions/schema.d.ts +1 -1
  46. package/dist/recipes/followup-link/schema.d.ts +1 -1
  47. package/dist/recipes/followup-timing/demo.json +25 -0
  48. package/dist/recipes/followup-timing/index.d.ts +5 -0
  49. package/dist/recipes/followup-timing/index.js +18 -0
  50. package/dist/recipes/followup-timing/schema.d.ts +45 -0
  51. package/dist/recipes/followup-timing/schema.js +21 -0
  52. package/dist/recipes/game-action/schema.d.ts +6 -6
  53. package/dist/recipes/game-action/schema.js +11 -6
  54. package/dist/recipes/handoff/schema.d.ts +3 -3
  55. package/dist/recipes/incident-match/schema.d.ts +1 -1
  56. package/dist/recipes/model-route/schema.d.ts +1 -1
  57. package/dist/recipes/next-chord/schema.d.ts +1 -1
  58. package/dist/recipes/next-duration/schema.d.ts +1 -1
  59. package/dist/recipes/next-note/schema.d.ts +1 -1
  60. package/dist/recipes/objection-kind/schema.d.ts +3 -3
  61. package/dist/recipes/reference-resolve/schema.d.ts +1 -1
  62. package/dist/recipes/reply-template-match/schema.d.ts +1 -1
  63. package/dist/recipes/route/schema.d.ts +2 -2
  64. package/dist/recipes/route/schema.js +3 -3
  65. package/dist/recipes/route-many/schema.d.ts +3 -3
  66. package/dist/recipes/route-many/schema.js +3 -3
  67. package/dist/recipes/style-kind/schema.d.ts +3 -3
  68. package/dist/src/answers.d.ts +1 -1
  69. package/dist/src/answers.js +6 -2
  70. package/dist/src/data.d.ts +6 -0
  71. package/dist/src/data.js +41 -0
  72. package/dist/src/decisions.d.ts +1 -1
  73. package/dist/src/index.d.ts +6 -0
  74. package/dist/src/index.js +3 -0
  75. package/dist/src/schema.d.ts +4 -4
  76. package/dist/src/schema.js +4 -3
  77. package/package.json +23 -2
@@ -0,0 +1,130 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { createClient } from '../src/client.js';
3
+ import { caseConfidence, isReady, mapWithConcurrencyLimit, matchesExpected, pickPaths, } from './decisions.js';
4
+ export const confidenceThresholds = [0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95];
5
+ export async function evaluateCases(recipe, cases, options = {}) {
6
+ let requests = 0;
7
+ let client = options.client;
8
+ const limitedClient = {
9
+ async systemOne(request, requestOptions) {
10
+ if (requests >= (options.maxRequests ?? 1000))
11
+ throw new Error('Evaluation request limit reached.');
12
+ requests++;
13
+ client ??= createClient();
14
+ return client.systemOne(request, requestOptions);
15
+ },
16
+ };
17
+ return mapWithConcurrencyLimit(cases, options.concurrency ?? 4, async (goldenCase, index) => {
18
+ const recorded = options.recorded?.find((row) => row.id === goldenCase.id);
19
+ if (options.recorded && !recorded)
20
+ throw new Error(`Missing archived case: ${goldenCase.id}`);
21
+ const playback = recorded ? replayResponses(recorded.exchanges) : undefined;
22
+ const row = await evaluateCase(recipe, goldenCase, playback?.client ?? limitedClient, options);
23
+ playback?.assertComplete();
24
+ if (recorded) {
25
+ row.durationMs = recorded.durationMs;
26
+ row.exchanges = recorded.exchanges;
27
+ }
28
+ await options.onCase?.(row, index);
29
+ return row;
30
+ });
31
+ }
32
+ async function evaluateCase(recipe, goldenCase, client, options) {
33
+ const row = {
34
+ id: goldenCase.id,
35
+ expected: goldenCase.expected,
36
+ contested: goldenCase.contested,
37
+ adversarial: goldenCase.adversarial,
38
+ correct: false,
39
+ ready: false,
40
+ durationMs: 0,
41
+ exchanges: [],
42
+ decisions: [],
43
+ };
44
+ const started = performance.now();
45
+ const recipeOptions = { client: recordResponses(client, row.exchanges) };
46
+ if (options.model !== undefined)
47
+ recipeOptions.model = options.model;
48
+ if (options.signal !== undefined)
49
+ recipeOptions.signal = options.signal;
50
+ try {
51
+ row.result = await recipe.run(goldenCase.input, recipeOptions);
52
+ row.actual = pickPaths(row.result, Object.keys(goldenCase.expected));
53
+ row.correct = matchesExpected(row.actual, row.expected);
54
+ row.ready = isReady(row.result);
55
+ row.confidence = caseConfidence(row.result, Object.keys(goldenCase.expected));
56
+ const model = pickPaths(row.result, ['model']).model;
57
+ if (typeof model === 'string')
58
+ row.model = model;
59
+ }
60
+ catch (error) {
61
+ if (error instanceof ReplayMismatch)
62
+ throw error;
63
+ row.error = safeError(error);
64
+ }
65
+ row.durationMs = Math.round((performance.now() - started) * 100) / 100;
66
+ if (row.error !== undefined)
67
+ return row;
68
+ for (const minConfidence of options.thresholds ?? []) {
69
+ const playback = replayResponses(row.exchanges);
70
+ const result = await recipe.run({ ...goldenCase.input, minConfidence }, { ...recipeOptions, client: playback.client });
71
+ playback.assertComplete();
72
+ row.decisions.push({
73
+ minConfidence,
74
+ ready: isReady(result),
75
+ correct: matchesExpected(pickPaths(result, Object.keys(row.expected)), row.expected),
76
+ });
77
+ }
78
+ return row;
79
+ }
80
+ function recordResponses(client, exchanges) {
81
+ return {
82
+ async systemOne(request, options) {
83
+ const exchange = { request: structuredClone(request), durationMs: 0 };
84
+ exchanges.push(exchange);
85
+ const started = performance.now();
86
+ try {
87
+ const response = await client.systemOne(request, options);
88
+ exchange.response = structuredClone(response);
89
+ return response;
90
+ }
91
+ catch (error) {
92
+ exchange.error = safeError(error);
93
+ throw error;
94
+ }
95
+ finally {
96
+ exchange.durationMs = Math.round((performance.now() - started) * 100) / 100;
97
+ }
98
+ },
99
+ };
100
+ }
101
+ class ReplayMismatch extends Error {
102
+ }
103
+ export function replayResponses(exchanges) {
104
+ const remaining = [...exchanges];
105
+ return {
106
+ client: {
107
+ async systemOne(request) {
108
+ const index = remaining.findIndex((exchange) => isDeepStrictEqual(exchange.request, request));
109
+ if (index === -1)
110
+ throw new ReplayMismatch('Replay requested an unrecorded model response. The prompt or model has changed.');
111
+ const [exchange] = remaining.splice(index, 1);
112
+ if (exchange.error !== undefined)
113
+ throw new Error(exchange.error);
114
+ return structuredClone(exchange.response);
115
+ },
116
+ },
117
+ assertComplete() {
118
+ if (remaining.length)
119
+ throw new ReplayMismatch('Replay did not consume every recorded request. The recipe has changed.');
120
+ },
121
+ };
122
+ }
123
+ function safeError(error) {
124
+ let message = error instanceof Error ? error.message : String(error);
125
+ for (const [name, value] of Object.entries(process.env)) {
126
+ if (/KEY|TOKEN|SECRET|PASSWORD/i.test(name) && value && value.length >= 6)
127
+ message = message.replaceAll(value, '[redacted]');
128
+ }
129
+ return message.replace(/Bearer\s+\S+/gi, 'Bearer [redacted]');
130
+ }
@@ -0,0 +1,5 @@
1
+ export { evaluate, replay, compare, readRun, freezePolicy } from './archive.js';
2
+ export type { EvaluationRun } from './archive.js';
3
+ export { readEvaluationCases } from './dataset.js';
4
+ export { evaluationCaseSchema, evaluationOptionsSchema, evaluationPolicySchema } from './schema.js';
5
+ export type { EvaluationCase, EvaluationPolicy, EvaluationOptions, Price } from './schema.js';
@@ -0,0 +1,3 @@
1
+ export { evaluate, replay, compare, readRun, freezePolicy } from './archive.js';
2
+ export { readEvaluationCases } from './dataset.js';
3
+ export { evaluationCaseSchema, evaluationOptionsSchema, evaluationPolicySchema } from './schema.js';
@@ -0,0 +1,186 @@
1
+ import type { EvaluationRow } from './engine.js';
2
+ import type { Price } from './schema.js';
3
+ import type { EvaluationCase, EvaluationPolicy } from './schema.js';
4
+ export interface ReportEvidence {
5
+ runId: string;
6
+ evaluatedAt: string;
7
+ packageVersion: string;
8
+ recipeFingerprint: string;
9
+ datasetFingerprint: string;
10
+ split: 'development' | 'held-out';
11
+ mode: 'live' | 'fixture' | 'replay';
12
+ policy: EvaluationPolicy;
13
+ provenance: {
14
+ method: EvaluationCase['provenance']['method'];
15
+ source: string;
16
+ cases: number;
17
+ }[];
18
+ }
19
+ export declare function buildReport(id: string, rows: EvaluationRow[], confidenceThresholds: number[], price?: Price, evidence?: ReportEvidence): {
20
+ contestedAccuracy: number | null;
21
+ adversarialAccuracy: number | null;
22
+ calibration: {
23
+ confidence: string;
24
+ cases: number;
25
+ accuracy: number | null;
26
+ }[];
27
+ thresholdEvaluation: string;
28
+ thresholds: {
29
+ minConfidence: number;
30
+ deferRate: number | null;
31
+ readyAccuracy: number | null;
32
+ readyCases: number;
33
+ reviewCases: number;
34
+ failedCases: number;
35
+ readyAccuracyInterval95: [number, number] | null;
36
+ }[];
37
+ suggestedMinConfidence: number | null;
38
+ latencyMs: {
39
+ p50: number | null;
40
+ p95: number | null;
41
+ totalCaseTime: number;
42
+ };
43
+ usage: {
44
+ requests: number;
45
+ input_tokens: number;
46
+ output_tokens: number;
47
+ unknownResponses: number;
48
+ failedRequests: number;
49
+ models: string[];
50
+ };
51
+ cost: {
52
+ estimated: number;
53
+ model: string;
54
+ inputPerMillion: number;
55
+ outputPerMillion: number;
56
+ currency: "USD";
57
+ date: string;
58
+ source: string;
59
+ } | null;
60
+ confusion: {
61
+ [k: string]: {
62
+ total: number;
63
+ correct: number;
64
+ misses: Record<string, number>;
65
+ };
66
+ };
67
+ failures: ({
68
+ adversarial?: true;
69
+ contested?: true;
70
+ confidence?: number;
71
+ actual: Record<string, unknown> | undefined;
72
+ id: string;
73
+ expected: Record<string, unknown>;
74
+ } | {
75
+ adversarial?: true;
76
+ contested?: true;
77
+ confidence?: number;
78
+ error: string;
79
+ id: string;
80
+ expected: Record<string, unknown>;
81
+ })[];
82
+ items?: never;
83
+ correctItems?: never;
84
+ itemAccuracy?: never;
85
+ recipe: string;
86
+ model: string;
87
+ models: string[];
88
+ cases: number;
89
+ completed: number;
90
+ failed: number;
91
+ correct: number;
92
+ wrong: number;
93
+ ready: number;
94
+ review: number;
95
+ accuracy: number | null;
96
+ completedAccuracy: number | null;
97
+ accuracyInterval95: [number, number] | null;
98
+ readyAccuracy: number | null;
99
+ readyAccuracyInterval95: [number, number] | null;
100
+ reviewRate: number | null;
101
+ evidence?: ReportEvidence;
102
+ } | {
103
+ contestedAccuracy: number | null;
104
+ adversarialAccuracy: number | null;
105
+ calibration: {
106
+ confidence: string;
107
+ cases: number;
108
+ accuracy: number | null;
109
+ }[];
110
+ thresholdEvaluation: string;
111
+ thresholds: {
112
+ minConfidence: number;
113
+ deferRate: number | null;
114
+ readyAccuracy: number | null;
115
+ readyCases: number;
116
+ reviewCases: number;
117
+ failedCases: number;
118
+ readyAccuracyInterval95: [number, number] | null;
119
+ }[];
120
+ suggestedMinConfidence: number | null;
121
+ latencyMs: {
122
+ p50: number | null;
123
+ p95: number | null;
124
+ totalCaseTime: number;
125
+ };
126
+ usage: {
127
+ requests: number;
128
+ input_tokens: number;
129
+ output_tokens: number;
130
+ unknownResponses: number;
131
+ failedRequests: number;
132
+ models: string[];
133
+ };
134
+ cost: {
135
+ estimated: number;
136
+ model: string;
137
+ inputPerMillion: number;
138
+ outputPerMillion: number;
139
+ currency: "USD";
140
+ date: string;
141
+ source: string;
142
+ } | null;
143
+ confusion: {
144
+ [k: string]: {
145
+ total: number;
146
+ correct: number;
147
+ misses: Record<string, number>;
148
+ };
149
+ };
150
+ failures: ({
151
+ adversarial?: true;
152
+ contested?: true;
153
+ confidence?: number;
154
+ actual: Record<string, unknown> | undefined;
155
+ id: string;
156
+ expected: Record<string, unknown>;
157
+ } | {
158
+ adversarial?: true;
159
+ contested?: true;
160
+ confidence?: number;
161
+ error: string;
162
+ id: string;
163
+ expected: Record<string, unknown>;
164
+ })[];
165
+ items: number;
166
+ correctItems: number;
167
+ itemAccuracy: number;
168
+ recipe: string;
169
+ model: string;
170
+ models: string[];
171
+ cases: number;
172
+ completed: number;
173
+ failed: number;
174
+ correct: number;
175
+ wrong: number;
176
+ ready: number;
177
+ review: number;
178
+ accuracy: number | null;
179
+ completedAccuracy: number | null;
180
+ accuracyInterval95: [number, number] | null;
181
+ readyAccuracy: number | null;
182
+ readyAccuracyInterval95: [number, number] | null;
183
+ reviewRate: number | null;
184
+ evidence?: ReportEvidence;
185
+ };
186
+ export declare function wilsonInterval(correct: number, total: number): [number, number] | null;
@@ -0,0 +1,177 @@
1
+ import { isRecord, matchesExpected, roundedTo } from './decisions.js';
2
+ export function buildReport(id, rows, confidenceThresholds, price, evidence) {
3
+ const completed = rows.filter((row) => row.error === undefined);
4
+ const ready = completed.filter((row) => row.ready);
5
+ const thresholds = confidenceThresholds.map((minConfidence) => {
6
+ const decisions = completed
7
+ .flatMap((row) => row.decisions)
8
+ .filter((decision) => decision.minConfidence === minConfidence);
9
+ const accepted = decisions.filter((decision) => decision.ready);
10
+ return {
11
+ minConfidence,
12
+ deferRate: completed.length
13
+ ? roundedTo(3, (completed.length - accepted.length) / completed.length)
14
+ : null,
15
+ readyAccuracy: accuracyOf(accepted),
16
+ readyCases: accepted.length,
17
+ reviewCases: completed.length - accepted.length,
18
+ failedCases: rows.length - completed.length,
19
+ readyAccuracyInterval95: wilsonInterval(accepted.filter((entry) => entry.correct).length, accepted.length),
20
+ };
21
+ });
22
+ const usage = usageOf(rows);
23
+ const models = [...new Set(rows.flatMap((row) => (row.model ? [row.model] : [])))].sort();
24
+ return {
25
+ ...(evidence ? { evidence } : {}),
26
+ recipe: id,
27
+ model: models.length === 1 ? models[0] : models.length ? 'mixed' : 'unknown',
28
+ models,
29
+ cases: rows.length,
30
+ completed: completed.length,
31
+ failed: rows.length - completed.length,
32
+ correct: rows.filter((row) => row.correct).length,
33
+ wrong: completed.filter((row) => !row.correct).length,
34
+ ready: ready.length,
35
+ review: completed.length - ready.length,
36
+ accuracy: accuracyOf(rows),
37
+ completedAccuracy: accuracyOf(completed),
38
+ accuracyInterval95: wilsonInterval(rows.filter((row) => row.correct).length, rows.length),
39
+ readyAccuracy: accuracyOf(ready),
40
+ readyAccuracyInterval95: wilsonInterval(ready.filter((row) => row.correct).length, ready.length),
41
+ reviewRate: completed.length
42
+ ? roundedTo(3, (completed.length - ready.length) / completed.length)
43
+ : null,
44
+ ...itemAccuracyOf(rows),
45
+ contestedAccuracy: accuracyOf(rows.filter((row) => row.contested)),
46
+ adversarialAccuracy: accuracyOf(rows.filter((row) => row.adversarial)),
47
+ calibration: [0, 0.5, 0.6, 0.7, 0.8, 0.9].map((lower, index, edges) => {
48
+ const upper = edges[index + 1] ?? 1.000001;
49
+ const bucket = rows.filter((row) => row.confidence !== undefined && row.confidence >= lower && row.confidence < upper);
50
+ return {
51
+ confidence: `${lower}-${Math.min(upper, 1)}`,
52
+ cases: bucket.length,
53
+ accuracy: accuracyOf(bucket),
54
+ };
55
+ }),
56
+ thresholdEvaluation: evidence?.split === 'held-out'
57
+ ? 'frozen-policy'
58
+ : confidenceThresholds.length
59
+ ? 'recipe-replay'
60
+ : 'not-applicable',
61
+ thresholds,
62
+ suggestedMinConfidence: thresholds.find((entry) => entry.readyAccuracy !== null && entry.readyAccuracy >= 0.95)
63
+ ?.minConfidence ?? null,
64
+ latencyMs: latencyOf(rows.map((row) => row.durationMs)),
65
+ usage,
66
+ cost: price &&
67
+ usage.models.every((model) => model === price.model) &&
68
+ usage.unknownResponses === 0 &&
69
+ usage.failedRequests === 0
70
+ ? {
71
+ ...price,
72
+ estimated: roundedTo(8, (usage.input_tokens * price.inputPerMillion +
73
+ usage.output_tokens * price.outputPerMillion) /
74
+ 1_000_000),
75
+ }
76
+ : null,
77
+ confusion: confusionOf(rows),
78
+ failures: rows
79
+ .filter((row) => !row.correct)
80
+ .map(({ id, expected, actual, error, confidence, contested, adversarial }) => ({
81
+ id,
82
+ expected,
83
+ ...(error === undefined ? { actual } : { error }),
84
+ ...(confidence === undefined ? {} : { confidence: roundedTo(3, confidence) }),
85
+ ...(contested ? { contested } : {}),
86
+ ...(adversarial ? { adversarial } : {}),
87
+ })),
88
+ };
89
+ }
90
+ function accuracyOf(rows) {
91
+ return rows.length ? roundedTo(3, rows.filter((row) => row.correct).length / rows.length) : null;
92
+ }
93
+ function itemAccuracyOf(rows) {
94
+ const paths = rows.flatMap((row) => Object.entries(row.expected).map(([path, expected]) => ({
95
+ expected,
96
+ actual: row.error === undefined ? row.actual?.[path] : undefined,
97
+ })));
98
+ if (paths.length <= rows.length)
99
+ return {};
100
+ const correct = paths.filter((entry) => matchesExpected(entry.actual, entry.expected)).length;
101
+ return {
102
+ items: paths.length,
103
+ correctItems: correct,
104
+ itemAccuracy: roundedTo(3, correct / paths.length),
105
+ };
106
+ }
107
+ export function wilsonInterval(correct, total) {
108
+ if (!total)
109
+ return null;
110
+ const z = 1.959963984540054;
111
+ const proportion = correct / total;
112
+ const denominator = 1 + (z * z) / total;
113
+ const center = (proportion + (z * z) / (2 * total)) / denominator;
114
+ const margin = (z * Math.sqrt((proportion * (1 - proportion)) / total + (z * z) / (4 * total * total))) /
115
+ denominator;
116
+ return [roundedTo(4, Math.max(0, center - margin)), roundedTo(4, Math.min(1, center + margin))];
117
+ }
118
+ function latencyOf(values) {
119
+ const ordered = [...values].sort((a, b) => a - b);
120
+ const percentile = (fraction) => ordered[Math.max(0, Math.ceil(ordered.length * fraction) - 1)] ?? null;
121
+ return {
122
+ p50: percentile(0.5),
123
+ p95: percentile(0.95),
124
+ totalCaseTime: roundedTo(2, values.reduce((sum, value) => sum + value, 0)),
125
+ };
126
+ }
127
+ function usageOf(rows) {
128
+ let input_tokens = 0, output_tokens = 0, unknownResponses = 0, failedRequests = 0;
129
+ const models = new Set();
130
+ const exchanges = rows.flatMap((row) => row.exchanges);
131
+ for (const exchange of exchanges) {
132
+ if (exchange.error !== undefined) {
133
+ failedRequests++;
134
+ continue;
135
+ }
136
+ const response = exchange.response;
137
+ if (isRecord(response) && typeof response.model === 'string')
138
+ models.add(response.model);
139
+ if (!isRecord(response) ||
140
+ !isRecord(response.usage) ||
141
+ typeof response.usage.input_tokens !== 'number' ||
142
+ typeof response.usage.output_tokens !== 'number') {
143
+ unknownResponses++;
144
+ continue;
145
+ }
146
+ input_tokens += response.usage.input_tokens;
147
+ output_tokens += response.usage.output_tokens;
148
+ }
149
+ return {
150
+ requests: exchanges.length,
151
+ input_tokens,
152
+ output_tokens,
153
+ unknownResponses,
154
+ failedRequests,
155
+ models: [...models].sort(),
156
+ };
157
+ }
158
+ function confusionOf(rows) {
159
+ const byExpected = new Map();
160
+ for (const row of rows) {
161
+ const label = JSON.stringify(row.expected);
162
+ const entry = byExpected.get(label) ?? {
163
+ total: 0,
164
+ correct: 0,
165
+ misses: Object.create(null),
166
+ };
167
+ entry.total++;
168
+ if (row.correct)
169
+ entry.correct++;
170
+ else {
171
+ const actual = row.error === undefined ? JSON.stringify(row.actual) : `error: ${row.error}`;
172
+ entry.misses[actual] = (entry.misses[actual] ?? 0) + 1;
173
+ }
174
+ byExpected.set(label, entry);
175
+ }
176
+ return Object.fromEntries([...byExpected.entries()].sort(([a], [b]) => a.localeCompare(b)));
177
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ export declare const provenanceSchema: z.ZodObject<{
3
+ method: z.ZodEnum<{
4
+ "author-synthetic": "author-synthetic";
5
+ "human-reviewed": "human-reviewed";
6
+ "public-dataset": "public-dataset";
7
+ unspecified: "unspecified";
8
+ }>;
9
+ source: z.ZodString;
10
+ }, z.core.$strip>;
11
+ export declare const evaluationCaseSchema: z.ZodObject<{
12
+ id: z.ZodString;
13
+ input: z.ZodNonOptional<z.ZodUnknown & z.ZodType<Record<string, import("../src/data.js").JsonValue>, unknown, z.core.$ZodTypeInternals<Record<string, import("../src/data.js").JsonValue>, unknown>>>;
14
+ expected: z.ZodNonOptional<z.ZodUnknown & z.ZodType<Record<string, import("../src/data.js").JsonValue>, unknown, z.core.$ZodTypeInternals<Record<string, import("../src/data.js").JsonValue>, unknown>>>;
15
+ rationale: z.ZodString;
16
+ family: z.ZodOptional<z.ZodString>;
17
+ split: z.ZodDefault<z.ZodEnum<{
18
+ development: "development";
19
+ "held-out": "held-out";
20
+ }>>;
21
+ provenance: z.ZodDefault<z.ZodObject<{
22
+ method: z.ZodEnum<{
23
+ "author-synthetic": "author-synthetic";
24
+ "human-reviewed": "human-reviewed";
25
+ "public-dataset": "public-dataset";
26
+ unspecified: "unspecified";
27
+ }>;
28
+ source: z.ZodString;
29
+ }, z.core.$strip>>;
30
+ contested: z.ZodDefault<z.ZodBoolean>;
31
+ adversarial: z.ZodDefault<z.ZodBoolean>;
32
+ }, z.core.$strip>;
33
+ export declare const evaluationPolicySchema: z.ZodObject<{
34
+ minConfidence: z.ZodOptional<z.ZodNumber>;
35
+ selectedOn: z.ZodOptional<z.ZodObject<{
36
+ runId: z.ZodString;
37
+ datasetFingerprint: z.ZodString;
38
+ recipeFingerprint: z.ZodString;
39
+ model: z.ZodString;
40
+ split: z.ZodLiteral<"development">;
41
+ }, z.core.$strip>>;
42
+ }, z.core.$strip>;
43
+ export declare const priceSchema: z.ZodObject<{
44
+ model: z.ZodString;
45
+ inputPerMillion: z.ZodNumber;
46
+ outputPerMillion: z.ZodNumber;
47
+ currency: z.ZodLiteral<"USD">;
48
+ date: z.ZodISODate;
49
+ source: z.ZodString;
50
+ }, z.core.$strip>;
51
+ export declare const evaluationOptionsSchema: z.ZodObject<{
52
+ concurrency: z.ZodDefault<z.ZodNumber>;
53
+ maxCases: z.ZodDefault<z.ZodNumber>;
54
+ maxRequests: z.ZodDefault<z.ZodNumber>;
55
+ model: z.ZodOptional<z.ZodString>;
56
+ split: z.ZodDefault<z.ZodEnum<{
57
+ development: "development";
58
+ "held-out": "held-out";
59
+ }>>;
60
+ policy: z.ZodDefault<z.ZodObject<{
61
+ minConfidence: z.ZodOptional<z.ZodNumber>;
62
+ selectedOn: z.ZodOptional<z.ZodObject<{
63
+ runId: z.ZodString;
64
+ datasetFingerprint: z.ZodString;
65
+ recipeFingerprint: z.ZodString;
66
+ model: z.ZodString;
67
+ split: z.ZodLiteral<"development">;
68
+ }, z.core.$strip>>;
69
+ }, z.core.$strip>>;
70
+ price: z.ZodOptional<z.ZodObject<{
71
+ model: z.ZodString;
72
+ inputPerMillion: z.ZodNumber;
73
+ outputPerMillion: z.ZodNumber;
74
+ currency: z.ZodLiteral<"USD">;
75
+ date: z.ZodISODate;
76
+ source: z.ZodString;
77
+ }, z.core.$strip>>;
78
+ }, z.core.$strip>;
79
+ export type EvaluationCase = z.infer<typeof evaluationCaseSchema>;
80
+ export type EvaluationPolicy = z.infer<typeof evaluationPolicySchema>;
81
+ export type EvaluationOptions = z.input<typeof evaluationOptionsSchema>;
82
+ export type Price = z.infer<typeof priceSchema>;
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod';
2
+ import { nonEmptyText, probability } from '../src/schema.js';
3
+ import { recordWithOwnKeys, jsonValueSchema } from '../src/data.js';
4
+ export const provenanceSchema = z.object({
5
+ method: z.enum(['author-synthetic', 'human-reviewed', 'public-dataset', 'unspecified']),
6
+ source: nonEmptyText,
7
+ });
8
+ export const evaluationCaseSchema = z.object({
9
+ id: nonEmptyText,
10
+ input: recordWithOwnKeys(z.string(), jsonValueSchema),
11
+ expected: recordWithOwnKeys(nonEmptyText, jsonValueSchema).refine((expected) => Object.keys(expected).length > 0, 'Provide at least one expected result field.'),
12
+ rationale: nonEmptyText,
13
+ family: nonEmptyText.optional(),
14
+ split: z.enum(['development', 'held-out']).default('development'),
15
+ provenance: provenanceSchema.default({
16
+ method: 'unspecified',
17
+ source: 'Not recorded by the dataset author.',
18
+ }),
19
+ contested: z.boolean().default(false),
20
+ adversarial: z.boolean().default(false),
21
+ });
22
+ export const evaluationPolicySchema = z.object({
23
+ minConfidence: probability.optional(),
24
+ selectedOn: z
25
+ .object({
26
+ runId: nonEmptyText,
27
+ datasetFingerprint: nonEmptyText,
28
+ recipeFingerprint: nonEmptyText,
29
+ model: nonEmptyText,
30
+ split: z.literal('development'),
31
+ })
32
+ .optional(),
33
+ });
34
+ export const priceSchema = z.object({
35
+ model: nonEmptyText,
36
+ inputPerMillion: z.number().nonnegative(),
37
+ outputPerMillion: z.number().nonnegative(),
38
+ currency: z.literal('USD'),
39
+ date: z.iso.date(),
40
+ source: nonEmptyText,
41
+ });
42
+ export const evaluationOptionsSchema = z.object({
43
+ concurrency: z.number().int().min(1).max(32).default(4),
44
+ maxCases: z.number().int().positive().default(1000),
45
+ maxRequests: z.number().int().positive().default(1000),
46
+ model: nonEmptyText.optional(),
47
+ split: z.enum(['development', 'held-out']).default('development'),
48
+ policy: evaluationPolicySchema.default({}),
49
+ price: priceSchema.optional(),
50
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "input": {
3
+ "conversation": "Avery: I will call you when the replacement arrives. Sam: Thanks, I will wait for your call.",
4
+ "roles": "Avery is the business representative. Sam is the customer.",
5
+ "minConfidence": 0.8
6
+ },
7
+ "response": {
8
+ "model": "demo-fixture",
9
+ "answers": {
10
+ "decision": {
11
+ "type": "choice",
12
+ "choice": "business",
13
+ "confidence": 0.94,
14
+ "probabilities": {
15
+ "business": 0.94,
16
+ "customer": 0.01,
17
+ "either": 0.01,
18
+ "none": 0.01,
19
+ "unclear": 0.03
20
+ }
21
+ }
22
+ },
23
+ "usage": { "input_tokens": 0, "output_tokens": 0 }
24
+ }
25
+ }
@@ -0,0 +1,5 @@
1
+ import type { RecipeOptions } from '../../src/schema.js';
2
+ import type { CallbackResponsibilityInput, CallbackResponsibilityResult } from './schema.js';
3
+ export declare function callbackResponsibility(input: CallbackResponsibilityInput, options?: RecipeOptions): Promise<CallbackResponsibilityResult>;
4
+ export { callbackResponsibilityInputSchema, callbackResponsibilityResultSchema, callbackResponsibilityVerdictSchema, } from './schema.js';
5
+ export type { CallbackResponsibilityInput, CallbackResponsibilityResult, CallbackResponsibilityVerdict, } from './schema.js';