@botpress/zai 1.0.0-beta.2 → 1.0.0-beta.3
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/package.json +9 -2
- package/dist/src/operations/check.test.js +0 -129
- package/dist/src/operations/extract.test.js +0 -163
- package/dist/src/operations/filter.test.js +0 -151
- package/dist/src/operations/label.test.js +0 -213
- package/dist/src/operations/rewrite.test.js +0 -96
- package/dist/src/operations/summarize.test.js +0 -22
- package/dist/src/operations/text.test.js +0 -51
- package/dist/src/operations/zai-learn.test.js +0 -71
- package/dist/src/operations/zai-retry.test.js +0 -50
- package/src/adapters/adapter.ts +0 -35
- package/src/adapters/botpress-table.ts +0 -213
- package/src/adapters/memory.ts +0 -13
- package/src/env.ts +0 -54
- package/src/index.ts +0 -11
- package/src/models.ts +0 -347
- package/src/operations/__tests/botpress_docs.txt +0 -26040
- package/src/operations/__tests/index.ts +0 -30
- package/src/operations/check.test.ts +0 -155
- package/src/operations/check.ts +0 -187
- package/src/operations/constants.ts +0 -2
- package/src/operations/errors.ts +0 -9
- package/src/operations/extract.test.ts +0 -209
- package/src/operations/extract.ts +0 -291
- package/src/operations/filter.test.ts +0 -182
- package/src/operations/filter.ts +0 -231
- package/src/operations/label.test.ts +0 -239
- package/src/operations/label.ts +0 -332
- package/src/operations/rewrite.test.ts +0 -114
- package/src/operations/rewrite.ts +0 -148
- package/src/operations/summarize.test.ts +0 -25
- package/src/operations/summarize.ts +0 -193
- package/src/operations/text.test.ts +0 -60
- package/src/operations/text.ts +0 -63
- package/src/operations/zai-learn.test.ts +0 -85
- package/src/operations/zai-retry.test.ts +0 -64
- package/src/scripts/update-models.ts +0 -74
- package/src/utils.ts +0 -61
- package/src/zai.ts +0 -185
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botpress/zai",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Zui AI (zai) – An LLM utility library written on top of Zui and the Botpress API",
|
|
7
|
-
"main": "src/index.
|
|
7
|
+
"main": "dist/src/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/src/index.js",
|
|
11
|
+
"require": "./dist/src/index.js",
|
|
12
|
+
"types": "./dist/src/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"scripts": {
|
|
9
16
|
"build": "tsc && tsc --project tsconfig.types.json",
|
|
10
17
|
"build-with-latest-models": "pnpm run update-models && pnpm run build",
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterAll, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { BotpressDocumentation, getClient, getZai, metadata } from './__tests';
|
|
3
|
-
import { TableAdapter } from '../adapters/botpress-table';
|
|
4
|
-
describe('zai.check', { timeout: 60_000 }, () => {
|
|
5
|
-
const zai = getZai();
|
|
6
|
-
it('basic check on a string', async () => {
|
|
7
|
-
const value = await zai.check('This text is very clearly written in English.', 'is an english sentence');
|
|
8
|
-
expect(value).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
it('text that is too long gets truncated', async () => {
|
|
11
|
-
const isBotpressDocumentation = await zai.check(BotpressDocumentation, 'is about botpress and looks like documentation');
|
|
12
|
-
const isAboutBirds = await zai.check(BotpressDocumentation, 'is a book about birds and their species');
|
|
13
|
-
expect(isBotpressDocumentation).toBe(true);
|
|
14
|
-
expect(isAboutBirds).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
it('works with any input type', async () => {
|
|
17
|
-
const sly = { name: 'Sylvain Perron', age: 30, job: 'CEO', company: 'Botpress', location: 'Quebec' };
|
|
18
|
-
const american = await zai.check(sly, 'person lives in north america');
|
|
19
|
-
const european = await zai.check(sly, 'person lives in europe');
|
|
20
|
-
expect(american).toBe(true);
|
|
21
|
-
expect(european).toBe(false);
|
|
22
|
-
});
|
|
23
|
-
it('check with examples', async () => {
|
|
24
|
-
const examples = [
|
|
25
|
-
{
|
|
26
|
-
input: 'Rasa (framework)',
|
|
27
|
-
check: true,
|
|
28
|
-
reason: 'Rasa is a chatbot framework, so it competes with us (Botpress).'
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
input: 'Rasa (coffee company)',
|
|
32
|
-
check: false,
|
|
33
|
-
reason: 'Rasa (coffee company) is not in the chatbot or AI agent industry, therefore it does not compete with us (Botpress).'
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
input: 'Dialogflow',
|
|
37
|
-
check: true,
|
|
38
|
-
reason: 'Dialogflow is a chatbot development product, so it competes with us (Botpress).'
|
|
39
|
-
}
|
|
40
|
-
];
|
|
41
|
-
const moveworks = await zai.check('Moveworks', 'competes with us', { examples });
|
|
42
|
-
const ada = await zai.check('Ada.cx', 'competes with us', { examples });
|
|
43
|
-
const voiceflow = await zai.check('Voiceflow', 'competes with us', { examples });
|
|
44
|
-
const nike = await zai.check('Nike', 'competes with us', { examples });
|
|
45
|
-
const adidas = await zai.check('Adidas', 'competes with us', { examples });
|
|
46
|
-
expect(moveworks).toBe(true);
|
|
47
|
-
expect(ada).toBe(true);
|
|
48
|
-
expect(voiceflow).toBe(true);
|
|
49
|
-
expect(nike).toBe(false);
|
|
50
|
-
expect(adidas).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe('zai.learn.check', { timeout: 60_000 }, () => {
|
|
54
|
-
const client = getClient();
|
|
55
|
-
let tableName = 'ZaiTestCheckInternalTable';
|
|
56
|
-
let taskId = 'check';
|
|
57
|
-
let zai = getZai();
|
|
58
|
-
beforeEach(async () => {
|
|
59
|
-
zai = getZai().with({
|
|
60
|
-
activeLearning: {
|
|
61
|
-
enable: true,
|
|
62
|
-
taskId,
|
|
63
|
-
tableName
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
afterEach(async () => {
|
|
68
|
-
try {
|
|
69
|
-
await client.deleteTableRows({ table: tableName, deleteAllRows: true });
|
|
70
|
-
}
|
|
71
|
-
catch (err) { }
|
|
72
|
-
});
|
|
73
|
-
afterAll(async () => {
|
|
74
|
-
try {
|
|
75
|
-
await client.deleteTable({ table: tableName });
|
|
76
|
-
}
|
|
77
|
-
catch (err) { }
|
|
78
|
-
});
|
|
79
|
-
it('learns a contradiction from examples', async () => {
|
|
80
|
-
const adapter = new TableAdapter({
|
|
81
|
-
client,
|
|
82
|
-
tableName
|
|
83
|
-
});
|
|
84
|
-
const value = await zai.learn(taskId).check(`What's up`, 'is a greeting');
|
|
85
|
-
expect(value).toBe(true);
|
|
86
|
-
let rows = await client.findTableRows({ table: tableName });
|
|
87
|
-
expect(rows.rows.length).toBe(1);
|
|
88
|
-
expect(rows.rows[0].output.value).toEqual(value);
|
|
89
|
-
await adapter.saveExample({
|
|
90
|
-
key: 't1',
|
|
91
|
-
taskId: `zai/${taskId}`,
|
|
92
|
-
taskType: 'zai.check',
|
|
93
|
-
instructions: 'is a greeting',
|
|
94
|
-
input: 'what is up',
|
|
95
|
-
output: false,
|
|
96
|
-
explanation: `"What's up" is our business scenario is NOT considered an official greeting.`,
|
|
97
|
-
metadata,
|
|
98
|
-
status: 'approved'
|
|
99
|
-
});
|
|
100
|
-
await adapter.saveExample({
|
|
101
|
-
key: 't2',
|
|
102
|
-
taskId: `zai/${taskId}`,
|
|
103
|
-
taskType: 'zai.check',
|
|
104
|
-
instructions: 'is a greeting',
|
|
105
|
-
input: 'hello! how are you?',
|
|
106
|
-
output: true,
|
|
107
|
-
explanation: `"hello!" is a common greeting in English.`,
|
|
108
|
-
metadata,
|
|
109
|
-
status: 'approved'
|
|
110
|
-
});
|
|
111
|
-
await adapter.saveExample({
|
|
112
|
-
key: 't3',
|
|
113
|
-
taskId: `zai/${taskId}`,
|
|
114
|
-
taskType: 'zai.check',
|
|
115
|
-
instructions: 'is a greeting',
|
|
116
|
-
input: 'wassup',
|
|
117
|
-
output: false,
|
|
118
|
-
explanation: `"wassup" is a slang term and not considered a formal greeting. It is therefore NOT considered a greeting.`,
|
|
119
|
-
metadata,
|
|
120
|
-
status: 'approved'
|
|
121
|
-
});
|
|
122
|
-
const second = await zai.learn(taskId).check(`What's up`, 'is a greeting');
|
|
123
|
-
expect(second).toBe(false);
|
|
124
|
-
rows = await client.findTableRows({ table: tableName });
|
|
125
|
-
expect(rows.rows.length).toBe(4);
|
|
126
|
-
expect(rows.rows[0].output.value).toEqual(second);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
//# sourceMappingURL=check.test.js.map
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
|
|
2
|
-
import { BotpressDocumentation, getClient, getZai, metadata } from './__tests';
|
|
3
|
-
import { z } from '@bpinternal/zui';
|
|
4
|
-
import { check } from '@botpress/vai';
|
|
5
|
-
import { TableAdapter } from '../adapters/botpress-table';
|
|
6
|
-
describe('zai.extract', () => {
|
|
7
|
-
const zai = getZai();
|
|
8
|
-
it('extract simple object from paragraph', async () => {
|
|
9
|
-
const person = await zai.extract('My name is John Doe, I am 30 years old and I live in Quebec', z.object({
|
|
10
|
-
name: z.string(),
|
|
11
|
-
age: z.number(),
|
|
12
|
-
location: z.string()
|
|
13
|
-
}));
|
|
14
|
-
expect(person).toMatchInlineSnapshot(`
|
|
15
|
-
{
|
|
16
|
-
"age": 30,
|
|
17
|
-
"location": "Quebec",
|
|
18
|
-
"name": "John Doe",
|
|
19
|
-
}
|
|
20
|
-
`);
|
|
21
|
-
});
|
|
22
|
-
it('extract an array of objects from paragraph', async () => {
|
|
23
|
-
const people = await zai.extract(`
|
|
24
|
-
My name is John Doe, I am 30 years old and I live in Quebec.
|
|
25
|
-
My name is Jane Doe, I am 25 years old and I live in Montreal.
|
|
26
|
-
His name is Jack Doe, he is 35 years old and he lives in Toronto.
|
|
27
|
-
Her name is Jill Doe, she is 40 years old and she lives in Vancouver.`, z.array(z.object({
|
|
28
|
-
name: z.string(),
|
|
29
|
-
age: z.number(),
|
|
30
|
-
location: z.string()
|
|
31
|
-
})));
|
|
32
|
-
expect(people).toMatchInlineSnapshot(`
|
|
33
|
-
[
|
|
34
|
-
{
|
|
35
|
-
"age": 30,
|
|
36
|
-
"location": "Quebec",
|
|
37
|
-
"name": "John Doe",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"age": 25,
|
|
41
|
-
"location": "Montreal",
|
|
42
|
-
"name": "Jane Doe",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"age": 35,
|
|
46
|
-
"location": "Toronto",
|
|
47
|
-
"name": "Jack Doe",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"age": 40,
|
|
51
|
-
"location": "Vancouver",
|
|
52
|
-
"name": "Jill Doe",
|
|
53
|
-
},
|
|
54
|
-
]
|
|
55
|
-
`);
|
|
56
|
-
});
|
|
57
|
-
it('extract an object from anything as input', async () => {
|
|
58
|
-
const person = await zai.extract({
|
|
59
|
-
person: { first: 'John', last: 'Doe', age: 30 }
|
|
60
|
-
}, z.object({
|
|
61
|
-
a: z.string().describe('The full name of the person in the text'),
|
|
62
|
-
b: z.number().describe('The age of the person in the text')
|
|
63
|
-
}));
|
|
64
|
-
expect(person).toMatchInlineSnapshot(`
|
|
65
|
-
{
|
|
66
|
-
"a": "John Doe",
|
|
67
|
-
"b": 30,
|
|
68
|
-
}
|
|
69
|
-
`);
|
|
70
|
-
});
|
|
71
|
-
it('extract an array of objects from a super long text', async () => {
|
|
72
|
-
const features = await zai.extract(BotpressDocumentation, z.array(z
|
|
73
|
-
.object({
|
|
74
|
-
feature: z.string().describe('The name of the feature'),
|
|
75
|
-
parent: z.string().optional().describe('The parent feature').nullable(),
|
|
76
|
-
description: z.string().describe('The description of the feature')
|
|
77
|
-
})
|
|
78
|
-
.describe('A feature of Botpress')), {
|
|
79
|
-
instructions: 'Extract all things that looks like a Botpress feature in the provided input. You must extract a minimum of one element.'
|
|
80
|
-
});
|
|
81
|
-
check(features, 'Contains an element about flows / workflows').toBe(true);
|
|
82
|
-
check(features, 'Contains an element about tables').toBe(true);
|
|
83
|
-
check(features, 'Contains an element about webchat').toBe(true);
|
|
84
|
-
check(features, 'Contains an element about integrations').toBe(true);
|
|
85
|
-
check(features, 'Contains an element about HITL (human in the loop)').toBe(true);
|
|
86
|
-
check(features, 'Contains an element about knowledge bases (KB) or Files API').toBe(true);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe('zai.learn.extract', () => {
|
|
90
|
-
const client = getClient();
|
|
91
|
-
let tableName = 'ZaiTestExtractInternalTable';
|
|
92
|
-
let taskId = 'extract';
|
|
93
|
-
let zai = getZai();
|
|
94
|
-
beforeEach(async () => {
|
|
95
|
-
zai = getZai().with({
|
|
96
|
-
activeLearning: {
|
|
97
|
-
enable: true,
|
|
98
|
-
taskId,
|
|
99
|
-
tableName
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
afterEach(async () => {
|
|
104
|
-
try {
|
|
105
|
-
await client.deleteTableRows({ table: tableName, deleteAllRows: true });
|
|
106
|
-
}
|
|
107
|
-
catch (err) { }
|
|
108
|
-
});
|
|
109
|
-
afterAll(async () => {
|
|
110
|
-
try {
|
|
111
|
-
await client.deleteTable({ table: tableName });
|
|
112
|
-
}
|
|
113
|
-
catch (err) { }
|
|
114
|
-
});
|
|
115
|
-
it('learns a extraction format from examples', async () => {
|
|
116
|
-
const adapter = new TableAdapter({
|
|
117
|
-
client,
|
|
118
|
-
tableName
|
|
119
|
-
});
|
|
120
|
-
const value = await zai.learn(taskId).extract(`I really liked Casino Royale`, z.object({
|
|
121
|
-
name: z.string(),
|
|
122
|
-
movie: z.string()
|
|
123
|
-
}), { instructions: 'extract the name of the movie and name of the main character' });
|
|
124
|
-
check(value, 'extracted james bond and casino royale').toBe(true);
|
|
125
|
-
check(value, 'the values are NOT IN ALL CAPS').toBe(true);
|
|
126
|
-
let rows = await client.findTableRows({ table: tableName });
|
|
127
|
-
expect(rows.rows.length).toBe(1);
|
|
128
|
-
await adapter.saveExample({
|
|
129
|
-
key: 't1',
|
|
130
|
-
taskId: `zai/${taskId}`,
|
|
131
|
-
taskType: 'zai.extract',
|
|
132
|
-
instructions: 'extract name of movie and main character',
|
|
133
|
-
input: `I went to see the Titanic yesterday and I fell asleep`,
|
|
134
|
-
output: { name: 'JACK DAWSON', movie: 'TITANIC' },
|
|
135
|
-
metadata,
|
|
136
|
-
status: 'approved'
|
|
137
|
-
});
|
|
138
|
-
await adapter.saveExample({
|
|
139
|
-
key: 't2',
|
|
140
|
-
taskId: `zai/${taskId}`,
|
|
141
|
-
taskType: 'zai.extract',
|
|
142
|
-
instructions: 'extract name of movie and main character',
|
|
143
|
-
input: `Did you know that the gladiator movie has a lot of fighting scenes?`,
|
|
144
|
-
output: { name: 'MAXIMUS DECIMUS MERIDIUS', movie: 'GLADIATOR' },
|
|
145
|
-
metadata,
|
|
146
|
-
status: 'approved'
|
|
147
|
-
});
|
|
148
|
-
const second = await zai.learn(taskId).extract(`I really liked Casino Royale`, z.object({
|
|
149
|
-
name: z.string(),
|
|
150
|
-
movie: z.string()
|
|
151
|
-
}), { instructions: 'extract the name of the movie and name of the main character' });
|
|
152
|
-
expect(second).toMatchInlineSnapshot(`
|
|
153
|
-
{
|
|
154
|
-
"movie": "CASINO ROYALE",
|
|
155
|
-
"name": "JAMES BOND",
|
|
156
|
-
}
|
|
157
|
-
`);
|
|
158
|
-
rows = await client.findTableRows({ table: tableName });
|
|
159
|
-
expect(rows.rows.length).toBe(3);
|
|
160
|
-
expect(rows.rows[0].output.value).toMatchObject(second);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
//# sourceMappingURL=extract.test.js.map
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
|
|
2
|
-
import { getClient, getZai, metadata } from './__tests';
|
|
3
|
-
import { TableAdapter } from '../adapters/botpress-table';
|
|
4
|
-
describe('zai.filter', { timeout: 60_000 }, () => {
|
|
5
|
-
let zai = getZai();
|
|
6
|
-
beforeEach(async () => {
|
|
7
|
-
zai = getZai();
|
|
8
|
-
});
|
|
9
|
-
it('basic filter with small items', async () => {
|
|
10
|
-
const value = await zai.filter([
|
|
11
|
-
{ name: 'John', description: 'is a bad person' },
|
|
12
|
-
{ name: 'Alice', description: 'is a good person' },
|
|
13
|
-
{ name: 'Bob', description: 'is a good person' },
|
|
14
|
-
{ name: 'Eve', description: 'is a bad person' },
|
|
15
|
-
{ name: 'Alex', description: 'is a good person' },
|
|
16
|
-
{ name: 'Sara', description: 'donates to charity every month' },
|
|
17
|
-
{ name: 'Tom', description: 'commits crimes and is in jail' }
|
|
18
|
-
], 'generally good people');
|
|
19
|
-
const names = value.map((v) => v.name);
|
|
20
|
-
expect(names).toMatchInlineSnapshot(`
|
|
21
|
-
[
|
|
22
|
-
"Alice",
|
|
23
|
-
"Bob",
|
|
24
|
-
"Alex",
|
|
25
|
-
"Sara",
|
|
26
|
-
]
|
|
27
|
-
`);
|
|
28
|
-
});
|
|
29
|
-
it('filtering huge array chunks it up', async () => {
|
|
30
|
-
const callAction = vi.fn();
|
|
31
|
-
const client = { ...getClient(), callAction };
|
|
32
|
-
zai = getZai().with({
|
|
33
|
-
client
|
|
34
|
-
});
|
|
35
|
-
const hugeArray = Array.from({ length: 100 }, (_, i) => ({
|
|
36
|
-
name: `Person #${i}#`,
|
|
37
|
-
description: 'blah blah '.repeat(50_000)
|
|
38
|
-
}));
|
|
39
|
-
try {
|
|
40
|
-
await zai.filter(hugeArray, 'generally good people', { tokensPerItem: 100_000 });
|
|
41
|
-
}
|
|
42
|
-
catch (err) { }
|
|
43
|
-
expect(callAction.mock.calls.length).toBeGreaterThan(20);
|
|
44
|
-
expect(JSON.stringify(callAction.mock.calls.at(0))).toContain('Person #0#');
|
|
45
|
-
expect(JSON.stringify(callAction.mock.calls.at(0))).not.toContain('Person #99#');
|
|
46
|
-
expect(JSON.stringify(callAction.mock.calls.at(-1))).not.toContain('Person #0#');
|
|
47
|
-
expect(JSON.stringify(callAction.mock.calls.at(-1))).toContain('Person #99#');
|
|
48
|
-
callAction.mockReset();
|
|
49
|
-
try {
|
|
50
|
-
await zai.filter(hugeArray, 'generally good people', { tokensPerItem: 100 });
|
|
51
|
-
}
|
|
52
|
-
catch (err) { }
|
|
53
|
-
expect(callAction.mock.calls.length).toBe(2);
|
|
54
|
-
});
|
|
55
|
-
it('filter with examples', async () => {
|
|
56
|
-
const examples = [
|
|
57
|
-
{
|
|
58
|
-
input: 'Rasa (framework)',
|
|
59
|
-
filter: true,
|
|
60
|
-
reason: 'Rasa is a chatbot framework, so it competes with us (Botpress).'
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
input: 'Rasa (coffee company)',
|
|
64
|
-
filter: false,
|
|
65
|
-
reason: 'Rasa (coffee company) is not in the chatbot or AI agent industry, therefore it does not compete with us (Botpress).'
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
input: 'Dialogflow',
|
|
69
|
-
filter: true,
|
|
70
|
-
reason: 'Dialogflow is a chatbot development product, so it competes with us (Botpress).'
|
|
71
|
-
}
|
|
72
|
-
];
|
|
73
|
-
const value = await zai.filter([{ name: 'Moveworks' }, { name: 'Ada.cx' }, { name: 'Nike' }, { name: 'Voiceflow' }, { name: 'Adidas' }], 'competes with us', { examples });
|
|
74
|
-
const names = value.map((v) => v.name);
|
|
75
|
-
expect(names).toMatchInlineSnapshot(`
|
|
76
|
-
[
|
|
77
|
-
"Moveworks",
|
|
78
|
-
"Ada.cx",
|
|
79
|
-
"Voiceflow",
|
|
80
|
-
]
|
|
81
|
-
`);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
describe('zai.learn.filter', { timeout: 60_000 }, () => {
|
|
85
|
-
const client = getClient();
|
|
86
|
-
let tableName = 'ZaiTestFilterInternalTable';
|
|
87
|
-
let taskId = 'filter';
|
|
88
|
-
let zai = getZai();
|
|
89
|
-
beforeEach(async () => {
|
|
90
|
-
zai = getZai().with({
|
|
91
|
-
activeLearning: {
|
|
92
|
-
enable: true,
|
|
93
|
-
taskId,
|
|
94
|
-
tableName
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
afterEach(async () => {
|
|
99
|
-
try {
|
|
100
|
-
await client.deleteTableRows({ table: tableName, deleteAllRows: true });
|
|
101
|
-
}
|
|
102
|
-
catch (err) { }
|
|
103
|
-
});
|
|
104
|
-
afterAll(async () => {
|
|
105
|
-
try {
|
|
106
|
-
await client.deleteTable({ table: tableName });
|
|
107
|
-
}
|
|
108
|
-
catch (err) { }
|
|
109
|
-
});
|
|
110
|
-
it('learns a filtering rule from examples', async () => {
|
|
111
|
-
const adapter = new TableAdapter({
|
|
112
|
-
client,
|
|
113
|
-
tableName
|
|
114
|
-
});
|
|
115
|
-
await adapter.saveExample({
|
|
116
|
-
key: 't1',
|
|
117
|
-
taskId: `zai/${taskId}`,
|
|
118
|
-
taskType: 'zai.filter',
|
|
119
|
-
instructions: 'competes with us?',
|
|
120
|
-
input: ['Rasa (framework)', 'Rasa (coffee company)'],
|
|
121
|
-
output: ['Rasa (framework)'],
|
|
122
|
-
explanation: `Rasa is a chatbot framework, so it competes with us (Botpress). We should keep it. Rasa (coffee company) is not in the chatbot or AI agent industry, therefore it does not compete with us (Botpress). We should filter it out.`,
|
|
123
|
-
metadata,
|
|
124
|
-
status: 'approved'
|
|
125
|
-
});
|
|
126
|
-
await adapter.saveExample({
|
|
127
|
-
key: 't2',
|
|
128
|
-
taskId: `zai/${taskId}`,
|
|
129
|
-
taskType: 'zai.filter',
|
|
130
|
-
instructions: 'competes with us?',
|
|
131
|
-
input: ['Voiceflow', 'Dialogflow'],
|
|
132
|
-
output: ['Voiceflow', 'Dialogflow'],
|
|
133
|
-
explanation: `Voiceflow is a chatbot development product, so it competes with us (Botpress). We should keep it. Dialogflow is a chatbot development product, so it competes with us (Botpress). We should keep it.`,
|
|
134
|
-
metadata,
|
|
135
|
-
status: 'approved'
|
|
136
|
-
});
|
|
137
|
-
const second = await zai
|
|
138
|
-
.learn(taskId)
|
|
139
|
-
.filter(['Nike', 'Ada.cx', 'Adidas', 'Moveworks', 'Lululemon'], 'competes with us? (botpress)');
|
|
140
|
-
expect(second).toMatchInlineSnapshot(`
|
|
141
|
-
[
|
|
142
|
-
"Ada.cx",
|
|
143
|
-
"Moveworks",
|
|
144
|
-
]
|
|
145
|
-
`);
|
|
146
|
-
const rows = await client.findTableRows({ table: tableName });
|
|
147
|
-
expect(rows.rows.length).toBe(3);
|
|
148
|
-
expect(rows.rows.at(-1).output.value).toEqual(second);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
//# sourceMappingURL=filter.test.js.map
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
|
|
2
|
-
import { BotpressDocumentation, getClient, getZai, metadata } from './__tests';
|
|
3
|
-
import { TableAdapter } from '../adapters/botpress-table';
|
|
4
|
-
import { check } from '@botpress/vai';
|
|
5
|
-
describe('zai.label', { timeout: 60_000 }, () => {
|
|
6
|
-
const zai = getZai();
|
|
7
|
-
it('simple labels on small text', async () => {
|
|
8
|
-
const labels = await zai.label({
|
|
9
|
-
name: 'John',
|
|
10
|
-
story: ['John donated to charity last month.', 'John is loved by his community.'],
|
|
11
|
-
criminal_record: 'John has no criminal record.'
|
|
12
|
-
}, {
|
|
13
|
-
is_human: 'is the person a human?',
|
|
14
|
-
good_person: 'is the person a good person?',
|
|
15
|
-
bad_person: 'is the person a bad person?',
|
|
16
|
-
is_criminal: 'is the person a criminal?'
|
|
17
|
-
});
|
|
18
|
-
expect(labels).toMatchInlineSnapshot(`
|
|
19
|
-
{
|
|
20
|
-
"bad_person": false,
|
|
21
|
-
"good_person": true,
|
|
22
|
-
"is_criminal": false,
|
|
23
|
-
"is_human": true,
|
|
24
|
-
}
|
|
25
|
-
`);
|
|
26
|
-
});
|
|
27
|
-
it('simple labels with example', async () => {
|
|
28
|
-
const labels = {
|
|
29
|
-
is_human: 'is the person a human?',
|
|
30
|
-
good_person: 'is the person a good person?',
|
|
31
|
-
bad_person: 'is the person a bad person?',
|
|
32
|
-
canadian: 'is the person canadian?',
|
|
33
|
-
is_french: 'is the person french?'
|
|
34
|
-
};
|
|
35
|
-
const initial = await zai.label(`Sylvain Perron has no criminal record.`, labels);
|
|
36
|
-
expect(initial.canadian).toBe(false);
|
|
37
|
-
expect(initial.is_french).toBe(false);
|
|
38
|
-
expect(initial.bad_person).toBe(false);
|
|
39
|
-
expect(initial.is_human).toBe(true);
|
|
40
|
-
const second = await zai.label(`Sylvain Perron has no criminal record.`, labels, {
|
|
41
|
-
examples: [
|
|
42
|
-
{
|
|
43
|
-
input: 'Sylvain Pellerin has no criminal record.',
|
|
44
|
-
labels: {
|
|
45
|
-
is_french: {
|
|
46
|
-
label: 'ABSOLUTELY_YES',
|
|
47
|
-
explanation: 'Important: Sylvain Pellerin is a common French name.'
|
|
48
|
-
},
|
|
49
|
-
canadian: {
|
|
50
|
-
label: 'ABSOLUTELY_YES',
|
|
51
|
-
explanation: 'Important: We assume all person named Sylvain are Canadian (business rule).'
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
input: 'Sylvain Bouchard is a criminal.',
|
|
57
|
-
labels: {
|
|
58
|
-
bad_person: {
|
|
59
|
-
label: 'PROBABLY_YES',
|
|
60
|
-
explanation: 'Important: Sylvain Bouchard is a criminal, so probably a bad person.'
|
|
61
|
-
},
|
|
62
|
-
is_french: {
|
|
63
|
-
label: 'ABSOLUTELY_YES',
|
|
64
|
-
explanation: 'Important: Sylvain is a common French name.'
|
|
65
|
-
},
|
|
66
|
-
canadian: {
|
|
67
|
-
label: 'ABSOLUTELY_YES',
|
|
68
|
-
explanation: 'Important: We assume all person named Sylvain are Canadian (business rule).'
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
});
|
|
74
|
-
expect(second.canadian).toBe(true);
|
|
75
|
-
expect(second.is_french).toBe(true);
|
|
76
|
-
expect(second.is_human).toBe(true);
|
|
77
|
-
expect(second.bad_person).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
it('label a huge text', async () => {
|
|
80
|
-
const labels = await zai.label(BotpressDocumentation, {
|
|
81
|
-
is_about_animals: 'is the text about animals?',
|
|
82
|
-
contains_lua_code: 'does the text contain Lua code?',
|
|
83
|
-
contains_python_code: 'does the text contain Python code?',
|
|
84
|
-
contains_js_code: 'does the text contain JavaScript code?',
|
|
85
|
-
is_botpress: 'is the text about Botpress?',
|
|
86
|
-
is_rasa: 'is the text about Rasa?',
|
|
87
|
-
has_flows: 'does the text mention flows?',
|
|
88
|
-
has_api: 'does the text mention the Botpress API?',
|
|
89
|
-
has_enterprise: 'does the text mention Botpress Enterprise?',
|
|
90
|
-
has_workspaces: 'does the text mention workspaces?',
|
|
91
|
-
has_webchat: 'does the text mention the Webchat?',
|
|
92
|
-
has_hitl: 'does the text mention HITL (human in the loop)?'
|
|
93
|
-
});
|
|
94
|
-
expect(labels).toMatchInlineSnapshot(`
|
|
95
|
-
{
|
|
96
|
-
"contains_js_code": true,
|
|
97
|
-
"contains_lua_code": false,
|
|
98
|
-
"contains_python_code": false,
|
|
99
|
-
"has_api": true,
|
|
100
|
-
"has_enterprise": true,
|
|
101
|
-
"has_flows": true,
|
|
102
|
-
"has_hitl": true,
|
|
103
|
-
"has_webchat": true,
|
|
104
|
-
"has_workspaces": true,
|
|
105
|
-
"is_about_animals": false,
|
|
106
|
-
"is_botpress": true,
|
|
107
|
-
"is_rasa": false,
|
|
108
|
-
}
|
|
109
|
-
`);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
describe('zai.learn.label', { timeout: 60_000 }, () => {
|
|
113
|
-
const client = getClient();
|
|
114
|
-
let tableName = 'ZaiTestLabelInternalTable';
|
|
115
|
-
let taskId = 'label';
|
|
116
|
-
let zai = getZai();
|
|
117
|
-
beforeEach(async () => {
|
|
118
|
-
zai = getZai().with({
|
|
119
|
-
activeLearning: {
|
|
120
|
-
enable: true,
|
|
121
|
-
taskId,
|
|
122
|
-
tableName
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
afterEach(async () => {
|
|
127
|
-
try {
|
|
128
|
-
await client.deleteTableRows({ table: tableName, deleteAllRows: true });
|
|
129
|
-
}
|
|
130
|
-
catch (err) { }
|
|
131
|
-
});
|
|
132
|
-
afterAll(async () => {
|
|
133
|
-
try {
|
|
134
|
-
await client.deleteTable({ table: tableName });
|
|
135
|
-
}
|
|
136
|
-
catch (err) { }
|
|
137
|
-
});
|
|
138
|
-
it('learns a labelling rule from examples', async () => {
|
|
139
|
-
const adapter = new TableAdapter({
|
|
140
|
-
client,
|
|
141
|
-
tableName
|
|
142
|
-
});
|
|
143
|
-
const value = await zai.learn(taskId).label(`Sylvain Perron has no criminal record.`, {
|
|
144
|
-
is_human: 'is the person a human?',
|
|
145
|
-
good_person: 'is the person a good person?',
|
|
146
|
-
bad_person: 'is the person a bad person?',
|
|
147
|
-
canadian: 'is the person canadian?',
|
|
148
|
-
is_french: 'is the person french?'
|
|
149
|
-
});
|
|
150
|
-
expect(value.is_human).toBe(true);
|
|
151
|
-
expect(value.is_french).toBe(false);
|
|
152
|
-
expect(value.canadian).toBe(false);
|
|
153
|
-
let rows = await client.findTableRows({ table: tableName });
|
|
154
|
-
expect(rows.rows.length).toBe(1);
|
|
155
|
-
await adapter.saveExample({
|
|
156
|
-
key: 't1',
|
|
157
|
-
taskId: `zai/${taskId}`,
|
|
158
|
-
taskType: 'zai.label',
|
|
159
|
-
instructions: 'label the sentence',
|
|
160
|
-
input: 'Sylvain Pellerin has no criminal record.',
|
|
161
|
-
output: {
|
|
162
|
-
is_french: {
|
|
163
|
-
label: 'ABSOLUTELY_YES',
|
|
164
|
-
explanation: 'Important: Sylvain is a common French name.'
|
|
165
|
-
},
|
|
166
|
-
canadian: {
|
|
167
|
-
label: 'ABSOLUTELY_YES',
|
|
168
|
-
explanation: 'Since we are doing business only in Canada, we assume all users are Canadians.'
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
// The below doesn't make sense on purpose, it's just to test the influence of the explanation on the next prediction
|
|
172
|
-
explanation: `IMPORTANT: Sylvain is a common French name and since we're doing business only in Canada, we assume ALL users are Canadians as soon as they mention a French name.`,
|
|
173
|
-
metadata,
|
|
174
|
-
status: 'approved'
|
|
175
|
-
});
|
|
176
|
-
await adapter.saveExample({
|
|
177
|
-
key: 't2',
|
|
178
|
-
taskId: `zai/${taskId}`,
|
|
179
|
-
taskType: 'zai.label',
|
|
180
|
-
instructions: 'label the sentence',
|
|
181
|
-
input: 'Joannie Côté has a dog.',
|
|
182
|
-
output: {
|
|
183
|
-
is_french: {
|
|
184
|
-
label: 'ABSOLUTELY_YES',
|
|
185
|
-
explanation: 'Important: Joannie is a common French name and Côté is a common French last name.'
|
|
186
|
-
},
|
|
187
|
-
canadian: {
|
|
188
|
-
label: 'ABSOLUTELY_YES',
|
|
189
|
-
explanation: 'Since we are doing business only in Canada, we assume all users are Canadians.'
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
metadata,
|
|
193
|
-
status: 'approved'
|
|
194
|
-
});
|
|
195
|
-
rows = await client.findTableRows({ table: tableName });
|
|
196
|
-
expect(rows.rows.length).toBe(3);
|
|
197
|
-
const second = await zai.learn(taskId).label(`Sylvain Perron has no criminal record.`, {
|
|
198
|
-
is_human: 'is the person a human?',
|
|
199
|
-
good_person: 'is the person a good person?',
|
|
200
|
-
bad_person: 'is the person a bad person?',
|
|
201
|
-
canadian: 'is the person canadian?',
|
|
202
|
-
is_french: 'is the person french?'
|
|
203
|
-
});
|
|
204
|
-
expect(second.is_human).toBe(true);
|
|
205
|
-
expect(second.is_french).toBe(true);
|
|
206
|
-
expect(second.canadian).toBe(true);
|
|
207
|
-
rows = await client.findTableRows({ table: tableName });
|
|
208
|
-
expect(rows.rows.length).toBe(3);
|
|
209
|
-
check(rows.rows[0].output.value.canadian, 'label is positive (yes) and there is an explanation of why').toBe(true);
|
|
210
|
-
check(rows.rows[0].output.value.is_french, 'label is positive (yes) and there is an explanation of why').toBe(true);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
//# sourceMappingURL=label.test.js.map
|