@valerypopoff/trivet 2.0.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.
- package/LICENSE +7 -0
- package/README.md +12 -0
- package/dist/cjs/bundle.cjs +381 -0
- package/dist/cjs/bundle.cjs.map +7 -0
- package/dist/esm/api.js +184 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/serialization/serialization_v1.js +56 -0
- package/dist/esm/trivetTypes.js +1 -0
- package/dist/esm/validateTestCaseFormat.js +17 -0
- package/dist/esm/validateValidationGraphFormat.js +33 -0
- package/dist/types/api.d.ts +16 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/serialization/serialization_v1.d.ts +26 -0
- package/dist/types/trivetTypes.d.ts +49 -0
- package/dist/types/validateTestCaseFormat.d.ts +7 -0
- package/dist/types/validateValidationGraphFormat.d.ts +5 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Val P.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @valerypopoff/trivet
|
|
2
|
+
|
|
3
|
+
Graph-oriented testing utilities for Rivet 2.0 projects.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn workspace @valerypopoff/trivet run build
|
|
9
|
+
yarn workspace @valerypopoff/trivet run lint
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
See the root [README](../../README.md) and [package docs](../../developer-docs/PACKAGES.md) for the current workspace contract.
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
DummyNativeApi: () => DummyNativeApi,
|
|
34
|
+
createTestGraphRunner: () => createTestGraphRunner,
|
|
35
|
+
deserializeTestCase: () => deserializeTestCase,
|
|
36
|
+
deserializeTestSuite: () => deserializeTestSuite,
|
|
37
|
+
deserializeTrivetData: () => deserializeTrivetData,
|
|
38
|
+
deserializeTrivetDataFromString: () => deserializeTrivetDataFromString,
|
|
39
|
+
runTrivet: () => runTrivet,
|
|
40
|
+
serializeTestCase: () => serializeTestCase,
|
|
41
|
+
serializeTestSuite: () => serializeTestSuite,
|
|
42
|
+
serializeTrivetData: () => serializeTrivetData,
|
|
43
|
+
serializeTrivetDataToString: () => serializeTrivetDataToString,
|
|
44
|
+
validateTestCaseFormat: () => validateTestCaseFormat,
|
|
45
|
+
validateValidationGraphFormat: () => validateValidationGraphFormat
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(src_exports);
|
|
48
|
+
|
|
49
|
+
// src/trivetTypes.ts
|
|
50
|
+
var import_rivet2_core = require("@valerypopoff/rivet2-core");
|
|
51
|
+
|
|
52
|
+
// src/serialization/serialization_v1.ts
|
|
53
|
+
var yaml = __toESM(require("yaml"), 1);
|
|
54
|
+
function serializeTestCase(testCase) {
|
|
55
|
+
return {
|
|
56
|
+
id: testCase.id,
|
|
57
|
+
input: testCase.input,
|
|
58
|
+
expectedOutput: testCase.expectedOutput
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function deserializeTestCase(data) {
|
|
62
|
+
return {
|
|
63
|
+
id: data.id,
|
|
64
|
+
input: data.input,
|
|
65
|
+
expectedOutput: data.expectedOutput
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function serializeTestSuite(testSuite) {
|
|
69
|
+
return {
|
|
70
|
+
id: testSuite.id,
|
|
71
|
+
name: testSuite.name,
|
|
72
|
+
description: testSuite.description,
|
|
73
|
+
testGraph: testSuite.testGraph,
|
|
74
|
+
validationGraph: testSuite.validationGraph,
|
|
75
|
+
testCases: testSuite.testCases.map((testCase) => serializeTestCase(testCase))
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function deserializeTestSuite(data) {
|
|
79
|
+
return {
|
|
80
|
+
id: data.id,
|
|
81
|
+
name: data.name,
|
|
82
|
+
description: data.description,
|
|
83
|
+
testGraph: data.testGraph,
|
|
84
|
+
validationGraph: data.validationGraph,
|
|
85
|
+
testCases: data.testCases.map((testCase) => deserializeTestCase(testCase))
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function serializeTrivetData(data) {
|
|
89
|
+
return {
|
|
90
|
+
version: 1,
|
|
91
|
+
testSuites: data.testSuites.map((testSuite) => serializeTestSuite(testSuite))
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function deserializeTrivetData(data) {
|
|
95
|
+
if (data.version !== 1) {
|
|
96
|
+
throw new Error(`Unsupported version: ${data.version}`);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
testSuites: data.testSuites.map((testSuite) => deserializeTestSuite(testSuite))
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function serializeTrivetDataToString(data) {
|
|
103
|
+
return yaml.stringify(serializeTrivetData(data), null, 2);
|
|
104
|
+
}
|
|
105
|
+
function deserializeTrivetDataFromString(data) {
|
|
106
|
+
return deserializeTrivetData(yaml.parse(data));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/api.ts
|
|
110
|
+
var import_rivet2_core2 = require("@valerypopoff/rivet2-core");
|
|
111
|
+
var import_lodash_es = require("lodash");
|
|
112
|
+
var TRUTHY_STRINGS = /* @__PURE__ */ new Set(["true", "TRUE"]);
|
|
113
|
+
function validateOutput(v) {
|
|
114
|
+
switch (v.type) {
|
|
115
|
+
case "boolean":
|
|
116
|
+
return v.value;
|
|
117
|
+
case "string":
|
|
118
|
+
return TRUTHY_STRINGS.has(v.value);
|
|
119
|
+
default:
|
|
120
|
+
throw new Error(`Unexpected output type: ${v.type}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
var DummyNativeApi = class {
|
|
124
|
+
readdir(path, baseDir, options) {
|
|
125
|
+
throw new Error(`Method not implemented. ${path} ${baseDir} ${options}`);
|
|
126
|
+
}
|
|
127
|
+
readTextFile(path, baseDir) {
|
|
128
|
+
throw new Error(`Method not implemented. ${path} ${baseDir}`);
|
|
129
|
+
}
|
|
130
|
+
readBinaryFile(path, baseDir) {
|
|
131
|
+
throw new Error(`Method not implemented. ${path} ${baseDir}`);
|
|
132
|
+
}
|
|
133
|
+
writeTextFile(path, data, baseDir) {
|
|
134
|
+
throw new Error(`Method not implemented. ${path} ${baseDir} ${data}`);
|
|
135
|
+
}
|
|
136
|
+
exec(command, args, options) {
|
|
137
|
+
throw new Error(`Method not implemented. ${command} ${args} ${options}`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
function createTestGraphRunner(opts) {
|
|
141
|
+
return async (project, graphId, inputs) => {
|
|
142
|
+
const processor = new import_rivet2_core2.GraphProcessor(project, graphId, import_rivet2_core2.globalRivetNodeRegistry);
|
|
143
|
+
processor.executor = opts.executor;
|
|
144
|
+
const resolvedContextValues = {};
|
|
145
|
+
const outputs = await processor.processGraph(
|
|
146
|
+
{
|
|
147
|
+
nativeApi: new DummyNativeApi(),
|
|
148
|
+
tokenizer: new import_rivet2_core2.GptTokenizerTokenizer(),
|
|
149
|
+
settings: (0, import_rivet2_core2.resolveProcessSettings)({ openAiKey: opts.openAiKey })
|
|
150
|
+
},
|
|
151
|
+
inputs,
|
|
152
|
+
resolvedContextValues
|
|
153
|
+
);
|
|
154
|
+
return outputs;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function runTrivet(opts) {
|
|
158
|
+
var _a;
|
|
159
|
+
const { project, testSuites, runGraph, onUpdate, iterationCount = 1 } = opts;
|
|
160
|
+
const graphsById = (0, import_lodash_es.keyBy)(project.graphs, (g) => {
|
|
161
|
+
var _a2;
|
|
162
|
+
return ((_a2 = g.metadata) == null ? void 0 : _a2.id) ?? "";
|
|
163
|
+
});
|
|
164
|
+
const trivetResults = {
|
|
165
|
+
testSuiteResults: [],
|
|
166
|
+
iterationCount
|
|
167
|
+
};
|
|
168
|
+
for (const testSuite of testSuites) {
|
|
169
|
+
const testGraph = graphsById[testSuite.testGraph];
|
|
170
|
+
const validationGraph = graphsById[testSuite.validationGraph];
|
|
171
|
+
if (testGraph === void 0) {
|
|
172
|
+
(0, import_rivet2_core2.logRuntimeWarn)("Missing Trivet test graph; skipping.", { testGraphId: testSuite.testGraph });
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (validationGraph === void 0) {
|
|
176
|
+
(0, import_rivet2_core2.logRuntimeWarn)("Missing Trivet validation graph; skipping.", { validationGraphId: testSuite.validationGraph });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const validationOutputNodesById = (0, import_lodash_es.keyBy)(
|
|
180
|
+
validationGraph.nodes.filter((n) => n.type === "graphOutput"),
|
|
181
|
+
(n) => n.data.id
|
|
182
|
+
);
|
|
183
|
+
const testCaseResults = [];
|
|
184
|
+
onUpdate == null ? void 0 : onUpdate({
|
|
185
|
+
...trivetResults,
|
|
186
|
+
testSuiteResults: [
|
|
187
|
+
...trivetResults.testSuiteResults,
|
|
188
|
+
{
|
|
189
|
+
id: testSuite.id,
|
|
190
|
+
testGraph: testSuite.testGraph,
|
|
191
|
+
validationGraph: testSuite.validationGraph,
|
|
192
|
+
name: testSuite.name ?? "Test",
|
|
193
|
+
description: testSuite.description ?? "It should pass.",
|
|
194
|
+
testCaseResults: testCaseResults.slice(),
|
|
195
|
+
passing: testCaseResults.every((r) => r.passing)
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
for (const testCase of testSuite.testCases) {
|
|
200
|
+
for (let i = 0; i < iterationCount; i++) {
|
|
201
|
+
try {
|
|
202
|
+
const resolvedInputs = (0, import_lodash_es.mapValues)(testCase.input, import_rivet2_core2.inferType);
|
|
203
|
+
const startTime = Date.now();
|
|
204
|
+
const outputs = await runGraph(project, testGraph.metadata.id, resolvedInputs);
|
|
205
|
+
const duration = Date.now() - startTime;
|
|
206
|
+
const costOutput = outputs.cost;
|
|
207
|
+
const cost = costOutput && costOutput.type === "number" ? costOutput.value : 0;
|
|
208
|
+
(0, import_rivet2_core2.logRuntimeInfo)("Ran Trivet test graph", {
|
|
209
|
+
testSuiteId: testSuite.id,
|
|
210
|
+
testCaseId: testCase.id,
|
|
211
|
+
iteration: i + 1,
|
|
212
|
+
duration,
|
|
213
|
+
outputCount: Object.keys(outputs).length
|
|
214
|
+
});
|
|
215
|
+
(0, import_rivet2_core2.logRuntimeDebug)("Trivet test graph output summary", {
|
|
216
|
+
testSuiteId: testSuite.id,
|
|
217
|
+
testCaseId: testCase.id,
|
|
218
|
+
iteration: i + 1,
|
|
219
|
+
outputs: (0, import_rivet2_core2.summarizePortMapForLog)(outputs)
|
|
220
|
+
});
|
|
221
|
+
const validationInputs = {
|
|
222
|
+
input: {
|
|
223
|
+
type: "object",
|
|
224
|
+
value: testCase.input
|
|
225
|
+
},
|
|
226
|
+
expectedOutput: {
|
|
227
|
+
type: "object",
|
|
228
|
+
value: testCase.expectedOutput
|
|
229
|
+
},
|
|
230
|
+
output: {
|
|
231
|
+
type: "object",
|
|
232
|
+
value: (0, import_lodash_es.mapValues)(outputs, (dataValue) => dataValue.value)
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
(0, import_rivet2_core2.logRuntimeInfo)("Running Trivet validation graph", {
|
|
236
|
+
testSuiteId: testSuite.id,
|
|
237
|
+
testCaseId: testCase.id,
|
|
238
|
+
iteration: i + 1,
|
|
239
|
+
inputCount: Object.keys(validationInputs).length
|
|
240
|
+
});
|
|
241
|
+
(0, import_rivet2_core2.logRuntimeDebug)("Trivet validation input summary", {
|
|
242
|
+
testSuiteId: testSuite.id,
|
|
243
|
+
testCaseId: testCase.id,
|
|
244
|
+
iteration: i + 1,
|
|
245
|
+
inputs: (0, import_rivet2_core2.summarizePortMapForLog)(validationInputs)
|
|
246
|
+
});
|
|
247
|
+
const validationOutputs = (0, import_lodash_es.omit)(
|
|
248
|
+
await runGraph(project, validationGraph.metadata.id, validationInputs),
|
|
249
|
+
"cost"
|
|
250
|
+
);
|
|
251
|
+
const validationResults = Object.entries(validationOutputs).map(([outputId, result]) => {
|
|
252
|
+
const node = validationOutputNodesById[outputId];
|
|
253
|
+
if (node === void 0) {
|
|
254
|
+
throw new Error("Missing node for validation");
|
|
255
|
+
}
|
|
256
|
+
const valid = validateOutput(result);
|
|
257
|
+
return {
|
|
258
|
+
id: node.id,
|
|
259
|
+
title: node.title ?? "Validation",
|
|
260
|
+
description: node.description ?? "It should be valid.",
|
|
261
|
+
valid
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
testCaseResults.push({
|
|
265
|
+
id: testCase.id,
|
|
266
|
+
iteration: i + 1,
|
|
267
|
+
passing: validationResults.every((r) => r.valid),
|
|
268
|
+
message: ((_a = validationResults.find((r) => !r.valid)) == null ? void 0 : _a.description) ?? "PASS",
|
|
269
|
+
outputs: (0, import_lodash_es.mapValues)((0, import_lodash_es.omit)(outputs, "cost"), (dataValue) => dataValue.value),
|
|
270
|
+
duration,
|
|
271
|
+
cost
|
|
272
|
+
});
|
|
273
|
+
} catch (err) {
|
|
274
|
+
testCaseResults.push({
|
|
275
|
+
id: testCase.id,
|
|
276
|
+
iteration: i + 1,
|
|
277
|
+
passing: false,
|
|
278
|
+
message: "An error occurred",
|
|
279
|
+
outputs: {},
|
|
280
|
+
duration: 0,
|
|
281
|
+
cost: 0,
|
|
282
|
+
error: err
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
let existingTestSuite = trivetResults.testSuiteResults.find((ts) => ts.id === testSuite.id);
|
|
286
|
+
if (existingTestSuite == null) {
|
|
287
|
+
existingTestSuite = {
|
|
288
|
+
id: testSuite.id,
|
|
289
|
+
testGraph: testSuite.testGraph,
|
|
290
|
+
validationGraph: testSuite.validationGraph,
|
|
291
|
+
name: testSuite.name ?? "Test",
|
|
292
|
+
description: testSuite.description ?? "It should pass.",
|
|
293
|
+
testCaseResults: [],
|
|
294
|
+
passing: false
|
|
295
|
+
};
|
|
296
|
+
trivetResults.testSuiteResults.push(existingTestSuite);
|
|
297
|
+
}
|
|
298
|
+
existingTestSuite.testCaseResults = testCaseResults.slice();
|
|
299
|
+
existingTestSuite.passing = testCaseResults.every((r) => r.passing);
|
|
300
|
+
onUpdate == null ? void 0 : onUpdate((0, import_lodash_es.cloneDeep)(trivetResults));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return trivetResults;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/validateValidationGraphFormat.ts
|
|
308
|
+
var import_lodash_es2 = require("lodash");
|
|
309
|
+
var import_rivet2_core3 = require("@valerypopoff/rivet2-core");
|
|
310
|
+
function validateValidationGraphFormat(validationGraph) {
|
|
311
|
+
const inputNodes = validationGraph.nodes.filter((n) => n.type === "graphInput");
|
|
312
|
+
const inputNodesById = (0, import_lodash_es2.keyBy)(inputNodes, (n) => n.data.id);
|
|
313
|
+
const validationResult = {
|
|
314
|
+
valid: true,
|
|
315
|
+
errorMessages: []
|
|
316
|
+
};
|
|
317
|
+
if (inputNodesById.input == null) {
|
|
318
|
+
validationResult.valid = false;
|
|
319
|
+
validationResult.errorMessages.push('Must have an input node with id "input"');
|
|
320
|
+
}
|
|
321
|
+
if (inputNodesById.expectedOutput == null) {
|
|
322
|
+
validationResult.valid = false;
|
|
323
|
+
validationResult.errorMessages.push('Must have an input node with id "expectedOutput"');
|
|
324
|
+
}
|
|
325
|
+
if (inputNodesById.output == null) {
|
|
326
|
+
validationResult.valid = false;
|
|
327
|
+
validationResult.errorMessages.push('Must have an input node with id "output"');
|
|
328
|
+
}
|
|
329
|
+
const outputNodes = validationGraph.nodes.filter((n) => n.type === "graphOutput");
|
|
330
|
+
if (outputNodes.length === 0) {
|
|
331
|
+
validationResult.valid = false;
|
|
332
|
+
validationResult.errorMessages.push(
|
|
333
|
+
"Must have at least one output node (that returns true/false as string or boolean)"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const invalidOutputs = outputNodes.filter(
|
|
337
|
+
(n) => n.data.dataType !== "boolean" && n.data.dataType !== "string" && n.data.dataType !== "any"
|
|
338
|
+
);
|
|
339
|
+
if (invalidOutputs.length > 0) {
|
|
340
|
+
validationResult.valid = false;
|
|
341
|
+
validationResult.errorMessages.push(
|
|
342
|
+
`Output nodes must return boolean or string, but found ${invalidOutputs.map((n) => n.data.dataType).join(", ")}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return validationResult;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/validateTestCaseFormat.ts
|
|
349
|
+
var import_rivet2_core4 = require("@valerypopoff/rivet2-core");
|
|
350
|
+
function validateTestCaseFormat(testGraph, testCase) {
|
|
351
|
+
const inputNodes = testGraph.nodes.filter((n) => n.type === "graphInput");
|
|
352
|
+
const outputNodes = testGraph.nodes.filter((n) => n.type === "graphOutput");
|
|
353
|
+
const inputNodeIds = inputNodes.map((n) => n.data.id);
|
|
354
|
+
const outputNodeIds = outputNodes.map((n) => n.data.id);
|
|
355
|
+
const tcInputIds = new Set(Object.keys(testCase.input));
|
|
356
|
+
const missingInputIds = inputNodeIds.filter((id) => !tcInputIds.has(id));
|
|
357
|
+
const tcOutputIds = new Set(Object.keys(testCase.expectedOutput));
|
|
358
|
+
const missingOutputIds = outputNodeIds.filter((id) => !tcOutputIds.has(id));
|
|
359
|
+
return {
|
|
360
|
+
valid: missingInputIds.length === 0 && missingOutputIds.length === 0,
|
|
361
|
+
missingInputIds,
|
|
362
|
+
missingOutputIds
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
366
|
+
0 && (module.exports = {
|
|
367
|
+
DummyNativeApi,
|
|
368
|
+
createTestGraphRunner,
|
|
369
|
+
deserializeTestCase,
|
|
370
|
+
deserializeTestSuite,
|
|
371
|
+
deserializeTrivetData,
|
|
372
|
+
deserializeTrivetDataFromString,
|
|
373
|
+
runTrivet,
|
|
374
|
+
serializeTestCase,
|
|
375
|
+
serializeTestSuite,
|
|
376
|
+
serializeTrivetData,
|
|
377
|
+
serializeTrivetDataToString,
|
|
378
|
+
validateTestCaseFormat,
|
|
379
|
+
validateValidationGraphFormat
|
|
380
|
+
});
|
|
381
|
+
//# sourceMappingURL=bundle.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts", "../../src/trivetTypes.ts", "../../src/serialization/serialization_v1.ts", "../../src/api.ts", "../../src/validateValidationGraphFormat.ts", "../../src/validateTestCaseFormat.ts"],
|
|
4
|
+
"sourcesContent": ["export * from './trivetTypes.js';\nexport * from './serialization/serialization_v1.js';\nexport * from './api.js';\nexport * from './validateValidationGraphFormat.js';\nexport * from './validateTestCaseFormat.js';\n", "import { type GraphId, type GraphInputs, type GraphOutputs, type Project } from '@valerypopoff/rivet2-core';\n\nexport type TrivetGraphRunner = (project: Project, graphId: GraphId, inputs: GraphInputs) => Promise<GraphOutputs>;\n\nexport interface TrivetOpts {\n project: Project;\n testSuites: TrivetTestSuite[];\n\n /** Runs each test in each suite N times. Defaults to just 1. A test passes if all iterations pass. */\n iterationCount?: number;\n\n runGraph: TrivetGraphRunner;\n onUpdate?: (results: TrivetResults) => void;\n}\n\nexport interface TrivetTestSuite {\n id: string;\n name?: string;\n description?: string;\n testGraph: string;\n validationGraph: string;\n testCases: TrivetTestCase[];\n}\n\nexport interface TrivetTestCase {\n id: string;\n input: Record<string, unknown>;\n expectedOutput: Record<string, unknown>;\n}\n\nexport interface TrivetResults {\n testSuiteResults: TrivetTestSuiteResult[];\n iterationCount: number;\n}\n\nexport interface TrivetTestSuiteResult {\n id: string;\n testGraph: string;\n validationGraph: string;\n name: string;\n description: string;\n testCaseResults: TrivetTestCaseResult[];\n passing: boolean;\n}\n\nexport interface TrivetTestCaseResult {\n id: string;\n iteration: number;\n passing: boolean;\n message: string;\n outputs: Record<string, unknown>;\n duration: number;\n cost: number;\n error?: Error | string | any;\n}\n\nexport type TrivetData = {\n testSuites: TrivetTestSuite[];\n};\n", "import * as yaml from 'yaml';\nimport { type TrivetData, type TrivetTestCase, type TrivetTestSuite } from '../index.js';\n\nexport interface SerializedTrivetTestSuite {\n id: string;\n name?: string;\n description?: string;\n testGraph: string;\n validationGraph: string;\n testCases: SerializedTrivetTestCase[];\n}\n\nexport interface SerializedTrivetTestCase {\n id: string;\n input: Record<string, unknown>;\n expectedOutput: Record<string, unknown>;\n}\n\nexport interface SerializedTrivetData {\n version: 1;\n testSuites: SerializedTrivetTestSuite[];\n}\n\nexport function serializeTestCase(testCase: TrivetTestCase): SerializedTrivetTestCase {\n return {\n id: testCase.id,\n input: testCase.input,\n expectedOutput: testCase.expectedOutput,\n };\n}\n\nexport function deserializeTestCase(data: SerializedTrivetTestCase): TrivetTestCase {\n return {\n id: data.id,\n input: data.input,\n expectedOutput: data.expectedOutput,\n };\n}\n\nexport function serializeTestSuite(testSuite: TrivetTestSuite): SerializedTrivetTestSuite {\n return {\n id: testSuite.id,\n name: testSuite.name,\n description: testSuite.description,\n testGraph: testSuite.testGraph,\n validationGraph: testSuite.validationGraph,\n testCases: testSuite.testCases.map((testCase) => serializeTestCase(testCase)),\n };\n}\n\nexport function deserializeTestSuite(data: SerializedTrivetTestSuite): TrivetTestSuite {\n return {\n id: data.id,\n name: data.name,\n description: data.description,\n testGraph: data.testGraph,\n validationGraph: data.validationGraph,\n testCases: data.testCases.map((testCase) => deserializeTestCase(testCase)),\n };\n}\n\nexport function serializeTrivetData(data: TrivetData): SerializedTrivetData {\n return {\n version: 1,\n testSuites: data.testSuites.map((testSuite) => serializeTestSuite(testSuite)),\n };\n}\n\nexport function deserializeTrivetData(data: SerializedTrivetData): TrivetData {\n if (data.version !== 1) {\n throw new Error(`Unsupported version: ${data.version}`);\n }\n\n return {\n testSuites: data.testSuites.map((testSuite) => deserializeTestSuite(testSuite)),\n };\n}\n\nexport function serializeTrivetDataToString(data: TrivetData): string {\n return yaml.stringify(serializeTrivetData(data), null, 2);\n}\n\nexport function deserializeTrivetDataFromString(data: string): TrivetData {\n return deserializeTrivetData(yaml.parse(data));\n}\n", "import {\n type DataValue,\n type NativeApi,\n type BaseDir,\n type ReadDirOptions,\n GraphProcessor,\n globalRivetNodeRegistry,\n type GraphOutputNode,\n GptTokenizerTokenizer,\n inferType,\n logRuntimeDebug,\n logRuntimeInfo,\n logRuntimeWarn,\n resolveProcessSettings,\n summarizePortMapForLog,\n} from '@valerypopoff/rivet2-core';\nimport { cloneDeep, keyBy, mapValues, omit } from 'lodash-es';\nimport {\n type TrivetGraphRunner,\n type TrivetOpts,\n type TrivetResults,\n type TrivetTestCaseResult,\n} from './trivetTypes.js';\n\nconst TRUTHY_STRINGS = new Set(['true', 'TRUE']);\n\nfunction validateOutput(v: DataValue) {\n switch (v.type) {\n case 'boolean':\n return v.value;\n case 'string':\n return TRUTHY_STRINGS.has(v.value);\n default:\n throw new Error(`Unexpected output type: ${v.type}`);\n }\n}\n\nexport class DummyNativeApi implements NativeApi {\n readdir(path: string, baseDir?: BaseDir | undefined, options?: ReadDirOptions | undefined): Promise<string[]> {\n throw new Error(`Method not implemented. ${path} ${baseDir} ${options}`);\n }\n readTextFile(path: string, baseDir?: BaseDir | undefined): Promise<string> {\n throw new Error(`Method not implemented. ${path} ${baseDir}`);\n }\n readBinaryFile(path: string, baseDir?: BaseDir | undefined): Promise<Blob> {\n throw new Error(`Method not implemented. ${path} ${baseDir}`);\n }\n writeTextFile(path: string, data: string, baseDir?: BaseDir | undefined): Promise<void> {\n throw new Error(`Method not implemented. ${path} ${baseDir} ${data}`);\n }\n exec(command: string, args: string[], options?: { cwd?: string | undefined } | undefined): Promise<void> {\n throw new Error(`Method not implemented. ${command} ${args} ${options}`);\n }\n}\n\nexport function createTestGraphRunner(opts: { openAiKey: string; executor?: 'nodejs' | 'browser' }): TrivetGraphRunner {\n return async (project, graphId, inputs) => {\n const processor = new GraphProcessor(project, graphId, globalRivetNodeRegistry);\n processor.executor = opts.executor;\n const resolvedContextValues: Record<string, DataValue> = {};\n const outputs = await processor.processGraph(\n {\n nativeApi: new DummyNativeApi(),\n tokenizer: new GptTokenizerTokenizer(),\n settings: resolveProcessSettings({ openAiKey: opts.openAiKey }),\n },\n inputs,\n resolvedContextValues,\n );\n return outputs;\n };\n}\n\nexport async function runTrivet(opts: TrivetOpts): Promise<TrivetResults> {\n const { project, testSuites, runGraph, onUpdate, iterationCount = 1 } = opts;\n\n const graphsById = keyBy(project.graphs, (g) => g.metadata?.id ?? '');\n\n const trivetResults: TrivetResults = {\n testSuiteResults: [],\n iterationCount,\n };\n\n for (const testSuite of testSuites) {\n const testGraph = graphsById[testSuite.testGraph];\n const validationGraph = graphsById[testSuite.validationGraph];\n if (testGraph === undefined) {\n logRuntimeWarn('Missing Trivet test graph; skipping.', { testGraphId: testSuite.testGraph });\n continue;\n }\n if (validationGraph === undefined) {\n logRuntimeWarn('Missing Trivet validation graph; skipping.', { validationGraphId: testSuite.validationGraph });\n continue;\n }\n\n const validationOutputNodesById = keyBy(\n validationGraph.nodes.filter((n): n is GraphOutputNode => n.type === 'graphOutput'),\n (n) => n.data.id,\n );\n\n const testCaseResults: TrivetTestCaseResult[] = [];\n\n onUpdate?.({\n ...trivetResults,\n testSuiteResults: [\n ...trivetResults.testSuiteResults,\n {\n id: testSuite.id,\n testGraph: testSuite.testGraph,\n validationGraph: testSuite.validationGraph,\n name: testSuite.name ?? 'Test',\n description: testSuite.description ?? 'It should pass.',\n testCaseResults: testCaseResults.slice(),\n passing: testCaseResults.every((r) => r.passing),\n },\n ],\n });\n\n for (const testCase of testSuite.testCases) {\n for (let i = 0; i < iterationCount; i++) {\n try {\n const resolvedInputs: Record<string, DataValue> = mapValues(testCase.input, inferType);\n const startTime = Date.now();\n const outputs = await runGraph(project, testGraph.metadata!.id!, resolvedInputs);\n const duration = Date.now() - startTime;\n const costOutput = outputs.cost;\n const cost = costOutput && costOutput.type === 'number' ? costOutput.value : 0;\n\n logRuntimeInfo('Ran Trivet test graph', {\n testSuiteId: testSuite.id,\n testCaseId: testCase.id,\n iteration: i + 1,\n duration,\n outputCount: Object.keys(outputs).length,\n });\n logRuntimeDebug('Trivet test graph output summary', {\n testSuiteId: testSuite.id,\n testCaseId: testCase.id,\n iteration: i + 1,\n outputs: summarizePortMapForLog(outputs),\n });\n\n const validationInputs: Record<string, DataValue> = {\n input: {\n type: 'object',\n value: testCase.input,\n },\n expectedOutput: {\n type: 'object',\n value: testCase.expectedOutput,\n },\n output: {\n type: 'object',\n value: mapValues(outputs, (dataValue) => dataValue.value),\n },\n };\n\n logRuntimeInfo('Running Trivet validation graph', {\n testSuiteId: testSuite.id,\n testCaseId: testCase.id,\n iteration: i + 1,\n inputCount: Object.keys(validationInputs).length,\n });\n logRuntimeDebug('Trivet validation input summary', {\n testSuiteId: testSuite.id,\n testCaseId: testCase.id,\n iteration: i + 1,\n inputs: summarizePortMapForLog(validationInputs),\n });\n\n const validationOutputs = omit(\n await runGraph(project, validationGraph.metadata!.id!, validationInputs),\n 'cost',\n );\n\n const validationResults = Object.entries(validationOutputs).map(([outputId, result]) => {\n const node = validationOutputNodesById[outputId];\n if (node === undefined) {\n throw new Error('Missing node for validation');\n }\n const valid = validateOutput(result);\n return {\n id: node.id,\n title: node.title ?? 'Validation',\n description: node.description ?? 'It should be valid.',\n valid,\n };\n });\n testCaseResults.push({\n id: testCase.id,\n iteration: i + 1,\n passing: validationResults.every((r) => r.valid),\n message: validationResults.find((r) => !r.valid)?.description ?? 'PASS',\n outputs: mapValues(omit(outputs, 'cost'), (dataValue) => dataValue.value),\n duration,\n cost,\n });\n } catch (err) {\n testCaseResults.push({\n id: testCase.id,\n iteration: i + 1,\n passing: false,\n message: 'An error occurred',\n outputs: {},\n duration: 0,\n cost: 0,\n error: err,\n });\n }\n\n let existingTestSuite = trivetResults.testSuiteResults.find((ts) => ts.id === testSuite.id);\n if (existingTestSuite == null) {\n existingTestSuite = {\n id: testSuite.id,\n testGraph: testSuite.testGraph,\n validationGraph: testSuite.validationGraph,\n name: testSuite.name ?? 'Test',\n description: testSuite.description ?? 'It should pass.',\n testCaseResults: [],\n passing: false,\n };\n trivetResults.testSuiteResults.push(existingTestSuite);\n }\n existingTestSuite.testCaseResults = testCaseResults.slice();\n existingTestSuite.passing = testCaseResults.every((r) => r.passing);\n\n onUpdate?.(cloneDeep(trivetResults));\n }\n }\n }\n return trivetResults;\n}\n", "import { keyBy } from 'lodash-es';\nimport { type GraphInputNode, type NodeGraph } from '@valerypopoff/rivet2-core';\n\nexport function validateValidationGraphFormat(validationGraph: NodeGraph): { valid: boolean; errorMessages: string[] } {\n const inputNodes = validationGraph.nodes.filter((n): n is GraphInputNode => n.type === 'graphInput');\n const inputNodesById = keyBy(inputNodes, (n) => n.data.id);\n const validationResult = {\n valid: true,\n errorMessages: [] as string[],\n };\n if (inputNodesById.input == null) {\n validationResult.valid = false;\n validationResult.errorMessages.push('Must have an input node with id \"input\"');\n }\n if (inputNodesById.expectedOutput == null) {\n validationResult.valid = false;\n validationResult.errorMessages.push('Must have an input node with id \"expectedOutput\"');\n }\n if (inputNodesById.output == null) {\n validationResult.valid = false;\n validationResult.errorMessages.push('Must have an input node with id \"output\"');\n }\n const outputNodes = validationGraph.nodes.filter((n): n is GraphInputNode => n.type === 'graphOutput');\n if (outputNodes.length === 0) {\n validationResult.valid = false;\n validationResult.errorMessages.push(\n 'Must have at least one output node (that returns true/false as string or boolean)',\n );\n }\n const invalidOutputs = outputNodes.filter(\n (n) => n.data.dataType !== 'boolean' && n.data.dataType !== 'string' && n.data.dataType !== 'any',\n );\n if (invalidOutputs.length > 0) {\n validationResult.valid = false;\n validationResult.errorMessages.push(\n `Output nodes must return boolean or string, but found ${invalidOutputs.map((n) => n.data.dataType).join(', ')}`,\n );\n }\n return validationResult;\n}\n", "import { type GraphInputNode, type GraphOutputNode, type NodeGraph } from '@valerypopoff/rivet2-core';\nimport { type TrivetTestCase } from './trivetTypes.js';\n\nexport function validateTestCaseFormat(\n testGraph: NodeGraph,\n testCase: TrivetTestCase,\n): {\n valid: boolean;\n missingInputIds: string[];\n missingOutputIds: string[];\n} {\n const inputNodes = testGraph.nodes.filter((n): n is GraphInputNode => n.type === 'graphInput');\n const outputNodes = testGraph.nodes.filter((n): n is GraphOutputNode => n.type === 'graphOutput');\n const inputNodeIds = inputNodes.map((n) => n.data.id);\n const outputNodeIds = outputNodes.map((n) => n.data.id);\n\n const tcInputIds = new Set(Object.keys(testCase.input));\n const missingInputIds = inputNodeIds.filter((id) => !tcInputIds.has(id));\n\n const tcOutputIds = new Set(Object.keys(testCase.expectedOutput));\n const missingOutputIds = outputNodeIds.filter((id) => !tcOutputIds.has(id));\n\n return {\n valid: missingInputIds.length === 0 && missingOutputIds.length === 0,\n missingInputIds,\n missingOutputIds,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_rivet2_core", "_a", "import_lodash_es", "import_rivet2_core", "import_rivet2_core"]
|
|
7
|
+
}
|
package/dist/esm/api.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { GraphProcessor, globalRivetNodeRegistry, GptTokenizerTokenizer, inferType, logRuntimeDebug, logRuntimeInfo, logRuntimeWarn, resolveProcessSettings, summarizePortMapForLog, } from '@valerypopoff/rivet2-core';
|
|
2
|
+
import { cloneDeep, keyBy, mapValues, omit } from 'lodash-es';
|
|
3
|
+
import {} from './trivetTypes.js';
|
|
4
|
+
const TRUTHY_STRINGS = new Set(['true', 'TRUE']);
|
|
5
|
+
function validateOutput(v) {
|
|
6
|
+
switch (v.type) {
|
|
7
|
+
case 'boolean':
|
|
8
|
+
return v.value;
|
|
9
|
+
case 'string':
|
|
10
|
+
return TRUTHY_STRINGS.has(v.value);
|
|
11
|
+
default:
|
|
12
|
+
throw new Error(`Unexpected output type: ${v.type}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class DummyNativeApi {
|
|
16
|
+
readdir(path, baseDir, options) {
|
|
17
|
+
throw new Error(`Method not implemented. ${path} ${baseDir} ${options}`);
|
|
18
|
+
}
|
|
19
|
+
readTextFile(path, baseDir) {
|
|
20
|
+
throw new Error(`Method not implemented. ${path} ${baseDir}`);
|
|
21
|
+
}
|
|
22
|
+
readBinaryFile(path, baseDir) {
|
|
23
|
+
throw new Error(`Method not implemented. ${path} ${baseDir}`);
|
|
24
|
+
}
|
|
25
|
+
writeTextFile(path, data, baseDir) {
|
|
26
|
+
throw new Error(`Method not implemented. ${path} ${baseDir} ${data}`);
|
|
27
|
+
}
|
|
28
|
+
exec(command, args, options) {
|
|
29
|
+
throw new Error(`Method not implemented. ${command} ${args} ${options}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function createTestGraphRunner(opts) {
|
|
33
|
+
return async (project, graphId, inputs) => {
|
|
34
|
+
const processor = new GraphProcessor(project, graphId, globalRivetNodeRegistry);
|
|
35
|
+
processor.executor = opts.executor;
|
|
36
|
+
const resolvedContextValues = {};
|
|
37
|
+
const outputs = await processor.processGraph({
|
|
38
|
+
nativeApi: new DummyNativeApi(),
|
|
39
|
+
tokenizer: new GptTokenizerTokenizer(),
|
|
40
|
+
settings: resolveProcessSettings({ openAiKey: opts.openAiKey }),
|
|
41
|
+
}, inputs, resolvedContextValues);
|
|
42
|
+
return outputs;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function runTrivet(opts) {
|
|
46
|
+
const { project, testSuites, runGraph, onUpdate, iterationCount = 1 } = opts;
|
|
47
|
+
const graphsById = keyBy(project.graphs, (g) => g.metadata?.id ?? '');
|
|
48
|
+
const trivetResults = {
|
|
49
|
+
testSuiteResults: [],
|
|
50
|
+
iterationCount,
|
|
51
|
+
};
|
|
52
|
+
for (const testSuite of testSuites) {
|
|
53
|
+
const testGraph = graphsById[testSuite.testGraph];
|
|
54
|
+
const validationGraph = graphsById[testSuite.validationGraph];
|
|
55
|
+
if (testGraph === undefined) {
|
|
56
|
+
logRuntimeWarn('Missing Trivet test graph; skipping.', { testGraphId: testSuite.testGraph });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (validationGraph === undefined) {
|
|
60
|
+
logRuntimeWarn('Missing Trivet validation graph; skipping.', { validationGraphId: testSuite.validationGraph });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const validationOutputNodesById = keyBy(validationGraph.nodes.filter((n) => n.type === 'graphOutput'), (n) => n.data.id);
|
|
64
|
+
const testCaseResults = [];
|
|
65
|
+
onUpdate?.({
|
|
66
|
+
...trivetResults,
|
|
67
|
+
testSuiteResults: [
|
|
68
|
+
...trivetResults.testSuiteResults,
|
|
69
|
+
{
|
|
70
|
+
id: testSuite.id,
|
|
71
|
+
testGraph: testSuite.testGraph,
|
|
72
|
+
validationGraph: testSuite.validationGraph,
|
|
73
|
+
name: testSuite.name ?? 'Test',
|
|
74
|
+
description: testSuite.description ?? 'It should pass.',
|
|
75
|
+
testCaseResults: testCaseResults.slice(),
|
|
76
|
+
passing: testCaseResults.every((r) => r.passing),
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
for (const testCase of testSuite.testCases) {
|
|
81
|
+
for (let i = 0; i < iterationCount; i++) {
|
|
82
|
+
try {
|
|
83
|
+
const resolvedInputs = mapValues(testCase.input, inferType);
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
const outputs = await runGraph(project, testGraph.metadata.id, resolvedInputs);
|
|
86
|
+
const duration = Date.now() - startTime;
|
|
87
|
+
const costOutput = outputs.cost;
|
|
88
|
+
const cost = costOutput && costOutput.type === 'number' ? costOutput.value : 0;
|
|
89
|
+
logRuntimeInfo('Ran Trivet test graph', {
|
|
90
|
+
testSuiteId: testSuite.id,
|
|
91
|
+
testCaseId: testCase.id,
|
|
92
|
+
iteration: i + 1,
|
|
93
|
+
duration,
|
|
94
|
+
outputCount: Object.keys(outputs).length,
|
|
95
|
+
});
|
|
96
|
+
logRuntimeDebug('Trivet test graph output summary', {
|
|
97
|
+
testSuiteId: testSuite.id,
|
|
98
|
+
testCaseId: testCase.id,
|
|
99
|
+
iteration: i + 1,
|
|
100
|
+
outputs: summarizePortMapForLog(outputs),
|
|
101
|
+
});
|
|
102
|
+
const validationInputs = {
|
|
103
|
+
input: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
value: testCase.input,
|
|
106
|
+
},
|
|
107
|
+
expectedOutput: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
value: testCase.expectedOutput,
|
|
110
|
+
},
|
|
111
|
+
output: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
value: mapValues(outputs, (dataValue) => dataValue.value),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
logRuntimeInfo('Running Trivet validation graph', {
|
|
117
|
+
testSuiteId: testSuite.id,
|
|
118
|
+
testCaseId: testCase.id,
|
|
119
|
+
iteration: i + 1,
|
|
120
|
+
inputCount: Object.keys(validationInputs).length,
|
|
121
|
+
});
|
|
122
|
+
logRuntimeDebug('Trivet validation input summary', {
|
|
123
|
+
testSuiteId: testSuite.id,
|
|
124
|
+
testCaseId: testCase.id,
|
|
125
|
+
iteration: i + 1,
|
|
126
|
+
inputs: summarizePortMapForLog(validationInputs),
|
|
127
|
+
});
|
|
128
|
+
const validationOutputs = omit(await runGraph(project, validationGraph.metadata.id, validationInputs), 'cost');
|
|
129
|
+
const validationResults = Object.entries(validationOutputs).map(([outputId, result]) => {
|
|
130
|
+
const node = validationOutputNodesById[outputId];
|
|
131
|
+
if (node === undefined) {
|
|
132
|
+
throw new Error('Missing node for validation');
|
|
133
|
+
}
|
|
134
|
+
const valid = validateOutput(result);
|
|
135
|
+
return {
|
|
136
|
+
id: node.id,
|
|
137
|
+
title: node.title ?? 'Validation',
|
|
138
|
+
description: node.description ?? 'It should be valid.',
|
|
139
|
+
valid,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
testCaseResults.push({
|
|
143
|
+
id: testCase.id,
|
|
144
|
+
iteration: i + 1,
|
|
145
|
+
passing: validationResults.every((r) => r.valid),
|
|
146
|
+
message: validationResults.find((r) => !r.valid)?.description ?? 'PASS',
|
|
147
|
+
outputs: mapValues(omit(outputs, 'cost'), (dataValue) => dataValue.value),
|
|
148
|
+
duration,
|
|
149
|
+
cost,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
testCaseResults.push({
|
|
154
|
+
id: testCase.id,
|
|
155
|
+
iteration: i + 1,
|
|
156
|
+
passing: false,
|
|
157
|
+
message: 'An error occurred',
|
|
158
|
+
outputs: {},
|
|
159
|
+
duration: 0,
|
|
160
|
+
cost: 0,
|
|
161
|
+
error: err,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
let existingTestSuite = trivetResults.testSuiteResults.find((ts) => ts.id === testSuite.id);
|
|
165
|
+
if (existingTestSuite == null) {
|
|
166
|
+
existingTestSuite = {
|
|
167
|
+
id: testSuite.id,
|
|
168
|
+
testGraph: testSuite.testGraph,
|
|
169
|
+
validationGraph: testSuite.validationGraph,
|
|
170
|
+
name: testSuite.name ?? 'Test',
|
|
171
|
+
description: testSuite.description ?? 'It should pass.',
|
|
172
|
+
testCaseResults: [],
|
|
173
|
+
passing: false,
|
|
174
|
+
};
|
|
175
|
+
trivetResults.testSuiteResults.push(existingTestSuite);
|
|
176
|
+
}
|
|
177
|
+
existingTestSuite.testCaseResults = testCaseResults.slice();
|
|
178
|
+
existingTestSuite.passing = testCaseResults.every((r) => r.passing);
|
|
179
|
+
onUpdate?.(cloneDeep(trivetResults));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return trivetResults;
|
|
184
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as yaml from 'yaml';
|
|
2
|
+
import {} from '../index.js';
|
|
3
|
+
export function serializeTestCase(testCase) {
|
|
4
|
+
return {
|
|
5
|
+
id: testCase.id,
|
|
6
|
+
input: testCase.input,
|
|
7
|
+
expectedOutput: testCase.expectedOutput,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function deserializeTestCase(data) {
|
|
11
|
+
return {
|
|
12
|
+
id: data.id,
|
|
13
|
+
input: data.input,
|
|
14
|
+
expectedOutput: data.expectedOutput,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function serializeTestSuite(testSuite) {
|
|
18
|
+
return {
|
|
19
|
+
id: testSuite.id,
|
|
20
|
+
name: testSuite.name,
|
|
21
|
+
description: testSuite.description,
|
|
22
|
+
testGraph: testSuite.testGraph,
|
|
23
|
+
validationGraph: testSuite.validationGraph,
|
|
24
|
+
testCases: testSuite.testCases.map((testCase) => serializeTestCase(testCase)),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function deserializeTestSuite(data) {
|
|
28
|
+
return {
|
|
29
|
+
id: data.id,
|
|
30
|
+
name: data.name,
|
|
31
|
+
description: data.description,
|
|
32
|
+
testGraph: data.testGraph,
|
|
33
|
+
validationGraph: data.validationGraph,
|
|
34
|
+
testCases: data.testCases.map((testCase) => deserializeTestCase(testCase)),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function serializeTrivetData(data) {
|
|
38
|
+
return {
|
|
39
|
+
version: 1,
|
|
40
|
+
testSuites: data.testSuites.map((testSuite) => serializeTestSuite(testSuite)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function deserializeTrivetData(data) {
|
|
44
|
+
if (data.version !== 1) {
|
|
45
|
+
throw new Error(`Unsupported version: ${data.version}`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
testSuites: data.testSuites.map((testSuite) => deserializeTestSuite(testSuite)),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function serializeTrivetDataToString(data) {
|
|
52
|
+
return yaml.stringify(serializeTrivetData(data), null, 2);
|
|
53
|
+
}
|
|
54
|
+
export function deserializeTrivetDataFromString(data) {
|
|
55
|
+
return deserializeTrivetData(yaml.parse(data));
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {} from '@valerypopoff/rivet2-core';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {} from '@valerypopoff/rivet2-core';
|
|
2
|
+
import {} from './trivetTypes.js';
|
|
3
|
+
export function validateTestCaseFormat(testGraph, testCase) {
|
|
4
|
+
const inputNodes = testGraph.nodes.filter((n) => n.type === 'graphInput');
|
|
5
|
+
const outputNodes = testGraph.nodes.filter((n) => n.type === 'graphOutput');
|
|
6
|
+
const inputNodeIds = inputNodes.map((n) => n.data.id);
|
|
7
|
+
const outputNodeIds = outputNodes.map((n) => n.data.id);
|
|
8
|
+
const tcInputIds = new Set(Object.keys(testCase.input));
|
|
9
|
+
const missingInputIds = inputNodeIds.filter((id) => !tcInputIds.has(id));
|
|
10
|
+
const tcOutputIds = new Set(Object.keys(testCase.expectedOutput));
|
|
11
|
+
const missingOutputIds = outputNodeIds.filter((id) => !tcOutputIds.has(id));
|
|
12
|
+
return {
|
|
13
|
+
valid: missingInputIds.length === 0 && missingOutputIds.length === 0,
|
|
14
|
+
missingInputIds,
|
|
15
|
+
missingOutputIds,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { keyBy } from 'lodash-es';
|
|
2
|
+
import {} from '@valerypopoff/rivet2-core';
|
|
3
|
+
export function validateValidationGraphFormat(validationGraph) {
|
|
4
|
+
const inputNodes = validationGraph.nodes.filter((n) => n.type === 'graphInput');
|
|
5
|
+
const inputNodesById = keyBy(inputNodes, (n) => n.data.id);
|
|
6
|
+
const validationResult = {
|
|
7
|
+
valid: true,
|
|
8
|
+
errorMessages: [],
|
|
9
|
+
};
|
|
10
|
+
if (inputNodesById.input == null) {
|
|
11
|
+
validationResult.valid = false;
|
|
12
|
+
validationResult.errorMessages.push('Must have an input node with id "input"');
|
|
13
|
+
}
|
|
14
|
+
if (inputNodesById.expectedOutput == null) {
|
|
15
|
+
validationResult.valid = false;
|
|
16
|
+
validationResult.errorMessages.push('Must have an input node with id "expectedOutput"');
|
|
17
|
+
}
|
|
18
|
+
if (inputNodesById.output == null) {
|
|
19
|
+
validationResult.valid = false;
|
|
20
|
+
validationResult.errorMessages.push('Must have an input node with id "output"');
|
|
21
|
+
}
|
|
22
|
+
const outputNodes = validationGraph.nodes.filter((n) => n.type === 'graphOutput');
|
|
23
|
+
if (outputNodes.length === 0) {
|
|
24
|
+
validationResult.valid = false;
|
|
25
|
+
validationResult.errorMessages.push('Must have at least one output node (that returns true/false as string or boolean)');
|
|
26
|
+
}
|
|
27
|
+
const invalidOutputs = outputNodes.filter((n) => n.data.dataType !== 'boolean' && n.data.dataType !== 'string' && n.data.dataType !== 'any');
|
|
28
|
+
if (invalidOutputs.length > 0) {
|
|
29
|
+
validationResult.valid = false;
|
|
30
|
+
validationResult.errorMessages.push(`Output nodes must return boolean or string, but found ${invalidOutputs.map((n) => n.data.dataType).join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
return validationResult;
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type NativeApi, type BaseDir, type ReadDirOptions } from '@valerypopoff/rivet2-core';
|
|
2
|
+
import { type TrivetGraphRunner, type TrivetOpts, type TrivetResults } from './trivetTypes.js';
|
|
3
|
+
export declare class DummyNativeApi implements NativeApi {
|
|
4
|
+
readdir(path: string, baseDir?: BaseDir | undefined, options?: ReadDirOptions | undefined): Promise<string[]>;
|
|
5
|
+
readTextFile(path: string, baseDir?: BaseDir | undefined): Promise<string>;
|
|
6
|
+
readBinaryFile(path: string, baseDir?: BaseDir | undefined): Promise<Blob>;
|
|
7
|
+
writeTextFile(path: string, data: string, baseDir?: BaseDir | undefined): Promise<void>;
|
|
8
|
+
exec(command: string, args: string[], options?: {
|
|
9
|
+
cwd?: string | undefined;
|
|
10
|
+
} | undefined): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createTestGraphRunner(opts: {
|
|
13
|
+
openAiKey: string;
|
|
14
|
+
executor?: 'nodejs' | 'browser';
|
|
15
|
+
}): TrivetGraphRunner;
|
|
16
|
+
export declare function runTrivet(opts: TrivetOpts): Promise<TrivetResults>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type TrivetData, type TrivetTestCase, type TrivetTestSuite } from '../index.js';
|
|
2
|
+
export interface SerializedTrivetTestSuite {
|
|
3
|
+
id: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
testGraph: string;
|
|
7
|
+
validationGraph: string;
|
|
8
|
+
testCases: SerializedTrivetTestCase[];
|
|
9
|
+
}
|
|
10
|
+
export interface SerializedTrivetTestCase {
|
|
11
|
+
id: string;
|
|
12
|
+
input: Record<string, unknown>;
|
|
13
|
+
expectedOutput: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface SerializedTrivetData {
|
|
16
|
+
version: 1;
|
|
17
|
+
testSuites: SerializedTrivetTestSuite[];
|
|
18
|
+
}
|
|
19
|
+
export declare function serializeTestCase(testCase: TrivetTestCase): SerializedTrivetTestCase;
|
|
20
|
+
export declare function deserializeTestCase(data: SerializedTrivetTestCase): TrivetTestCase;
|
|
21
|
+
export declare function serializeTestSuite(testSuite: TrivetTestSuite): SerializedTrivetTestSuite;
|
|
22
|
+
export declare function deserializeTestSuite(data: SerializedTrivetTestSuite): TrivetTestSuite;
|
|
23
|
+
export declare function serializeTrivetData(data: TrivetData): SerializedTrivetData;
|
|
24
|
+
export declare function deserializeTrivetData(data: SerializedTrivetData): TrivetData;
|
|
25
|
+
export declare function serializeTrivetDataToString(data: TrivetData): string;
|
|
26
|
+
export declare function deserializeTrivetDataFromString(data: string): TrivetData;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type GraphId, type GraphInputs, type GraphOutputs, type Project } from '@valerypopoff/rivet2-core';
|
|
2
|
+
export type TrivetGraphRunner = (project: Project, graphId: GraphId, inputs: GraphInputs) => Promise<GraphOutputs>;
|
|
3
|
+
export interface TrivetOpts {
|
|
4
|
+
project: Project;
|
|
5
|
+
testSuites: TrivetTestSuite[];
|
|
6
|
+
/** Runs each test in each suite N times. Defaults to just 1. A test passes if all iterations pass. */
|
|
7
|
+
iterationCount?: number;
|
|
8
|
+
runGraph: TrivetGraphRunner;
|
|
9
|
+
onUpdate?: (results: TrivetResults) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface TrivetTestSuite {
|
|
12
|
+
id: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
testGraph: string;
|
|
16
|
+
validationGraph: string;
|
|
17
|
+
testCases: TrivetTestCase[];
|
|
18
|
+
}
|
|
19
|
+
export interface TrivetTestCase {
|
|
20
|
+
id: string;
|
|
21
|
+
input: Record<string, unknown>;
|
|
22
|
+
expectedOutput: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface TrivetResults {
|
|
25
|
+
testSuiteResults: TrivetTestSuiteResult[];
|
|
26
|
+
iterationCount: number;
|
|
27
|
+
}
|
|
28
|
+
export interface TrivetTestSuiteResult {
|
|
29
|
+
id: string;
|
|
30
|
+
testGraph: string;
|
|
31
|
+
validationGraph: string;
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
testCaseResults: TrivetTestCaseResult[];
|
|
35
|
+
passing: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface TrivetTestCaseResult {
|
|
38
|
+
id: string;
|
|
39
|
+
iteration: number;
|
|
40
|
+
passing: boolean;
|
|
41
|
+
message: string;
|
|
42
|
+
outputs: Record<string, unknown>;
|
|
43
|
+
duration: number;
|
|
44
|
+
cost: number;
|
|
45
|
+
error?: Error | string | any;
|
|
46
|
+
}
|
|
47
|
+
export type TrivetData = {
|
|
48
|
+
testSuites: TrivetTestSuite[];
|
|
49
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type NodeGraph } from '@valerypopoff/rivet2-core';
|
|
2
|
+
import { type TrivetTestCase } from './trivetTypes.js';
|
|
3
|
+
export declare function validateTestCaseFormat(testGraph: NodeGraph, testCase: TrivetTestCase): {
|
|
4
|
+
valid: boolean;
|
|
5
|
+
missingInputIds: string[];
|
|
6
|
+
missingOutputIds: string[];
|
|
7
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@valerypopoff/trivet",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "dist/cjs/bundle.cjs",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
"node": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"import": "./dist/esm/index.js",
|
|
13
|
+
"require": "./dist/cjs/bundle.cjs"
|
|
14
|
+
},
|
|
15
|
+
"browser": {
|
|
16
|
+
"types": "./dist/types/index.d.ts",
|
|
17
|
+
"import": "./dist/esm/index.js",
|
|
18
|
+
"require": "./dist/cjs/bundle.cjs"
|
|
19
|
+
},
|
|
20
|
+
"default": {
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"import": "./dist/esm/index.js",
|
|
23
|
+
"require": "./dist/cjs/bundle.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/cjs/**",
|
|
28
|
+
"dist/esm/**",
|
|
29
|
+
"dist/types/**"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/valerypopoff/rivet2.0.git"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@valerypopoff/rivet2-core": "^2.0.1",
|
|
40
|
+
"lodash-es": "^4.17.21",
|
|
41
|
+
"yaml": "^2.3.3"
|
|
42
|
+
}
|
|
43
|
+
}
|