@botpress/zai 2.0.7 → 2.0.10

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/e2e/utils.ts CHANGED
@@ -4,10 +4,9 @@ import fs from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import { beforeAll } from 'vitest'
6
6
  import { Zai } from '../src'
7
- import { fastHash } from '../src/utils'
7
+ import { getCachedCognitiveClient } from './client'
8
8
 
9
9
  const DATA_PATH = path.join(__dirname, 'data')
10
- const CACHE_PATH = path.join(DATA_PATH, 'cache.jsonl')
11
10
  const DOC_PATH = path.join(DATA_PATH, 'botpress_docs.txt')
12
11
 
13
12
  export const getClient = () => {
@@ -18,59 +17,8 @@ export const getClient = () => {
18
17
  })
19
18
  }
20
19
 
21
- function readJSONL<T>(filePath: string, keyProperty: keyof T): Map<string, T> {
22
- const lines = fs.readFileSync(filePath, 'utf-8').split(/\r?\n/).filter(Boolean)
23
-
24
- const map = new Map<string, T>()
25
-
26
- for (const line of lines) {
27
- const obj = JSON.parse(line) as T
28
- const key = String(obj[keyProperty])
29
- map.set(key, obj)
30
- }
31
-
32
- return map
33
- }
34
-
35
- const cache: Map<string, { key: string; value: any }> = readJSONL(CACHE_PATH, 'key')
36
-
37
20
  export const getCachedClient = () => {
38
- const client = getClient()
39
-
40
- const proxy = new Proxy(client, {
41
- get(target, prop) {
42
- if (prop === 'callAction') {
43
- return async (...args: Parameters<Client['callAction']>) => {
44
- const key = fastHash(JSON.stringify(args))
45
- const cached = cache.get(key)
46
-
47
- if (cached) {
48
- return cached.value
49
- }
50
-
51
- const response = await target.callAction(...args)
52
- cache.set(key, { key, value: response })
53
-
54
- fs.appendFileSync(
55
- CACHE_PATH,
56
- JSON.stringify({
57
- key,
58
- value: response,
59
- }) + '\n'
60
- )
61
-
62
- return response
63
- }
64
- }
65
- return Reflect.get(target, prop)
66
- },
67
- })
68
-
69
- ;(proxy as any).clone = () => {
70
- return getCachedClient()
71
- }
72
-
73
- return proxy
21
+ return getCachedCognitiveClient()
74
22
  }
75
23
 
76
24
  export const getZai = () => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@botpress/zai",
3
3
  "description": "Zui AI (zai) – An LLM utility library written on top of Zui and the Botpress API",
