@botpress/zai 2.2.0 → 2.3.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 CHANGED
@@ -129,7 +129,56 @@ const organized = await zai.group(largeArray, {
129
129
  })
130
130
  ```
131
131
 
132
- ### 7. Text - Generate content
132
+ ### 7. Rate - Score items on a 1-5 scale
133
+
134
+ ```typescript
135
+ // Auto-generate criteria (returns total score)
136
+ const scores = await zai.rate(products, 'is it a good value product?')
137
+ // Result: [12, 8, 15] (total scores for each item)
138
+
139
+ // Get detailed ratings
140
+ const { output } = await zai.rate(products, 'is it a good value product?').result()
141
+ // Result: [
142
+ // { affordability: 4, quality: 5, features: 3, total: 12 },
143
+ // { affordability: 3, quality: 2, features: 3, total: 8 },
144
+ // ...
145
+ // ]
146
+
147
+ // Use fixed criteria
148
+ const ratings = await zai.rate(passwords, {
149
+ length: 'password length (12+ chars = very_good, 8-11 = good, 6-7 = average, 4-5 = bad, <4 = very_bad)',
150
+ complexity: 'character variety (all types = very_good, 3 types = good, 2 types = average, 1 type = bad)',
151
+ strength: 'overall password strength',
152
+ })
153
+ // Result: [
154
+ // { length: 5, complexity: 5, strength: 5, total: 15 },
155
+ // { length: 1, complexity: 1, strength: 1, total: 3 },
156
+ // ]
157
+
158
+ // Rate large datasets efficiently (parallelized)
159
+ const allRatings = await zai.rate(Array(500).fill(item), 'how complete is this?')
160
+ // Processes ~500 items in ~120ms with automatic chunking
161
+ ```
162
+
163
+ ### 8. Sort - Order items with natural language
164
+
165
+ ```typescript
166
+ // Sort by natural criteria
167
+ const sorted = await zai.sort(emails, 'sort by urgency')
168
+ // LLM determines criteria and orders items accordingly
169
+
170
+ // Sort with detailed results
171
+ const { output } = await zai.sort(tasks, 'sort by priority').result()
172
+ // output includes scoring breakdown for each item
173
+
174
+ // Complex multi-criteria sorting
175
+ const prioritized = await zai.sort(tickets, 'sort by customer importance and issue severity')
176
+
177
+ // Sort large datasets efficiently (parallelized with chunking)
178
+ const orderedItems = await zai.sort(Array(500).fill(item), 'sort by relevance')
179
+ ```
180
+
181
+ ### 9. Text - Generate content
133
182
 
134
183
  ```typescript
