ak-gemini 1.0.4 → 1.0.51
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 +40 -20
- package/index.cjs +25 -2
- package/index.js +46 -3
- package/package.json +2 -2
- package/types.ts +7 -4
package/README.md
CHANGED
|
@@ -7,12 +7,13 @@ Use this to power LLM-driven data pipelines, JSON mapping, or any automated AI t
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
* **Model-Agnostic
|
|
11
|
-
* **Declarative Examples
|
|
12
|
-
* **Automatic Validation & Repair
|
|
13
|
-
* **
|
|
14
|
-
* **
|
|
15
|
-
* **
|
|
10
|
+
* **Model-Agnostic:** Use any Gemini model (`gemini-2.0-flash` by default)
|
|
11
|
+
* **Declarative Few-shot Examples:** Seed transformations using example mappings, with support for custom keys (`PROMPT`, `ANSWER`, `CONTEXT`, or your own)
|
|
12
|
+
* **Automatic Validation & Repair:** Validate outputs with your own async function; auto-repair failed payloads with LLM feedback loop (exponential backoff, fully configurable)
|
|
13
|
+
* **Token Counting & Safety:** Preview the *exact* Gemini token consumption for any operation—including all examples, instructions, and your input—before sending, so you can avoid window errors and manage costs.
|
|
14
|
+
* **Strong TypeScript/JSDoc Typings:** All public APIs fully typed (see `/types`)
|
|
15
|
+
* **Minimal API Surface:** Dead simple, no ceremony—init, seed, transform, validate.
|
|
16
|
+
* **Robust Logging:** Pluggable logger for all steps, easy debugging
|
|
16
17
|
|
|
17
18
|
---
|
|
18
19
|
|
|
@@ -43,7 +44,7 @@ or pass it directly in the constructor options.
|
|
|
43
44
|
### 2. **Basic Example**
|
|
44
45
|
|
|
45
46
|
```js
|
|
46
|
-
import AITransformer from '
|
|
47
|
+
import AITransformer from 'ak-gemini';
|
|
47
48
|
|
|
48
49
|
const transformer = new AITransformer({
|
|
49
50
|
modelName: 'gemini-2.0-flash', // or your preferred Gemini model
|
|
@@ -72,7 +73,22 @@ console.log(result);
|
|
|
72
73
|
|
|
73
74
|
---
|
|
74
75
|
|
|
75
|
-
### 3. **
|
|
76
|
+
### 3. **Token Window Safety/Preview**
|
|
77
|
+
|
|
78
|
+
Before calling `.message()` or `.seed()`, you can preview the exact token usage that will be sent to Gemini—*including* your system instructions, examples, and user input. This is vital for avoiding window errors and managing context size:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const { totalTokens, breakdown } = await transformer.estimateTokenUsage({ name: "Bob" });
|
|
82
|
+
console.log(`Total tokens: ${totalTokens}`);
|
|
83
|
+
console.log(breakdown); // See per-section token counts
|
|
84
|
+
|
|
85
|
+
// Optional: abort or trim if over limit
|
|
86
|
+
if (totalTokens > 32000) throw new Error("Request too large for selected Gemini model");
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### 4. **Automatic Validation & Self-Healing**
|
|
76
92
|
|
|
77
93
|
You can pass a custom async validator—if it fails, the transformer will attempt to self-correct using LLM feedback, retrying up to `maxRetries` times:
|
|
78
94
|
|
|
@@ -127,7 +143,12 @@ You can omit `examples` to use the `examplesFile` (if provided).
|
|
|
127
143
|
|
|
128
144
|
#### `await transformer.message(sourcePayload)`
|
|
129
145
|
|
|
130
|
-
Transforms input JSON to output JSON using the seeded examples and system instructions.
|
|
146
|
+
Transforms input JSON to output JSON using the seeded examples and system instructions. Throws if estimated token window would be exceeded.
|
|
147
|
+
|
|
148
|
+
#### `await transformer.estimateTokenUsage(sourcePayload)`
|
|
149
|
+
|
|
150
|
+
Returns `{ totalTokens, breakdown }` for the *full request* that would be sent to Gemini (system instructions + all examples + your sourcePayload as the new prompt).
|
|
151
|
+
Lets you preview token window safety and abort/trim as needed.
|
|
131
152
|
|
|
132
153
|
#### `await transformer.transformWithValidation(sourcePayload, validatorFn, options?)`
|
|
133
154
|
|
|
@@ -187,10 +208,19 @@ const result = await transformer.transformWithValidation(
|
|
|
187
208
|
|
|
188
209
|
---
|
|
189
210
|
|
|
211
|
+
## Token Window Management & Error Handling
|
|
212
|
+
|
|
213
|
+
* Throws on missing `GEMINI_API_KEY`
|
|
214
|
+
* `.message()` and `.seed()` will *estimate* and prevent calls that would exceed Gemini's model window
|
|
215
|
+
* All API and parsing errors surfaced as `Error` with context
|
|
216
|
+
* Validator and retry failures include the number of attempts and last error
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
190
220
|
## Testing
|
|
191
221
|
|
|
192
222
|
* **Jest test suite included**
|
|
193
|
-
*
|
|
223
|
+
* Real API integration tests as well as local unit tests
|
|
194
224
|
* 100% coverage for all error cases, configuration options, edge cases
|
|
195
225
|
|
|
196
226
|
Run tests with:
|
|
@@ -200,13 +230,3 @@ npm test
|
|
|
200
230
|
```
|
|
201
231
|
|
|
202
232
|
---
|
|
203
|
-
|
|
204
|
-
## Error Handling
|
|
205
|
-
|
|
206
|
-
* Throws on missing `GEMINI_API_KEY`
|
|
207
|
-
* All API and parsing errors surfaced as `Error` with context
|
|
208
|
-
* Validator and retry failures include the number of attempts and last error
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
|
package/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
// index.js
|
|
30
30
|
var index_exports = {};
|
|
31
31
|
__export(index_exports, {
|
|
32
|
+
AITransformer: () => AITransformer,
|
|
32
33
|
default: () => AITransformer,
|
|
33
34
|
log: () => logger_default
|
|
34
35
|
});
|
|
@@ -108,6 +109,7 @@ var AITransformer = class {
|
|
|
108
109
|
this.reset = resetChat.bind(this);
|
|
109
110
|
this.getHistory = getChatHistory.bind(this);
|
|
110
111
|
this.transformWithValidation = transformWithValidation.bind(this);
|
|
112
|
+
this.estimate = estimateTokenUsage.bind(this);
|
|
111
113
|
}
|
|
112
114
|
};
|
|
113
115
|
function AITransformFactory(options = {}) {
|
|
@@ -135,7 +137,8 @@ function AITransformFactory(options = {}) {
|
|
|
135
137
|
}
|
|
136
138
|
logger_default.debug(`Creating AI Transformer with model: ${this.modelName}`);
|
|
137
139
|
logger_default.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
|
|
138
|
-
|
|
140
|
+
const ai = new import_genai.GoogleGenAI({ apiKey: this.apiKey });
|
|
141
|
+
this.genAIClient = ai;
|
|
139
142
|
this.chat = null;
|
|
140
143
|
}
|
|
141
144
|
async function initChat() {
|
|
@@ -186,7 +189,7 @@ async function seedWithExamples(examples) {
|
|
|
186
189
|
historyToAdd.push({ role: "model", parts: [{ text: answerText }] });
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
|
-
const currentHistory = this
|
|
192
|
+
const currentHistory = this?.chat?.getHistory() || [];
|
|
190
193
|
this.chat = await this.genAIClient.chats.create({
|
|
191
194
|
model: this.modelName,
|
|
192
195
|
// @ts-ignore
|
|
@@ -246,6 +249,25 @@ async function transformWithValidation(sourcePayload, validatorFn, options = {})
|
|
|
246
249
|
}
|
|
247
250
|
}
|
|
248
251
|
}
|
|
252
|
+
async function estimateTokenUsage(nextPayload) {
|
|
253
|
+
const contents = [];
|
|
254
|
+
if (this.systemInstructions) {
|
|
255
|
+
contents.push({ parts: [{ text: this.systemInstructions }] });
|
|
256
|
+
}
|
|
257
|
+
if (this.chat && typeof this.chat.getHistory === "function") {
|
|
258
|
+
const history = this.chat.getHistory();
|
|
259
|
+
if (Array.isArray(history) && history.length > 0) {
|
|
260
|
+
contents.push(...history);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const nextMessage = typeof nextPayload === "string" ? nextPayload : JSON.stringify(nextPayload, null, 2);
|
|
264
|
+
contents.push({ parts: [{ text: nextMessage }] });
|
|
265
|
+
const resp = await this.genAIClient.models.countTokens({
|
|
266
|
+
model: this.modelName,
|
|
267
|
+
contents
|
|
268
|
+
});
|
|
269
|
+
return resp;
|
|
270
|
+
}
|
|
249
271
|
async function rebuildPayload(lastPayload, serverError) {
|
|
250
272
|
await this.init();
|
|
251
273
|
const prompt = `
|
|
@@ -353,5 +375,6 @@ if (import_meta.url === new URL(`file://${process.argv[1]}`).href) {
|
|
|
353
375
|
}
|
|
354
376
|
// Annotate the CommonJS export names for ESM import in node:
|
|
355
377
|
0 && (module.exports = {
|
|
378
|
+
AITransformer,
|
|
356
379
|
log
|
|
357
380
|
});
|
package/index.js
CHANGED
|
@@ -96,9 +96,10 @@ export default class AITransformer {
|
|
|
96
96
|
this.reset = resetChat.bind(this);
|
|
97
97
|
this.getHistory = getChatHistory.bind(this);
|
|
98
98
|
this.transformWithValidation = transformWithValidation.bind(this);
|
|
99
|
+
this.estimate = estimateTokenUsage.bind(this);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
+
export { AITransformer };
|
|
102
103
|
/**
|
|
103
104
|
* factory function to create an AI Transformer instance
|
|
104
105
|
* @param {AITransformerOptions} [options={}] - Configuration options for the transformer
|
|
@@ -143,7 +144,8 @@ function AITransformFactory(options = {}) {
|
|
|
143
144
|
log.debug(`Creating AI Transformer with model: ${this.modelName}`);
|
|
144
145
|
log.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
const ai = new GoogleGenAI({ apiKey: this.apiKey });
|
|
148
|
+
this.genAIClient = ai;
|
|
147
149
|
this.chat = null;
|
|
148
150
|
}
|
|
149
151
|
|
|
@@ -221,7 +223,7 @@ async function seedWithExamples(examples) {
|
|
|
221
223
|
}
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
const currentHistory = this
|
|
226
|
+
const currentHistory = this?.chat?.getHistory() || [];
|
|
225
227
|
|
|
226
228
|
this.chat = await this.genAIClient.chats.create({
|
|
227
229
|
model: this.modelName,
|
|
@@ -317,6 +319,47 @@ async function transformWithValidation(sourcePayload, validatorFn, options = {})
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Estimate total token usage if you were to send a new payload as the next message.
|
|
325
|
+
* Considers system instructions, current chat history (including examples), and the new message.
|
|
326
|
+
* @param {object|string} nextPayload - The next user message to be sent (object or string)
|
|
327
|
+
* @returns {Promise<{ totalTokens: number, ... }>} - The result of Gemini's countTokens API
|
|
328
|
+
*/
|
|
329
|
+
async function estimateTokenUsage(nextPayload) {
|
|
330
|
+
// Compose the conversation contents, Gemini-style
|
|
331
|
+
const contents = [];
|
|
332
|
+
|
|
333
|
+
// (1) System instructions (if applicable)
|
|
334
|
+
if (this.systemInstructions) {
|
|
335
|
+
// Add as a 'system' part; adjust role if Gemini supports
|
|
336
|
+
contents.push({ parts: [{ text: this.systemInstructions }] });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// (2) All current chat history (seeded examples + real user/model turns)
|
|
340
|
+
if (this.chat && typeof this.chat.getHistory === "function") {
|
|
341
|
+
const history = this.chat.getHistory();
|
|
342
|
+
if (Array.isArray(history) && history.length > 0) {
|
|
343
|
+
contents.push(...history);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// (3) The next user message
|
|
348
|
+
const nextMessage = typeof nextPayload === "string"
|
|
349
|
+
? nextPayload
|
|
350
|
+
: JSON.stringify(nextPayload, null, 2);
|
|
351
|
+
|
|
352
|
+
contents.push({ parts: [{ text: nextMessage }] });
|
|
353
|
+
|
|
354
|
+
// Call Gemini's token estimator
|
|
355
|
+
const resp = await this.genAIClient.models.countTokens({
|
|
356
|
+
model: this.modelName,
|
|
357
|
+
contents,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return resp; // includes totalTokens, possibly breakdown
|
|
361
|
+
}
|
|
362
|
+
|
|
320
363
|
/**
|
|
321
364
|
* Rebuilds a payload based on server error feedback
|
|
322
365
|
* @param {Object} lastPayload - The payload that failed validation
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "ak-gemini",
|
|
3
3
|
"author": "ak@mixpanel.com",
|
|
4
4
|
"description": "AK's Generative AI Helper for doing... transforms",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.51",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"index.js",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"license": "ISC",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@google-cloud/functions-framework": "^4.0.0",
|
|
50
|
-
"@google/genai": "^1.
|
|
50
|
+
"@google/genai": "^1.4.0",
|
|
51
51
|
"ak-tools": "^1.0.64",
|
|
52
52
|
"dotenv": "^16.5.0",
|
|
53
53
|
"pino": "^9.7.0",
|
package/types.ts
CHANGED
|
@@ -28,9 +28,9 @@ export interface AITransformerContext {
|
|
|
28
28
|
contextKey?: string;
|
|
29
29
|
maxRetries?: number;
|
|
30
30
|
retryDelay?: number;
|
|
31
|
-
init
|
|
32
|
-
seed
|
|
33
|
-
message
|
|
31
|
+
init?: () => Promise<void>; // Initialization function
|
|
32
|
+
seed?: () => Promise<void>; // Function to seed the transformer with examples
|
|
33
|
+
message?: (payload: Record<string, unknown>) => Promise<Record<string, unknown>>; // Function to send messages to the model
|
|
34
34
|
genAIClient?: GoogleGenAI; // Google GenAI client instance
|
|
35
35
|
|
|
36
36
|
}
|
|
@@ -62,4 +62,7 @@ export interface AITransformerOptions {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Async validator function type
|
|
65
|
-
export type AsyncValidatorFunction = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
65
|
+
export type AsyncValidatorFunction = (payload: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export declare class AITransformer implements AITransformerContext {}
|