4
- "version": "2.0.7",
4
+ "version": "2.0.10",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "exports": {
@@ -23,7 +23,7 @@
23
23
  "author": "",
24
24
  "license": "ISC",
25
25
  "dependencies": {
26
- "@botpress/cognitive": "0.1.22",
26
+ "@botpress/cognitive": "0.1.24",
27
27
  "json5": "^2.2.3",
28
28
  "jsonrepair": "^3.10.0",
29
29
  "lodash-es": "^4.17.21"
@@ -33,6 +33,7 @@
33
33
  "@botpress/common": "workspace:*",
34
34
  "@botpress/vai": "workspace:*",
35
35
  "@types/lodash-es": "^4.17.12",
36
+ "diff": "^8.0.1",
36
37
  "dotenv": "^16.4.4",
37
38
  "esbuild": "^0.16.12",
38
39
  "glob": "^9.3.4",
@@ -41,7 +42,7 @@
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@bpinternal/thicktoken": "^1.0.0",
44
- "@bpinternal/zui": "^0.17.1"
45
+ "@bpinternal/zui": "^0.22.5"
45
46
  },
46
47
  "engines": {
47
48
  "node": ">=18.0.0"
@@ -20,6 +20,11 @@ const OPTIONAL_TAGS = {
20
20
 
21
21
  const FACTOR = 30
22
22
 
23
+ type Props = {
24
+ client: Client
25
+ tableName: string
26
+ }
27
+
23
28
  const Props = z.object({
24
29
  client: z.custom(() => true),
25
30
  tableName: z
@@ -30,7 +35,22 @@ const Props = z.object({
30
35
  ),
31
36
  })
32
37
 
33
- export type TableSchema = (typeof TableSchema)['_input']
38
+ export type TableSchema = {
39
+ taskType: string
40
+ taskId: string
41
+ key: string
42
+ instructions: string
43
+ input: Record<string, unknown>
44
+ output: Record<string, unknown>
45
+ explanation: string | null
46
+ metadata: GenerationMetadata
47
+ status: 'pending' | 'rejected' | 'approved'
48
+ feedback: {
49
+ rating: 'very-bad' | 'bad' | 'good' | 'very-good'
50
+ comment: string | null
51
+ } | null
52
+ }
53
+
34
54
  const TableSchema = z.object({
35
55
  taskType: z.string().describe('The type of the task (filter, extract, etc.)'),
36
56
  taskId: z.string(),
@@ -39,7 +59,7 @@ const TableSchema = z.object({
39
59
  input: z.object({}).passthrough().describe('The input to the task'),
40
60
  output: z.object({}).passthrough().describe('The expected output'),
41
61
  explanation: z.string().nullable(),
42
- metadata: GenerationMetadata,
62
+ metadata: z.object({}).passthrough(),
43
63
  status: z.enum(['pending', 'rejected', 'approved']),
44
64
  feedback: z
45
65
  .object({
@@ -65,9 +85,9 @@ export class TableAdapter extends Adapter {
65
85
 
66
86
  private _status: 'initialized' | 'ready' | 'error'
67
87
 
68
- public constructor(props: (typeof Props)['_input']) {
88
+ public constructor(props: Props) {
69
89
  super()
70
- props = Props.parse(props)
90
+ props = Props.parse(props) as Props
71
91
  this._client = props.client
72
92
  this._tableName = props.tableName
73
93
  this._status = 'ready'
@@ -131,6 +151,7 @@ export class TableAdapter extends Adapter {
131
151
  explanation: explanation ?? null,
132
152
  status,
133
153
  metadata,
154
+ feedback: null, // Feedback is not provided at this point
134
155
  } satisfies TableSchema,
135
156
  ],
136
157
  })
@@ -5,15 +5,27 @@ import { fastHash, stringify, takeUntilTokens } from '../utils'
5
5
  import { Zai } from '../zai'
6
6
  import { PROMPT_INPUT_BUFFER } from './constants'
7
7
 
8
- const Example = z.object({
8
+ const _Example = z.object({
9
9
  input: z.any(),
10
10
  check: z.boolean(),
11
11
  reason: z.string().optional(),
12
+ condition: z.string().optional(),
12
13
  })
13
14
 
14
- export type Options = (typeof Options)['_input']
15
- const Options = z.object({
16
- examples: z.array(Example).describe('Examples to check the condition against').default([]),
15
+ type Example = {
16
+ input: unknown
17
+ check: boolean
18
+ reason?: string
19
+ condition?: string
20
+ }
21
+
22
+ export type Options = {
23
+ /** Examples to check the condition against */
24
+ examples?: Array<Example>
25
+ }
26
+
27
+ const _Options = z.object({
28
+ examples: z.array(_Example).describe('Examples to check the condition against').default([]),
17
29
  })
18
30
 
19
31
  declare module '@botpress/zai' {
@@ -36,8 +48,8 @@ const TRUE = '■TRUE■'
36
48
  const FALSE = '■FALSE■'
37
49
  const END = '■END■'
38
50
 
39
- Zai.prototype.check = async function (this: Zai, input, condition, _options) {
40
- const options = Options.parse(_options ?? {})
51
+ Zai.prototype.check = async function (this: Zai, input: unknown, condition: string, _options: Options | undefined) {
52
+ const options = _Options.parse(_options ?? {}) as Options
41
53
  const tokenizer = await this.getTokenizer()
42
54
  await this.fetchModelDetails()
43
55
  const PROMPT_COMPONENT = Math.max(this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER, 100)
@@ -80,14 +92,20 @@ Zai.prototype.check = async function (this: Zai, input, condition, _options) {
80
92
  }
81
93
 
82
94
  const defaultExamples = [
83
- { input: '50 Cent', check: true, reason: '50 Cent is widely recognized as a public personality.' },
95
+ {
96
+ input: '50 Cent',
97
+ check: true,
98
+ reason: '50 Cent is widely recognized as a public personality.',
99
+ condition: 'Is the input a public personality?',
100
+ },
84
101
  {
85
102
  input: ['apple', 'banana', 'carrot', 'house'],
86
103
  check: false,
87
104
  reason:
88
105
  'The list contains a house, which is not a fruit. Also, the list contains a carrot, which is a vegetable.',
106
+ condition: 'Is the input exclusively a list of fruits?',
89
107
  },
90
- ]
108
+ ] satisfies Example[]
91
109
 
92
110
  const userExamples = [
93
111
  ...examples.map((e) => ({ input: e.input, check: e.output, reason: e.explanation })),
@@ -115,8 +133,12 @@ ${END}
115
133
  `.trim()
116
134
  }
117
135
 
118
- const formatExample = (example: { input?: any; check: boolean; reason?: string }) => [
119
- { type: 'text' as const, content: formatInput(stringify(example.input ?? null), condition), role: 'user' as const },
136
+ const formatExample = (example: Example) => [
137
+ {
138
+ type: 'text' as const,
139
+ content: formatInput(stringify(example.input ?? null), example.condition ?? condition),
140
+ role: 'user' as const,
141
+ },
120
142
  {
121
143
  type: 'text' as const,
122
144
  content: formatOutput(example.check, example.reason ?? ''),
@@ -10,7 +10,13 @@ import { Zai } from '../zai'
10
10
  import { PROMPT_INPUT_BUFFER } from './constants'
11
11
  import { JsonParsingError } from './errors'
12
12
 
13
- export type Options = (typeof Options)['_input']
13
+ export type Options = {
14
+ /** Instructions to guide the user on how to extract the data */
15
+ instructions?: string
16
+ /** The maximum number of tokens per chunk */
17
+ chunkLength?: number
18
+ }
19
+
14
20
  const Options = z.object({
15
21
  instructions: z.string().optional().describe('Instructions to guide the user on how to extract the data'),
16
22
  chunkLength: z
@@ -6,15 +6,26 @@ import { fastHash, stringify, takeUntilTokens } from '../utils'
6
6
  import { Zai } from '../zai'
7
7
  import { PROMPT_INPUT_BUFFER, PROMPT_OUTPUT_BUFFER } from './constants'
8
8
 
9
- type Example = (typeof Example)['_input']
10
- const Example = z.object({
9
+ type Example = {
10
+ input: unknown
11
+ filter: boolean
12
+ reason?: string
13
+ }
14
+
15
+ const _Example = z.object({
11
16
  input: z.any(),
12
17
  filter: z.boolean(),
13
18
  reason: z.string().optional(),
14
19
  })
15
20
 
16
- export type Options = (typeof Options)['_input']
17
- const Options = z.object({
21
+ export type Options = {
22
+ /** The maximum number of tokens per item */
23
+ tokensPerItem?: number
24
+ /** Examples to filter the condition against */
25
+ examples?: Array<Example>
26
+ }
27
+
28
+ const _Options = z.object({
18
29
  tokensPerItem: z
19
30
  .number()
20
31
  .min(1)
@@ -22,7 +33,7 @@ const Options = z.object({
22
33
  .optional()
23
34
  .describe('The maximum number of tokens per item')
24
35
  .default(250),
25
- examples: z.array(Example).describe('Examples to filter the condition against').default([]),
36
+ examples: z.array(_Example).describe('Examples to filter the condition against').default([]),
26
37
  })
27
38
 
28
39
  declare module '@botpress/zai' {
@@ -35,7 +46,7 @@ declare module '@botpress/zai' {
35
46
  const END = '■END■'
36
47
 
37
48
  Zai.prototype.filter = async function (this: Zai, input, condition, _options) {
38
- const options = Options.parse(_options ?? {})
49
+ const options = _Options.parse(_options ?? {}) as Options
39
50
  const tokenizer = await this.getTokenizer()
40
51
  await this.fetchModelDetails()
41
52
 
@@ -22,11 +22,16 @@ type Example<T extends string> = {
22
22
  labels: Partial<Record<T, { label: Label; explanation?: string }>>
23
23
  }
24
24
 
25
- export type Options<T extends string> = Omit<(typeof Options)['_input'], 'examples'> & {
26
- examples?: Array<Partial<Example<T>>>
25
+ export type Options<T extends string> = {
26
+ /** Examples to help the user make a decision */
27
+ examples?: Array<Example<T>>
28
+ /** Instructions to guide the user on how to extract the data */
29
+ instructions?: string
30
+ /** The maximum number of tokens per chunk */
31
+ chunkLength?: number
27
32
  }
28
33
 
29
- const Options = z.object({
34
+ const _Options = z.object({
30
35
  examples: z
31
36
  .array(
32
37
  z.object({
@@ -48,7 +53,7 @@ const Options = z.object({
48
53
 
49
54
  type Labels<T extends string> = Record<T, string>
50
55
 
51
- const Labels = z.record(z.string().min(1).max(250), z.string()).superRefine((labels, ctx) => {
56
+ const _Labels = z.record(z.string().min(1).max(250), z.string()).superRefine((labels, ctx) => {
52
57
  const keys = Object.keys(labels)
53
58
 
54
59
  for (const key of keys) {
@@ -119,9 +124,14 @@ const getConfidence = (label: Label) => {
119
124
  }
120
125
  }
121
126
 
122
- Zai.prototype.label = async function <T extends string>(this: Zai, input, _labels, _options) {
123
- const options = Options.parse(_options ?? {})
124
- const labels = Labels.parse(_labels)
127
+ Zai.prototype.label = async function <T extends string>(
128
+ this: Zai,
129
+ input: unknown,
130
+ _labels: Labels<T>,
131
+ _options: Options<T> | undefined
132
+ ) {
133
+ const options = _Options.parse(_options ?? {}) as unknown as Options<T>
134
+ const labels = _Labels.parse(_labels) as Labels<T>
125
135
  const tokenizer = await this.getTokenizer()
126
136
  await this.fetchModelDetails()
127
137
 
@@ -211,7 +221,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
211
221
  options.examples.forEach((example) => {
212
222
  examples.push({
213
223
  key: fastHash(JSON.stringify(example)),
214
- input: example.input,
224
+ input: stringify(example.input),
215
225
  similarity: 1,
216
226
  explanation: '',
217
227
  output: example.labels as unknown as {
@@ -5,15 +5,26 @@ import { fastHash, stringify, takeUntilTokens } from '../utils'
5
5
  import { Zai } from '../zai'
6
6
  import { PROMPT_INPUT_BUFFER } from './constants'
7
7
 
8
- type Example = (typeof Example)['_input'] & { instructions?: string }
9
- const Example = z.object({
8
+ type Example = {
9
+ input: string
10
+ output: string
11
+ instructions?: string
12
+ }
13
+
14
+ const _Example = z.object({
10
15
  input: z.string(),
11
16
  output: z.string(),
12
17
  })
13
18
 
14
- export type Options = (typeof Options)['_input']
19
+ export type Options = {
20
+ /** Examples to guide the rewriting */
21
+ examples?: Array<Example>
22
+ /** The maximum number of tokens to generate */
23
+ length?: number
24
+ }
25
+
15
26
  const Options = z.object({
16
- examples: z.array(Example).default([]),
27
+ examples: z.array(_Example).default([]),
17
28
  length: z.number().min(10).max(16_000).optional().describe('The maximum number of tokens to generate'),
18
29
  })
19
30
 
@@ -28,7 +39,7 @@ const START = '■START■'
28
39
  const END = '■END■'
29
40
 
30
41
  Zai.prototype.rewrite = async function (this: Zai, original, prompt, _options) {
31
- const options = Options.parse(_options ?? {})
42
+ const options = Options.parse(_options ?? {}) as Options
32
43
  const tokenizer = await this.getTokenizer()
33
44
  await this.fetchModelDetails()
34
45
 
@@ -101,7 +112,7 @@ ${END}
101
112
  }
102
113
 
103
114
  const savedExamples: Example[] = [
104
- ...tableExamples.map((x) => ({ input: x.input as string, output: x.output as string })),
115
+ ...tableExamples.map((x) => ({ input: x.input as string, output: x.output as string }) satisfies Example),
105
116
  ...options.examples,
106
117
  ]
107
118
 
@@ -5,7 +5,24 @@ import { chunk } from 'lodash-es'
5
5
  import { Zai } from '../zai'
6
6
  import { PROMPT_INPUT_BUFFER, PROMPT_OUTPUT_BUFFER } from './constants'
7
7
 
8
- export type Options = (typeof Options)['_input']
8
+ export type Options = {
9
+ /** What should the text be summarized to? */
10
+ prompt?: string
11
+ /** How to format the example text */
12
+ format?: string
13
+ /** The length of the summary in tokens */
14
+ length?: number
15
+ /** How many times longer (than final length) are the intermediate summaries generated */
16
+ intermediateFactor?: number
17
+ /** The maximum number of iterations to perform */
18
+ maxIterations?: number
19
+ /** Sliding window options */
20
+ sliding?: {
21
+ window: number
22
+ overlap: number
23
+ }
24
+ }
25
+
9
26
  const Options = z.object({
10
27
  prompt: z
11
28
  .string()
@@ -45,7 +62,7 @@ const START = '■START■'
45
62
  const END = '■END■'
46
63
 
47
64
  Zai.prototype.summarize = async function (this: Zai, original, _options) {
48
- const options = Options.parse(_options ?? {})
65
+ const options = Options.parse(_options ?? {}) as Options
49
66
  const tokenizer = await this.getTokenizer()
50
67
  await this.fetchModelDetails()
51
68
 
@@ -5,7 +5,11 @@ import { clamp } from 'lodash-es'
5
5
  import { Zai } from '../zai'
6
6
  import { PROMPT_INPUT_BUFFER, PROMPT_OUTPUT_BUFFER } from './constants'
7
7
 
8
- export type Options = (typeof Options)['_input']
8
+ export type Options = {
9
+ /** The maximum number of tokens to generate */
10
+ length?: number
11
+ }
12
+
9
13
  const Options = z.object({
10
14
  length: z.number().min(1).max(100_000).optional().describe('The maximum number of tokens to generate'),
11
15
  })
package/src/utils.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { z } from '@bpinternal/zui'
2
-
3
1
  export const stringify = (input: unknown, beautify = true) => {
4
2
  return typeof input === 'string' && !!input.length
5
3
  ? input
@@ -33,20 +31,15 @@ export const takeUntilTokens = <T>(arr: T[], tokens: number, count: (el: T) => n
33
31
  return result
34
32
  }
35
33
 
36
- export type GenerationMetadata = (typeof GenerationMetadata)['_input']
37
- export const GenerationMetadata = z.object({
38
- model: z.string(),
39
- cost: z
40
- .object({
41
- input: z.number(),
42
- output: z.number(),
43
- })
44
- .describe('Cost in $USD'),
45
- latency: z.number().describe('Latency in milliseconds'),
46
- tokens: z
47
- .object({
48
- input: z.number(),
49
- output: z.number(),
50
- })
51
- .describe('Number of tokens used'),
52
- })
34
+ export type GenerationMetadata = {
35
+ model: string
36
+ cost: {
37
+ input: number
38
+ output: number
39
+ }
40
+ latency: number
41
+ tokens: {
42
+ input: number
43
+ output: number
44
+ }
45
+ }
package/src/zai.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Client } from '@botpress/client'
1
2
  import { BotpressClientLike, Cognitive, Model } from '@botpress/cognitive'
2
3
 
3
4
  import { type TextTokenizer, getWasmTokenizer } from '@bpinternal/thicktoken'
@@ -9,8 +10,13 @@ import { MemoryAdapter } from './adapters/memory'
9
10
 
10
11
  type ModelId = Required<Parameters<Cognitive['generateContent']>[0]['model']>
11
12
 
12
- type ActiveLearning = (typeof ActiveLearning)['_input']
13
- const ActiveLearning = z.object({
13
+ type ActiveLearning = {
14
+ enable: boolean
15
+ tableName: string
16
+ taskId: string
17
+ }
18
+
19
+ const _ActiveLearning = z.object({
14
20
  enable: z.boolean().describe('Whether to enable active learning').default(false),
15
21
  tableName: z
16
22
  .string()
@@ -30,8 +36,15 @@ const ActiveLearning = z.object({
30
36
  .default('default'),
31
37
  })
32
38
 
33
- type ZaiConfig = (typeof ZaiConfig)['_input']
34
- const ZaiConfig = z.object({
39
+ type ZaiConfig = {
40
+ client: BotpressClientLike | Cognitive
41
+ userId?: string
42
+ modelId?: ModelId | string
43
+ activeLearning?: ActiveLearning
44
+ namespace?: string
45
+ }
46
+
47
+ const _ZaiConfig = z.object({
35
48
  client: z.custom<BotpressClientLike | Cognitive>(),
36
49
  userId: z.string().describe('The ID of the user consuming the API').optional(),
37
50
  modelId: z
@@ -53,7 +66,7 @@ const ZaiConfig = z.object({
53
66
  )
54
67
  .describe('The ID of the model you want to use')
55
68
  .default('best' satisfies ModelId),
56
- activeLearning: ActiveLearning.default({ enable: false }),
69
+ activeLearning: _ActiveLearning.default({ enable: false }),
57
70
  namespace: z
58
71
  .string()
59
72
  .regex(
@@ -79,7 +92,7 @@ export class Zai {
79
92
 
80
93
  public constructor(config: ZaiConfig) {
81
94
  this._originalConfig = config
82
- const parsed = ZaiConfig.parse(config)
95
+ const parsed = _ZaiConfig.parse(config) as ZaiConfig
83
96
 
84
97
  this.client = Cognitive.isCognitiveClient(parsed.client)
85
98
  ? (parsed.client as unknown as Cognitive)
@@ -88,10 +101,13 @@ export class Zai {
88
101
  this.namespace = parsed.namespace
89
102
  this._userId = parsed.userId
90
103
  this.Model = parsed.modelId as ModelId
91
- this.activeLearning = parsed.activeLearning
104
+ this.activeLearning = parsed.activeLearning as ActiveLearning
92
105
 
93
106
  this.adapter = parsed.activeLearning?.enable
94
- ? new TableAdapter({ client: this.client.client, tableName: parsed.activeLearning.tableName })
107
+ ? new TableAdapter({
108
+ client: this.client.client as unknown as Client,
109
+ tableName: parsed.activeLearning.tableName,
110
+ })
95
111
  : new MemoryAdapter([])
96
112
  }
97
113