@botpress/zai 2.1.18 → 2.1.20

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Cognitive, Model, BotpressClientLike, GenerateContentInput, GenerateContentOutput } from '@botpress/cognitive';
1
+ import { Cognitive, Models, Model, BotpressClientLike, GenerateContentInput, GenerateContentOutput } from '@botpress/cognitive';
2
2
  import { TextTokenizer } from '@bpinternal/thicktoken';
3
3
 
4
4
  type GenerationMetadata = {
@@ -41,7 +41,6 @@ declare abstract class Adapter {
41
41
  abstract saveExample<TInput, TOutput>(props: SaveExampleProps<TInput, TOutput>): Promise<void>;
42
42
  }
43
43
 
44
- type ModelId = Required<Parameters<Cognitive['generateContent']>[0]['model']>;
45
44
  type ActiveLearning = {
46
45
  enable: boolean;
47
46
  tableName: string;
@@ -50,7 +49,7 @@ type ActiveLearning = {
50
49
  type ZaiConfig = {
51
50
  client: BotpressClientLike | Cognitive;
52
51
  userId?: string;
53
- modelId?: ModelId | string;
52
+ modelId?: Models;
54
53
  activeLearning?: ActiveLearning;
55
54
  namespace?: string;
56
55
  };
@@ -59,7 +58,7 @@ declare class Zai {
59
58
  protected client: Cognitive;
60
59
  private _originalConfig;
61
60
  private _userId;
62
- protected Model: ModelId;
61
+ protected Model: Models;
63
62
  protected ModelDetails: Model;
64
63
  protected namespace: string;
65
64
  protected adapter: Adapter;
@@ -1,15 +1,119 @@
1
+ import { ZodError } from "@bpinternal/zui";
1
2
  export class JsonParsingError extends Error {
2
3
  constructor(json, error) {
3
- const message = `Error parsing JSON:
4
-
5
- ---JSON---
6
- ${json}
7
-
8
- ---Error---
9
-
10
- ${error}`;
4
+ const message = JsonParsingError._formatError(json, error);
11
5
  super(message);
12
6
  this.json = json;
13
7
  this.error = error;
14
8
  }
9
+ static _formatError(json, error) {
10
+ let errorMessage = "Error parsing JSON:\n\n";
11
+ errorMessage += `---JSON---
12
+ ${json}
13
+
14
+ `;
15
+ if (error instanceof ZodError) {
16
+ errorMessage += "---Validation Errors---\n\n";
17
+ errorMessage += JsonParsingError._formatZodError(error);
18
+ } else {
19
+ errorMessage += "---Error---\n\n";
20
+ errorMessage += "The JSON provided is not valid JSON.\n";
21
+ errorMessage += `Details: ${error.message}
22
+ `;
23
+ }
24
+ return errorMessage;
25
+ }
26
+ static _formatZodError(zodError) {
27
+ const issues = zodError.issues;
28
+ if (issues.length === 0) {
29
+ return "Unknown validation error\n";
30
+ }
31
+ let message = "";
32
+ for (let i = 0; i < issues.length; i++) {
33
+ const issue = issues[i];
34
+ const path = issue.path.length > 0 ? issue.path.join(".") : "root";
35
+ message += `${i + 1}. Field: "${path}"
36
+ `;
37
+ switch (issue.code) {
38
+ case "invalid_type":
39
+ message += ` Problem: Expected ${issue.expected}, but received ${issue.received}
40
+ `;
41
+ message += ` Message: ${issue.message}
42
+ `;
43
+ break;
44
+ case "invalid_string":
45
+ if ("validation" in issue) {
46
+ message += ` Problem: Invalid ${issue.validation} format
47
+ `;
48
+ }
49
+ message += ` Message: ${issue.message}
50
+ `;
51
+ break;
52
+ case "too_small":
53
+ if (issue.type === "string") {
54
+ if (issue.exact) {
55
+ message += ` Problem: String must be exactly ${issue.minimum} characters
56
+ `;
57
+ } else {
58
+ message += ` Problem: String must be at least ${issue.minimum} characters
59
+ `;
60
+ }
61
+ } else if (issue.type === "number") {
62
+ message += ` Problem: Number must be ${issue.inclusive ? "at least" : "greater than"} ${issue.minimum}
63
+ `;
64
+ } else if (issue.type === "array") {
65
+ message += ` Problem: Array must contain ${issue.inclusive ? "at least" : "more than"} ${issue.minimum} items
66
+ `;
67
+ }
68
+ message += ` Message: ${issue.message}
69
+ `;
70
+ break;
71
+ case "too_big":
72
+ if (issue.type === "string") {
73
+ if (issue.exact) {
74
+ message += ` Problem: String must be exactly ${issue.maximum} characters
75
+ `;
76
+ } else {
77
+ message += ` Problem: String must be at most ${issue.maximum} characters
78
+ `;
79
+ }
80
+ } else if (issue.type === "number") {
81
+ message += ` Problem: Number must be ${issue.inclusive ? "at most" : "less than"} ${issue.maximum}
82
+ `;
83
+ } else if (issue.type === "array") {
84
+ message += ` Problem: Array must contain ${issue.inclusive ? "at most" : "fewer than"} ${issue.maximum} items
85
+ `;
86
+ }
87
+ message += ` Message: ${issue.message}
88
+ `;
89
+ break;
90
+ case "invalid_enum_value":
91
+ message += ` Problem: Invalid value "${issue.received}"
92
+ `;
93
+ message += ` Allowed values: ${issue.options.map((o) => `"${o}"`).join(", ")}
94
+ `;
95
+ message += ` Message: ${issue.message}
96
+ `;
97
+ break;
98
+ case "invalid_literal":
99
+ message += ` Problem: Expected the literal value "${issue.expected}", but received "${issue.received}"
100
+ `;
101
+ message += ` Message: ${issue.message}
102
+ `;
103
+ break;
104
+ case "invalid_union":
105
+ message += " Problem: Value doesn't match any of the expected formats\n";
106
+ message += ` Message: ${issue.message}
107
+ `;
108
+ break;
109
+ default:
110
+ message += ` Problem: ${issue.message}
111
+ `;
112
+ }
113
+ if (i < issues.length - 1) {
114
+ message += "\n";
115
+ }
116
+ }
117
+ return message;
118
+ }
15
119
  }
@@ -1,7 +1,8 @@
1
- import { z } from "@bpinternal/zui";
1
+ import { z, transforms } from "@bpinternal/zui";
2
2
  import JSON5 from "json5";
3
3
  import { jsonrepair } from "jsonrepair";
4
4
  import { chunk, isArray } from "lodash-es";
5
+ import pLimit from "p-limit";
5
6
  import { ZaiContext } from "../context";
6
7
  import { Response } from "../response";
7
8
  import { getTokenizer } from "../tokenizer";
@@ -17,9 +18,10 @@ const Options = z.object({
17
18
  const START = "\u25A0json_start\u25A0";
18
19
  const END = "\u25A0json_end\u25A0";
19
20
  const NO_MORE = "\u25A0NO_MORE_ELEMENT\u25A0";
21
+ const ZERO_ELEMENTS = "\u25A0ZERO_ELEMENTS\u25A0";
20
22
  const extract = async (input, _schema, _options, ctx) => {
21
23
  ctx.controller.signal.throwIfAborted();
22
- let schema = _schema;
24
+ let schema = transforms.fromJSONSchema(transforms.toJSONSchema(_schema));
23
25
  const options = Options.parse(_options ?? {});
24
26
  const tokenizer = await getTokenizer();
25
27
  const model = await ctx.getModel();
@@ -62,19 +64,22 @@ const extract = async (input, _schema, _options, ctx) => {
62
64
  const keys = Object.keys(schema.shape);
63
65
  const inputAsString = stringify(input);
64
66
  if (tokenizer.count(inputAsString) > options.chunkLength) {
67
+ const limit = pLimit(10);
65
68
  const tokens = tokenizer.split(inputAsString);
66
69
  const chunks = chunk(tokens, options.chunkLength).map((x) => x.join(""));
67
70
  const all = await Promise.allSettled(
68
71
  chunks.map(
69
- (chunk2) => extract(
70
- chunk2,
71
- originalSchema,
72
- {
73
- ...options,
74
- strict: false
75
- // We don't want to fail on strict mode for sub-chunks
76
- },
77
- ctx
72
+ (chunk2) => limit(
73
+ () => extract(
74
+ chunk2,
75
+ originalSchema,
76
+ {
77
+ ...options,
78
+ strict: false
79
+ // We don't want to fail on strict mode for sub-chunks
80
+ },
81
+ ctx
82
+ )
78
83
  )
79
84
  )
80
85
  ).then(
@@ -110,8 +115,11 @@ Merge it back into a final result.`.trim(),
110
115
  instructions.push("You may have multiple elements, or zero elements in the input.");
111
116
  instructions.push("You must extract each element separately.");
112
117
  instructions.push(`Each element must be a JSON object with exactly the format: ${START}${shape}${END}`);
118
+ instructions.push(`If there are no elements to extract, respond with ${ZERO_ELEMENTS}.`);
113
119
  instructions.push(`When you are done extracting all elements, type "${NO_MORE}" to finish.`);
114
- instructions.push(`For example, if you have zero elements, the output should look like this: ${NO_MORE}`);
120
+ instructions.push(
121
+ `For example, if you have zero elements, the output should look like this: ${ZERO_ELEMENTS}${NO_MORE}`
122
+ );
115
123
  instructions.push(
116
124
  `For example, if you have two elements, the output should look like this: ${START}${abbv}${END}${START}${abbv}${END}${NO_MORE}`
117
125
  );
@@ -1,5 +1,6 @@
1
1
  import { z } from "@bpinternal/zui";
2
2
  import { clamp } from "lodash-es";
3
+ import pLimit from "p-limit";
3
4
  import { ZaiContext } from "../context";
4
5
  import { Response } from "../response";
5
6
  import { getTokenizer } from "../tokenizer";
@@ -191,7 +192,8 @@ The condition is: "${condition}"
191
192
  }
192
193
  return partial;
193
194
  };
194
- const filteredChunks = await Promise.all(chunks.map(filterChunk));
195
+ const limit = pLimit(10);
196
+ const filteredChunks = await Promise.all(chunks.map((chunk) => limit(() => filterChunk(chunk))));
195
197
  return filteredChunks.flat();
196
198
  };
197
199
  Zai.prototype.filter = function(input, condition, _options) {
@@ -1,5 +1,6 @@
1
1
  import { z } from "@bpinternal/zui";
2
2
  import { chunk, clamp } from "lodash-es";
3
+ import pLimit from "p-limit";
3
4
  import { ZaiContext } from "../context";
4
5
  import { Response } from "../response";
5
6
  import { getTokenizer } from "../tokenizer";
@@ -87,9 +88,10 @@ const label = async (input, _labels, _options, ctx) => {
87
88
  );
88
89
  const inputAsString = stringify(input);
89
90
  if (tokenizer.count(inputAsString) > CHUNK_INPUT_MAX_TOKENS) {
91
+ const limit = pLimit(10);
90
92
  const tokens = tokenizer.split(inputAsString);
91
93
  const chunks = chunk(tokens, CHUNK_INPUT_MAX_TOKENS).map((x) => x.join(""));
92
- const allLabels = await Promise.all(chunks.map((chunk2) => label(chunk2, _labels, _options, ctx)));
94
+ const allLabels = await Promise.all(chunks.map((chunk2) => limit(() => label(chunk2, _labels, _options, ctx))));
93
95
  return allLabels.reduce((acc, x) => {
94
96
  Object.keys(x).forEach((key) => {
95
97
  if (acc[key]?.value === true) {
@@ -1,5 +1,6 @@
1
1
  import { z } from "@bpinternal/zui";
2
2
  import { chunk } from "lodash-es";
3
+ import pLimit from "p-limit";
3
4
  import { ZaiContext } from "../context";
4
5
  import { Response } from "../response";
5
6
  import { getTokenizer } from "../tokenizer";
@@ -54,8 +55,9 @@ ${newText}
54
55
  const useMergeSort = parts >= Math.pow(2, N);
55
56
  const chunkSize = Math.ceil(tokens.length / (parts * N));
56
57
  if (useMergeSort) {
58
+ const limit = pLimit(10);
57
59
  const chunks = chunk(tokens, chunkSize).map((x) => x.join(""));
58
- const allSummaries = (await Promise.allSettled(chunks.map((chunk2) => summarize(chunk2, options, ctx)))).filter((x) => x.status === "fulfilled").map((x) => x.value);
60
+ const allSummaries = (await Promise.allSettled(chunks.map((chunk2) => limit(() => summarize(chunk2, options, ctx))))).filter((x) => x.status === "fulfilled").map((x) => x.value);
59
61
  return summarize(allSummaries.join("\n\n============\n\n"), options, ctx);
60
62
  }
61
63
  const summaries = [];