@botpress/zai 2.5.18 → 2.6.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/dist/context.js +14 -0
- package/dist/index.d.ts +28 -1
- package/dist/operations/answer.js +2 -1
- package/dist/operations/check.js +2 -1
- package/dist/operations/extract.js +2 -1
- package/dist/operations/filter.js +2 -1
- package/dist/operations/group.js +2 -1
- package/dist/operations/label.js +2 -1
- package/dist/operations/patch.js +2 -1
- package/dist/operations/rate.js +2 -1
- package/dist/operations/rewrite.js +2 -1
- package/dist/operations/sort.js +2 -1
- package/dist/operations/summarize.js +2 -1
- package/dist/operations/text.js +2 -1
- package/dist/zai.js +9 -0
- package/e2e/data/cache.jsonl +5 -0
- package/package.json +1 -1
- package/src/context.ts +21 -0
- package/src/index.ts +2 -1
- package/src/operations/answer.ts +1 -0
- package/src/operations/check.ts +1 -0
- package/src/operations/extract.ts +1 -0
- package/src/operations/filter.ts +1 -0
- package/src/operations/group.ts +1 -0
- package/src/operations/label.ts +1 -0
- package/src/operations/patch.ts +1 -0
- package/src/operations/rate.ts +1 -0
- package/src/operations/rewrite.ts +1 -0
- package/src/operations/sort.ts +1 -0
- package/src/operations/summarize.ts +1 -0
- package/src/operations/text.ts +1 -0
- package/src/zai.ts +32 -0
package/dist/context.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from "./emitter";
|
|
2
|
+
import { fastHash } from "./utils";
|
|
2
3
|
export class ZaiContext {
|
|
3
4
|
_startedAt = Date.now();
|
|
4
5
|
_inputCost = 0;
|
|
@@ -15,8 +16,10 @@ export class ZaiContext {
|
|
|
15
16
|
adapter;
|
|
16
17
|
source;
|
|
17
18
|
_eventEmitter;
|
|
19
|
+
_memoizer;
|
|
18
20
|
controller = new AbortController();
|
|
19
21
|
_client;
|
|
22
|
+
static _noopMemoizer = { run: (_id, fn) => fn() };
|
|
20
23
|
constructor(props) {
|
|
21
24
|
this._client = props.client.clone();
|
|
22
25
|
this.taskId = props.taskId;
|
|
@@ -24,6 +27,7 @@ export class ZaiContext {
|
|
|
24
27
|
this.adapter = props.adapter;
|
|
25
28
|
this.source = props.source;
|
|
26
29
|
this.taskType = props.taskType;
|
|
30
|
+
this._memoizer = props.memoizer ?? ZaiContext._noopMemoizer;
|
|
27
31
|
this._eventEmitter = new EventEmitter();
|
|
28
32
|
this._client.on("request", () => {
|
|
29
33
|
this._totalRequests++;
|
|
@@ -57,6 +61,16 @@ export class ZaiContext {
|
|
|
57
61
|
this._eventEmitter.clear();
|
|
58
62
|
}
|
|
59
63
|
async generateContent(props) {
|
|
64
|
+
const memoKey = `zai:memo:${this.taskType}:${this.taskId || "default"}:${fastHash(
|
|
65
|
+
JSON.stringify({
|
|
66
|
+
s: props.systemPrompt,
|
|
67
|
+
m: props.messages?.map((m) => "content" in m ? m.content : ""),
|
|
68
|
+
st: props.stopSequences
|
|
69
|
+
})
|
|
70
|
+
)}`;
|
|
71
|
+
return this._memoizer.run(memoKey, () => this._generateContentInner(props));
|
|
72
|
+
}
|
|
73
|
+
async _generateContentInner(props) {
|
|
60
74
|
const maxRetries = Math.max(props.maxRetries ?? 3, 0);
|
|
61
75
|
const transform = props.transform;
|
|
62
76
|
let lastError = null;
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,16 @@ declare abstract class Adapter {
|
|
|
41
41
|
abstract saveExample<TInput, TOutput>(props: SaveExampleProps<TInput, TOutput>): Promise<void>;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* A memoizer that caches the result of async operations by a unique key.
|
|
46
|
+
*
|
|
47
|
+
* When used with the Botpress ADK workflow `step` function, this enables
|
|
48
|
+
* Zai operations to resume where they left off if a workflow is interrupted.
|
|
49
|
+
*
|
|
50
|
+
*/
|
|
51
|
+
type Memoizer = {
|
|
52
|
+
run: <T>(id: string, fn: () => Promise<T>) => Promise<T>;
|
|
53
|
+
};
|
|
44
54
|
/**
|
|
45
55
|
* Active learning configuration for improving AI operations over time.
|
|
46
56
|
*
|
|
@@ -98,6 +108,16 @@ type ZaiConfig = {
|
|
|
98
108
|
activeLearning?: ActiveLearning;
|
|
99
109
|
/** Namespace for organizing tasks (default: 'zai') */
|
|
100
110
|
namespace?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Memoizer (or factory returning one) for caching cognitive call results.
|
|
113
|
+
*
|
|
114
|
+
* When provided, all LLM calls are wrapped in the memoizer, allowing results
|
|
115
|
+
* to be cached and replayed. This is useful for resuming workflow runs where
|
|
116
|
+
* Zai operations have already completed their cognitive calls.
|
|
117
|
+
*
|
|
118
|
+
* If a factory function is provided, it is called once per Zai operation invocation.
|
|
119
|
+
*/
|
|
120
|
+
memoize?: Memoizer | (() => Memoizer);
|
|
101
121
|
};
|
|
102
122
|
/**
|
|
103
123
|
* Zai - A type-safe LLM utility library for production-ready AI operations.
|
|
@@ -171,6 +191,7 @@ declare class Zai {
|
|
|
171
191
|
protected namespace: string;
|
|
172
192
|
protected adapter: Adapter;
|
|
173
193
|
protected activeLearning: ActiveLearning;
|
|
194
|
+
protected _memoize?: Memoizer | (() => Memoizer);
|
|
174
195
|
/**
|
|
175
196
|
* Creates a new Zai instance with the specified configuration.
|
|
176
197
|
*
|
|
@@ -195,6 +216,8 @@ declare class Zai {
|
|
|
195
216
|
constructor(config: ZaiConfig);
|
|
196
217
|
/** @internal */
|
|
197
218
|
protected callModel(props: Parameters<Cognitive['generateContent']>[0]): ReturnType<Cognitive['generateContent']>;
|
|
219
|
+
/** @internal */
|
|
220
|
+
protected _resolveMemoizer(): Memoizer | undefined;
|
|
198
221
|
protected getTokenizer(): Promise<TextTokenizer>;
|
|
199
222
|
protected fetchModelDetails(): Promise<void>;
|
|
200
223
|
protected get taskId(): string;
|
|
@@ -299,6 +322,7 @@ type ZaiContextProps = {
|
|
|
299
322
|
modelId: string;
|
|
300
323
|
adapter?: Adapter;
|
|
301
324
|
source?: GenerateContentInput['meta'];
|
|
325
|
+
memoizer?: Memoizer;
|
|
302
326
|
};
|
|
303
327
|
/**
|
|
304
328
|
* Usage statistics tracking tokens, cost, and request metrics for an operation.
|
|
@@ -370,8 +394,10 @@ declare class ZaiContext {
|
|
|
370
394
|
adapter?: Adapter;
|
|
371
395
|
source?: GenerateContentInput['meta'];
|
|
372
396
|
private _eventEmitter;
|
|
397
|
+
private _memoizer;
|
|
373
398
|
controller: AbortController;
|
|
374
399
|
private _client;
|
|
400
|
+
private static _noopMemoizer;
|
|
375
401
|
constructor(props: ZaiContextProps);
|
|
376
402
|
getModel(): Promise<Model>;
|
|
377
403
|
on<K extends keyof ContextEvents>(type: K, listener: (event: ContextEvents[K]) => void): this;
|
|
@@ -382,6 +408,7 @@ declare class ZaiContext {
|
|
|
382
408
|
text: string | undefined;
|
|
383
409
|
extracted: Out;
|
|
384
410
|
}>;
|
|
411
|
+
private _generateContentInner;
|
|
385
412
|
get elapsedTime(): number;
|
|
386
413
|
get usage(): Usage;
|
|
387
414
|
}
|
|
@@ -2143,4 +2170,4 @@ declare module '@botpress/zai' {
|
|
|
2143
2170
|
}
|
|
2144
2171
|
}
|
|
2145
2172
|
|
|
2146
|
-
export { Zai };
|
|
2173
|
+
export { type Memoizer, Zai };
|
|
@@ -373,7 +373,8 @@ Zai.prototype.answer = function(documents, question, _options) {
|
|
|
373
373
|
modelId: this.Model,
|
|
374
374
|
taskId: this.taskId,
|
|
375
375
|
taskType: "zai.answer",
|
|
376
|
-
adapter: this.adapter
|
|
376
|
+
adapter: this.adapter,
|
|
377
|
+
memoizer: this._resolveMemoizer()
|
|
377
378
|
});
|
|
378
379
|
return new Response(
|
|
379
380
|
context,
|
package/dist/operations/check.js
CHANGED
|
@@ -181,7 +181,8 @@ Zai.prototype.check = function(input, condition, _options) {
|
|
|
181
181
|
modelId: this.Model,
|
|
182
182
|
taskId: this.taskId,
|
|
183
183
|
taskType: "zai.check",
|
|
184
|
-
adapter: this.adapter
|
|
184
|
+
adapter: this.adapter,
|
|
185
|
+
memoizer: this._resolveMemoizer()
|
|
185
186
|
});
|
|
186
187
|
return new Response(context, check(input, condition, options, context), (result) => result.value);
|
|
187
188
|
};
|
|
@@ -313,7 +313,8 @@ Zai.prototype.extract = function(input, schema, _options) {
|
|
|
313
313
|
modelId: this.Model,
|
|
314
314
|
taskId: this.taskId,
|
|
315
315
|
taskType: "zai.extract",
|
|
316
|
-
adapter: this.adapter
|
|
316
|
+
adapter: this.adapter,
|
|
317
|
+
memoizer: this._resolveMemoizer()
|
|
317
318
|
});
|
|
318
319
|
return new Response(context, extract(input, schema, _options, context), (result) => result);
|
|
319
320
|
};
|
|
@@ -202,7 +202,8 @@ Zai.prototype.filter = function(input, condition, _options) {
|
|
|
202
202
|
modelId: this.Model,
|
|
203
203
|
taskId: this.taskId,
|
|
204
204
|
taskType: "zai.filter",
|
|
205
|
-
adapter: this.adapter
|
|
205
|
+
adapter: this.adapter,
|
|
206
|
+
memoizer: this._resolveMemoizer()
|
|
206
207
|
});
|
|
207
208
|
return new Response(context, filter(input, condition, _options, context), (result) => result);
|
|
208
209
|
};
|
package/dist/operations/group.js
CHANGED
|
@@ -541,7 +541,8 @@ Zai.prototype.group = function(input, _options) {
|
|
|
541
541
|
modelId: this.Model,
|
|
542
542
|
taskId: this.taskId,
|
|
543
543
|
taskType: "zai.group",
|
|
544
|
-
adapter: this.adapter
|
|
544
|
+
adapter: this.adapter,
|
|
545
|
+
memoizer: this._resolveMemoizer()
|
|
545
546
|
});
|
|
546
547
|
return new Response(context, group(input, _options, context), (result) => {
|
|
547
548
|
const merged = {};
|
package/dist/operations/label.js
CHANGED
|
@@ -276,7 +276,8 @@ Zai.prototype.label = function(input, labels, _options) {
|
|
|
276
276
|
modelId: this.Model,
|
|
277
277
|
taskId: this.taskId,
|
|
278
278
|
taskType: "zai.label",
|
|
279
|
-
adapter: this.adapter
|
|
279
|
+
adapter: this.adapter,
|
|
280
|
+
memoizer: this._resolveMemoizer()
|
|
280
281
|
});
|
|
281
282
|
return new Response(
|
|
282
283
|
context,
|
package/dist/operations/patch.js
CHANGED
|
@@ -392,7 +392,8 @@ Zai.prototype.patch = function(files, instructions, _options) {
|
|
|
392
392
|
modelId: this.Model,
|
|
393
393
|
taskId: this.taskId,
|
|
394
394
|
taskType: "zai.patch",
|
|
395
|
-
adapter: this.adapter
|
|
395
|
+
adapter: this.adapter,
|
|
396
|
+
memoizer: this._resolveMemoizer()
|
|
396
397
|
});
|
|
397
398
|
return new Response(context, patch(files, instructions, _options, context), (result) => result);
|
|
398
399
|
};
|
package/dist/operations/rate.js
CHANGED
|
@@ -335,7 +335,8 @@ Zai.prototype.rate = function(input, instructions, _options) {
|
|
|
335
335
|
modelId: this.Model,
|
|
336
336
|
taskId: this.taskId,
|
|
337
337
|
taskType: "zai.rate",
|
|
338
|
-
adapter: this.adapter
|
|
338
|
+
adapter: this.adapter,
|
|
339
|
+
memoizer: this._resolveMemoizer()
|
|
339
340
|
});
|
|
340
341
|
return new Response(
|
|
341
342
|
context,
|
|
@@ -136,7 +136,8 @@ Zai.prototype.rewrite = function(original, prompt, _options) {
|
|
|
136
136
|
modelId: this.Model,
|
|
137
137
|
taskId: this.taskId,
|
|
138
138
|
taskType: "zai.rewrite",
|
|
139
|
-
adapter: this.adapter
|
|
139
|
+
adapter: this.adapter,
|
|
140
|
+
memoizer: this._resolveMemoizer()
|
|
140
141
|
});
|
|
141
142
|
return new Response(context, rewrite(original, prompt, _options, context), (result) => result);
|
|
142
143
|
};
|
package/dist/operations/sort.js
CHANGED
|
@@ -511,7 +511,8 @@ Zai.prototype.sort = function(input, instructions, _options) {
|
|
|
511
511
|
modelId: this.Model,
|
|
512
512
|
taskId: this.taskId,
|
|
513
513
|
taskType: "zai.sort",
|
|
514
|
-
adapter: this.adapter
|
|
514
|
+
adapter: this.adapter,
|
|
515
|
+
memoizer: this._resolveMemoizer()
|
|
515
516
|
});
|
|
516
517
|
return new Response(
|
|
517
518
|
context,
|
|
@@ -148,7 +148,8 @@ Zai.prototype.summarize = function(original, _options) {
|
|
|
148
148
|
modelId: this.Model,
|
|
149
149
|
taskId: this.taskId,
|
|
150
150
|
taskType: "summarize",
|
|
151
|
-
adapter: this.adapter
|
|
151
|
+
adapter: this.adapter,
|
|
152
|
+
memoizer: this._resolveMemoizer()
|
|
152
153
|
});
|
|
153
154
|
return new Response(context, summarize(original, options, context), (value) => value);
|
|
154
155
|
};
|
package/dist/operations/text.js
CHANGED
|
@@ -60,7 +60,8 @@ Zai.prototype.text = function(prompt, _options) {
|
|
|
60
60
|
modelId: this.Model,
|
|
61
61
|
taskId: this.taskId,
|
|
62
62
|
taskType: "zai.text",
|
|
63
|
-
adapter: this.adapter
|
|
63
|
+
adapter: this.adapter,
|
|
64
|
+
memoizer: this._resolveMemoizer()
|
|
64
65
|
});
|
|
65
66
|
return new Response(context, text(prompt, _options, context), (result) => result);
|
|
66
67
|
};
|
package/dist/zai.js
CHANGED
|
@@ -47,6 +47,7 @@ export class Zai {
|
|
|
47
47
|
namespace;
|
|
48
48
|
adapter;
|
|
49
49
|
activeLearning;
|
|
50
|
+
_memoize;
|
|
50
51
|
/**
|
|
51
52
|
* Creates a new Zai instance with the specified configuration.
|
|
52
53
|
*
|
|
@@ -80,6 +81,7 @@ export class Zai {
|
|
|
80
81
|
client: this.client.client,
|
|
81
82
|
tableName: parsed.activeLearning.tableName
|
|
82
83
|
}) : new MemoryAdapter([]);
|
|
84
|
+
this._memoize = config.memoize;
|
|
83
85
|
}
|
|
84
86
|
/** @internal */
|
|
85
87
|
async callModel(props) {
|
|
@@ -90,6 +92,13 @@ export class Zai {
|
|
|
90
92
|
userId: this._userId
|
|
91
93
|
});
|
|
92
94
|
}
|
|
95
|
+
/** @internal */
|
|
96
|
+
_resolveMemoizer() {
|
|
97
|
+
if (!this._memoize) {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
return typeof this._memoize === "function" ? this._memoize() : this._memoize;
|
|
101
|
+
}
|
|
93
102
|
async getTokenizer() {
|
|
94
103
|
Zai.tokenizer ??= await (async () => {
|
|
95
104
|
while (!getWasmTokenizer) {
|
package/e2e/data/cache.jsonl
CHANGED
|
@@ -1971,3 +1971,8 @@
|
|
|
1971
1971
|
{"key":"f8a39096","input":"{\"body\":{\"messages\":[{\"content\":\"You are grouping elements into cohesive groups.\\n\\n**Instructions:** Group by food type\\n\\n\\n**Important:**\\n- Each element gets exactly ONE group label\\n- Use EXACT SAME label for similar items (case-sensitive)\\n- Create new descriptive labels when needed\\n\\n**Output Format:**\\nOne line per element:\\n■0:Group Label■\\n■1:Group Label■\\n■END■\",\"role\":\"system\"},{\"content\":\"**Elements (■0 to ■11):**\\n■0: apple■\\n■1: banana■\\n■2: orange■\\n■3: mango■\\n■4: grape■\\n■5: carrot■\\n■6: broccoli■\\n■7: spinach■\\n■8: celery■\\n■9: kale■\\n■10: rice■\\n■11: wheat■\\n\\n**Task:** For each element, output one line with its group label.\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.group\",\"promptSource\":\"zai:zai.group:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■0:Fruit■ \n■1:Fruit■ \n■2:Fruit■ \n■3:Fruit■ \n■4:Fruit■ \n■5:Vegetable■ \n■6:Vegetable■ \n■7:Vegetable■ \n■8:Vegetable■ \n■9:Vegetable■ \n■10:Grain■ \n■11:Grain■ \n","metadata":{"provider":"cerebras","usage":{"inputTokens":250,"outputTokens":220,"inputCost":0.0000875,"outputCost":0.000165},"model":"cerebras:gpt-oss-120b","ttft":150,"latency":281,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0002525}}}
|
|
1972
1972
|
{"key":"f5cec64f","input":"{\"body\":{\"messages\":[{\"content\":\"You are grouping elements into cohesive groups.\\n\\n**Instructions:** Group by food type\\n\\n\\n**Important:**\\n- Each element gets exactly ONE group label\\n- Use EXACT SAME label for similar items (case-sensitive)\\n- Create new descriptive labels when needed\\n\\n**Output Format:**\\nOne line per element:\\n■0:Group Label■\\n■1:Group Label■\\n■END■\",\"role\":\"system\"},{\"content\":\"**Existing Groups (prefer reusing these):**\\n- Fruit\\n- Vegetable\\n- Grain\\n\\n**Elements (■0 to ■11):**\\n■0: apple■\\n■1: banana■\\n■2: orange■\\n■3: mango■\\n■4: grape■\\n■5: carrot■\\n■6: broccoli■\\n■7: spinach■\\n■8: celery■\\n■9: kale■\\n■10: rice■\\n■11: wheat■\\n\\n**Task:** For each element, output one line with its group label.\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.group\",\"promptSource\":\"zai:zai.group:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■0:Fruit■ \n■1:Fruit■ \n■2:Fruit■ \n■3:Fruit■ \n■4:Fruit■ \n■5:Vegetable■ \n■6:Vegetable■ \n■7:Vegetable■ \n■8:Vegetable■ \n■9:Vegetable■ \n■10:Grain■ \n■11:Grain■ \n","metadata":{"provider":"cerebras","usage":{"inputTokens":269,"outputTokens":222,"inputCost":0.00009415,"outputCost":0.0001665},"model":"cerebras:gpt-oss-120b","ttft":154,"latency":296,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00026065}}}
|
|
1973
1973
|
{"key":"b2435b9e","input":"{\"body\":{\"messages\":[{\"content\":\"You are grouping elements into cohesive groups.\\n\\n**Instructions:** Group by food type\\n\\n\\n**Important:**\\n- Each element gets exactly ONE group label\\n- Use EXACT SAME label for similar items (case-sensitive)\\n- Create new descriptive labels when needed\\n\\n**Output Format:**\\nOne line per element:\\n■0:Group Label■\\n■1:Group Label■\\n■END■\",\"role\":\"system\"},{\"content\":\"**Existing Groups (prefer reusing these):**\\n- Fruit\\n- Vegetable\\n\\n**Elements (■0 to ■1):**\\n■0: rice■\\n■1: wheat■\\n\\n**Task:** For each element, output one line with its group label.\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.group\",\"promptSource\":\"zai:zai.group:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■0:Grain■\n■1:Grain■\n","metadata":{"provider":"cerebras","usage":{"inputTokens":206,"outputTokens":172,"inputCost":0.0000721,"outputCost":0.000129},"model":"cerebras:gpt-oss-120b","ttft":192,"latency":2310,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0002011}}}
|
|
1974
|
+
{"key":"f0425e37","input":"{\"body\":{\"messages\":[{\"content\":\"You are rating items based on evaluation criteria.\\n\\nEvaluation Criteria:\\n**relevance**:\\n - very_bad (1): Content rarely relevant to your interests.\\n - bad (2): Mostly irrelevant, occasional useful info.\\n - average (3): Balanced relevance, some useful content.\\n - good (4): Often relevant, aligns with interests.\\n - very_good (5): Consistently highly relevant and valuable.\\n\\n**authority**:\\n - very_bad (1): Sender lacks credibility, unknown source.\\n - bad (2): Low credibility, questionable expertise overall.\\n - average (3): Moderate credibility, recognized but not expert.\\n - good (4): Credible source, recognized expertise in industry.\\n - very_good (5): High authority, leading expert in field.\\n\\n**frequency**:\\n - very_bad (1): Excessive emails, overwhelming inbox daily.\\n - bad (2): Too many emails, frequent interruptions.\\n - average (3): Moderate volume, acceptable cadence for work.\\n - good (4): Well-paced, occasional useful messages that add value.\\n - very_good (5): Sporadic, only essential communications when needed.\\n\\n**responsiveness**:\\n - very_bad (1): Never replies, unresponsive to queries.\\n - bad (2): Rarely replies, slow response times.\\n - average (3): Occasional replies, average speed in normal timeframe.\\n - good (4): Usually responsive, timely replies within days.\\n - very_good (5): Rapid, consistently helpful responses to all requests.\\n\\nFor each item, rate it on EACH criterion using one of these labels:\\nvery_bad, bad, average, good, very_good\\n\\nOutput format:\\n■0:criterion1=label;criterion2=label;criterion3=label■\\n■1:criterion1=label;criterion2=label;criterion3=label■\\n■END■\\n\\nIMPORTANT:\\n- Rate every item (■0 to ■3)\\n- Use exact criterion names: relevance, authority, frequency, responsiveness\\n- Use exact label names: very_bad, bad, average, good, very_good\\n- Use semicolons (;) between criteria\\n- Use equals (=) between criterion and label\",\"role\":\"system\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"partner@sequoia.vc\\\",\\\"subject\\\":\\\"Q4 Review\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=very_good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @sequoia.vc is our investor - highest importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"analyst@bankofamerica.com\\\",\\\"subject\\\":\\\"Market Report\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=very_bad■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: analyst@* prefix is spam - lowest importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"ben@a16z.com\\\",\\\"subject\\\":\\\"Investment Discussion\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @a16z.com is potential investor - high importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"team@google.com\\\",\\\"subject\\\":\\\"Partnership Proposal\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=average■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @google.com is competitor - medium importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"roelof@sequoia.vc\\\",\\\"subject\\\":\\\"Portfolio Update\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=very_good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @sequoia.vc is our investor - highest importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Items to rate (■0 to ■3):\\n■0: {\\\"from\\\":\\\"sarah@sequoia.vc\\\",\\\"subject\\\":\\\"Board Meeting\\\"}■\\n■1: {\\\"from\\\":\\\"analyst@goldmansachs.com\\\",\\\"subject\\\":\\\"Earnings Report\\\"}■\\n■2: {\\\"from\\\":\\\"marc@a16z.com\\\",\\\"subject\\\":\\\"Funding Round\\\"}■\\n■3: {\\\"from\\\":\\\"recruiter@google.com\\\",\\\"subject\\\":\\\"Hiring\\\"}■\\n\\nRate each item on all criteria.\\nOutput format: ■index:criterion1=label;criterion2=label■\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.rate\",\"promptSource\":\"zai:zai.rate:zai/rate\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■0:relevance=very_good;authority=very_good;frequency=good;responsiveness=good■\n■1:relevance=good;authority=good;frequency=average;responsiveness=average■\n■2:relevance=good;authority=good;frequency=good;responsiveness=good■\n■3:relevance=average;authority=average;frequency=average;responsiveness=average■\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1049,"outputTokens":617,"inputCost":0.00036715,"outputCost":0.00046275},"model":"cerebras:gpt-oss-120b","ttft":207,"latency":494,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0008299}}}
|
|
1975
|
+
{"key":"61f23c23","input":"{\"body\":{\"messages\":[{\"content\":\"You are rating items based on evaluation criteria.\\n\\nEvaluation Criteria:\\n**relevance**:\\n - very_bad (1): Content rarely relevant to your interests.\\n - bad (2): Mostly irrelevant, occasional useful info.\\n - average (3): Balanced relevance, some useful content.\\n - good (4): Often relevant, aligns with interests.\\n - very_good (5): Consistently highly relevant and valuable.\\n\\n**authority**:\\n - very_bad (1): Sender lacks credibility, unknown source.\\n - bad (2): Low credibility, questionable expertise overall.\\n - average (3): Moderate credibility, recognized but not expert.\\n - good (4): Credible source, recognized expertise in industry.\\n - very_good (5): High authority, leading expert in field.\\n\\n**frequency**:\\n - very_bad (1): Excessive emails, overwhelming inbox daily.\\n - bad (2): Too many emails, frequent interruptions.\\n - average (3): Moderate volume, acceptable cadence for work.\\n - good (4): Well-paced, occasional useful messages that add value.\\n - very_good (5): Sporadic, only essential communications when needed.\\n\\n**responsiveness**:\\n - very_bad (1): Never replies, unresponsive to queries.\\n - bad (2): Rarely replies, slow response times.\\n - average (3): Occasional replies, average speed in normal timeframe.\\n - good (4): Usually responsive, timely replies within days.\\n - very_good (5): Rapid, consistently helpful responses to all requests.\\n\\nFor each item, rate it on EACH criterion using one of these labels:\\nvery_bad, bad, average, good, very_good\\n\\nOutput format:\\n■0:criterion1=label;criterion2=label;criterion3=label■\\n■1:criterion1=label;criterion2=label;criterion3=label■\\n■END■\\n\\nIMPORTANT:\\n- Rate every item (■0 to ■3)\\n- Use exact criterion names: relevance, authority, frequency, responsiveness\\n- Use exact label names: very_bad, bad, average, good, very_good\\n- Use semicolons (;) between criteria\\n- Use equals (=) between criterion and label\",\"role\":\"system\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"analyst@bankofamerica.com\\\",\\\"subject\\\":\\\"Market Report\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=very_bad■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: analyst@* prefix is spam - lowest importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"ben@a16z.com\\\",\\\"subject\\\":\\\"Investment Discussion\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @a16z.com is potential investor - high importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"partner@sequoia.vc\\\",\\\"subject\\\":\\\"Q4 Review\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=very_good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @sequoia.vc is our investor - highest importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"team@google.com\\\",\\\"subject\\\":\\\"Partnership Proposal\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=average■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @google.com is competitor - medium importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"from\\\":\\\"roelof@sequoia.vc\\\",\\\"subject\\\":\\\"Portfolio Update\\\"}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:relevance=very_good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: RULE: @sequoia.vc is our investor - highest importance rating\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Items to rate (■0 to ■3):\\n■0: {\\\"from\\\":\\\"sarah@sequoia.vc\\\",\\\"subject\\\":\\\"Board Meeting\\\"}■\\n■1: {\\\"from\\\":\\\"analyst@goldmansachs.com\\\",\\\"subject\\\":\\\"Earnings Report\\\"}■\\n■2: {\\\"from\\\":\\\"marc@a16z.com\\\",\\\"subject\\\":\\\"Funding Round\\\"}■\\n■3: {\\\"from\\\":\\\"recruiter@google.com\\\",\\\"subject\\\":\\\"Hiring\\\"}■\\n\\nRate each item on all criteria.\\nOutput format: ■index:criterion1=label;criterion2=label■\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.rate\",\"promptSource\":\"zai:zai.rate:zai/rate\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■0:relevance=very_good;authority=very_good;frequency=very_good;responsiveness=good■\n■1:relevance=average;authority=good;frequency=average;responsiveness=average■\n■2:relevance=good;authority=very_good;frequency=very_good;responsiveness=good■\n■3:relevance=average;authority=good;frequency=average;responsiveness=average■\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1049,"outputTokens":727,"inputCost":0.00036715,"outputCost":0.00054525},"model":"cerebras:gpt-oss-120b","ttft":155,"latency":695,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0009124}}}
|
|
1976
|
+
{"key":"85d80390","input":"{\"body\":{\"messages\":[{\"content\":\"You are rating items based on evaluation criteria.\\n\\nEvaluation Criteria:\\n**length**:\\n - very_bad (1): Less than 4 characters\\n - bad (2): 4 to 5 characters\\n - average (3): 6 to 7 characters\\n - good (4): 8 to 11 characters\\n - very_good (5): 12 or more characters\\n\\n**complexity**:\\n - very_bad (1): Only one character type used\\n - bad (2): Two character types present\\n - average (3): Three character types present\\n - good (4): All four types, but predictable\\n - very_good (5): All four types, highly random\\n\\n**strength**:\\n - very_bad (1): Easily guessable, weak overall\\n - bad (2): Low entropy, vulnerable to attacks\\n - average (3): Moderate security, some protections\\n - good (4): Strong security, resistant to cracking\\n - very_good (5): Very high security, excellent protection\\n\\nFor each item, rate it on EACH criterion using one of these labels:\\nvery_bad, bad, average, good, very_good\\n\\nOutput format:\\n■0:criterion1=label;criterion2=label;criterion3=label■\\n■1:criterion1=label;criterion2=label;criterion3=label■\\n■END■\\n\\nIMPORTANT:\\n- Rate every item (■0 to ■1)\\n- Use exact criterion names: length, complexity, strength\\n- Use exact label names: very_bad, bad, average, good, very_good\\n- Use semicolons (;) between criteria\\n- Use equals (=) between criterion and label\",\"role\":\"system\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"password\\\":\\\"Str0ng!P@ss#2024\\\",\\\"length\\\":16,\\\"hasAll\\\":true}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:length=very_good;complexity=very_good;strength=very_good■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: Strong password: 16 chars, all character types\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example - Items to rate:\\n■0: {\\\"password\\\":\\\"weak\\\",\\\"length\\\":4,\\\"hasAll\\\":false}■\\n\\nRate each item on all criteria.\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■0:length=very_bad;complexity=very_bad;strength=very_bad■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Reasoning: Weak password: only 4 chars, missing character types\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Items to rate (■0 to ■1):\\n■0: {\\\"password\\\":\\\"MyStr0ng!Pass\\\",\\\"length\\\":13,\\\"hasAll\\\":true}■\\n■1: {\\\"password\\\":\\\"bad\\\",\\\"length\\\":3,\\\"hasAll\\\":false}■\\n\\nRate each item on all criteria.\\nOutput format: ■index:criterion1=label;criterion2=label■\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.rate\",\"promptSource\":\"zai:zai.rate:zai/rate\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■0:length=very_good;complexity=very_good;strength=very_good■\n■1:length=very_bad;complexity=very_bad;strength=very_bad■\n","metadata":{"provider":"cerebras","usage":{"inputTokens":682,"outputTokens":323,"inputCost":0.0002387,"outputCost":0.00024225},"model":"cerebras:gpt-oss-120b","ttft":171,"latency":357,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00048095}}}
|
|
1977
|
+
{"key":"c99aa38f","input":"{\"body\":{\"messages\":[{\"content\":\"Check if the following condition is true or false for the given input. Before answering, make sure to read the input and the condition carefully.\\nJustify your answer, then answer with either ■TRUE■ or ■FALSE■ at the very end, then add ■END■ to finish the response.\\nIMPORTANT: Make sure to answer with either ■TRUE■ or ■FALSE■ at the end of your response, but NOT both.\\n---\\nExpert Examples (#1 to #3):\\n- You have been provided with examples from previous experts. Make sure to read them carefully before making your decision.\\n- Make sure to refer to the examples provided by the experts to justify your decision (when applicable).\\n- When in doubt, ground your decision on the examples provided by the experts instead of your own intuition.\\n- When no example is similar to the input, make sure to provide a clear justification for your decision while inferring the decision-making process from the examples provided by the experts.\",\"role\":\"system\"},{\"content\":\"Expert Example #1\\n<|start_input|>\\nwhat is up\\n<|end_input|>\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"Analysis: \\\"What's up\\\" in our business scenario is NOT considered an official greeting.\\nFinal Answer: ■FALSE■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example #2\\n<|start_input|>\\nhello! how are you?\\n<|end_input|>\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"Analysis: \\\"hello!\\\" is a common greeting in English.\\nFinal Answer: ■TRUE■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Expert Example #3\\n<|start_input|>\\nwassup\\n<|end_input|>\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"Analysis: \\\"wassup\\\" is a slang term and not considered a formal greeting. It is therefore NOT considered a greeting.\\nFinal Answer: ■FALSE■\\n■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Considering the below input and above examples, is the following condition true or false?\\nExpert Example #4\\n<|start_input|>\\nWhat's up\\n<|end_input|>\\nIn your \\\"Analysis\\\", please refer to the Expert Examples # to justify your decision.\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.check\",\"promptSource\":\"zai:zai.check:zai/check\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"**Analysis:** \nThe task is to decide whether the provided text qualifies as a greeting. The expert examples illustrate the decision criteria:\n\n- **Example #1** (“what is up”) – judged **FALSE** because this informal phrase is not counted as a greeting. \n- **Example #2** (“hello! how are you?”) – judged **TRUE** as it contains a standard greeting (“hello”). \n- **Example #3** (“wassup”) – judged **FALSE** since it is slang and not a formal greeting.\n\nThe input in **Example #4** is “What’s up”. This expression is essentially the same informal phrase as “what is up” and “wassup”, both of which were classified as **FALSE** in the earlier examples. Therefore, following the established pattern, “What’s up” should also be considered **not** a greeting.\n\n**Conclusion:** The condition (that the input is a greeting) is **false** for the given input.\n\n■FALSE■ \n","metadata":{"provider":"cerebras","usage":{"inputTokens":497,"outputTokens":373,"inputCost":0.00017395,"outputCost":0.00027975},"model":"cerebras:gpt-oss-120b","ttft":171,"latency":415,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0004537}}}
|
|
1978
|
+
{"key":"b8f325eb","input":"{\"body\":{\"messages\":[{\"content\":\"Rewrite the text between the ■START■ and ■END■ tags to match the user prompt.\",\"role\":\"system\"},{\"content\":\"Prompt: write it like we want it\\n\\n■START■\\nMicrosoft is a big company\\n■END■\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■START■# MICROSOFT IS A BIG COMPANY■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Prompt: write it like we want it\\n\\n■START■\\nGoogle is an evil company\\n■END■\",\"role\":\"user\",\"type\":\"text\"},{\"content\":\"■START■# GOOGLE IS AN EVIL COMPANY■END■\",\"role\":\"assistant\",\"type\":\"text\"},{\"content\":\"Prompt: write it like we want it\\n\\n■START■\\nBotpress is awesome\\n■END■\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.rewrite\",\"promptSource\":\"zai:zai.rewrite:zai/rewrite\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[\"■END■\"]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"■START■# BOTPRESS IS AWESOME","metadata":{"provider":"cerebras","usage":{"inputTokens":205,"outputTokens":171,"inputCost":0.00007175,"outputCost":0.00012825},"model":"cerebras:gpt-oss-120b","ttft":174,"latency":953,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0002}}}
|
package/package.json
CHANGED
package/src/context.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Cognitive, Model, GenerateContentInput, GenerateContentOutput } from '@botpress/cognitive'
|
|
2
2
|
import { Adapter } from './adapters/adapter'
|
|
3
3
|
import { EventEmitter } from './emitter'
|
|
4
|
+
import { fastHash } from './utils'
|
|
5
|
+
import type { Memoizer } from './zai'
|
|
4
6
|
|
|
5
7
|
type Meta = Awaited<ReturnType<Cognitive['generateContent']>>['meta']
|
|
6
8
|
|
|
@@ -16,6 +18,7 @@ export type ZaiContextProps = {
|
|
|
16
18
|
modelId: string
|
|
17
19
|
adapter?: Adapter
|
|
18
20
|
source?: GenerateContentInput['meta']
|
|
21
|
+
memoizer?: Memoizer
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -94,10 +97,13 @@ export class ZaiContext {
|
|
|
94
97
|
public source?: GenerateContentInput['meta']
|
|
95
98
|
|
|
96
99
|
private _eventEmitter: EventEmitter<ContextEvents>
|
|
100
|
+
private _memoizer: Memoizer
|
|
97
101
|
|
|
98
102
|
public controller: AbortController = new AbortController()
|
|
99
103
|
private _client: Cognitive
|
|
100
104
|
|
|
105
|
+
private static _noopMemoizer: Memoizer = { run: (_id, fn) => fn() }
|
|
106
|
+
|
|
101
107
|
public constructor(props: ZaiContextProps) {
|
|
102
108
|
this._client = props.client.clone()
|
|
103
109
|
this.taskId = props.taskId
|
|
@@ -105,6 +111,7 @@ export class ZaiContext {
|
|
|
105
111
|
this.adapter = props.adapter
|
|
106
112
|
this.source = props.source
|
|
107
113
|
this.taskType = props.taskType
|
|
114
|
+
this._memoizer = props.memoizer ?? ZaiContext._noopMemoizer
|
|
108
115
|
this._eventEmitter = new EventEmitter<ContextEvents>()
|
|
109
116
|
|
|
110
117
|
this._client.on('request', () => {
|
|
@@ -148,6 +155,20 @@ export class ZaiContext {
|
|
|
148
155
|
|
|
149
156
|
public async generateContent<Out = string>(
|
|
150
157
|
props: GenerateContentProps<Out>
|
|
158
|
+
): Promise<{ meta: Meta; output: GenerateContentOutput; text: string | undefined; extracted: Out }> {
|
|
159
|
+
const memoKey = `zai:memo:${this.taskType}:${this.taskId || 'default'}:${fastHash(
|
|
160
|
+
JSON.stringify({
|
|
161
|
+
s: props.systemPrompt,
|
|
162
|
+
m: props.messages?.map((m) => ('content' in m ? m.content : '')),
|
|
163
|
+
st: props.stopSequences,
|
|
164
|
+
})
|
|
165
|
+
)}`
|
|
166
|
+
|
|
167
|
+
return this._memoizer.run(memoKey, () => this._generateContentInner(props))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async _generateContentInner<Out = string>(
|
|
171
|
+
props: GenerateContentProps<Out>
|
|
151
172
|
): Promise<{ meta: Meta; output: GenerateContentOutput; text: string | undefined; extracted: Out }> {
|
|
152
173
|
const maxRetries = Math.max(props.maxRetries ?? 3, 0)
|
|
153
174
|
const transform = props.transform
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Zai } from './zai'
|
|
1
|
+
import { Zai, type Memoizer } from './zai'
|
|
2
2
|
|
|
3
3
|
import './operations/text'
|
|
4
4
|
import './operations/rewrite'
|
|
@@ -14,3 +14,4 @@ import './operations/answer'
|
|
|
14
14
|
import './operations/patch'
|
|
15
15
|
|
|
16
16
|
export { Zai }
|
|
17
|
+
export type { Memoizer }
|
package/src/operations/answer.ts
CHANGED
package/src/operations/check.ts
CHANGED
|
@@ -484,6 +484,7 @@ Zai.prototype.extract = function <S extends OfType<AnyObjectOrArray>>(
|
|
|
484
484
|
taskId: this.taskId,
|
|
485
485
|
taskType: 'zai.extract',
|
|
486
486
|
adapter: this.adapter,
|
|
487
|
+
memoizer: this._resolveMemoizer(),
|
|
487
488
|
})
|
|
488
489
|
|
|
489
490
|
return new Response<S['_output']>(context, extract(input, schema, _options, context), (result) => result)
|
package/src/operations/filter.ts
CHANGED
|
@@ -363,6 +363,7 @@ Zai.prototype.filter = function <T>(
|
|
|
363
363
|
taskId: this.taskId,
|
|
364
364
|
taskType: 'zai.filter',
|
|
365
365
|
adapter: this.adapter,
|
|
366
|
+
memoizer: this._resolveMemoizer(),
|
|
366
367
|
})
|
|
367
368
|
|
|
368
369
|
return new Response<Array<T>>(context, filter(input, condition, _options, context), (result) => result)
|
package/src/operations/group.ts
CHANGED
|
@@ -955,6 +955,7 @@ Zai.prototype.group = function <T>(
|
|
|
955
955
|
taskId: this.taskId,
|
|
956
956
|
taskType: 'zai.group',
|
|
957
957
|
adapter: this.adapter,
|
|
958
|
+
memoizer: this._resolveMemoizer(),
|
|
958
959
|
})
|
|
959
960
|
|
|
960
961
|
return new Response<Array<Group<T>>, Record<string, T[]>>(context, group(input, _options, context), (result) => {
|
package/src/operations/label.ts
CHANGED
package/src/operations/patch.ts
CHANGED
|
@@ -650,6 +650,7 @@ Zai.prototype.patch = function (
|
|
|
650
650
|
taskId: this.taskId,
|
|
651
651
|
taskType: 'zai.patch',
|
|
652
652
|
adapter: this.adapter,
|
|
653
|
+
memoizer: this._resolveMemoizer(),
|
|
653
654
|
})
|
|
654
655
|
|
|
655
656
|
return new Response<Array<File>>(context, patch(files, instructions, _options, context), (result) => result)
|
package/src/operations/rate.ts
CHANGED
|
@@ -611,6 +611,7 @@ Zai.prototype.rate = function <T, I extends RatingInstructions>(
|
|
|
611
611
|
taskId: this.taskId,
|
|
612
612
|
taskType: 'zai.rate',
|
|
613
613
|
adapter: this.adapter,
|
|
614
|
+
memoizer: this._resolveMemoizer(),
|
|
614
615
|
})
|
|
615
616
|
|
|
616
617
|
return new Response<Array<RatingResult<I>>, Array<SimplifiedRatingResult<I>>>(
|
|
@@ -277,6 +277,7 @@ Zai.prototype.rewrite = function (this: Zai, original: string, prompt: string, _
|
|
|
277
277
|
taskId: this.taskId,
|
|
278
278
|
taskType: 'zai.rewrite',
|
|
279
279
|
adapter: this.adapter,
|
|
280
|
+
memoizer: this._resolveMemoizer(),
|
|
280
281
|
})
|
|
281
282
|
|
|
282
283
|
return new Response<string>(context, rewrite(original, prompt, _options, context), (result) => result)
|
package/src/operations/sort.ts
CHANGED
|
@@ -306,6 +306,7 @@ Zai.prototype.summarize = function (this: Zai, original, _options): Response<str
|
|
|
306
306
|
taskId: this.taskId,
|
|
307
307
|
taskType: 'summarize',
|
|
308
308
|
adapter: this.adapter,
|
|
309
|
+
memoizer: this._resolveMemoizer(),
|
|
309
310
|
})
|
|
310
311
|
|
|
311
312
|
return new Response<string, string>(context, summarize(original, options, context), (value) => value)
|
package/src/operations/text.ts
CHANGED
|
@@ -135,6 +135,7 @@ Zai.prototype.text = function (this: Zai, prompt: string, _options?: Options): R
|
|
|
135
135
|
taskId: this.taskId,
|
|
136
136
|
taskType: 'zai.text',
|
|
137
137
|
adapter: this.adapter,
|
|
138
|
+
memoizer: this._resolveMemoizer(),
|
|
138
139
|
})
|
|
139
140
|
|
|
140
141
|
return new Response<string>(context, text(prompt, _options, context), (result) => result)
|
package/src/zai.ts
CHANGED
|
@@ -8,6 +8,17 @@ import { Adapter } from './adapters/adapter'
|
|
|
8
8
|
import { TableAdapter } from './adapters/botpress-table'
|
|
9
9
|
import { MemoryAdapter } from './adapters/memory'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* A memoizer that caches the result of async operations by a unique key.
|
|
13
|
+
*
|
|
14
|
+
* When used with the Botpress ADK workflow `step` function, this enables
|
|
15
|
+
* Zai operations to resume where they left off if a workflow is interrupted.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
export type Memoizer = {
|
|
19
|
+
run: <T>(id: string, fn: () => Promise<T>) => Promise<T>
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
/**
|
|
12
23
|
* Active learning configuration for improving AI operations over time.
|
|
13
24
|
*
|
|
@@ -86,6 +97,16 @@ type ZaiConfig = {
|
|
|
86
97
|
activeLearning?: ActiveLearning
|
|
87
98
|
/** Namespace for organizing tasks (default: 'zai') */
|
|
88
99
|
namespace?: string
|
|
100
|
+
/**
|
|
101
|
+
* Memoizer (or factory returning one) for caching cognitive call results.
|
|
102
|
+
*
|
|
103
|
+
* When provided, all LLM calls are wrapped in the memoizer, allowing results
|
|
104
|
+
* to be cached and replayed. This is useful for resuming workflow runs where
|
|
105
|
+
* Zai operations have already completed their cognitive calls.
|
|
106
|
+
*
|
|
107
|
+
* If a factory function is provided, it is called once per Zai operation invocation.
|
|
108
|
+
*/
|
|
109
|
+
memoize?: Memoizer | (() => Memoizer)
|
|
89
110
|
}
|
|
90
111
|
|
|
91
112
|
const _ZaiConfig = z.object({
|
|
@@ -195,6 +216,7 @@ export class Zai {
|
|
|
195
216
|
protected namespace: string
|
|
196
217
|
protected adapter: Adapter
|
|
197
218
|
protected activeLearning: ActiveLearning
|
|
219
|
+
protected _memoize?: Memoizer | (() => Memoizer)
|
|
198
220
|
|
|
199
221
|
/**
|
|
200
222
|
* Creates a new Zai instance with the specified configuration.
|
|
@@ -236,6 +258,8 @@ export class Zai {
|
|
|
236
258
|
tableName: parsed.activeLearning.tableName,
|
|
237
259
|
})
|
|
238
260
|
: new MemoryAdapter([])
|
|
261
|
+
|
|
262
|
+
this._memoize = config.memoize
|
|
239
263
|
}
|
|
240
264
|
|
|
241
265
|
/** @internal */
|
|
@@ -250,6 +274,14 @@ export class Zai {
|
|
|
250
274
|
})
|
|
251
275
|
}
|
|
252
276
|
|
|
277
|
+
/** @internal */
|
|
278
|
+
protected _resolveMemoizer(): Memoizer | undefined {
|
|
279
|
+
if (!this._memoize) {
|
|
280
|
+
return undefined
|
|
281
|
+
}
|
|
282
|
+
return typeof this._memoize === 'function' ? this._memoize() : this._memoize
|
|
283
|
+
}
|
|
284
|
+
|
|
253
285
|
protected async getTokenizer() {
|
|
254
286
|
Zai.tokenizer ??= await (async () => {
|
|
255
287
|
while (!getWasmTokenizer) {
|