@botpress/zai 1.0.0-beta.1 → 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.
Files changed (47) hide show
  1. package/dist/src/env.d.ts +0 -36
  2. package/dist/src/zai.d.ts +6 -12
  3. package/dist/src/zai.js +2 -0
  4. package/dist/src/zai.js.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/dist/typings.d.ts +0 -6
  7. package/dist/typings.tsbuildinfo +1 -1
  8. package/package.json +18 -9
  9. package/tsconfig.json +1 -0
  10. package/dist/src/operations/check.test.js +0 -129
  11. package/dist/src/operations/extract.test.js +0 -163
  12. package/dist/src/operations/filter.test.js +0 -151
  13. package/dist/src/operations/label.test.js +0 -213
  14. package/dist/src/operations/rewrite.test.js +0 -96
  15. package/dist/src/operations/summarize.test.js +0 -22
  16. package/dist/src/operations/text.test.js +0 -51
  17. package/dist/src/operations/zai-learn.test.js +0 -71
  18. package/dist/src/operations/zai-retry.test.js +0 -50
  19. package/src/adapters/adapter.ts +0 -35
  20. package/src/adapters/botpress-table.ts +0 -213
  21. package/src/adapters/memory.ts +0 -13
  22. package/src/env.ts +0 -54
  23. package/src/index.ts +0 -11
  24. package/src/models.ts +0 -347
  25. package/src/operations/__tests/botpress_docs.txt +0 -26040
  26. package/src/operations/__tests/index.ts +0 -30
  27. package/src/operations/check.test.ts +0 -155
  28. package/src/operations/check.ts +0 -187
  29. package/src/operations/constants.ts +0 -2
  30. package/src/operations/errors.ts +0 -9
  31. package/src/operations/extract.test.ts +0 -209
  32. package/src/operations/extract.ts +0 -291
  33. package/src/operations/filter.test.ts +0 -182
  34. package/src/operations/filter.ts +0 -231
  35. package/src/operations/label.test.ts +0 -239
  36. package/src/operations/label.ts +0 -332
  37. package/src/operations/rewrite.test.ts +0 -114
  38. package/src/operations/rewrite.ts +0 -148
  39. package/src/operations/summarize.test.ts +0 -25
  40. package/src/operations/summarize.ts +0 -193
  41. package/src/operations/text.test.ts +0 -60
  42. package/src/operations/text.ts +0 -63
  43. package/src/operations/zai-learn.test.ts +0 -85
  44. package/src/operations/zai-retry.test.ts +0 -64
  45. package/src/scripts/update-models.ts +0 -74
  46. package/src/utils.ts +0 -61
  47. package/src/zai.ts +0 -179
