@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.
Files changed (39) hide show
  1. package/package.json +9 -2
  2. package/dist/src/operations/check.test.js +0 -129
  3. package/dist/src/operations/extract.test.js +0 -163
  4. package/dist/src/operations/filter.test.js +0 -151
  5. package/dist/src/operations/label.test.js +0 -213
  6. package/dist/src/operations/rewrite.test.js +0 -96
  7. package/dist/src/operations/summarize.test.js +0 -22
  8. package/dist/src/operations/text.test.js +0 -51
  9. package/dist/src/operations/zai-learn.test.js +0 -71
  10. package/dist/src/operations/zai-retry.test.js +0 -50
  11. package/src/adapters/adapter.ts +0 -35
  12. package/src/adapters/botpress-table.ts +0 -213
  13. package/src/adapters/memory.ts +0 -13
  14. package/src/env.ts +0 -54
  15. package/src/index.ts +0 -11
  16. package/src/models.ts +0 -347
  17. package/src/operations/__tests/botpress_docs.txt +0 -26040
  18. package/src/operations/__tests/index.ts +0 -30
  19. package/src/operations/check.test.ts +0 -155
  20. package/src/operations/check.ts +0 -187
  21. package/src/operations/constants.ts +0 -2
  22. package/src/operations/errors.ts +0 -9
  23. package/src/operations/extract.test.ts +0 -209
  24. package/src/operations/extract.ts +0 -291
  25. package/src/operations/filter.test.ts +0 -182
  26. package/src/operations/filter.ts +0 -231
  27. package/src/operations/label.test.ts +0 -239
  28. package/src/operations/label.ts +0 -332
  29. package/src/operations/rewrite.test.ts +0 -114
  30. package/src/operations/rewrite.ts +0 -148
  31. package/src/operations/summarize.test.ts +0 -25
  32. package/src/operations/summarize.ts +0 -193
  33. package/src/operations/text.test.ts +0 -60
  34. package/src/operations/text.ts +0 -63
  35. package/src/operations/zai-learn.test.ts +0 -85
  36. package/src/operations/zai-retry.test.ts +0 -64
  37. package/src/scripts/update-models.ts +0 -74
  38. package/src/utils.ts +0 -61
  39. package/src/zai.ts +0 -185
