@botpress/zai 1.1.0 → 1.2.0
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/README.md +1 -1
- package/build.ts +9 -0
- package/dist/adapters/botpress-table.js +21 -21
- package/dist/index.d.ts +22 -521
- package/dist/operations/check.js +15 -3
- package/dist/operations/extract.js +28 -8
- package/dist/operations/filter.js +15 -3
- package/dist/operations/label.js +15 -3
- package/dist/operations/rewrite.js +18 -6
- package/dist/operations/summarize.js +6 -5
- package/dist/operations/text.js +4 -3
- package/dist/utils.js +0 -6
- package/dist/zai.js +28 -68
- package/e2e/data/cache.jsonl +107 -0
- package/{src/operations/__tests/index.ts → e2e/utils.ts} +18 -16
- package/package.json +23 -21
- package/src/adapters/adapter.ts +2 -2
- package/src/adapters/botpress-table.ts +36 -36
- package/src/adapters/memory.ts +3 -3
- package/src/operations/check.ts +31 -17
- package/src/operations/errors.ts +1 -1
- package/src/operations/extract.ts +49 -31
- package/src/operations/filter.ts +36 -23
- package/src/operations/label.ts +32 -19
- package/src/operations/rewrite.ts +28 -15
- package/src/operations/summarize.ts +11 -9
- package/src/operations/text.ts +7 -5
- package/src/utils.ts +5 -14
- package/src/zai.ts +45 -91
- package/tsconfig.json +2 -22
- package/dist/models.js +0 -387
- package/src/models.ts +0 -394
- package/src/operations/__tests/cache.jsonl +0 -101
- package/src/sdk-interfaces/llm/generateContent.ts +0 -127
- package/src/sdk-interfaces/llm/listLanguageModels.ts +0 -19
- /package/{src/operations/__tests → e2e/data}/botpress_docs.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
// eslint-disable consistent-type-definitions
|
|
2
|
+
import { z, ZodObject } from '@bpinternal/zui'
|
|
2
3
|
|
|
3
4
|
import JSON5 from 'json5'
|
|
4
5
|
import { jsonrepair } from 'jsonrepair'
|
|
@@ -18,18 +19,13 @@ const Options = z.object({
|
|
|
18
19
|
.max(100_000)
|
|
19
20
|
.optional()
|
|
20
21
|
.describe('The maximum number of tokens per chunk')
|
|
21
|
-
.default(16_000)
|
|
22
|
+
.default(16_000),
|
|
22
23
|
})
|
|
23
24
|
|
|
24
25
|
declare module '@botpress/zai' {
|
|
25
26
|
interface Zai {
|
|
26
27
|
/** Extracts one or many elements from an arbitrary input */
|
|
27
|
-
extract<S extends z.AnyZodObject>(input: unknown, schema: S, options?: Options): Promise<z.
|
|
28
|
-
extract<S extends z.AnyZodObject>(
|
|
29
|
-
input: unknown,
|
|
30
|
-
schema: z.ZodArray<S>,
|
|
31
|
-
options?: Options
|
|
32
|
-
): Promise<Array<z.infer<S>>>
|
|
28
|
+
extract<S extends z.AnyZodObject | z.ZodArray>(input: unknown, schema: S, options?: Options): Promise<z.TypeOf<S>>
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
|
|
@@ -40,21 +36,29 @@ const NO_MORE = '■NO_MORE_ELEMENT■'
|
|
|
40
36
|
Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
|
|
41
37
|
const options = Options.parse(_options ?? {})
|
|
42
38
|
const tokenizer = await this.getTokenizer()
|
|
39
|
+
await this.fetchModelDetails()
|
|
43
40
|
|
|
44
41
|
const taskId = this.taskId
|
|
45
42
|
const taskType = 'zai.extract'
|
|
46
43
|
|
|
47
|
-
const PROMPT_COMPONENT = Math.max(this.
|
|
44
|
+
const PROMPT_COMPONENT = Math.max(this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER, 100)
|
|
48
45
|
|
|
49
46
|
let isArrayOfObjects = false
|
|
50
47
|
const originalSchema = schema
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
const baseType = (schema.naked ? schema.naked() : schema)?.constructor?.name ?? 'unknown'
|
|
50
|
+
|
|
51
|
+
if (baseType === 'ZodObject') {
|
|
53
52
|
// Do nothing
|
|
54
|
-
} else if (
|
|
55
|
-
|
|
53
|
+
} else if (baseType === 'ZodArray') {
|
|
54
|
+
let elementType = (schema as any).element
|
|
55
|
+
if (elementType.naked) {
|
|
56
|
+
elementType = elementType.naked()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (elementType?.constructor?.name === 'ZodObject') {
|
|
56
60
|
isArrayOfObjects = true
|
|
57
|
-
schema =
|
|
61
|
+
schema = elementType
|
|
58
62
|
} else {
|
|
59
63
|
throw new Error('Schema must be a ZodObject or a ZodArray<ZodObject>')
|
|
60
64
|
}
|
|
@@ -65,9 +69,12 @@ Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
|
|
|
65
69
|
const schemaTypescript = schema.toTypescript({ declaration: false })
|
|
66
70
|
const schemaLength = tokenizer.count(schemaTypescript)
|
|
67
71
|
|
|
68
|
-
options.chunkLength = Math.min(
|
|
72
|
+
options.chunkLength = Math.min(
|
|
73
|
+
options.chunkLength,
|
|
74
|
+
this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER - schemaLength
|
|
75
|
+
)
|
|
69
76
|
|
|
70
|
-
const keys = Object.keys(schema.shape)
|
|
77
|
+
const keys = Object.keys((schema as ZodObject).shape)
|
|
71
78
|
|
|
72
79
|
let inputAsString = stringify(input)
|
|
73
80
|
|
|
@@ -116,7 +123,7 @@ Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
|
|
|
116
123
|
taskType,
|
|
117
124
|
taskId,
|
|
118
125
|
input: inputAsString,
|
|
119
|
-
instructions: options.instructions
|
|
126
|
+
instructions: options.instructions,
|
|
120
127
|
})
|
|
121
128
|
)
|
|
122
129
|
|
|
@@ -124,7 +131,7 @@ Zai.prototype.extract = async function (this: Zai, input, schema, _options) {
|
|
|
124
131
|
? await this.adapter.getExamples<string, unknown>({
|
|
125
132
|
input: inputAsString,
|
|
126
133
|
taskType,
|
|
127
|
-
taskId
|
|
134
|
+
taskId,
|
|
128
135
|
})
|
|
129
136
|
: []
|
|
130
137
|
|
|
@@ -144,13 +151,13 @@ The end.`,
|
|
|
144
151
|
extracted: [
|
|
145
152
|
{
|
|
146
153
|
name: 'Alice',
|
|
147
|
-
age: 30
|
|
154
|
+
age: 30,
|
|
148
155
|
},
|
|
149
156
|
{
|
|
150
157
|
name: 'Bob',
|
|
151
|
-
age: 25
|
|
152
|
-
}
|
|
153
|
-
]
|
|
158
|
+
age: 25,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
154
161
|
}
|
|
155
162
|
: {
|
|
156
163
|
input: `The story goes as follow.
|
|
@@ -158,14 +165,14 @@ Once upon a time, there was a person named Alice who was 30 years old.
|
|
|
158
165
|
The end.`,
|
|
159
166
|
schema: '{ name: string, age: number }',
|
|
160
167
|
instructions: 'Extract the person',
|
|
161
|
-
extracted: { name: 'Alice', age: 30 }
|
|
168
|
+
extracted: { name: 'Alice', age: 30 },
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
const userExamples = examples.map((e) => ({
|
|
165
172
|
input: e.input,
|
|
166
173
|
extracted: e.output,
|
|
167
174
|
schema: schemaTypescript,
|
|
168
|
-
instructions: options.instructions
|
|
175
|
+
instructions: options.instructions,
|
|
169
176
|
}))
|
|
170
177
|
|
|
171
178
|
let exampleId = 1
|
|
@@ -211,13 +218,13 @@ ${END}`.trim()
|
|
|
211
218
|
{
|
|
212
219
|
type: 'text' as const,
|
|
213
220
|
content: formatInput(stringify(example.input ?? null), example.schema, example.instructions),
|
|
214
|
-
role: 'user' as const
|
|
221
|
+
role: 'user' as const,
|
|
215
222
|
},
|
|
216
223
|
{
|
|
217
224
|
type: 'text' as const,
|
|
218
225
|
content: formatOutput(example.extracted),
|
|
219
|
-
role: 'assistant' as const
|
|
220
|
-
}
|
|
226
|
+
role: 'assistant' as const,
|
|
227
|
+
},
|
|
221
228
|
]
|
|
222
229
|
|
|
223
230
|
const allExamples = takeUntilTokens(
|
|
@@ -228,7 +235,7 @@ ${END}`.trim()
|
|
|
228
235
|
.map(formatExample)
|
|
229
236
|
.flat()
|
|
230
237
|
|
|
231
|
-
const output = await this.callModel({
|
|
238
|
+
const { output, meta } = await this.callModel({
|
|
232
239
|
systemPrompt: `
|
|
233
240
|
Extract the following information from the input:
|
|
234
241
|
${schemaTypescript}
|
|
@@ -242,9 +249,9 @@ ${instructions.map((x) => `• ${x}`).join('\n')}
|
|
|
242
249
|
{
|
|
243
250
|
role: 'user',
|
|
244
251
|
type: 'text',
|
|
245
|
-
content: formatInput(inputAsString, schemaTypescript, options.instructions ?? '')
|
|
246
|
-
}
|
|
247
|
-
]
|
|
252
|
+
content: formatInput(inputAsString, schemaTypescript, options.instructions ?? ''),
|
|
253
|
+
},
|
|
254
|
+
],
|
|
248
255
|
})
|
|
249
256
|
|
|
250
257
|
const answer = output.choices[0]?.content as string
|
|
@@ -283,7 +290,18 @@ ${instructions.map((x) => `• ${x}`).join('\n')}
|
|
|
283
290
|
instructions: options.instructions ?? 'No specific instructions',
|
|
284
291
|
input: inputAsString,
|
|
285
292
|
output: final,
|
|
286
|
-
metadata:
|
|
293
|
+
metadata: {
|
|
294
|
+
cost: {
|
|
295
|
+
input: meta.cost.input,
|
|
296
|
+
output: meta.cost.output,
|
|
297
|
+
},
|
|
298
|
+
latency: meta.latency,
|
|
299
|
+
model: this.Model,
|
|
300
|
+
tokens: {
|
|
301
|
+
input: meta.tokens.input,
|
|
302
|
+
output: meta.tokens.output,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
287
305
|
})
|
|
288
306
|
}
|
|
289
307
|
|
package/src/operations/filter.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// eslint-disable consistent-type-definitions
|
|
1
2
|
import { z } from '@bpinternal/zui'
|
|
2
3
|
|
|
3
4
|
import { clamp } from 'lodash-es'
|
|
@@ -9,7 +10,7 @@ type Example = z.input<typeof Example>
|
|
|
9
10
|
const Example = z.object({
|
|
10
11
|
input: z.any(),
|
|
11
12
|
filter: z.boolean(),
|
|
12
|
-
reason: z.string().optional()
|
|
13
|
+
reason: z.string().optional(),
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
export type Options = z.input<typeof Options>
|
|
@@ -21,7 +22,7 @@ const Options = z.object({
|
|
|
21
22
|
.optional()
|
|
22
23
|
.describe('The maximum number of tokens per item')
|
|
23
24
|
.default(250),
|
|
24
|
-
examples: z.array(Example).describe('Examples to filter the condition against').default([])
|
|
25
|
+
examples: z.array(Example).describe('Examples to filter the condition against').default([]),
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
declare module '@botpress/zai' {
|
|
@@ -36,12 +37,13 @@ const END = '■END■'
|
|
|
36
37
|
Zai.prototype.filter = async function (this: Zai, input, condition, _options) {
|
|
37
38
|
const options = Options.parse(_options ?? {})
|
|
38
39
|
const tokenizer = await this.getTokenizer()
|
|
40
|
+
await this.fetchModelDetails()
|
|
39
41
|
|
|
40
42
|
const taskId = this.taskId
|
|
41
43
|
const taskType = 'zai.filter'
|
|
42
44
|
|
|
43
45
|
const MAX_ITEMS_PER_CHUNK = 50
|
|
44
|
-
const TOKENS_TOTAL_MAX = this.
|
|
46
|
+
const TOKENS_TOTAL_MAX = this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER - PROMPT_OUTPUT_BUFFER
|
|
45
47
|
const TOKENS_EXAMPLES_MAX = Math.floor(Math.max(250, TOKENS_TOTAL_MAX * 0.5))
|
|
46
48
|
const TOKENS_CONDITION_MAX = clamp(TOKENS_TOTAL_MAX * 0.25, 250, tokenizer.count(condition))
|
|
47
49
|
const TOKENS_INPUT_ARRAY_MAX = TOKENS_TOTAL_MAX - TOKENS_EXAMPLES_MAX - TOKENS_CONDITION_MAX
|
|
@@ -99,36 +101,36 @@ ${examples.map((x, idx) => `■${idx}:${!!x.filter ? 'true' : 'false'}:${x.reaso
|
|
|
99
101
|
{
|
|
100
102
|
input: 'apple',
|
|
101
103
|
filter: true,
|
|
102
|
-
reason: 'Apples are fruits'
|
|
104
|
+
reason: 'Apples are fruits',
|
|
103
105
|
},
|
|
104
106
|
{
|
|
105
107
|
input: 'Apple Inc.',
|
|
106
108
|
filter: false,
|
|
107
|
-
reason: 'Apple Inc. is a company, not a fruit'
|
|
109
|
+
reason: 'Apple Inc. is a company, not a fruit',
|
|
108
110
|
},
|
|
109
111
|
{
|
|
110
112
|
input: 'banana',
|
|
111
113
|
filter: true,
|
|
112
|
-
reason: 'Bananas are fruits'
|
|
114
|
+
reason: 'Bananas are fruits',
|
|
113
115
|
},
|
|
114
116
|
{
|
|
115
117
|
input: 'potato',
|
|
116
118
|
filter: false,
|
|
117
|
-
reason: 'Potatoes are vegetables'
|
|
118
|
-
}
|
|
119
|
+
reason: 'Potatoes are vegetables',
|
|
120
|
+
},
|
|
119
121
|
]
|
|
120
122
|
|
|
121
123
|
const genericExamplesMessages = [
|
|
122
124
|
{
|
|
123
125
|
type: 'text' as const,
|
|
124
126
|
content: formatInput(genericExamples, 'is a fruit'),
|
|
125
|
-
role: 'user' as const
|
|
127
|
+
role: 'user' as const,
|
|
126
128
|
},
|
|
127
129
|
{
|
|
128
130
|
type: 'text' as const,
|
|
129
131
|
content: formatExamples(genericExamples),
|
|
130
|
-
role: 'assistant' as const
|
|
131
|
-
}
|
|
132
|
+
role: 'assistant' as const,
|
|
133
|
+
},
|
|
132
134
|
]
|
|
133
135
|
|
|
134
136
|
const filterChunk = async (chunk: typeof input) => {
|
|
@@ -138,10 +140,10 @@ ${examples.map((x, idx) => `■${idx}:${!!x.filter ? 'true' : 'false'}:${x.reaso
|
|
|
138
140
|
// The Table API can't search for a huge input string
|
|
139
141
|
input: JSON.stringify(chunk).slice(0, 1000),
|
|
140
142
|
taskType,
|
|
141
|
-
taskId
|
|
143
|
+
taskId,
|
|
142
144
|
})
|
|
143
145
|
.then((x) =>
|
|
144
|
-
x.map((y) => ({ filter: y.output as boolean, input: y.input, reason: y.explanation } satisfies Example)
|
|
146
|
+
x.map((y) => ({ filter: y.output as boolean, input: y.input, reason: y.explanation }) satisfies Example)
|
|
145
147
|
)
|
|
146
148
|
: []
|
|
147
149
|
|
|
@@ -153,16 +155,16 @@ ${examples.map((x, idx) => `■${idx}:${!!x.filter ? 'true' : 'false'}:${x.reaso
|
|
|
153
155
|
{
|
|
154
156
|
type: 'text' as const,
|
|
155
157
|
content: formatInput(allExamples, condition),
|
|
156
|
-
role: 'user' as const
|
|
158
|
+
role: 'user' as const,
|
|
157
159
|
},
|
|
158
160
|
{
|
|
159
161
|
type: 'text' as const,
|
|
160
162
|
content: formatExamples(allExamples),
|
|
161
|
-
role: 'assistant' as const
|
|
162
|
-
}
|
|
163
|
+
role: 'assistant' as const,
|
|
164
|
+
},
|
|
163
165
|
]
|
|
164
166
|
|
|
165
|
-
const output = await this.callModel({
|
|
167
|
+
const { output, meta } = await this.callModel({
|
|
166
168
|
systemPrompt: `
|
|
167
169
|
You are given a list of items. Your task is to filter out the items that meet the condition below.
|
|
168
170
|
You need to return the full list of items with the format:
|
|
@@ -179,12 +181,12 @@ The condition is: "${condition}"
|
|
|
179
181
|
{
|
|
180
182
|
type: 'text',
|
|
181
183
|
content: formatInput(
|
|
182
|
-
chunk.map((x) => ({ input: x } as Example)
|
|
184
|
+
chunk.map((x) => ({ input: x }) as Example),
|
|
183
185
|
condition
|
|
184
186
|
),
|
|
185
|
-
role: 'user'
|
|
186
|
-
}
|
|
187
|
-
]
|
|
187
|
+
role: 'user',
|
|
188
|
+
},
|
|
189
|
+
],
|
|
188
190
|
})
|
|
189
191
|
|
|
190
192
|
const answer = output.choices[0]?.content as string
|
|
@@ -207,7 +209,7 @@ The condition is: "${condition}"
|
|
|
207
209
|
taskId,
|
|
208
210
|
taskType,
|
|
209
211
|
input: JSON.stringify(chunk),
|
|
210
|
-
condition
|
|
212
|
+
condition,
|
|
211
213
|
})
|
|
212
214
|
)
|
|
213
215
|
|
|
@@ -218,7 +220,18 @@ The condition is: "${condition}"
|
|
|
218
220
|
input: JSON.stringify(chunk),
|
|
219
221
|
output: partial,
|
|
220
222
|
instructions: condition,
|
|
221
|
-
metadata:
|
|
223
|
+
metadata: {
|
|
224
|
+
cost: {
|
|
225
|
+
input: meta.cost.input,
|
|
226
|
+
output: meta.cost.output,
|
|
227
|
+
},
|
|
228
|
+
latency: meta.latency,
|
|
229
|
+
model: this.Model,
|
|
230
|
+
tokens: {
|
|
231
|
+
input: meta.tokens.input,
|
|
232
|
+
output: meta.tokens.output,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
222
235
|
})
|
|
223
236
|
}
|
|
224
237
|
|
package/src/operations/label.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// eslint-disable consistent-type-definitions
|
|
1
2
|
import { z } from '@bpinternal/zui'
|
|
2
3
|
|
|
3
4
|
import { clamp, chunk } from 'lodash-es'
|
|
@@ -11,7 +12,7 @@ const LABELS = {
|
|
|
11
12
|
PROBABLY_NOT: 'PROBABLY_NOT',
|
|
12
13
|
AMBIGUOUS: 'AMBIGUOUS',
|
|
13
14
|
PROBABLY_YES: 'PROBABLY_YES',
|
|
14
|
-
ABSOLUTELY_YES: 'ABSOLUTELY_YES'
|
|
15
|
+
ABSOLUTELY_YES: 'ABSOLUTELY_YES',
|
|
15
16
|
} as const
|
|
16
17
|
const ALL_LABELS = Object.values(LABELS).join(' | ')
|
|
17
18
|
|
|
@@ -29,7 +30,7 @@ const Options = z.object({
|
|
|
29
30
|
.array(
|
|
30
31
|
z.object({
|
|
31
32
|
input: z.any(),
|
|
32
|
-
labels: z.record(z.object({ label: z.enum(ALL_LABELS as never), explanation: z.string().optional() }))
|
|
33
|
+
labels: z.record(z.object({ label: z.enum(ALL_LABELS as never), explanation: z.string().optional() })),
|
|
33
34
|
})
|
|
34
35
|
)
|
|
35
36
|
.default([])
|
|
@@ -41,7 +42,7 @@ const Options = z.object({
|
|
|
41
42
|
.max(100_000)
|
|
42
43
|
.optional()
|
|
43
44
|
.describe('The maximum number of tokens per chunk')
|
|
44
|
-
.default(16_000)
|
|
45
|
+
.default(16_000),
|
|
45
46
|
})
|
|
46
47
|
|
|
47
48
|
type Labels<T extends string> = Record<T, string>
|
|
@@ -61,7 +62,7 @@ const Labels = z.record(z.string().min(1).max(250), z.string()).superRefine((lab
|
|
|
61
62
|
if (/[^a-zA-Z0-9_]/.test(key)) {
|
|
62
63
|
ctx.addIssue({
|
|
63
64
|
message: `The label key "${key}" must only contain alphanumeric characters and underscores`,
|
|
64
|
-
code: 'custom'
|
|
65
|
+
code: 'custom',
|
|
65
66
|
})
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -103,11 +104,12 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
|
|
|
103
104
|
const options = Options.parse(_options ?? {})
|
|
104
105
|
const labels = Labels.parse(_labels)
|
|
105
106
|
const tokenizer = await this.getTokenizer()
|
|
107
|
+
await this.fetchModelDetails()
|
|
106
108
|
|
|
107
109
|
const taskId = this.taskId
|
|
108
110
|
const taskType = 'zai.label'
|
|
109
111
|
|
|
110
|
-
const TOTAL_MAX_TOKENS = clamp(options.chunkLength, 1000, this.
|
|
112
|
+
const TOTAL_MAX_TOKENS = clamp(options.chunkLength, 1000, this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER)
|
|
111
113
|
const CHUNK_EXAMPLES_MAX_TOKENS = clamp(Math.floor(TOTAL_MAX_TOKENS * 0.5), 250, 10_000)
|
|
112
114
|
const CHUNK_INPUT_MAX_TOKENS = clamp(
|
|
113
115
|
TOTAL_MAX_TOKENS - CHUNK_EXAMPLES_MAX_TOKENS,
|
|
@@ -144,7 +146,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
|
|
|
144
146
|
taskType,
|
|
145
147
|
taskId,
|
|
146
148
|
input: inputAsString,
|
|
147
|
-
instructions: options.instructions ?? ''
|
|
149
|
+
instructions: options.instructions ?? '',
|
|
148
150
|
})
|
|
149
151
|
)
|
|
150
152
|
|
|
@@ -167,7 +169,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
|
|
|
167
169
|
>({
|
|
168
170
|
input: inputAsString,
|
|
169
171
|
taskType,
|
|
170
|
-
taskId
|
|
172
|
+
taskId,
|
|
171
173
|
})
|
|
172
174
|
: []
|
|
173
175
|
|
|
@@ -182,7 +184,7 @@ Zai.prototype.label = async function <T extends string>(this: Zai, input, _label
|
|
|
182
184
|
explanation: string
|
|
183
185
|
label: Label
|
|
184
186
|
}
|
|
185
|
-
}
|
|
187
|
+
},
|
|
186
188
|
})
|
|
187
189
|
})
|
|
188
190
|
|
|
@@ -209,7 +211,7 @@ Expert Example #${idx + 1}
|
|
|
209
211
|
|
|
210
212
|
<|start_input|>
|
|
211
213
|
${stringify(example.input)}
|
|
212
|
-
<|end_input|>`.trim()
|
|
214
|
+
<|end_input|>`.trim(),
|
|
213
215
|
},
|
|
214
216
|
{
|
|
215
217
|
type: 'text' as const,
|
|
@@ -225,8 +227,8 @@ ${Object.keys(example.output)
|
|
|
225
227
|
)
|
|
226
228
|
.join('\n')}
|
|
227
229
|
${END}
|
|
228
|
-
`.trim()
|
|
229
|
-
}
|
|
230
|
+
`.trim(),
|
|
231
|
+
},
|
|
230
232
|
])
|
|
231
233
|
.flat()
|
|
232
234
|
|
|
@@ -238,7 +240,7 @@ ${END}
|
|
|
238
240
|
})
|
|
239
241
|
.join('\n\n')
|
|
240
242
|
|
|
241
|
-
const output = await this.callModel({
|
|
243
|
+
const { output, meta } = await this.callModel({
|
|
242
244
|
stopSequences: [END],
|
|
243
245
|
systemPrompt: `
|
|
244
246
|
You need to tag the input with the following labels based on the question asked:
|
|
@@ -286,9 +288,9 @@ Where \`x\` is one of the following: ${ALL_LABELS}
|
|
|
286
288
|
|
|
287
289
|
Remember: In your \`explanation\`, please refer to the Expert Examples # (and quote them) that are relevant to ground your decision-making process.
|
|
288
290
|
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
|
-
]
|
|
291
|
+
For example, you can say: "According to Expert Example #1, ..."`.trim(),
|
|
292
|
+
},
|
|
293
|
+
],
|
|
292
294
|
})
|
|
293
295
|
|
|
294
296
|
const answer = output.choices[0].content as string
|
|
@@ -300,12 +302,12 @@ For example, you can say: "According to Expert Example #1, ..."`.trim()
|
|
|
300
302
|
const label = parseLabel(match[2])
|
|
301
303
|
acc[key] = {
|
|
302
304
|
explanation,
|
|
303
|
-
label
|
|
305
|
+
label,
|
|
304
306
|
}
|
|
305
307
|
} else {
|
|
306
308
|
acc[key] = {
|
|
307
309
|
explanation: '',
|
|
308
|
-
label: LABELS.AMBIGUOUS
|
|
310
|
+
label: LABELS.AMBIGUOUS,
|
|
309
311
|
}
|
|
310
312
|
}
|
|
311
313
|
return acc
|
|
@@ -322,9 +324,20 @@ For example, you can say: "According to Expert Example #1, ..."`.trim()
|
|
|
322
324
|
taskType,
|
|
323
325
|
taskId,
|
|
324
326
|
instructions: options.instructions ?? '',
|
|
325
|
-
metadata:
|
|
327
|
+
metadata: {
|
|
328
|
+
cost: {
|
|
329
|
+
input: meta.cost.input,
|
|
330
|
+
output: meta.cost.output,
|
|
331
|
+
},
|
|
332
|
+
latency: meta.latency,
|
|
333
|
+
model: this.Model,
|
|
334
|
+
tokens: {
|
|
335
|
+
input: meta.tokens.input,
|
|
336
|
+
output: meta.tokens.output,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
326
339
|
input: inputAsString,
|
|
327
|
-
output: final
|
|
340
|
+
output: final,
|
|
328
341
|
})
|
|
329
342
|
}
|
|
330
343
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// eslint-disable consistent-type-definitions
|
|
1
2
|
import { z } from '@bpinternal/zui'
|
|
2
3
|
|
|
3
4
|
import { fastHash, stringify, takeUntilTokens } from '../utils'
|
|
@@ -7,13 +8,13 @@ import { PROMPT_INPUT_BUFFER } from './constants'
|
|
|
7
8
|
type Example = z.input<typeof Example> & { instructions?: string }
|
|
8
9
|
const Example = z.object({
|
|
9
10
|
input: z.string(),
|
|
10
|
-
output: z.string()
|
|
11
|
+
output: z.string(),
|
|
11
12
|
})
|
|
12
13
|
|
|
13
14
|
export type Options = z.input<typeof Options>
|
|
14
15
|
const Options = z.object({
|
|
15
16
|
examples: z.array(Example).default([]),
|
|
16
|
-
length: z.number().min(10).max(16_000).optional().describe('The maximum number of tokens to generate')
|
|
17
|
+
length: z.number().min(10).max(16_000).optional().describe('The maximum number of tokens to generate'),
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
declare module '@botpress/zai' {
|
|
@@ -29,18 +30,19 @@ const END = '■END■'
|
|
|
29
30
|
Zai.prototype.rewrite = async function (this: Zai, original, prompt, _options) {
|
|
30
31
|
const options = Options.parse(_options ?? {})
|
|
31
32
|
const tokenizer = await this.getTokenizer()
|
|
33
|
+
await this.fetchModelDetails()
|
|
32
34
|
|
|
33
35
|
const taskId = this.taskId
|
|
34
36
|
const taskType = 'zai.rewrite'
|
|
35
37
|
|
|
36
|
-
const INPUT_COMPONENT_SIZE = Math.max(100, (this.
|
|
38
|
+
const INPUT_COMPONENT_SIZE = Math.max(100, (this.ModelDetails.input.maxTokens - PROMPT_INPUT_BUFFER) / 2)
|
|
37
39
|
prompt = tokenizer.truncate(prompt, INPUT_COMPONENT_SIZE)
|
|
38
40
|
|
|
39
41
|
const inputSize = tokenizer.count(original) + tokenizer.count(prompt)
|
|
40
|
-
const maxInputSize = this.
|
|
42
|
+
const maxInputSize = this.ModelDetails.input.maxTokens - tokenizer.count(prompt) - PROMPT_INPUT_BUFFER
|
|
41
43
|
if (inputSize > maxInputSize) {
|
|
42
44
|
throw new Error(
|
|
43
|
-
`The input size is ${inputSize} tokens long, which is more than the maximum of ${maxInputSize} tokens for this model (${this.
|
|
45
|
+
`The input size is ${inputSize} tokens long, which is more than the maximum of ${maxInputSize} tokens for this model (${this.ModelDetails.name} = ${this.ModelDetails.input.maxTokens} tokens)`
|
|
44
46
|
)
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -69,27 +71,27 @@ ${END}
|
|
|
69
71
|
taskId,
|
|
70
72
|
taskType,
|
|
71
73
|
input: original,
|
|
72
|
-
prompt
|
|
74
|
+
prompt,
|
|
73
75
|
})
|
|
74
76
|
)
|
|
75
77
|
|
|
76
78
|
const formatExample = ({ input, output, instructions }: Example) => {
|
|
77
79
|
return [
|
|
78
80
|
{ type: 'text' as const, role: 'user' as const, content: format(input, instructions || prompt) },
|
|
79
|
-
{ type: 'text' as const, role: 'assistant' as const, content: `${START}${output}${END}` }
|
|
81
|
+
{ type: 'text' as const, role: 'assistant' as const, content: `${START}${output}${END}` },
|
|
80
82
|
]
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
const defaultExamples: Example[] = [
|
|
84
86
|
{ input: 'Hello, how are you?', output: 'Bonjour, comment ça va?', instructions: 'translate to French' },
|
|
85
|
-
{ input: '1\n2\n3', output: '3\n2\n1', instructions: 'reverse the order' }
|
|
87
|
+
{ input: '1\n2\n3', output: '3\n2\n1', instructions: 'reverse the order' },
|
|
86
88
|
]
|
|
87
89
|
|
|
88
90
|
const tableExamples = taskId
|
|
89
91
|
? await this.adapter.getExamples<string, string>({
|
|
90
92
|
input: original,
|
|
91
93
|
taskId,
|
|
92
|
-
taskType
|
|
94
|
+
taskType,
|
|
93
95
|
})
|
|
94
96
|
: []
|
|
95
97
|
|
|
@@ -100,10 +102,10 @@ ${END}
|
|
|
100
102
|
|
|
101
103
|
const savedExamples: Example[] = [
|
|
102
104
|
...tableExamples.map((x) => ({ input: x.input as string, output: x.output as string })),
|
|
103
|
-
...options.examples
|
|
105
|
+
...options.examples,
|
|
104
106
|
]
|
|
105
107
|
|
|
106
|
-
const REMAINING_TOKENS = this.
|
|
108
|
+
const REMAINING_TOKENS = this.ModelDetails.input.maxTokens - tokenizer.count(prompt) - PROMPT_INPUT_BUFFER
|
|
107
109
|
const examples = takeUntilTokens(
|
|
108
110
|
savedExamples.length ? savedExamples : defaultExamples,
|
|
109
111
|
REMAINING_TOKENS,
|
|
@@ -112,14 +114,14 @@ ${END}
|
|
|
112
114
|
.map(formatExample)
|
|
113
115
|
.flat()
|
|
114
116
|
|
|
115
|
-
const output = await this.callModel({
|
|
117
|
+
const { output, meta } = await this.callModel({
|
|
116
118
|
systemPrompt: `
|
|
117
119
|
Rewrite the text between the ${START} and ${END} tags to match the user prompt.
|
|
118
120
|
${instructions.map((x) => `• ${x}`).join('\n')}
|
|
119
121
|
`.trim(),
|
|
120
122
|
messages: [...examples, { type: 'text', content: format(original, prompt), role: 'user' }],
|
|
121
123
|
maxTokens: options.length,
|
|
122
|
-
stopSequences: [END]
|
|
124
|
+
stopSequences: [END],
|
|
123
125
|
})
|
|
124
126
|
|
|
125
127
|
let result = output.choices[0]?.content as string
|
|
@@ -135,12 +137,23 @@ ${instructions.map((x) => `• ${x}`).join('\n')}
|
|
|
135
137
|
if (taskId) {
|
|
136
138
|
await this.adapter.saveExample({
|
|
137
139
|
key: Key,
|
|
138
|
-
metadata:
|
|
140
|
+
metadata: {
|
|
141
|
+
cost: {
|
|
142
|
+
input: meta.cost.input,
|
|
143
|
+
output: meta.cost.output,
|
|
144
|
+
},
|
|
145
|
+
latency: meta.latency,
|
|
146
|
+
model: this.Model,
|
|
147
|
+
tokens: {
|
|
148
|
+
input: meta.tokens.input,
|
|
149
|
+
output: meta.tokens.output,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
139
152
|
instructions: prompt,
|
|
140
153
|
input: original,
|
|
141
154
|
output: result,
|
|
142
155
|
taskType,
|
|
143
|
-
taskId
|
|
156
|
+
taskId,
|
|
144
157
|
})
|
|
145
158
|
}
|
|
146
159
|
|