135
184
  const blogPost = await zai.text('Write about the future of AI', {
@@ -138,7 +187,7 @@ const blogPost = await zai.text('Write about the future of AI', {
138
187
  })
139
188
  ```
140
189
 
141
- ### 8. Summarize - Create summaries
190
+ ### 10. Summarize - Create summaries
142
191
 
143
192
  ```typescript
144
193
  // Simple summary
@@ -263,6 +312,8 @@ setTimeout(() => controller.abort(), 5000)
263
312
  - `.rewrite(content, instruction, options?)` - Transform text
264
313
  - `.filter(items, condition, options?)` - Filter array items
265
314
  - `.group(items, options?)` - Organize items into categories
315
+ - `.rate(items, instructions, options?)` - Rate items on 1-5 scale
316
+ - `.sort(items, instructions, options?)` - Order items with natural language
266
317
  - `.text(prompt, options?)` - Generate text
267
318
  - `.summarize(content, options?)` - Create summary
268
319
 
package/dist/index.d.ts CHANGED
@@ -166,14 +166,14 @@ declare class Response<T = any, S = T> implements PromiseLike<S> {
166
166
  }>;
167
167
  }
168
168
 
169
- type Options$7 = {
169
+ type Options$9 = {
170
170
  /** The maximum number of tokens to generate */
171
171
  length?: number;
172
172
  };
173
173
  declare module '@botpress/zai' {
174
174
  interface Zai {
175
175
  /** Generates a text of the desired length according to the prompt */
176
- text(prompt: string, options?: Options$7): Response<string>;
176
+ text(prompt: string, options?: Options$9): Response<string>;
177
177
  }
178
178
  }
179
179
 
@@ -182,7 +182,7 @@ type Example$3 = {
182
182
  output: string;
183
183
  instructions?: string;
184
184
  };
185
- type Options$6 = {
185
+ type Options$8 = {
186
186
  /** Examples to guide the rewriting */
187
187
  examples?: Array<Example$3>;
188
188
  /** The maximum number of tokens to generate */
@@ -191,11 +191,11 @@ type Options$6 = {
191
191
  declare module '@botpress/zai' {
192
192
  interface Zai {
193
193
  /** Rewrites a string according to match the prompt */
194
- rewrite(original: string, prompt: string, options?: Options$6): Response<string>;
194
+ rewrite(original: string, prompt: string, options?: Options$8): Response<string>;
195
195
  }
196
196
  }
197
197
 
198
- type Options$5 = {
198
+ type Options$7 = {
199
199
  /** What should the text be summarized to? */
200
200
  prompt?: string;
201
201
  /** How to format the example text */
@@ -215,7 +215,7 @@ type Options$5 = {
215
215
  declare module '@botpress/zai' {
216
216
  interface Zai {
217
217
  /** Summarizes a text of any length to a summary of the desired length */
218
- summarize(original: string, options?: Options$5): Response<string>;
218
+ summarize(original: string, options?: Options$7): Response<string>;
219
219
  }
220
220
  }
221
221
 
@@ -225,14 +225,14 @@ type Example$2 = {
225
225
  reason?: string;
226
226
  condition?: string;
227
227
  };
228
- type Options$4 = {
228
+ type Options$6 = {
229
229
  /** Examples to check the condition against */
230
230
  examples?: Array<Example$2>;
231
231
  };
232
232
  declare module '@botpress/zai' {
233
233
  interface Zai {
234
234
  /** Checks wether a condition is true or not */
235
- check(input: unknown, condition: string, options?: Options$4): Response<{
235
+ check(input: unknown, condition: string, options?: Options$6): Response<{
236
236
  /** Whether the condition is true or not */
237
237
  value: boolean;
238
238
  /** The explanation of the decision */
@@ -246,7 +246,7 @@ type Example$1 = {
246
246
  filter: boolean;
247
247
  reason?: string;
248
248
  };
249
- type Options$3 = {
249
+ type Options$5 = {
250
250
  /** The maximum number of tokens per item */
251
251
  tokensPerItem?: number;
252
252
  /** Examples to filter the condition against */
@@ -255,11 +255,11 @@ type Options$3 = {
255
255
  declare module '@botpress/zai' {
256
256
  interface Zai {
257
257
  /** Filters elements of an array against a condition */
258
- filter<T>(input: Array<T>, condition: string, options?: Options$3): Response<Array<T>>;
258
+ filter<T>(input: Array<T>, condition: string, options?: Options$5): Response<Array<T>>;
259
259
  }
260
260
  }
261
261
 
262
- type Options$2 = {
262
+ type Options$4 = {
263
263
  /** Instructions to guide the user on how to extract the data */
264
264
  instructions?: string;
265
265
  /** The maximum number of tokens per chunk */
@@ -274,7 +274,7 @@ type OfType<O, T extends __Z = __Z<O>> = T extends __Z<O> ? T : never;
274
274
  declare module '@botpress/zai' {
275
275
  interface Zai {
276
276
  /** Extracts one or many elements from an arbitrary input */
277
- extract<S extends OfType<any>>(input: unknown, schema: S, options?: Options$2): Response<S['_output']>;
277
+ extract<S extends OfType<any>>(input: unknown, schema: S, options?: Options$4): Response<S['_output']>;
278
278
  }
279
279
  }
280
280
 
@@ -293,7 +293,7 @@ type Example<T extends string> = {
293
293
  explanation?: string;
294
294
  }>>;
295
295
  };
296
- type Options$1<T extends string> = {
296
+ type Options$3<T extends string> = {
297
297
  /** Examples to help the user make a decision */
298
298
  examples?: Array<Example<T>>;
299
299
  /** Instructions to guide the user on how to extract the data */
@@ -305,7 +305,7 @@ type Labels<T extends string> = Record<T, string>;
305
305
  declare module '@botpress/zai' {
306
306
  interface Zai {
307
307
  /** Tags the provided input with a list of predefined labels */
308
- label<T extends string>(input: unknown, labels: Labels<T>, options?: Options$1<T>): Response<{
308
+ label<T extends string>(input: unknown, labels: Labels<T>, options?: Options$3<T>): Response<{
309
309
  [K in T]: {
310
310
  explanation: string;
311
311
  value: boolean;
@@ -327,7 +327,7 @@ type InitialGroup = {
327
327
  label: string;
328
328
  elements?: unknown[];
329
329
  };
330
- type Options = {
330
+ type Options$2 = {
331
331
  instructions?: string;
332
332
  tokensPerElement?: number;
333
333
  chunkLength?: number;
@@ -335,7 +335,56 @@ type Options = {
335
335
  };
336
336
  declare module '@botpress/zai' {
337
337
  interface Zai {
338
- group<T>(input: Array<T>, options?: Options): Response<Array<Group<T>>, Record<string, T[]>>;
338
+ group<T>(input: Array<T>, options?: Options$2): Response<Array<Group<T>>, Record<string, T[]>>;
339
+ }
340
+ }
341
+
342
+ type RatingInstructions = string | Record<string, string>;
343
+ type Options$1 = {
344
+ /** The maximum number of tokens per item */
345
+ tokensPerItem?: number;
346
+ /** The maximum number of items to rate per chunk */
347
+ maxItemsPerChunk?: number;
348
+ };
349
+ type RatingResult<T extends RatingInstructions> = T extends string ? {
350
+ [key: string]: number;
351
+ total: number;
352
+ } : T extends Record<string, string> ? {
353
+ [K in keyof T]: number;
354
+ } & {
355
+ total: number;
356
+ } : never;
357
+ type SimplifiedRatingResult<T extends RatingInstructions> = T extends string ? number : RatingResult<T>;
358
+ declare module '@botpress/zai' {
359
+ interface Zai {
360
+ /**
361
+ * Rates an array of items based on provided instructions.
362
+ * Returns a number (1-5) if instructions is a string, or a Record<string, number> if instructions is a Record.
363
+ */
364
+ rate<T, I extends RatingInstructions>(input: Array<T>, instructions: I, options?: Options$1): Response<Array<RatingResult<I>>, Array<SimplifiedRatingResult<I>>>;
365
+ }
366
+ }
367
+
368
+ type Options = {
369
+ /** The maximum number of tokens per item */
370
+ tokensPerItem?: number;
371
+ };
372
+ declare module '@botpress/zai' {
373
+ interface Zai {
374
+ /**
375
+ * Sorts an array of items based on provided instructions.
376
+ * Returns the sorted array directly when awaited.
377
+ * Use .result() to get detailed scoring information including why each item got its position.
378
+ *
379
+ * @example
380
+ * // Simple usage
381
+ * const sorted = await zai.sort(items, 'from least expensive to most expensive')
382
+ *
383
+ * @example
384
+ * // Get detailed results
385
+ * const { output: sorted, usage } = await zai.sort(items, 'by priority').result()
386
+ */
387
+ sort<T>(input: Array<T>, instructions: string, options?: Options): Response<Array<T>, Array<T>>;
339
388
  }
340
389
  }
341
390
 
package/dist/index.js CHANGED
@@ -7,4 +7,6 @@ import "./operations/filter";
7
7
  import "./operations/extract";
8
8
  import "./operations/label";
9
9
  import "./operations/group";
10
+ import "./operations/rate";
11
+ import "./operations/sort";
10
12
  export { Zai };
@@ -4,7 +4,7 @@ import pLimit from "p-limit";
4
4
  import { ZaiContext } from "../context";
5
5
  import { Response } from "../response";
6
6
  import { getTokenizer } from "../tokenizer";
7
- import { stringify } from "../utils";
7
+ import { fastHash, stringify } from "../utils";
8
8
  import { Zai } from "../zai";
9
9
  import { PROMPT_INPUT_BUFFER, PROMPT_OUTPUT_BUFFER } from "./constants";
10
10
  const _InitialGroup = z.object({
@@ -27,6 +27,8 @@ const group = async (input, _options, ctx) => {
27
27
  const options = _Options.parse(_options ?? {});
28
28
  const tokenizer = await getTokenizer();
29
29
  const model = await ctx.getModel();
30
+ const taskId = ctx.taskId;
31
+ const taskType = "zai.group";
30
32
  if (input.length === 0) {
31
33
  return [];
32
34
  }
@@ -92,6 +94,27 @@ const group = async (input, _options, ctx) => {
92
94
  return chunks.length > 0 ? chunks : [[]];
93
95
  };
94
96
  const processChunk = async (elementIndices, groupIds) => {
97
+ const chunkElements = elementIndices.map((idx) => elements[idx].element);
98
+ const chunkInputStr = JSON.stringify(chunkElements);
99
+ const examples = taskId && ctx.adapter ? await ctx.adapter.getExamples({
100
+ input: chunkInputStr.slice(0, 1e3),
101
+ // Limit search string length
102
+ taskType,
103
+ taskId
104
+ }) : [];
105
+ const key = fastHash(
106
+ stringify({
107
+ taskId,
108
+ taskType,
109
+ input: chunkInputStr,
110
+ instructions: options.instructions ?? "",
111
+ groupIds: groupIds.join(",")
112
+ })
113
+ );
114
+ const exactMatch = examples.find((x) => x.key === key);
115
+ if (exactMatch && exactMatch.output) {
116
+ return exactMatch.output;
117
+ }
95
118
  const elementsText = elementIndices.map((idx, i) => {
96
119
  const elem = elements[idx];
97
120
  const truncated = tokenizer.truncate(elem.stringified, options.tokensPerElement);
@@ -102,6 +125,40 @@ const group = async (input, _options, ctx) => {
102
125
  ${groupsList.map((l) => `- ${l}`).join("\n")}
103
126
 
104
127
  ` : "";
128
+ const exampleMessages = [];
129
+ for (const example of examples.slice(0, 5)) {
130
+ try {
131
+ const exampleInput = JSON.parse(example.input);
132
+ const exampleElements = Array.isArray(exampleInput) ? exampleInput : [exampleInput];
133
+ const exampleElementsText = exampleElements.map((el, i) => `\u25A0${i}: ${stringify(el, false).slice(0, 200)}\u25A0`).join("\n");
134
+ exampleMessages.push({
135
+ type: "text",
136
+ role: "user",
137
+ content: `Expert Example - Elements to group:
138
+ ${exampleElementsText}
139
+
140
+ Group each element.`
141
+ });
142
+ const exampleOutput = example.output;
143
+ if (Array.isArray(exampleOutput) && exampleOutput.length > 0) {
144
+ const formattedAssignments = exampleOutput.map((assignment) => `\u25A0${assignment.elementIndex}:${assignment.label}\u25A0`).join("\n");
145
+ exampleMessages.push({
146
+ type: "text",
147
+ role: "assistant",
148
+ content: `${formattedAssignments}
149
+ ${END}`
150
+ });
151
+ if (example.explanation) {
152
+ exampleMessages.push({
153
+ type: "text",
154
+ role: "assistant",
155
+ content: `Reasoning: ${example.explanation}`
156
+ });
157
+ }
158
+ }
159
+ } catch {
160
+ }
161
+ }
105
162
  const systemPrompt = `You are grouping elements into cohesive groups.
106
163
 
107
164
  ${options.instructions ? `**Instructions:** ${options.instructions}
@@ -125,7 +182,7 @@ ${END}`.trim();
125
182
  const { extracted } = await ctx.generateContent({
126
183
  systemPrompt,
127
184
  stopSequences: [END],
128
- messages: [{ type: "text", role: "user", content: userPrompt }],
185
+ messages: [...exampleMessages, { type: "text", role: "user", content: userPrompt }],
129
186
  transform: (text) => {
130
187
  const assignments = [];
131
188
  const regex = /■(\d+):([^■]+)■/g;
@@ -255,6 +312,40 @@ ${END}`.trim();
255
312
  });
256
313
  }
257
314
  }
315
+ if (taskId && ctx.adapter && !ctx.controller.signal.aborted) {
316
+ const key = fastHash(
317
+ stringify({
318
+ taskId,
319
+ taskType,
320
+ input: JSON.stringify(input),
321
+ instructions: options.instructions ?? ""
322
+ })
323
+ );
324
+ const outputAssignments = [];
325
+ for (const [groupId, elementIndices] of groupElements.entries()) {
326
+ const groupInfo = groups.get(groupId);
327
+ for (const idx of elementIndices) {
328
+ outputAssignments.push({
329
+ elementIndex: idx,
330
+ label: groupInfo.label
331
+ });
332
+ }
333
+ }
334
+ await ctx.adapter.saveExample({
335
+ key,
336
+ taskType,
337
+ taskId,
338
+ input: JSON.stringify(input),
339
+ output: result,
340
+ instructions: options.instructions ?? "",
341
+ metadata: {
342
+ cost: { input: 0, output: 0 },
343
+ latency: 0,
344
+ model: ctx.modelId,
345
+ tokens: { input: 0, output: 0 }
346
+ }
347
+ });
348
+ }
258
349
  return result;
259
350
  };
260
351
  Zai.prototype.group = function(input, _options) {