@botpress/vai 0.0.1-beta.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/dist/index.js ADDED
@@ -0,0 +1,476 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __typeError = (msg) => {
8
+ throw TypeError(msg);
9
+ };
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
24
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
25
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
26
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
27
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
28
+
29
+ // src/task/compare.ts
30
+ import { z } from "@botpress/sdk";
31
+ import { createTaskCollector, getCurrentSuite } from "vitest/suite";
32
+
33
+ // src/utils/deferred.ts
34
+ var _Deferred = class _Deferred {
35
+ constructor() {
36
+ this.promise = new Promise((resolve, reject) => {
37
+ this._resolve = resolve;
38
+ this._reject = reject;
39
+ });
40
+ }
41
+ resolve(value) {
42
+ this._resolve(value);
43
+ }
44
+ reject(reason) {
45
+ this._reject(reason);
46
+ }
47
+ };
48
+ __name(_Deferred, "Deferred");
49
+ var Deferred = _Deferred;
50
+
51
+ // src/task/compare.ts
52
+ var scenarioId = z.string().trim().min(1, "Scenario ID/name must not be empty").max(50, "Scenario ID/name is too long");
53
+ var ScenarioLike = z.union([
54
+ scenarioId,
55
+ z.object({ name: scenarioId }).passthrough(),
56
+ z.object({ id: scenarioId }).passthrough()
57
+ ]);
58
+ var getScenarioName = /* @__PURE__ */ __name((scenario) => typeof scenario === "string" ? scenario : "name" in scenario ? scenario == null ? void 0 : scenario.name : scenario == null ? void 0 : scenario.id, "getScenarioName");
59
+ var scenarioArgs = z.array(ScenarioLike).min(2, "You need at least two scenarios to compare").max(10, "You can only compare up to 10 scenarios").refine((scenarios) => {
60
+ const set = /* @__PURE__ */ new Set();
61
+ scenarios.forEach((scenario) => set.add(getScenarioName(scenario)));
62
+ return set.size === scenarios.length;
63
+ }, "Scenarios names must be unique");
64
+ function compare(name, scenarios, fn) {
65
+ scenarios = scenarioArgs.parse(scenarios);
66
+ return createTaskCollector((_name, fn2, timeout) => {
67
+ const currentSuite = getCurrentSuite();
68
+ let completedCount = 0;
69
+ const finished = new Deferred();
70
+ for (const scenario of scenarios) {
71
+ const key = getScenarioName(scenario);
72
+ currentSuite.task(key, {
73
+ meta: {
74
+ scenario: key,
75
+ isVaiTest: true
76
+ },
77
+ handler: /* @__PURE__ */ __name(async (context) => {
78
+ const extendedContext = Object.freeze({
79
+ scenario
80
+ });
81
+ context.onTestFinished(() => {
82
+ if (++completedCount === scenarios.length) {
83
+ finished.resolve();
84
+ }
85
+ });
86
+ await fn2(__spreadValues(__spreadValues({}, context), extendedContext));
87
+ }, "handler"),
88
+ timeout: timeout != null ? timeout : 1e4
89
+ });
90
+ }
91
+ })(name, fn);
92
+ }
93
+ __name(compare, "compare");
94
+
95
+ // src/assertions/check.ts
96
+ import { z as z3 } from "@botpress/sdk";
97
+
98
+ // src/context.ts
99
+ import { onTestFinished } from "vitest";
100
+ import { getCurrentTest } from "vitest/suite";
101
+ var getTestMetadata = /* @__PURE__ */ __name(() => {
102
+ var _a;
103
+ const test = getCurrentTest();
104
+ return (_a = test == null ? void 0 : test.meta) != null ? _a : {
105
+ isVaiTest: false
106
+ };
107
+ }, "getTestMetadata");
108
+ var _client, _wrapError;
109
+ var _VaiContext = class _VaiContext {
110
+ constructor() {
111
+ __privateAdd(this, _client, null);
112
+ __privateAdd(this, _wrapError, false);
113
+ }
114
+ get wrapError() {
115
+ return __privateGet(this, _wrapError);
116
+ }
117
+ get client() {
118
+ if (!__privateGet(this, _client)) {
119
+ throw new Error("Botpress client is not set");
120
+ }
121
+ return __privateGet(this, _client);
122
+ }
123
+ get evaluatorModel() {
124
+ var _a;
125
+ return (_a = getTestMetadata().evaluatorModel) != null ? _a : "openai__gpt-4o-mini-2024-07-18";
126
+ }
127
+ get scenario() {
128
+ return getTestMetadata().scenario;
129
+ }
130
+ get isVaiTest() {
131
+ return getTestMetadata().isVaiTest;
132
+ }
133
+ setClient(cognitive) {
134
+ __privateSet(this, _client, cognitive);
135
+ }
136
+ swallowErrors() {
137
+ if (!getCurrentTest()) {
138
+ throw new Error("cancelBail is a Vitest hook and must be called within a test");
139
+ }
140
+ __privateSet(this, _wrapError, true);
141
+ onTestFinished(() => {
142
+ __privateSet(this, _wrapError, false);
143
+ });
144
+ }
145
+ };
146
+ _client = new WeakMap();
147
+ _wrapError = new WeakMap();
148
+ __name(_VaiContext, "VaiContext");
149
+ var VaiContext = _VaiContext;
150
+ var Context = new VaiContext();
151
+
152
+ // src/utils/asyncAssertion.ts
153
+ import { expect } from "vitest";
154
+ import { getCurrentTest as getCurrentTest2 } from "vitest/suite";
155
+ var _AsyncExpectError = class _AsyncExpectError extends Error {
156
+ constructor(message, output) {
157
+ super(message);
158
+ this.output = output;
159
+ this.name = "AsyncExpectError";
160
+ }
161
+ };
162
+ __name(_AsyncExpectError, "AsyncExpectError");
163
+ var AsyncExpectError = _AsyncExpectError;
164
+ var getErrorMessages = /* @__PURE__ */ __name((e) => {
165
+ if (e instanceof Error) {
166
+ return e.message;
167
+ } else if (typeof e === "string") {
168
+ return e;
169
+ } else if (typeof e === "object" && e !== null) {
170
+ return JSON.stringify(e);
171
+ }
172
+ return `Unknown error: ${e}`;
173
+ }, "getErrorMessages");
174
+ var asyncExpect = /* @__PURE__ */ __name((output, assertion) => {
175
+ var _a, _b;
176
+ const promise = output.then((x) => {
177
+ try {
178
+ assertion(expect(x.result, x.reason));
179
+ } catch (e) {
180
+ if (Context.wrapError) {
181
+ return new AsyncExpectError(getErrorMessages(e), x);
182
+ }
183
+ throw e;
184
+ }
185
+ return x;
186
+ });
187
+ (_b = (_a = getCurrentTest2()).promises) != null ? _b : _a.promises = [];
188
+ getCurrentTest2().promises.push(promise);
189
+ return promise;
190
+ }, "asyncExpect");
191
+
192
+ // src/utils/predictJson.ts
193
+ import { z as z2, ZodSchema } from "@botpress/sdk";
194
+ import JSON5 from "json5";
195
+ var nonEmptyString = z2.string().trim().min(1);
196
+ var nonEmptyObject = z2.object({}).passthrough().refine((value) => Object.keys(value).length > 0, {
197
+ message: "Expected a non-empty object"
198
+ });
199
+ var Input = nonEmptyString.or(nonEmptyObject).or(z2.array(z2.any()));
200
+ var Output = z2.object({
201
+ reason: nonEmptyString.describe("A human-readable explanation of the result"),
202
+ result: z2.any().describe(
203
+ "Your best guess at the output according to the instructions provided, rooted in the context of the input and the reason above"
204
+ )
205
+ });
206
+ var Example = z2.object({
207
+ input: Input,
208
+ output: Output
209
+ });
210
+ var Options = z2.object({
211
+ systemMessage: z2.string(),
212
+ examples: z2.array(Example).default([]),
213
+ input: Input,
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ outputSchema: z2.custom((value) => value instanceof ZodSchema),
216
+ model: z2.string()
217
+ });
218
+ var isValidExample = /* @__PURE__ */ __name((outputSchema) => (example) => Input.safeParse(example.input).success && Output.safeParse(example.output).success && outputSchema.safeParse(example.output.result).success, "isValidExample");
219
+ async function predictJson(_options) {
220
+ var _a, _b;
221
+ const options = Options.parse(_options);
222
+ const [integration, model] = options.model.split("__");
223
+ if (!(model == null ? void 0 : model.length)) {
224
+ throw new Error("Invalid model");
225
+ }
226
+ const exampleMessages = options.examples.filter(isValidExample(options.outputSchema)).flatMap(({ input, output: output2 }) => [
227
+ { role: "user", content: JSON.stringify(input, null, 2) },
228
+ { role: "assistant", content: JSON.stringify(output2, null, 2) }
229
+ ]);
230
+ const outputSchema = Output.extend({
231
+ result: options.outputSchema.describe(Output.shape.result.description)
232
+ });
233
+ const result = await Context.client.callAction({
234
+ type: `${integration}:generateContent`,
235
+ input: {
236
+ systemPrompt: `
237
+ ${options.systemMessage}
238
+
239
+ ---
240
+ Please generate a JSON response with the following format:
241
+ \`\`\`typescript
242
+ ${await outputSchema.toTypescriptAsync()}
243
+ \`\`\`
244
+ `.trim(),
245
+ messages: [
246
+ ...exampleMessages,
247
+ {
248
+ role: "user",
249
+ content: JSON.stringify(options.input, null, 2)
250
+ }
251
+ ],
252
+ temperature: 0,
253
+ responseFormat: "json_object",
254
+ model: { id: model }
255
+ }
256
+ });
257
+ const output = result.output;
258
+ if (!output.choices.length || typeof ((_b = (_a = output.choices) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) !== "string") {
259
+ throw new Error("Invalid response from the model");
260
+ }
261
+ const json = output.choices[0].content.trim();
262
+ if (!json.length) {
263
+ throw new Error("No response from the model");
264
+ }
265
+ return outputSchema.parse(JSON5.parse(json));
266
+ }
267
+ __name(predictJson, "predictJson");
268
+
269
+ // src/assertions/extension.ts
270
+ import json5 from "json5";
271
+ import { expect as expect2 } from "vitest";
272
+ import { getCurrentTest as getCurrentTest3 } from "vitest/suite";
273
+ var toAssertion = /* @__PURE__ */ __name((promise) => {
274
+ return {
275
+ then: promise.then.bind(promise),
276
+ value: promise.then((value) => value.result)
277
+ };
278
+ }, "toAssertion");
279
+ var makeToMatchInlineSnapshot = /* @__PURE__ */ __name((promise) => async (expected) => {
280
+ const stack = new Error().stack.split("\n")[2];
281
+ const newStack = `
282
+ at __INLINE_SNAPSHOT__ (node:internal/process/task_queues:1:1)
283
+ at randomLine (node:internal/process/task_queues:1:1)
284
+ ${stack}
285
+ `.trim();
286
+ const obj = json5.parse(expected != null ? expected : '""');
287
+ const expectation = asyncExpect(promise, (expect3) => expect3.toMatchObject(obj)).catch(() => {
288
+ });
289
+ try {
290
+ expect2((await promise).result).toMatchObject(obj);
291
+ } catch (err) {
292
+ const newError = new Error();
293
+ newError.stack = newStack;
294
+ expect2.getState().snapshotState.match({
295
+ isInline: true,
296
+ received: (await promise).result,
297
+ testName: getCurrentTest3().name,
298
+ error: newError,
299
+ inlineSnapshot: expected
300
+ });
301
+ }
302
+ return expectation;
303
+ }, "makeToMatchInlineSnapshot");
304
+
305
+ // src/assertions/check.ts
306
+ function check(value, condition, options) {
307
+ var _a;
308
+ const promise = predictJson({
309
+ systemMessage: `Check that the value meets the condition: ${condition}`,
310
+ examples: (_a = options == null ? void 0 : options.examples) == null ? void 0 : _a.map(({ value: value2, reason, expected }) => ({
311
+ input: value2,
312
+ output: { reason, result: expected }
313
+ })),
314
+ outputSchema: z3.boolean(),
315
+ model: Context.evaluatorModel,
316
+ input: value
317
+ });
318
+ return __spreadProps(__spreadValues({}, toAssertion(promise)), {
319
+ toBe: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toEqual(expected)), "toBe"),
320
+ toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise)
321
+ });
322
+ }
323
+ __name(check, "check");
324
+
325
+ // src/assertions/extract.ts
326
+ function extract(value, shape, options) {
327
+ var _a;
328
+ const additionalMessage = (options == null ? void 0 : options.description) ? `
329
+ In order to extract the right information, follow these instructions:
330
+ ${options.description}
331
+ ` : "";
332
+ const promise = predictJson({
333
+ systemMessage: "From the given input, extract the required information into the requested format." + additionalMessage.trim(),
334
+ examples: (_a = options == null ? void 0 : options.examples) == null ? void 0 : _a.map(({ value: value2, reason, extracted }) => ({
335
+ input: value2,
336
+ output: { reason, result: extracted }
337
+ })),
338
+ outputSchema: shape,
339
+ model: Context.evaluatorModel,
340
+ input: value
341
+ });
342
+ return __spreadProps(__spreadValues({}, toAssertion(promise)), {
343
+ toBe: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toEqual(expected)), "toBe"),
344
+ toMatchObject: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toMatchObject(expected)), "toMatchObject"),
345
+ toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise)
346
+ });
347
+ }
348
+ __name(extract, "extract");
349
+
350
+ // src/assertions/filter.ts
351
+ import { literal, z as z4 } from "@botpress/sdk";
352
+ function filter(values, condition, options) {
353
+ var _a, _b;
354
+ const mappedValues = values.map(
355
+ (_, idx) => z4.object({
356
+ index: literal(idx),
357
+ reason: z4.string(),
358
+ keep: z4.boolean()
359
+ })
360
+ );
361
+ const input = values.map((value, idx) => ({
362
+ index: idx,
363
+ value
364
+ }));
365
+ const schema = z4.tuple(mappedValues).describe(
366
+ "An array of the objects with the index and a boolean value indicating if the object should be kept or not"
367
+ );
368
+ const promise = predictJson({
369
+ systemMessage: `
370
+ Based on the following qualification criteria, you need to filter the given list of objects.
371
+ Citeria: ${condition}
372
+
373
+ ---
374
+ You need to return an array of objects with the index and a boolean value indicating if the object should be kept or not.
375
+ `.trim(),
376
+ examples: (options == null ? void 0 : options.examples) ? [
377
+ {
378
+ input: (_a = options == null ? void 0 : options.examples) == null ? void 0 : _a.map((v, index) => ({
379
+ index,
380
+ value: v.value
381
+ })),
382
+ output: {
383
+ reason: "Here are some examples",
384
+ result: (_b = options == null ? void 0 : options.examples) == null ? void 0 : _b.map((v, idx) => ({
385
+ index: idx,
386
+ reason: v.reason,
387
+ keep: v.keep
388
+ }))
389
+ }
390
+ }
391
+ ] : void 0,
392
+ outputSchema: schema,
393
+ model: Context.evaluatorModel,
394
+ input
395
+ }).then((x) => {
396
+ const results = schema.parse(x.result);
397
+ return {
398
+ result: values.filter((_, idx) => {
399
+ var _a2;
400
+ return (_a2 = results.find((r) => r.index === idx)) == null ? void 0 : _a2.keep;
401
+ }),
402
+ reason: x.reason
403
+ };
404
+ });
405
+ return __spreadProps(__spreadValues({}, toAssertion(promise)), {
406
+ toBe: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toEqual(expected)), "toBe"),
407
+ toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise),
408
+ toHaveNoneFiltered: /* @__PURE__ */ __name(() => asyncExpect(promise, (expect3) => expect3.toEqual(values)), "toHaveNoneFiltered"),
409
+ toHaveSomeFiltered: /* @__PURE__ */ __name(() => asyncExpect(promise, (expect3) => expect3.not.toEqual(values)), "toHaveSomeFiltered"),
410
+ toBeEmpty: /* @__PURE__ */ __name(() => asyncExpect(promise, (expect3) => expect3.toHaveLength(0)), "toBeEmpty"),
411
+ length: {
412
+ toBe: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toHaveLength(expected)), "toBe"),
413
+ toBeGreaterThanOrEqual: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.length.greaterThanOrEqual(expected)), "toBeGreaterThanOrEqual"),
414
+ toBeLessThanOrEqual: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.length.lessThanOrEqual(expected)), "toBeLessThanOrEqual"),
415
+ toBeBetween: /* @__PURE__ */ __name((min, max) => asyncExpect(promise, (expect3) => expect3.length.within(min, max)), "toBeBetween")
416
+ }
417
+ });
418
+ }
419
+ __name(filter, "filter");
420
+
421
+ // src/assertions/rate.ts
422
+ import { z as z5 } from "@botpress/sdk";
423
+ function rate(value, condition, options) {
424
+ var _a;
425
+ const schema = z5.number().min(1).max(5).describe("Rating score, higher is better (1 is the worst, 5 is the best)");
426
+ const promise = predictJson({
427
+ systemMessage: `Based on the following qualification criteria, you need to rate the given situation from a score of 1 to 5.
428
+ Scoring: 1 is the worst score, 5 is the best score possible.
429
+ Criteria: ${condition}`,
430
+ examples: (_a = options == null ? void 0 : options.examples) == null ? void 0 : _a.map(({ value: value2, reason, rating }) => ({
431
+ input: value2,
432
+ output: { reason, result: rating }
433
+ })),
434
+ outputSchema: schema,
435
+ model: Context.evaluatorModel,
436
+ input: value
437
+ }).then((x) => {
438
+ return {
439
+ result: typeof x.result === "number" ? x.result : parseInt(x.result, 10),
440
+ reason: x.reason
441
+ };
442
+ });
443
+ return __spreadProps(__spreadValues({}, toAssertion(promise)), {
444
+ toBe: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toEqual(expected)), "toBe"),
445
+ toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise),
446
+ toBeGreaterThanOrEqual: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toBeGreaterThanOrEqual(expected)), "toBeGreaterThanOrEqual"),
447
+ toBeLessThanOrEqual: /* @__PURE__ */ __name((expected) => asyncExpect(promise, (expect3) => expect3.toBeLessThanOrEqual(expected)), "toBeLessThanOrEqual")
448
+ });
449
+ }
450
+ __name(rate, "rate");
451
+
452
+ // src/hooks/setEvaluator.ts
453
+ import { getCurrentTest as getCurrentTest4 } from "vitest/suite";
454
+ var setEvaluator = /* @__PURE__ */ __name((model) => {
455
+ const test = getCurrentTest4();
456
+ if (!test) {
457
+ throw new Error("setEvaluator is a Vitest hook and must be called within a test");
458
+ }
459
+ const meta = test.meta;
460
+ meta.evaluatorModel = model;
461
+ }, "setEvaluator");
462
+
463
+ // src/hooks/setupClient.ts
464
+ var setupClient = /* @__PURE__ */ __name((client) => {
465
+ Context.setClient(client);
466
+ }, "setupClient");
467
+ export {
468
+ check,
469
+ compare,
470
+ extract,
471
+ filter,
472
+ rate,
473
+ setEvaluator,
474
+ setupClient
475
+ };
476
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/task/compare.ts","../src/utils/deferred.ts","../src/assertions/check.ts","../src/context.ts","../src/utils/asyncAssertion.ts","../src/utils/predictJson.ts","../src/assertions/extension.ts","../src/assertions/extract.ts","../src/assertions/filter.ts","../src/assertions/rate.ts","../src/hooks/setEvaluator.ts","../src/hooks/setupClient.ts"],"sourcesContent":["import { z } from '@botpress/sdk'\nimport { TestFunction } from 'vitest'\nimport { createTaskCollector, getCurrentSuite } from 'vitest/suite'\nimport { TestMetadata } from '../context'\nimport { Deferred } from '../utils/deferred'\n\nconst scenarioId = z\n .string()\n .trim()\n .min(1, 'Scenario ID/name must not be empty')\n .max(50, 'Scenario ID/name is too long')\n\nexport type ScenarioLike = z.infer<typeof ScenarioLike>\nconst ScenarioLike = z.union([\n scenarioId,\n z.object({ name: scenarioId }).passthrough(),\n z.object({ id: scenarioId }).passthrough()\n])\n\nconst getScenarioName = (scenario: ScenarioLike) =>\n (typeof scenario === 'string' ? scenario : 'name' in scenario ? scenario?.name : scenario?.id) as string\n\nconst scenarioArgs = z\n .array(ScenarioLike)\n .min(2, 'You need at least two scenarios to compare')\n .max(10, 'You can only compare up to 10 scenarios')\n .refine((scenarios) => {\n const set = new Set<string>()\n scenarios.forEach((scenario) => set.add(getScenarioName(scenario)))\n return set.size === scenarios.length\n }, 'Scenarios names must be unique')\n\nexport function compare<T extends ReadonlyArray<ScenarioLike>>(\n name: string | Function,\n scenarios: T,\n fn?: TestFunction<{\n scenario: T[number]\n }>\n) {\n scenarios = scenarioArgs.parse(scenarios) as unknown as T\n\n return createTaskCollector((_name, fn, timeout) => {\n const currentSuite = getCurrentSuite()\n\n let completedCount = 0\n const finished = new Deferred<void>()\n\n for (const scenario of scenarios) {\n const key = getScenarioName(scenario)\n\n currentSuite.task(key, {\n meta: {\n scenario: key,\n isVaiTest: true\n } satisfies TestMetadata,\n handler: async (context) => {\n const extendedContext = Object.freeze({\n scenario\n })\n context.onTestFinished(() => {\n if (++completedCount === scenarios.length) {\n finished.resolve()\n }\n })\n\n await fn({ ...context, ...extendedContext })\n },\n timeout: timeout ?? 10_000\n })\n }\n })(name, fn)\n}\n","export class Deferred<T> {\n promise: Promise<T>\n private _resolve!: (value: T | PromiseLike<T>) => void\n private _reject!: (reason?: unknown) => void\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this._resolve = resolve\n this._reject = reject\n })\n }\n\n resolve(value: T | PromiseLike<T>): void {\n this._resolve(value)\n }\n\n reject(reason?: unknown): void {\n this._reject(reason)\n }\n}\n","import { z } from '@botpress/sdk'\nimport { Context } from '../context'\nimport { asyncExpect } from '../utils/asyncAssertion'\nimport { Input, predictJson } from '../utils/predictJson'\nimport { makeToMatchInlineSnapshot, toAssertion } from './extension'\n\nexport type CheckOptions<T> = {\n examples?: { value: T; expected: boolean; reason: string }[]\n}\n\nexport function check<T extends Input>(value: T, condition: string, options?: CheckOptions<T>) {\n const promise = predictJson({\n systemMessage: `Check that the value meets the condition: ${condition}`,\n examples: options?.examples?.map(({ value, reason, expected }) => ({\n input: value,\n output: { reason, result: expected }\n })),\n outputSchema: z.boolean(),\n model: Context.evaluatorModel,\n input: value\n })\n\n return {\n ...toAssertion(promise),\n toBe: (expected: boolean) => asyncExpect(promise, (expect) => expect.toEqual(expected)),\n toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise)\n }\n}\n","import type { Client } from '@botpress/client'\nimport { onTestFinished } from 'vitest'\nimport { getCurrentTest } from 'vitest/suite'\nimport { Models } from './models'\n\nexport type EvaluatorModel = (typeof Models)[number]['id']\n\nexport type TestMetadata = {\n isVaiTest: boolean\n scenario?: string\n evaluatorModel?: EvaluatorModel\n}\n\nconst getTestMetadata = (): TestMetadata => {\n const test = getCurrentTest()\n return (test?.meta ?? {\n isVaiTest: false\n }) as TestMetadata\n}\n\nclass VaiContext {\n #client: Client | null = null\n #wrapError = false\n\n get wrapError() {\n return this.#wrapError\n }\n\n get client() {\n if (!this.#client) {\n throw new Error('Botpress client is not set')\n }\n\n return this.#client\n }\n\n get evaluatorModel(): EvaluatorModel {\n return getTestMetadata().evaluatorModel ?? 'openai__gpt-4o-mini-2024-07-18'\n }\n\n get scenario() {\n return getTestMetadata().scenario\n }\n\n get isVaiTest() {\n return getTestMetadata().isVaiTest\n }\n\n setClient(cognitive: Client) {\n this.#client = cognitive\n }\n\n swallowErrors() {\n if (!getCurrentTest()) {\n throw new Error('cancelBail is a Vitest hook and must be called within a test')\n }\n\n this.#wrapError = true\n onTestFinished(() => {\n this.#wrapError = false\n })\n }\n}\n\nexport const Context = new VaiContext()\n","import { Assertion, expect } from 'vitest'\nimport { getCurrentTest } from 'vitest/suite'\nimport { Context } from '../context'\nimport { Output } from './predictJson'\n\nexport class AsyncExpectError<T> extends Error {\n constructor(message: string, public readonly output: Output<T>) {\n super(message)\n this.name = 'AsyncExpectError'\n }\n}\n\nconst getErrorMessages = (e: unknown): string => {\n if (e instanceof Error) {\n return e.message\n } else if (typeof e === 'string') {\n return e\n } else if (typeof e === 'object' && e !== null) {\n return JSON.stringify(e)\n }\n\n return `Unknown error: ${e}`\n}\n\nexport const asyncExpect = <T>(output: Promise<Output<T>>, assertion: (assert: Assertion<T>) => void) => {\n const promise = output.then((x) => {\n try {\n assertion(expect(x.result, x.reason))\n } catch (e: unknown) {\n if (Context.wrapError) {\n return new AsyncExpectError<T>(getErrorMessages(e), x)\n }\n throw e\n }\n return x\n })\n getCurrentTest()!.promises ??= []\n getCurrentTest()!.promises!.push(promise)\n return promise\n}\n","import { z, ZodSchema } from '@botpress/sdk'\nimport JSON5 from 'json5'\nimport { Context } from '../context'\nimport { llm } from '../sdk-interfaces/llm/generateContent'\n\nconst nonEmptyString = z.string().trim().min(1)\nconst nonEmptyObject = z\n .object({})\n .passthrough()\n .refine((value) => Object.keys(value).length > 0, {\n message: 'Expected a non-empty object'\n })\n\nexport type Input = z.infer<typeof Input>\nconst Input = nonEmptyString.or(nonEmptyObject).or(z.array(z.any()))\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type Output<T = any> = z.infer<typeof Output> & { result: T }\nconst Output = z.object({\n reason: nonEmptyString.describe('A human-readable explanation of the result'),\n result: z\n .any()\n .describe(\n 'Your best guess at the output according to the instructions provided, rooted in the context of the input and the reason above'\n )\n})\n\ntype Example = z.infer<typeof Example>\nconst Example = z.object({\n input: Input,\n output: Output\n})\n\ntype InputOptions<T extends ZodSchema = ZodSchema> = z.input<typeof Options> & { outputSchema: T }\ntype Options = z.infer<typeof Options>\nconst Options = z.object({\n systemMessage: z.string(),\n examples: z.array(Example).default([]),\n input: Input,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outputSchema: z.custom<ZodSchema<any>>((value) => value instanceof ZodSchema),\n model: z.string()\n})\n\ntype Message = {\n role: 'user' | 'assistant' | 'system'\n content: string\n}\n\nconst isValidExample =\n (outputSchema: ZodSchema) =>\n (example: Example): example is Example =>\n Input.safeParse(example.input).success &&\n Output.safeParse(example.output).success &&\n outputSchema.safeParse(example.output.result).success\n\nexport async function predictJson<T extends ZodSchema>(_options: InputOptions<T>): Promise<Output<z.infer<T>>> {\n const options = Options.parse(_options)\n const [integration, model] = options.model.split('__')\n\n if (!model?.length) {\n throw new Error('Invalid model')\n }\n\n const exampleMessages = options.examples\n .filter(isValidExample(options.outputSchema))\n .flatMap(({ input, output }) => [\n { role: 'user', content: JSON.stringify(input, null, 2) } satisfies Message,\n { role: 'assistant', content: JSON.stringify(output, null, 2) } satisfies Message\n ])\n\n const outputSchema = Output.extend({\n result: options.outputSchema.describe(Output.shape.result.description!)\n })\n\n const result = await Context.client.callAction({\n type: `${integration}:generateContent`,\n input: {\n systemPrompt: `\n${options.systemMessage}\n\n---\nPlease generate a JSON response with the following format:\n\\`\\`\\`typescript\n${await outputSchema.toTypescriptAsync()}\n\\`\\`\\`\n`.trim(),\n messages: [\n ...exampleMessages,\n {\n role: 'user',\n content: JSON.stringify(options.input, null, 2)\n }\n ],\n temperature: 0,\n responseFormat: 'json_object',\n model: { id: model! }\n } satisfies llm.generateContent.Input\n })\n\n const output = result.output as llm.generateContent.Output\n\n if (!output.choices.length || typeof output.choices?.[0]?.content !== 'string') {\n throw new Error('Invalid response from the model')\n }\n\n const json = output.choices[0].content.trim()\n\n if (!json.length) {\n throw new Error('No response from the model')\n }\n\n return outputSchema.parse(JSON5.parse(json)) as Output<z.infer<T>>\n}\n","import json5 from 'json5'\nimport { expect } from 'vitest'\nimport { getCurrentTest } from 'vitest/suite'\n\nimport { asyncExpect } from '../utils/asyncAssertion'\nimport { Output } from '../utils/predictJson'\n\nexport type ExtendedPromise<T> = PromiseLike<Output<T>> & {\n value: PromiseLike<T>\n}\n\nexport const toAssertion = <T>(promise: Promise<Output<T>>): ExtendedPromise<T> => {\n return {\n then: promise.then.bind(promise),\n value: promise.then((value) => value.result)\n }\n}\n\nexport const makeToMatchInlineSnapshot =\n <T>(promise: Promise<Output<T>>) =>\n async (expected?: string) => {\n const stack = new Error().stack!.split('\\n')[2]\n const newStack = `\nat __INLINE_SNAPSHOT__ (node:internal/process/task_queues:1:1)\nat randomLine (node:internal/process/task_queues:1:1)\n${stack}\n`.trim()\n\n const obj = json5.parse(expected ?? '\"\"')\n const expectation = asyncExpect(promise, (expect) => expect.toMatchObject(obj)).catch(() => {\n // we swallow the error here, as we're going to throw a new one with the correct stack\n // this is just to make vitest happy and show a nice error message\n })\n\n try {\n expect((await promise).result).toMatchObject(obj)\n } catch (err) {\n const newError = new Error()\n newError.stack = newStack\n\n expect.getState().snapshotState.match({\n isInline: true,\n received: (await promise).result,\n testName: getCurrentTest()!.name,\n error: newError,\n inlineSnapshot: expected\n })\n }\n\n return expectation\n }\n","import { z } from '@botpress/sdk'\n\nimport { Context } from '../context'\nimport { asyncExpect } from '../utils/asyncAssertion'\nimport { Input, predictJson } from '../utils/predictJson'\nimport { makeToMatchInlineSnapshot, toAssertion } from './extension'\n\nexport type ExtractOptions<T, S> = {\n description?: string\n examples?: { value: T; extracted: S; reason: string }[]\n}\n\nexport function extract<T extends Input, S extends z.AnyZodObject>(\n value: T,\n shape: S,\n options?: ExtractOptions<T, z.infer<S>>\n) {\n const additionalMessage = options?.description\n ? `\\nIn order to extract the right information, follow these instructions:\\n${options.description}\\n`\n : ''\n const promise = predictJson({\n systemMessage:\n 'From the given input, extract the required information into the requested format.' + additionalMessage.trim(),\n examples: options?.examples?.map(({ value, reason, extracted }) => ({\n input: value,\n output: { reason, result: extracted }\n })),\n outputSchema: shape,\n model: Context.evaluatorModel,\n input: value\n })\n\n return {\n ...toAssertion(promise),\n toBe: (expected: z.infer<S>) => asyncExpect(promise, (expect) => expect.toEqual(expected)),\n toMatchObject: (expected: Partial<z.infer<S>>) => asyncExpect(promise, (expect) => expect.toMatchObject(expected)),\n toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise)\n }\n}\n","import { literal, z } from '@botpress/sdk'\n\nimport { Context } from '../context'\nimport { asyncExpect } from '../utils/asyncAssertion'\nimport { predictJson } from '../utils/predictJson'\nimport { makeToMatchInlineSnapshot, toAssertion } from './extension'\n\nexport type FilterOptions<T> = {\n examples?: { value: T; reason: string; keep: boolean }[]\n}\n\nexport function filter<U>(values: U[], condition: string, options?: FilterOptions<U>) {\n const mappedValues = values.map((_, idx) =>\n z.object({\n index: literal(idx),\n reason: z.string(),\n keep: z.boolean()\n })\n )\n\n const input = values.map((value, idx) => ({\n index: idx,\n value\n }))\n\n const schema = z\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .tuple(mappedValues as any)\n .describe(\n 'An array of the objects with the index and a boolean value indicating if the object should be kept or not'\n )\n\n const promise = predictJson({\n systemMessage: `\nBased on the following qualification criteria, you need to filter the given list of objects.\nCiteria: ${condition}\n\n---\nYou need to return an array of objects with the index and a boolean value indicating if the object should be kept or not.\n`.trim(),\n examples: options?.examples\n ? [\n {\n input: options?.examples?.map((v, index) => ({\n index,\n value: v.value\n })),\n output: {\n reason: 'Here are some examples',\n result: options?.examples?.map((v, idx) => ({\n index: idx,\n reason: v.reason,\n keep: v.keep\n }))\n }\n }\n ]\n : undefined,\n outputSchema: schema,\n model: Context.evaluatorModel,\n input\n }).then((x) => {\n const results = schema.parse(x.result) as { index: number; keep: boolean }[]\n return {\n result: values.filter((_, idx) => results.find((r) => r.index === idx)?.keep),\n reason: x.reason\n }\n })\n\n return {\n ...toAssertion(promise),\n toBe: (expected: U[]) => asyncExpect(promise, (expect) => expect.toEqual(expected)),\n toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise),\n toHaveNoneFiltered: () => asyncExpect(promise, (expect) => expect.toEqual(values)),\n toHaveSomeFiltered: () => asyncExpect(promise, (expect) => expect.not.toEqual(values)),\n toBeEmpty: () => asyncExpect(promise, (expect) => expect.toHaveLength(0)),\n length: {\n toBe: (expected: number) => asyncExpect(promise, (expect) => expect.toHaveLength(expected)),\n toBeGreaterThanOrEqual: (expected: number) =>\n asyncExpect(promise, (expect) => expect.length.greaterThanOrEqual(expected)),\n toBeLessThanOrEqual: (expected: number) =>\n asyncExpect(promise, (expect) => expect.length.lessThanOrEqual(expected)),\n toBeBetween: (min: number, max: number) => asyncExpect(promise, (expect) => expect.length.within(min, max))\n }\n }\n}\n","import { z } from '@botpress/sdk'\n\nimport { Context } from '../context'\nimport { asyncExpect } from '../utils/asyncAssertion'\nimport { Input, predictJson } from '../utils/predictJson'\nimport { makeToMatchInlineSnapshot, toAssertion } from './extension'\n\nexport type RatingScore = 1 | 2 | 3 | 4 | 5\nexport type RateOptions<T> = {\n examples?: { value: T; rating: number; reason: string }[]\n}\n\nexport function rate<T extends Input>(value: T, condition: string, options?: RateOptions<T>) {\n const schema = z.number().min(1).max(5).describe('Rating score, higher is better (1 is the worst, 5 is the best)')\n const promise = predictJson({\n systemMessage: `Based on the following qualification criteria, you need to rate the given situation from a score of 1 to 5.\\nScoring: 1 is the worst score, 5 is the best score possible.\\nCriteria: ${condition}`,\n examples: options?.examples?.map(({ value, reason, rating }) => ({\n input: value,\n output: { reason, result: rating }\n })),\n outputSchema: schema,\n model: Context.evaluatorModel,\n input: value\n }).then((x) => {\n return {\n result: typeof x.result === 'number' ? x.result : parseInt(x.result, 10),\n reason: x.reason\n }\n })\n\n return {\n ...toAssertion(promise),\n toBe: (expected: number) => asyncExpect(promise, (expect) => expect.toEqual(expected)),\n toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise),\n toBeGreaterThanOrEqual: (expected: RatingScore) =>\n asyncExpect(promise, (expect) => expect.toBeGreaterThanOrEqual(expected)),\n toBeLessThanOrEqual: (expected: RatingScore) =>\n asyncExpect(promise, (expect) => expect.toBeLessThanOrEqual(expected))\n }\n}\n","import { getCurrentTest } from 'vitest/suite'\nimport { EvaluatorModel, TestMetadata } from '../context'\n\nexport const setEvaluator = (model: EvaluatorModel) => {\n const test = getCurrentTest()\n\n if (!test) {\n throw new Error('setEvaluator is a Vitest hook and must be called within a test')\n }\n\n const meta = test.meta as TestMetadata\n meta.evaluatorModel = model\n}\n","import { Client } from '@botpress/client'\nimport { Context } from '../context'\n\nexport const setupClient = (client: Client) => {\n Context.setClient(client)\n}\n"],"mappings":"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","names":["fn","z","getCurrentTest","getCurrentTest","z","z","output","expect","getCurrentTest","expect","getCurrentTest","value","z","expect","value","expect","z","z","_a","expect","z","z","value","expect","getCurrentTest","getCurrentTest"]}
package/ensure-env.cjs ADDED
@@ -0,0 +1,9 @@
1
+ ;(function () {
2
+ if (!process.env.CLOUD_BOT_ID) {
3
+ throw new Error('Env: CLOUD_BOT_ID is required')
4
+ }
5
+
6
+ if (!process.env.CLOUD_PAT) {
7
+ throw new Error('Env: CLOUD_PAT is required')
8
+ }
9
+ })()
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@botpress/vai",
3
+ "version": "0.0.1-beta.1",
4
+ "type": "module",
5
+ "description": "Vitest AI (vai) – a vitest extension for testing with LLMs",
6
+ "main": "src/index.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/src/index.d.ts",
10
+ "import": "./dist/src/index.js",
11
+ "require": "./dist/src/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "watch": "tsup --watch",
17
+ "test": "vitest run --config vitest.config.ts",
18
+ "test:update": "vitest -u run --config vitest.config.ts",
19
+ "test:watch": "vitest --config vitest.config.ts",
20
+ "build-with-latest-models": "pnpm run update-types && pnpm run update-models && pnpm run build",
21
+ "update-models": "ts-node ./src/scripts/update-models.ts",
22
+ "update-types": "ts-node ./src/scripts/update-types.ts"
23
+ },
24
+ "keywords": [],
25
+ "author": "",
26
+ "license": "ISC",
27
+ "dependencies": {
28
+ "json5": "^2.2.1",
29
+ "jsonrepair": "^3.2.0",
30
+ "lodash": "^4.17.21"
31
+ },
32
+ "devDependencies": {
33
+ "@types/lodash": "^4.17.0",
34
+ "dotenv": "^16.3.1",
35
+ "ts-node": "^10.9.2",
36
+ "tsup": "^8.3.5",
37
+ "typescript": "^5.7.2"
38
+ },
39
+ "peerDependencies": {
40
+ "@botpress/client": "^0.36.2",
41
+ "@botpress/sdk": "^1.6.1",
42
+ "@botpress/wasm": "^1.0.0",
43
+ "vitest": "^2.0.5"
44
+ }
45
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from '@botpress/sdk'
2
+ import { Context } from '../context'
3
+ import { asyncExpect } from '../utils/asyncAssertion'
4
+ import { Input, predictJson } from '../utils/predictJson'
5
+ import { makeToMatchInlineSnapshot, toAssertion } from './extension'
6
+
7
+ export type CheckOptions<T> = {
8
+ examples?: { value: T; expected: boolean; reason: string }[]
9
+ }
10
+
11
+ export function check<T extends Input>(value: T, condition: string, options?: CheckOptions<T>) {
12
+ const promise = predictJson({
13
+ systemMessage: `Check that the value meets the condition: ${condition}`,
14
+ examples: options?.examples?.map(({ value, reason, expected }) => ({
15
+ input: value,
16
+ output: { reason, result: expected }
17
+ })),
18
+ outputSchema: z.boolean(),
19
+ model: Context.evaluatorModel,
20
+ input: value
21
+ })
22
+
23
+ return {
24
+ ...toAssertion(promise),
25
+ toBe: (expected: boolean) => asyncExpect(promise, (expect) => expect.toEqual(expected)),
26
+ toMatchInlineSnapshot: makeToMatchInlineSnapshot(promise)
27
+ }
28
+ }