@@ -1,96 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
2
- import { check } from '@botpress/vai';
3
- import { getClient, getZai, metadata, tokenizer } from './__tests';
4
- import { TableAdapter } from '../adapters/botpress-table';
5
- const Zoe = `
6
- Part 1. Zoe walks to the park.
7
- Part 2. She meets her friend.
8
- Part 3. They play together.
9
- Part 4. They have a picnic.
10
- Part 5. They go home.
11
- `.trim();
12
- describe('zai.rewrite', { timeout: 60_000 }, () => {
13
- const zai = getZai();
14
- it('transforms text to all caps', async () => {
15
- const result = await zai.rewrite(`Hello, what is the time today?`, 'write in all caps');
16
- expect(result).toBe(`HELLO, WHAT IS THE TIME TODAY?`);
17
- });
18
- it('transforms text to all caps and respects tokens restrictions', async () => {
19
- const result = await zai.rewrite(Zoe, 'write in all caps', { length: 15 });
20
- expect(tokenizer.count(result)).toBeLessThanOrEqual(20);
21
- expect(result).toContain(`PART 1. ZOE WALKS TO THE PARK`);
22
- expect(result).not.toContain(`PART 3`);
23
- });
24
- it('french translation of the story', async () => {
25
- const result = await zai.rewrite(Zoe, 'translate to french');
26
- check(result, 'is a french story about Zeo and with 5 parts').toBe(true);
27
- });
28
- it('Throws if input is bigger than the model max tokens', async () => {
29
- await expect(zai.rewrite(Zoe.repeat(100_000), 'translate to french')).rejects.toThrow(/tokens/i);
30
- });
31
- });
32
- describe('zai.learn.rewrite', { timeout: 60_000 }, () => {
33
- const client = getClient();
34
- let tableName = 'ZaiTestRewriteInternalTable';
35
- let taskId = 'rewrite';
36
- let zai = getZai();
37
- beforeEach(async () => {
38
- zai = getZai().with({
39
- activeLearning: {
40
- enable: true,
41
- taskId,
42
- tableName
43
- }
44
- });
45
- });
46
- afterEach(async () => {
47
- try {
48
- await client.deleteTableRows({ table: tableName, deleteAllRows: true });
49
- }
50
- catch (err) { }
51
- });
52
- afterAll(async () => {
53
- try {
54
- await client.deleteTable({ table: tableName });
55
- }
56
- catch (err) { }
57
- });
58
- it('learns rewrite rules from examples', async () => {
59
- const adapter = new TableAdapter({
60
- client,
61
- tableName
62
- });
63
- const value = await zai.learn(taskId).rewrite(`Botpress is awesome`, 'write it like we want it');
64
- check(value, `The text means more or less the same as "Botpress is awesome" but slightly different`).toBe(true);
65
- let rows = await client.findTableRows({ table: tableName });
66
- expect(rows.rows.length).toBe(1);
67
- expect(rows.rows[0].output.value).toBe(value);
68
- await adapter.saveExample({
69
- key: 't1',
70
- taskId: `zai/${taskId}`,
71
- taskType: 'zai.rewrite',
72
- instructions: 'write it like we want it',
73
- input: 'Microsoft is a big company',
74
- output: `# MICROSOFT IS A BIG COMPANY`,
75
- metadata,
76
- status: 'approved'
77
- });
78
- await adapter.saveExample({
79
- key: 't2',
80
- taskId: `zai/${taskId}`,
81
- taskType: 'zai.rewrite',
82
- instructions: 'write it like we want it',
83
- input: 'Google is an evil company',
84
- output: `# GOOGLE IS AN EVIL COMPANY`,
85
- metadata,
86
- status: 'approved'
87
- });
88
- const second = await zai.learn(taskId).rewrite(`Botpress is awesome`, 'write it like we want it');
89
- rows = await client.findTableRows({ table: tableName });
90
- expect(rows.rows.length).toBe(3);
91
- check(second, `The text is "BOTPRESS IS AWESOME" and starts with a hashtag`).toBe(true);
92
- expect(rows.rows.length).toBe(3);
93
- expect(rows.rows[0].output.value).toBe(second);
94
- });
95
- });
96
- //# sourceMappingURL=rewrite.test.js.map
@@ -1,22 +0,0 @@
1
- import { check } from '@botpress/vai';
2
- import { describe, it } from 'vitest';
3
- import { BotpressDocumentation, getZai } from './__tests';
4
- describe('zai.summarize', () => {
5
- const zai = getZai();
6
- it.skip('summarize long document to a concise 2000 token summary', async () => {
7
- const result = await zai.summarize(BotpressDocumentation, {
8
- length: 2000,
9
- prompt: `Extract the Table of Contents for the Botpress Documentation. Pay special attention to all the different features. Focus on horizontal coverage of features rather than going in depth into one feature. The goal is to have a complete overview of what the documentation covers.`
10
- });
11
- check(result, 'The text is a summary of the Botpress documentation').toBe(true);
12
- check(result, 'The text explains shortly what botpress is').toBe(true);
13
- check(result, 'The text uses markdown format').toBe(true);
14
- check(result, 'The text has some information about integrations').toBe(true);
15
- check(result, 'The text has a section about Flows (or Workflows)').toBe(true);
16
- check(result, 'The text has a section about the Botpress API').toBe(true);
17
- check(result, 'The text mentions the notion of workspaces').toBe(true);
18
- check(result, 'The text has some information about the Webchat').toBe(true);
19
- check(result, 'The text has some information about HITL (human in the loop)').toBe(true);
20
- });
21
- });
22
- //# sourceMappingURL=summarize.test.js.map
@@ -1,51 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { check } from '@botpress/vai';
3
- import { getZai, tokenizer } from './__tests';
4
- describe('zai.text', { timeout: 60_000 }, () => {
5
- const zai = getZai();
6
- it('generate a horror novel with no params', async () => {
7
- const story = await zai.text('write a short horror novel');
8
- check(story, 'is a short horror story').toBe(true);
9
- });
10
- it('No fluffy text at the beginning', async () => {
11
- const story = await zai.text('write a short horror novel');
12
- check(story, 'There is no LLM fluff at the beginning', {
13
- examples: [
14
- {
15
- value: 'Title: A horror story\nChapter 1: The woods\nOnce upen a time, ...',
16
- expected: true,
17
- reason: 'It begins straight with a story, no fluff at the beginning'
18
- },
19
- { value: 'Once upon a time, a ...', expected: true, reason: 'The story starts directly' },
20
- {
21
- value: 'Sure, I will generate a story.\nOnce upen a time, a...',
22
- expected: false,
23
- reason: 'There is some fluff at the beginning'
24
- }
25
- ]
26
- }).toBe(true);
27
- });
28
- it('No fluffy text at the end', async () => {
29
- const story = await zai.text('write a short horror novel');
30
- check(story, 'There is no LLM fluff at the end', {
31
- examples: [
32
- {
33
- value: 'Title: A horror story\nChapter 1: The woods\nOnce upen a time, ... The End.',
34
- expected: true,
35
- reason: 'The end is clear and direct, no fluff at the end'
36
- },
37
- {
38
- value: 'Sure, I will generate a story.\nOnce upen a time, a... The End.\nLet me know if you want more or if you are happy with this.',
39
- expected: false,
40
- reason: 'There is some fluff from the assistant at the end.'
41
- }
42
- ]
43
- }).toBe(true);
44
- });
45
- it('length/max tokens param', async () => {
46
- const story = await zai.text('write a short but complete horror story (with conclusion)', { length: 100 });
47
- expect(tokenizer.count(story)).toBeLessThanOrEqual(110);
48
- check(story, 'could be the beginning of a horror story').toBe(true);
49
- });
50
- });
51
- //# sourceMappingURL=text.test.js.map
@@ -1,71 +0,0 @@
1
- import { describe, it, expect, afterAll, beforeEach, afterEach, vi } from 'vitest';
2
- import { getClient, getZai } from './__tests';
3
- import { check } from '@botpress/vai';
4
- describe('zai.learn / generic', { timeout: 60_000 }, () => {
5
- const client = getClient();
6
- let tableName = 'ZaiTestInternalTable';
7
- let taskId = 'test';
8
- let zai = getZai();
9
- beforeEach(async () => {
10
- zai = getZai().with({
11
- activeLearning: {
12
- enable: true,
13
- taskId,
14
- tableName
15
- }
16
- });
17
- });
18
- afterEach(async () => {
19
- try {
20
- await client.deleteTableRows({ table: tableName, deleteAllRows: true });
21
- }
22
- catch (err) { }
23
- });
24
- afterAll(async () => {
25
- try {
26
- await client.deleteTable({ table: tableName });
27
- }
28
- catch (err) { }
29
- });
30
- it('saves examples to tables', async () => {
31
- const value = await zai
32
- .learn(taskId)
33
- .check('This text is very clearly written in English.', 'is an english sentence');
34
- const { rows } = await client.findTableRows({ table: tableName });
35
- expect(value).toBe(true);
36
- expect(rows.length).toBe(1);
37
- check(rows[0].explanation, 'is an explanation sentence');
38
- expect(rows[0].explanation).not.toContain('Final Answer:');
39
- expect(rows[0].output).toMatchObject({ value: true });
40
- expect(rows[0].input).toMatchInlineSnapshot(`
41
- {
42
- "value": "This text is very clearly written in English.",
43
- }
44
- `);
45
- expect(rows[0].taskId).toEqual('zai/test');
46
- expect(rows[0].taskType).toBe('zai.check');
47
- });
48
- it('works even if tables are down', async () => {
49
- const upsertTableRows = vi.fn(async () => {
50
- throw new Error('Table is down');
51
- });
52
- const findTableRows = vi.fn(async () => {
53
- throw new Error('Table is down');
54
- });
55
- const client = {
56
- ...getClient(),
57
- findTableRows,
58
- upsertTableRows
59
- };
60
- const value = await zai
61
- .with({ client })
62
- .learn(taskId)
63
- .check('This text is very clearly written in English.', 'is an english sentence');
64
- const { rows } = await getClient().findTableRows({ table: tableName });
65
- expect(value).toBe(true);
66
- expect(rows.length).toBe(0);
67
- expect(upsertTableRows).toHaveBeenCalledTimes(1);
68
- expect(findTableRows).toHaveBeenCalledTimes(1);
69
- });
70
- });
71
- //# sourceMappingURL=zai-learn.test.js.map
@@ -1,50 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { getClient, getZai } from './__tests';
3
- describe('zai retry', { timeout: 60_000 }, () => {
4
- const client = getClient();
5
- let zai = getZai().with({ retry: { maxRetries: 3 } });
6
- it('retries 3 times and succeeds', async () => {
7
- let retryCount = 0;
8
- const throwingClient = {
9
- ...client,
10
- callAction: vi.fn((input) => {
11
- if (++retryCount < 3) {
12
- throw new Error('Failed to call model');
13
- }
14
- return client.callAction(input);
15
- })
16
- };
17
- const value = await zai
18
- .with({ client: throwingClient })
19
- .check('This text is very clearly written in English.', 'is an english sentence');
20
- expect(value).toBe(true);
21
- expect(retryCount).toBe(3);
22
- });
23
- it('retries 0 times when success', async () => {
24
- const fn = vi.fn((input) => client.callAction(input));
25
- const throwingClient = {
26
- ...client,
27
- callAction: fn
28
- };
29
- const value = await zai
30
- .with({ client: throwingClient })
31
- .check('This text is very clearly written in English.', 'is an english sentence');
32
- expect(value).toBe(true);
33
- expect(fn).toHaveBeenCalledOnce();
34
- });
35
- it('fails when exceeded max', async () => {
36
- const fn = vi.fn(() => {
37
- throw new Error('Failed to call model');
38
- });
39
- const throwingClient = {
40
- ...client,
41
- callAction: fn
42
- };
43
- const value = zai
44
- .with({ client: throwingClient, retry: { maxRetries: 1 } })
45
- .check('This text is very clearly written in English.', 'is an english sentence');
46
- await expect(value).rejects.toThrowError(/retries/);
47
- expect(fn).toHaveBeenCalledTimes(2);
48
- });
49
- });
50
- //# sourceMappingURL=zai-retry.test.js.map
@@ -1,35 +0,0 @@
1
- import { GenerationMetadata } from '../utils'
2
-
3
- export type SaveExampleProps<TInput, TOutput> = {
4
- key: string
5
- taskType: string
6
- taskId: string
7
- instructions: string
8
- input: TInput
9
- output: TOutput
10
- explanation?: string
11
- metadata: GenerationMetadata
12
- status?: 'pending' | 'approved'
13
- }
14
-
15
- export type GetExamplesProps<TInput> = {
16
- taskType: string
17
- taskId: string
18
- input: TInput
19
- }
20
-
21
- export abstract class Adapter {
22
- abstract getExamples<TInput, TOutput>(
23
- props: GetExamplesProps<TInput>
24
- ): Promise<
25
- Array<{
26
- key: string
27
- input: TInput
28
- output: TOutput
29
- explanation?: string
30
- similarity: number
31
- }>
32
- >
33
-
34
- abstract saveExample<TInput, TOutput>(props: SaveExampleProps<TInput, TOutput>): Promise<void>
35
- }
@@ -1,213 +0,0 @@
1
- import { type Client } from '@botpress/client'
2
- import { z } from '@bpinternal/zui'
3
-
4
- import { BotpressClient, GenerationMetadata } from '../utils'
5
- import { Adapter, GetExamplesProps, SaveExampleProps } from './adapter'
6
-
7
- const CRITICAL_TAGS = {
8
- system: 'true',
9
- 'schema-purpose': 'active-learning',
10
- 'schema-version': 'Oct-2024'
11
- } as const
12
-
13
- const OPTIONAL_TAGS = {
14
- 'x-studio-title': 'Active Learning',
15
- 'x-studio-description': 'Table for storing active learning tasks and examples',
16
- 'x-studio-readonly': 'true',
17
- 'x-studio-icon': 'lucide://atom',
18
- 'x-studio-color': 'green'
19
- } as const
20
-
21
- const FACTOR = 30
22
-
23
- const Props = z.object({
24
- client: BotpressClient,
25
- tableName: z
26
- .string()
27
- .regex(
28
- /^[a-zA-Z0-9_]{1,45}Table$/,
29
- 'Table name must be lowercase and contain only letters, numbers and underscores'
30
- )
31
- })
32
-
33
- export type TableSchema = z.input<typeof TableSchema>
34
- const TableSchema = z.object({
35
- taskType: z.string().describe('The type of the task (filter, extract, etc.)'),
36
- taskId: z.string(),
37
- key: z.string().describe('A unique key for the task (e.g. a hash of the input, taskId, taskType and instructions)'),
38
- instructions: z.string(),
39
- input: z.object({}).passthrough().describe('The input to the task'),
40
- output: z.object({}).passthrough().describe('The expected output'),
41
- explanation: z.string().nullable(),
42
- metadata: GenerationMetadata,
43
- status: z.enum(['pending', 'rejected', 'approved']),
44
- feedback: z
45
- .object({
46
- rating: z.enum(['very-bad', 'bad', 'good', 'very-good']),
47
- comment: z.string().nullable()
48
- })
49
- .nullable()
50
- .default(null)
51
- })
52
-
53
- const searchableColumns = ['input'] as const satisfies Array<keyof typeof TableSchema.shape> as string[]
54
-
55
- const TableJsonSchema = Object.entries(TableSchema.shape).reduce((acc, [key, value]) => {
56
- acc[key] = value.toJsonSchema()
57
- acc[key]['x-zui'] ??= {}
58
- acc[key]['x-zui'].searchable = searchableColumns.includes(key)
59
- return acc
60
- }, {})
61
-
62
- export class TableAdapter extends Adapter {
63
- private client: Client
64
- private tableName: string
65
-
66
- private status: 'initialized' | 'ready' | 'error'
67
- private errors = [] as string[]
68
-
69
- constructor(props: z.input<typeof Props>) {
70
- super()
71
- props = Props.parse(props)
72
- this.client = props.client
73
- this.tableName = props.tableName
74
- this.status = 'ready'
75
- }
76
-
77
- public async getExamples<TInput, TOutput>({ taskType, taskId, input }: GetExamplesProps<TInput>) {
78
- await this.assertTableExists()
79
-
80
- const { rows } = await this.client
81
- .findTableRows({
82
- table: this.tableName,
83
- search: JSON.stringify({ value: input }).substring(0, 1023), // Search is limited to 1024 characters
84
- limit: 10, // TODO
85
- filter: {
86
- // Proximity match of approved examples
87
- taskType,
88
- taskId,
89
- status: 'approved'
90
- } satisfies Partial<TableSchema>
91
- })
92
- .catch((err) => {
93
- // TODO: handle error
94
- console.error(`Error fetching examples: ${err.message}`)
95
- return { rows: [] }
96
- })
97
-
98
- return rows.map((row) => ({
99
- key: row.key,
100
- input: row.input.value as TInput,
101
- output: row.output.value as TOutput,
102
- explanation: row.explanation,
103
- similarity: row.similarity ?? 0
104
- }))
105
- }
106
-
107
- public async saveExample<TInput, TOutput>({
108
- key,
109
- taskType,
110
- taskId,
111
- instructions,
112
- input,
113
- output,
114
- explanation,
115
- metadata,
116
- status = 'pending'
117
- }: SaveExampleProps<TInput, TOutput>) {
118
- await this.assertTableExists()
119
-
120
- await this.client
121
- .upsertTableRows({
122
- table: this.tableName,
123
- keyColumn: 'key',
124
- rows: [
125
- {
126
- key,
127
- taskType,
128
- taskId,
129
- instructions,
130
- input: { value: input },
131
- output: { value: output },
132
- explanation: explanation ?? null,
133
- status,
134
- metadata
135
- } satisfies TableSchema
136
- ]
137
- })
138
- .catch(() => {
139
- // TODO: handle error
140
- })
141
- }
142
-
143
- private async assertTableExists() {
144
- if (this.status !== 'ready') {
145
- return
146
- }
147
-
148
- const { table, created } = await this.client
149
- .getOrCreateTable({
150
- table: this.tableName,
151
- factor: FACTOR,
152
- frozen: true,
153
- isComputeEnabled: false,
154
- tags: {
155
- ...CRITICAL_TAGS,
156
- ...OPTIONAL_TAGS
157
- },
158
- schema: TableJsonSchema
159
- })
160
- .catch((err) => {
161
- this.status = 'error'
162
- this.errors = [err.message]
163
- return { table: null, created: false }
164
- })
165
-
166
- if (!table) {
167
- return
168
- }
169
-
170
- if (!created) {
171
- const issues: string[] = []
172
-
173
- if (table.factor !== FACTOR) {
174
- issues.push(`Factor is ${table.factor} instead of ${FACTOR}`)
175
- }
176
-
177
- if (table.frozen !== true) {
178
- issues.push('Table is not frozen')
179
- }
180
-
181
- for (const [key, value] of Object.entries(CRITICAL_TAGS)) {
182
- if (table.tags?.[key] !== value) {
183
- issues.push(`Tag ${key} is ${table.tags?.[key]} instead of ${value}`)
184
- }
185
- }
186
-
187
- for (const key of Object.keys(TableJsonSchema)) {
188
- const column = table.schema?.properties[key]
189
- const expected = TableJsonSchema[key] as { type: string }
190
-
191
- if (!column) {
192
- issues.push(`Column ${key} is missing`)
193
- continue
194
- }
195
-
196
- if (column.type !== expected.type) {
197
- issues.push(`Column ${key} has type ${column.type} instead of ${expected.type}`)
198
- }
199
-
200
- if (expected['x-zui'].searchable && !column['x-zui'].searchable) {
201
- issues.push(`Column ${key} is not searchable but should be`)
202
- }
203
- }
204
-
205
- if (issues.length) {
206
- this.status = 'error'
207
- this.errors = issues
208
- }
209
- }
210
-
211
- this.status = 'initialized'
212
- }
213
- }
@@ -1,13 +0,0 @@
1
- import { Adapter } from './adapter'
2
-
3
- export class MemoryAdapter extends Adapter {
4
- constructor(public examples: any[]) {
5
- super()
6
- }
7
-
8
- async getExamples() {
9
- return this.examples
10
- }
11
-
12
- async saveExample() {}
13
- }
package/src/env.ts DELETED
@@ -1,54 +0,0 @@
1
- import { z } from '@bpinternal/zui'
2
- import dotenv from 'dotenv'
3
-
4
- dotenv.config({ override: false })
5
- dotenv.populate(
6
- process.env,
7
- {
8
- VITE_CLOUD_API_ENDPOINT: 'https://api.botpress.dev'
9
- },
10
- { override: false }
11
- )
12
-
13
- const envVariables = z.object({
14
- CI: z.coerce.boolean().optional().describe('Indicates whether the tests are running in a CI environment'),
15
- VITE_CLOUD_API_ENDPOINT: z.string(),
16
- VITE_BOT_ID: z.string(),
17
- VITE_CLOUD_PAT: z.string(),
18
- // for CI
19
- VITEST_CLOUD_PAT: z.string().optional(),
20
- VITEST_BOT_ID: z.string().optional()
21
- })
22
-
23
- const result = envVariables.safeParse(process.env)
24
-
25
- if (!result.success) {
26
- for (const error of result.error.errors) {
27
- if (error.code === 'invalid_type') {
28
- console.error(`Missing environment variable: ${error.path.join('.')}`)
29
- } else {
30
- console.error(error)
31
- }
32
- }
33
- }
34
-
35
- type Deprecated = Omit<
36
- {
37
- [key: string]: any
38
- },
39
- keyof z.infer<typeof envVariables>
40
- >
41
-
42
- type AllEnv = z.infer<typeof envVariables> & Deprecated
43
-
44
- /** @internal */
45
- declare global {
46
- namespace NodeJS {
47
- interface ProcessEnv extends AllEnv {
48
- /** @deprecated please define your ENV variable in {@link env.ts} */
49
- [key: keyof Deprecated]: any
50
- }
51
- }
52
- }
53
-
54
- export {}
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
1
- import { Zai } from './zai'
2
-
3
- import './operations/text'
4
- import './operations/rewrite'
5
- import './operations/summarize'
6
- import './operations/check'
7
- import './operations/filter'
8
- import './operations/extract'
9
- import './operations/label'
10
-
11
- export { Zai }