@@ -1,239 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest'
2
-
3
- import { BotpressDocumentation, getClient, getZai, metadata } from './__tests'
4
- import { TableAdapter } from '../adapters/botpress-table'
5
- import { check } from '@botpress/vai'
6
-
7
- describe('zai.label', { timeout: 60_000 }, () => {
8
- const zai = getZai()
9
-
10
- it('simple labels on small text', async () => {
11
- const labels = await zai.label(
12
- {
13
- name: 'John',
14
- story: ['John donated to charity last month.', 'John is loved by his community.'],
15
- criminal_record: 'John has no criminal record.'
16
- },
17
- {
18
- is_human: 'is the person a human?',
19
- good_person: 'is the person a good person?',
20
- bad_person: 'is the person a bad person?',
21
- is_criminal: 'is the person a criminal?'
22
- }
23
- )
24
-
25
- expect(labels).toMatchInlineSnapshot(`
26
- {
27
- "bad_person": false,
28
- "good_person": true,
29
- "is_criminal": false,
30
- "is_human": true,
31
- }
32
- `)
33
- })
34
-
35
- it('simple labels with example', async () => {
36
- const labels = {
37
- is_human: 'is the person a human?',
38
- good_person: 'is the person a good person?',
39
- bad_person: 'is the person a bad person?',
40
- canadian: 'is the person canadian?',
41
- is_french: 'is the person french?'
42
- }
43
-
44
- const initial = await zai.label(`Sylvain Perron has no criminal record.`, labels)
45
-
46
- expect(initial.canadian).toBe(false)
47
- expect(initial.is_french).toBe(false)
48
- expect(initial.bad_person).toBe(false)
49
- expect(initial.is_human).toBe(true)
50
-
51
- const second = await zai.label(`Sylvain Perron has no criminal record.`, labels, {
52
- examples: [
53
- {
54
- input: 'Sylvain Pellerin has no criminal record.',
55
- labels: {
56
- is_french: {
57
- label: 'ABSOLUTELY_YES',
58
- explanation: 'Important: Sylvain Pellerin is a common French name.'
59
- },
60
- canadian: {
61
- label: 'ABSOLUTELY_YES',
62
- explanation: 'Important: We assume all person named Sylvain are Canadian (business rule).'
63
- }
64
- }
65
- },
66
- {
67
- input: 'Sylvain Bouchard is a criminal.',
68
- labels: {
69
- bad_person: {
70
- label: 'PROBABLY_YES',
71
- explanation: 'Important: Sylvain Bouchard is a criminal, so probably a bad person.'
72
- },
73
- is_french: {
74
- label: 'ABSOLUTELY_YES',
75
- explanation: 'Important: Sylvain is a common French name.'
76
- },
77
- canadian: {
78
- label: 'ABSOLUTELY_YES',
79
- explanation: 'Important: We assume all person named Sylvain are Canadian (business rule).'
80
- }
81
- }
82
- }
83
- ]
84
- })
85
-
86
- expect(second.canadian).toBe(true)
87
- expect(second.is_french).toBe(true)
88
- expect(second.is_human).toBe(true)
89
- expect(second.bad_person).toBe(false)
90
- })
91
-
92
- it('label a huge text', async () => {
93
- const labels = await zai.label(BotpressDocumentation, {
94
- is_about_animals: 'is the text about animals?',
95
- contains_lua_code: 'does the text contain Lua code?',
96
- contains_python_code: 'does the text contain Python code?',
97
- contains_js_code: 'does the text contain JavaScript code?',
98
- is_botpress: 'is the text about Botpress?',
99
- is_rasa: 'is the text about Rasa?',
100
- has_flows: 'does the text mention flows?',
101
- has_api: 'does the text mention the Botpress API?',
102
- has_enterprise: 'does the text mention Botpress Enterprise?',
103
- has_workspaces: 'does the text mention workspaces?',
104
- has_webchat: 'does the text mention the Webchat?',
105
- has_hitl: 'does the text mention HITL (human in the loop)?'
106
- })
107
-
108
- expect(labels).toMatchInlineSnapshot(`
109
- {
110
- "contains_js_code": true,
111
- "contains_lua_code": false,
112
- "contains_python_code": false,
113
- "has_api": true,
114
- "has_enterprise": true,
115
- "has_flows": true,
116
- "has_hitl": true,
117
- "has_webchat": true,
118
- "has_workspaces": true,
119
- "is_about_animals": false,
120
- "is_botpress": true,
121
- "is_rasa": false,
122
- }
123
- `)
124
- })
125
- })
126
-
127
- describe('zai.learn.label', { timeout: 60_000 }, () => {
128
- const client = getClient()
129
- let tableName = 'ZaiTestLabelInternalTable'
130
- let taskId = 'label'
131
- let zai = getZai()
132
-
133
- beforeEach(async () => {
134
- zai = getZai().with({
135
- activeLearning: {
136
- enable: true,
137
- taskId,
138
- tableName
139
- }
140
- })
141
- })
142
-
143
- afterEach(async () => {
144
- try {
145
- await client.deleteTableRows({ table: tableName, deleteAllRows: true })
146
- } catch (err) {}
147
- })
148
-
149
- afterAll(async () => {
150
- try {
151
- await client.deleteTable({ table: tableName })
152
- } catch (err) {}
153
- })
154
-
155
- it('learns a labelling rule from examples', async () => {
156
- const adapter = new TableAdapter({
157
- client,
158
- tableName
159
- })
160
-
161
- const value = await zai.learn(taskId).label(`Sylvain Perron has no criminal record.`, {
162
- is_human: 'is the person a human?',
163
- good_person: 'is the person a good person?',
164
- bad_person: 'is the person a bad person?',
165
- canadian: 'is the person canadian?',
166
- is_french: 'is the person french?'
167
- })
168
-
169
- expect(value.is_human).toBe(true)
170
- expect(value.is_french).toBe(false)
171
- expect(value.canadian).toBe(false)
172
-
173
- let rows = await client.findTableRows({ table: tableName })
174
- expect(rows.rows.length).toBe(1)
175
-
176
- await adapter.saveExample({
177
- key: 't1',
178
- taskId: `zai/${taskId}`,
179
- taskType: 'zai.label',
180
- instructions: 'label the sentence',
181
- input: 'Sylvain Pellerin has no criminal record.',
182
- output: {
183
- is_french: {
184
- label: 'ABSOLUTELY_YES',
185
- explanation: 'Important: Sylvain is a common French 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
- // The below doesn't make sense on purpose, it's just to test the influence of the explanation on the next prediction
193
- 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.`,
194
- metadata,
195
- status: 'approved'
196
- })
197
-
198
- await adapter.saveExample({
199
- key: 't2',
200
- taskId: `zai/${taskId}`,
201
- taskType: 'zai.label',
202
- instructions: 'label the sentence',
203
- input: 'Joannie Côté has a dog.',
204
- output: {
205
- is_french: {
206
- label: 'ABSOLUTELY_YES',
207
- explanation: 'Important: Joannie is a common French name and Côté is a common French last name.'
208
- },
209
- canadian: {
210
- label: 'ABSOLUTELY_YES',
211
- explanation: 'Since we are doing business only in Canada, we assume all users are Canadians.'
212
- }
213
- },
214
- metadata,
215
- status: 'approved'
216
- })
217
-
218
- rows = await client.findTableRows({ table: tableName })
219
- expect(rows.rows.length).toBe(3)
220
-
221
- const second = await zai.learn(taskId).label(`Sylvain Perron has no criminal record.`, {
222
- is_human: 'is the person a human?',
223
- good_person: 'is the person a good person?',
224
- bad_person: 'is the person a bad person?',
225
- canadian: 'is the person canadian?',
226
- is_french: 'is the person french?'
227
- })
228
-
229
- expect(second.is_human).toBe(true)
230
- expect(second.is_french).toBe(true)
231
- expect(second.canadian).toBe(true)
232
-
233
- rows = await client.findTableRows({ table: tableName })
234
- expect(rows.rows.length).toBe(3)
235
-
236
- check(rows.rows[0].output.value.canadian, 'label is positive (yes) and there is an explanation of why').toBe(true)
237
- check(rows.rows[0].output.value.is_french, 'label is positive (yes) and there is an explanation of why').toBe(true)
238
- })
239
- })
@@ -1,332 +0,0 @@
1
- import { z } from '@bpinternal/zui'
2
-
3
- import _ from 'lodash'
4
- import { fastHash, stringify, takeUntilTokens } from '../utils'
5
- import { Zai } from '../zai'
6
- import { PROMPT_INPUT_BUFFER } from './constants'
7
-
8
- type Label = keyof typeof LABELS
9
- const LABELS = {
10
- ABSOLUTELY_NOT: 'ABSOLUTELY_NOT',
11
- PROBABLY_NOT: 'PROBABLY_NOT',
12
- AMBIGUOUS: 'AMBIGUOUS',
13
- PROBABLY_YES: 'PROBABLY_YES',
14
- ABSOLUTELY_YES: 'ABSOLUTELY_YES'
15
- } as const
16
- const ALL_LABELS = Object.values(LABELS).join(' | ')
17
-
18
- type Example<T extends string> = {
19
- input: unknown
20
- labels: Partial<Record<T, { label: Label; explanation?: string }>>
21
- }
22
-
23
- export type Options<T extends string> = Omit<z.input<typeof Options>, 'examples'> & {
24
- examples?: Array<Partial<Example<T>>>
25
- }
26
-
27
- const Options = z.object({
28
- examples: z
29
- .array(
30
- z.object({
31
- input: z.any(),
32
- labels: z.record(z.object({ label: z.enum(ALL_LABELS as never), explanation: z.string().optional() }))
33
- })
34
- )
35
- .default([])
36
- .describe('Examples to help the user make a decision'),
37
- instructions: z.string().optional().describe('Instructions to guide the user on how to extract the data'),
38
- chunkLength: z
39
- .number()
40
- .min(100)
41
- .max(100_000)
42
- .optional()
43
- .describe('The maximum number of tokens per chunk')
44
- .default(16_000)
45
- })
46
-
47
- type Labels<T extends string> = Record<T, string>
48
-
49
- const Labels = z.record(z.string().min(1).max(250), z.string()).superRefine((labels, ctx) => {
50
- const keys = Object.keys(labels)
51
-
52
- for (const key of keys) {
53
- if (key.length < 1 || key.length > 250) {
54
- ctx.addIssue({ message: `The label key "${key}" must be between 1 and 250 characters long`, code: 'custom' })
55
- }
56
-
57
- if (keys.lastIndexOf(key) !== keys.indexOf(key)) {
58
- ctx.addIssue({ message: `Duplicate label: ${labels[key]}`, code: 'custom' })
59
- }
60
-
61
- if (/[^a-zA-Z0-9_]/.test(key)) {
62
- ctx.addIssue({
63
- message: `The label key "${key}" must only contain alphanumeric characters and underscores`,
64
- code: 'custom'
65
- })
66
- }
67
- }
68
-
69
- return true
70
- })
71
-
72
- declare module '../zai' {
73
- interface Zai {
74
- /** Tags the provided input with a list of predefined labels */
75
- label<T extends string>(
76
- input: unknown,
77
- labels: Labels<T>,
78
- options?: Options<T>
79
- ): Promise<{
80
- [K in T]: boolean
81
- }>
82
- }
83
- }
84
-
85
- const parseLabel = (label: string): Label => {
86
- label = label.toUpperCase().replace(/\s+/g, '_').replace(/_{2,}/g, '_').trim()
87
- if (label.includes('ABSOLUTELY') && label.includes('NOT')) {
88
- return LABELS.ABSOLUTELY_NOT
89
- } else if (label.includes('NOT')) {
90
- return LABELS.PROBABLY_NOT
91
- } else if (label.includes('AMBIGUOUS')) {
92
- return LABELS.AMBIGUOUS
93
- }
94
- if (label.includes('YES')) {
95
- return LABELS.PROBABLY_YES
96
- } else if (label.includes('ABSOLUTELY') && label.includes('YES')) {
97
- return LABELS.ABSOLUTELY_YES
98
- }
99
- return LABELS.AMBIGUOUS
100
- }
101
-
102
- Zai.prototype.label = async function <T extends string>(this: Zai, input, _labels, _options) {
103
- const options = Options.parse(_options ?? {})
104
- const labels = Labels.parse(_labels)
105
- const tokenizer = await this.getTokenizer()
106
-
107
- const taskId = this.taskId
108
- const taskType = 'zai.label'
109
-
110
- const TOTAL_MAX_TOKENS = _.clamp(options.chunkLength, 1000, this.Model.input.maxTokens - PROMPT_INPUT_BUFFER)
111
- const CHUNK_EXAMPLES_MAX_TOKENS = _.clamp(Math.floor(TOTAL_MAX_TOKENS * 0.5), 250, 10_000)
112
- const CHUNK_INPUT_MAX_TOKENS = _.clamp(
113
- TOTAL_MAX_TOKENS - CHUNK_EXAMPLES_MAX_TOKENS,
114
- TOTAL_MAX_TOKENS * 0.5,
115
- TOTAL_MAX_TOKENS
116
- )
117
-
118
- const inputAsString = stringify(input)
119
-
120
- if (tokenizer.count(inputAsString) > CHUNK_INPUT_MAX_TOKENS) {
121
- const tokens = tokenizer.split(inputAsString)
122
- const chunks = _.chunk(tokens, CHUNK_INPUT_MAX_TOKENS).map((x) => x.join(''))
123
- const allLabels = await Promise.all(chunks.map((chunk) => this.label(chunk, _labels)))
124
-
125
- // Merge all the labels together (those who are true will remain true)
126
- return allLabels.reduce((acc, x) => {
127
- Object.keys(x).forEach((key) => {
128
- if (acc[key] === true) {
129
- acc[key] = true
130
- } else {
131
- acc[key] = acc[key] || x[key]
132
- }
133
- })
134
- return acc
135
- }, {}) as {
136
- [K in T]: boolean
137
- }
138
- }
139
-
140
- const END = '■END■'
141
-
142
- const Key = fastHash(
143
- JSON.stringify({
144
- taskType,
145
- taskId,
146
- input: inputAsString,
147
- instructions: options.instructions ?? ''
148
- })
149
- )
150
-
151
- const convertToAnswer = (mapping: { [K in T]: { explanation: string; label: Label } }) => {
152
- return Object.keys(labels).reduce((acc, key) => {
153
- acc[key] = mapping[key]?.label === 'ABSOLUTELY_YES' || mapping[key]?.label === 'PROBABLY_YES'
154
- return acc
155
- }, {}) as { [K in T]: boolean }
156
- }
157
-
158
- const examples = taskId
159
- ? await this.adapter.getExamples<
160
- string,
161
- {
162
- [K in T]: {
163
- explanation: string
164
- label: Label
165
- }
166
- }
167
- >({
168
- input: inputAsString,
169
- taskType,
170
- taskId
171
- })
172
- : []
173
-
174
- options.examples.forEach((example) => {
175
- examples.push({
176
- key: fastHash(JSON.stringify(example)),
177
- input: example.input,
178
- similarity: 1,
179
- explanation: '',
180
- output: example.labels as unknown as {
181
- [K in T]: {
182
- explanation: string
183
- label: Label
184
- }
185
- }
186
- })
187
- })
188
-
189
- const exactMatch = examples.find((x) => x.key === Key)
190
- if (exactMatch) {
191
- return convertToAnswer(exactMatch.output)
192
- }
193
-
194
- const allExamples = takeUntilTokens(
195
- examples,
196
- CHUNK_EXAMPLES_MAX_TOKENS,
197
- (el) =>
198
- tokenizer.count(stringify(el.input)) +
199
- tokenizer.count(stringify(el.output)) +
200
- tokenizer.count(el.explanation ?? '') +
201
- 100
202
- )
203
- .map((example, idx) => [
204
- {
205
- type: 'text' as const,
206
- role: 'user' as const,
207
- content: `
208
- Expert Example #${idx + 1}
209
-
210
- <|start_input|>
211
- ${stringify(example.input)}
212
- <|end_input|>`.trim()
213
- },
214
- {
215
- type: 'text' as const,
216
- role: 'assistant' as const,
217
- content: `
218
- Expert Example #${idx + 1}
219
- ============
220
- ${Object.keys(example.output)
221
- .map((key) =>
222
- `
223
- ■${key}:【${example.output[key]?.explanation}】:${example.output[key]?.label}■
224
- `.trim()
225
- )
226
- .join('\n')}
227
- ${END}
228
- `.trim()
229
- }
230
- ])
231
- .flat()
232
-
233
- const format = Object.keys(labels)
234
- .map((key) => {
235
- return `
236
- ■${key}:【explanation (where "explanation" is answering the question "${labels[key]}")】:x■ (where x is ${ALL_LABELS})
237
- `.trim()
238
- })
239
- .join('\n\n')
240
-
241
- const output = await this.callModel({
242
- stopSequences: [END],
243
- systemPrompt: `
244
- You need to tag the input with the following labels based on the question asked:
245
- ${LABELS.ABSOLUTELY_NOT}: You are absolutely sure that the answer is "NO" to the question.
246
- ${LABELS.PROBABLY_NOT}: You are leaning towards "NO" to the question.
247
- ${LABELS.AMBIGUOUS}: You are unsure about the answer to the question.
248
- ${LABELS.PROBABLY_YES}: You are leaning towards "YES" to the question.
249
- ${LABELS.ABSOLUTELY_YES}: You are absolutely sure that the answer is "YES" to the question.
250
-
251
- You need to return a mapping of the labels, an explanation and the answer for each label following the format below:
252
- \`\`\`
253
- ${format}
254
- ${END}
255
- \`\`\`
256
-
257
- ${options.instructions}
258
-
259
- ===
260
- You should consider the Expert Examples below to help you make your decision.
261
- In your "Analysis", please refer to the Expert Examples # to justify your decision.
262
- `.trim(),
263
- messages: [
264
- ...allExamples,
265
- {
266
- type: 'text',
267
- role: 'user',
268
- content: `
269
- Input to tag:
270
- <|start_input|>
271
- ${inputAsString}
272
- <|end_input|>
273
-
274
- Answer with this following format:
275
- \`\`\`
276
- ${format}
277
- ${END}
278
- \`\`\`
279
-
280
- Format cheatsheet:
281
- \`\`\`
282
- ■label:【explanation】:x■
283
- \`\`\`
284
-
285
- Where \`x\` is one of the following: ${ALL_LABELS}
286
-
287
- Remember: In your \`explanation\`, please refer to the Expert Examples # (and quote them) that are relevant to ground your decision-making process.
288
- The Expert Examples are there to help you make your decision. They have been provided by experts in the field and their answers (and reasoning) are considered the ground truth and should be used as a reference to make your decision when applicable.
289
- For example, you can say: "According to Expert Example #1, ..."`.trim()
290
- }
291
- ]
292
- })
293
-
294
- const answer = output.choices[0].content as string
295
-
296
- const final = Object.keys(labels).reduce((acc, key) => {
297
- const match = answer.match(new RegExp(`■${key}:【(.+)】:(\\w{2,})■`, 'i'))
298
- if (match) {
299
- const explanation = match[1].trim()
300
- const label = parseLabel(match[2])
301
- acc[key] = {
302
- explanation,
303
- label
304
- }
305
- } else {
306
- acc[key] = {
307
- explanation: '',
308
- label: LABELS.AMBIGUOUS
309
- }
310
- }
311
- return acc
312
- }, {}) as {
313
- [K in T]: {
314
- explanation: string
315
- label: Label
316
- }
317
- }
318
-
319
- if (taskId) {
320
- await this.adapter.saveExample({
321
- key: Key,
322
- taskType,
323
- taskId,
324
- instructions: options.instructions ?? '',
325
- metadata: output.metadata,
326
- input: inputAsString,
327
- output: final
328
- })
329
- }
330
-
331
- return convertToAnswer(final)
332
- }
@@ -1,114 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest'
2
- import { check } from '@botpress/vai'
3
-
4
- import { getClient, getZai, metadata, tokenizer } from './__tests'
5
- import { TableAdapter } from '../adapters/botpress-table'
6
-
7
- const Zoe = `
8
- Part 1. Zoe walks to the park.
9
- Part 2. She meets her friend.
10
- Part 3. They play together.
11
- Part 4. They have a picnic.
12
- Part 5. They go home.
13
- `.trim()
14
-
15
- describe('zai.rewrite', { timeout: 60_000 }, () => {
16
- const zai = getZai()
17
-
18
- it('transforms text to all caps', async () => {
19
- const result = await zai.rewrite(`Hello, what is the time today?`, 'write in all caps')
20
- expect(result).toBe(`HELLO, WHAT IS THE TIME TODAY?`)
21
- })
22
-
23
- it('transforms text to all caps and respects tokens restrictions', async () => {
24
- const result = await zai.rewrite(Zoe, 'write in all caps', { length: 15 })
25
- expect(tokenizer.count(result)).toBeLessThanOrEqual(20)
26
- expect(result).toContain(`PART 1. ZOE WALKS TO THE PARK`)
27
- expect(result).not.toContain(`PART 3`)
28
- })
29
-
30
- it('french translation of the story', async () => {
31
- const result = await zai.rewrite(Zoe, 'translate to french')
32
- check(result, 'is a french story about Zeo and with 5 parts').toBe(true)
33
- })
34
-
35
- it('Throws if input is bigger than the model max tokens', async () => {
36
- await expect(zai.rewrite(Zoe.repeat(100_000), 'translate to french')).rejects.toThrow(/tokens/i)
37
- })
38
- })
39
-
40
- describe('zai.learn.rewrite', { timeout: 60_000 }, () => {
41
- const client = getClient()
42
- let tableName = 'ZaiTestRewriteInternalTable'
43
- let taskId = 'rewrite'
44
- let zai = getZai()
45
-
46
- beforeEach(async () => {
47
- zai = getZai().with({
48
- activeLearning: {
49
- enable: true,
50
- taskId,
51
- tableName
52
- }
53
- })
54
- })
55
-
56
- afterEach(async () => {
57
- try {
58
- await client.deleteTableRows({ table: tableName, deleteAllRows: true })
59
- } catch (err) {}
60
- })
61
-
62
- afterAll(async () => {
63
- try {
64
- await client.deleteTable({ table: tableName })
65
- } catch (err) {}
66
- })
67
-
68
- it('learns rewrite rules from examples', async () => {
69
- const adapter = new TableAdapter({
70
- client,
71
- tableName
72
- })
73
-
74
- const value = await zai.learn(taskId).rewrite(`Botpress is awesome`, 'write it like we want it')
75
-
76
- check(value, `The text means more or less the same as "Botpress is awesome" but slightly different`).toBe(true)
77
-
78
- let rows = await client.findTableRows({ table: tableName })
79
- expect(rows.rows.length).toBe(1)
80
- expect(rows.rows[0].output.value).toBe(value)
81
-
82
- await adapter.saveExample({
83
- key: 't1',
84
- taskId: `zai/${taskId}`,
85
- taskType: 'zai.rewrite',
86
- instructions: 'write it like we want it',
87
- input: 'Microsoft is a big company',
88
- output: `# MICROSOFT IS A BIG COMPANY`,
89
- metadata,
90
- status: 'approved'
91
- })
92
-
93
- await adapter.saveExample({
94
- key: 't2',
95
- taskId: `zai/${taskId}`,
96
- taskType: 'zai.rewrite',
97
- instructions: 'write it like we want it',
98
- input: 'Google is an evil company',
99
- output: `# GOOGLE IS AN EVIL COMPANY`,
100
- metadata,
101
- status: 'approved'
102
- })
103
-
104
- const second = await zai.learn(taskId).rewrite(`Botpress is awesome`, 'write it like we want it')
105
-
106
- rows = await client.findTableRows({ table: tableName })
107
- expect(rows.rows.length).toBe(3)
108
-
109
- check(second, `The text is "BOTPRESS IS AWESOME" and starts with a hashtag`).toBe(true)
110
-
111
- expect(rows.rows.length).toBe(3)
112
- expect(rows.rows[0].output.value).toBe(second)
113
- })
114
- })