ak-gemini 1.0.10 → 1.0.12
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 +51 -1
- package/index.cjs +177 -10
- package/index.js +262 -11
- package/package.json +1 -1
- package/types.d.ts +76 -11
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Use this to power LLM-driven data pipelines, JSON mapping, or any automated AI t
|
|
|
11
11
|
* **Declarative Few-shot Examples:** Seed transformations using example mappings, with support for custom keys (`PROMPT`, `ANSWER`, `CONTEXT`, or your own)
|
|
12
12
|
* **Automatic Validation & Repair:** Validate outputs with your own async function; auto-repair failed payloads with LLM feedback loop (exponential backoff, fully configurable)
|
|
13
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
|
+
* **Conversation Management:** Clear conversation history while preserving examples, or send stateless one-off messages that don't affect history
|
|
15
|
+
* **Response Metadata:** Access actual model version and token counts from API responses for billing verification and debugging
|
|
14
16
|
* **Strong TypeScript/JSDoc Typings:** All public APIs fully typed (see `/types`)
|
|
15
17
|
* **Minimal API Surface:** Dead simple, no ceremony—init, seed, transform, validate.
|
|
16
18
|
* **Robust Logging:** Pluggable logger for all steps, easy debugging
|
|
@@ -106,6 +108,25 @@ console.log(validPayload);
|
|
|
106
108
|
|
|
107
109
|
---
|
|
108
110
|
|
|
111
|
+
### 5. **Conversation Management**
|
|
112
|
+
|
|
113
|
+
Manage chat history to control costs and isolate requests:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// Clear conversation history while preserving seeded examples
|
|
117
|
+
await transformer.clearConversation();
|
|
118
|
+
|
|
119
|
+
// Send a stateless message that doesn't affect chat history
|
|
120
|
+
const result = await transformer.message({ query: "one-off question" }, { stateless: true });
|
|
121
|
+
|
|
122
|
+
// Check actual model and token usage from last API call
|
|
123
|
+
console.log(transformer.lastResponseMetadata);
|
|
124
|
+
// → { modelVersion: 'gemini-2.5-flash-001', requestedModel: 'gemini-2.5-flash',
|
|
125
|
+
// promptTokens: 150, responseTokens: 42, totalTokens: 192, timestamp: 1703... }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
109
130
|
## API
|
|
110
131
|
|
|
111
132
|
### Constructor
|
|
@@ -142,10 +163,14 @@ Initializes Gemini chat session (idempotent).
|
|
|
142
163
|
Seeds the model with example transformations (uses keys from constructor).
|
|
143
164
|
You can omit `examples` to use the `examplesFile` (if provided).
|
|
144
165
|
|
|
145
|
-
#### `await transformer.message(sourcePayload)`
|
|
166
|
+
#### `await transformer.message(sourcePayload, options?)`
|
|
146
167
|
|
|
147
168
|
Transforms input JSON to output JSON using the seeded examples and system instructions. Throws if estimated token window would be exceeded.
|
|
148
169
|
|
|
170
|
+
**Options:**
|
|
171
|
+
- `stateless: true` — Send a one-off message without affecting chat history (uses `generateContent` instead of chat)
|
|
172
|
+
- `labels: {}` — Per-message billing labels
|
|
173
|
+
|
|
149
174
|
#### `await transformer.estimateTokenUsage(sourcePayload)`
|
|
150
175
|
|
|
151
176
|
Returns `{ totalTokens, breakdown }` for the *full request* that would be sent to Gemini (system instructions + all examples + your sourcePayload as the new prompt).
|
|
@@ -168,6 +193,31 @@ Resets the Gemini chat session, clearing all history/examples.
|
|
|
168
193
|
|
|
169
194
|
Returns the current chat history (for debugging).
|
|
170
195
|
|
|
196
|
+
#### `await transformer.clearConversation()`
|
|
197
|
+
|
|
198
|
+
Clears conversation history while preserving seeded examples. Useful for starting fresh user sessions without re-seeding.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Properties
|
|
203
|
+
|
|
204
|
+
#### `transformer.lastResponseMetadata`
|
|
205
|
+
|
|
206
|
+
After each API call, contains metadata from the response:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
{
|
|
210
|
+
modelVersion: string | null, // Actual model version that responded (e.g., 'gemini-2.5-flash-001')
|
|
211
|
+
requestedModel: string, // Model you requested (e.g., 'gemini-2.5-flash')
|
|
212
|
+
promptTokens: number, // Tokens in the prompt
|
|
213
|
+
responseTokens: number, // Tokens in the response
|
|
214
|
+
totalTokens: number, // Total tokens used
|
|
215
|
+
timestamp: number // When response was received
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Useful for verifying billing, debugging model behavior, and tracking token usage.
|
|
220
|
+
|
|
171
221
|
---
|
|
172
222
|
|
|
173
223
|
## Examples
|
package/index.cjs
CHANGED
|
@@ -82,7 +82,7 @@ var DEFAULT_THINKING_CONFIG = {
|
|
|
82
82
|
thinkingBudget: 0,
|
|
83
83
|
thinkingLevel: import_genai.ThinkingLevel.MINIMAL
|
|
84
84
|
};
|
|
85
|
-
var DEFAULT_MAX_OUTPUT_TOKENS =
|
|
85
|
+
var DEFAULT_MAX_OUTPUT_TOKENS = 5e4;
|
|
86
86
|
var THINKING_SUPPORTED_MODELS = [
|
|
87
87
|
/^gemini-3-flash(-preview)?$/,
|
|
88
88
|
/^gemini-3-pro(-preview|-image-preview)?$/,
|
|
@@ -120,6 +120,8 @@ var AITransformer = class {
|
|
|
120
120
|
this.onlyJSON = true;
|
|
121
121
|
this.asyncValidator = null;
|
|
122
122
|
this.logLevel = "info";
|
|
123
|
+
this.lastResponseMetadata = null;
|
|
124
|
+
this.exampleCount = 0;
|
|
123
125
|
AITransformFactory.call(this, options);
|
|
124
126
|
this.init = initChat.bind(this);
|
|
125
127
|
this.seed = seedWithExamples.bind(this);
|
|
@@ -135,6 +137,8 @@ var AITransformer = class {
|
|
|
135
137
|
this.estimate = estimateTokenUsage.bind(this);
|
|
136
138
|
this.estimateTokenUsage = estimateTokenUsage.bind(this);
|
|
137
139
|
this.updateSystemInstructions = updateSystemInstructions.bind(this);
|
|
140
|
+
this.estimateCost = estimateCost.bind(this);
|
|
141
|
+
this.clearConversation = clearConversation.bind(this);
|
|
138
142
|
}
|
|
139
143
|
};
|
|
140
144
|
var index_default = AITransformer;
|
|
@@ -164,8 +168,17 @@ function AITransformFactory(options = {}) {
|
|
|
164
168
|
this.logLevel = "info";
|
|
165
169
|
logger_default.level = "info";
|
|
166
170
|
}
|
|
171
|
+
this.vertexai = options.vertexai || false;
|
|
172
|
+
this.project = options.project || process.env.GOOGLE_CLOUD_PROJECT || null;
|
|
173
|
+
this.location = options.location || process.env.GOOGLE_CLOUD_LOCATION || "us-central1";
|
|
174
|
+
this.googleAuthOptions = options.googleAuthOptions || null;
|
|
167
175
|
this.apiKey = options.apiKey !== void 0 && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
|
|
168
|
-
if (!this.
|
|
176
|
+
if (!this.vertexai && !this.apiKey) {
|
|
177
|
+
throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var. For Vertex AI, set vertexai: true with project and location.");
|
|
178
|
+
}
|
|
179
|
+
if (this.vertexai && !this.project) {
|
|
180
|
+
throw new Error("Vertex AI requires a project ID. Provide via options.project or GOOGLE_CLOUD_PROJECT env var.");
|
|
181
|
+
}
|
|
169
182
|
this.chatConfig = {
|
|
170
183
|
...DEFAULT_CHAT_CONFIG,
|
|
171
184
|
...options.chatConfig,
|
|
@@ -226,6 +239,10 @@ function AITransformFactory(options = {}) {
|
|
|
226
239
|
this.onlyJSON = options.onlyJSON !== void 0 ? options.onlyJSON : true;
|
|
227
240
|
this.enableGrounding = options.enableGrounding || false;
|
|
228
241
|
this.groundingConfig = options.groundingConfig || {};
|
|
242
|
+
this.labels = options.labels || {};
|
|
243
|
+
if (Object.keys(this.labels).length > 0 && logger_default.level !== "silent") {
|
|
244
|
+
logger_default.debug(`Billing labels configured: ${JSON.stringify(this.labels)}`);
|
|
245
|
+
}
|
|
229
246
|
if (this.promptKey === this.answerKey) {
|
|
230
247
|
throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
|
|
231
248
|
}
|
|
@@ -233,10 +250,27 @@ function AITransformFactory(options = {}) {
|
|
|
233
250
|
logger_default.debug(`Creating AI Transformer with model: ${this.modelName}`);
|
|
234
251
|
logger_default.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
|
|
235
252
|
logger_default.debug(`Max output tokens set to: ${this.chatConfig.maxOutputTokens}`);
|
|
236
|
-
|
|
253
|
+
if (this.vertexai) {
|
|
254
|
+
logger_default.debug(`Using Vertex AI - Project: ${this.project}, Location: ${this.location}`);
|
|
255
|
+
if (this.googleAuthOptions?.keyFilename) {
|
|
256
|
+
logger_default.debug(`Auth: Service account key file: ${this.googleAuthOptions.keyFilename}`);
|
|
257
|
+
} else if (this.googleAuthOptions?.credentials) {
|
|
258
|
+
logger_default.debug(`Auth: Inline credentials provided`);
|
|
259
|
+
} else {
|
|
260
|
+
logger_default.debug(`Auth: Application Default Credentials (ADC)`);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
logger_default.debug(`Using Gemini API with key: ${this.apiKey.substring(0, 10)}...`);
|
|
264
|
+
}
|
|
237
265
|
logger_default.debug(`Grounding ${this.enableGrounding ? "ENABLED" : "DISABLED"} (costs $35/1k queries)`);
|
|
238
266
|
}
|
|
239
|
-
const
|
|
267
|
+
const clientOptions = this.vertexai ? {
|
|
268
|
+
vertexai: true,
|
|
269
|
+
project: this.project,
|
|
270
|
+
location: this.location,
|
|
271
|
+
...this.googleAuthOptions && { googleAuthOptions: this.googleAuthOptions }
|
|
272
|
+
} : { apiKey: this.apiKey };
|
|
273
|
+
const ai = new import_genai.GoogleGenAI(clientOptions);
|
|
240
274
|
this.genAIClient = ai;
|
|
241
275
|
this.chat = null;
|
|
242
276
|
}
|
|
@@ -246,7 +280,10 @@ async function initChat(force = false) {
|
|
|
246
280
|
const chatOptions = {
|
|
247
281
|
model: this.modelName,
|
|
248
282
|
// @ts-ignore
|
|
249
|
-
config:
|
|
283
|
+
config: {
|
|
284
|
+
...this.chatConfig,
|
|
285
|
+
...Object.keys(this.labels).length > 0 && { labels: this.labels }
|
|
286
|
+
},
|
|
250
287
|
history: []
|
|
251
288
|
};
|
|
252
289
|
if (this.enableGrounding) {
|
|
@@ -326,20 +363,47 @@ ${contextText}
|
|
|
326
363
|
this.chat = await this.genAIClient.chats.create({
|
|
327
364
|
model: this.modelName,
|
|
328
365
|
// @ts-ignore
|
|
329
|
-
config:
|
|
366
|
+
config: {
|
|
367
|
+
...this.chatConfig,
|
|
368
|
+
...Object.keys(this.labels).length > 0 && { labels: this.labels }
|
|
369
|
+
},
|
|
330
370
|
history: [...currentHistory, ...historyToAdd]
|
|
331
371
|
});
|
|
372
|
+
this.exampleCount = currentHistory.length + historyToAdd.length;
|
|
332
373
|
const newHistory = this.chat.getHistory();
|
|
333
374
|
logger_default.debug(`Created new chat session with ${newHistory.length} examples.`);
|
|
334
375
|
return newHistory;
|
|
335
376
|
}
|
|
336
|
-
async function rawMessage(sourcePayload) {
|
|
377
|
+
async function rawMessage(sourcePayload, messageOptions = {}) {
|
|
337
378
|
if (!this.chat) {
|
|
338
379
|
throw new Error("Chat session not initialized.");
|
|
339
380
|
}
|
|
340
381
|
const actualPayload = typeof sourcePayload === "string" ? sourcePayload : JSON.stringify(sourcePayload, null, 2);
|
|
382
|
+
const mergedLabels = { ...this.labels, ...messageOptions.labels || {} };
|
|
383
|
+
const hasLabels = Object.keys(mergedLabels).length > 0;
|
|
341
384
|
try {
|
|
342
|
-
const
|
|
385
|
+
const sendParams = { message: actualPayload };
|
|
386
|
+
if (hasLabels) {
|
|
387
|
+
sendParams.config = { labels: mergedLabels };
|
|
388
|
+
}
|
|
389
|
+
const result = await this.chat.sendMessage(sendParams);
|
|
390
|
+
this.lastResponseMetadata = {
|
|
391
|
+
modelVersion: result.modelVersion || null,
|
|
392
|
+
requestedModel: this.modelName,
|
|
393
|
+
promptTokens: result.usageMetadata?.promptTokenCount || 0,
|
|
394
|
+
responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
|
|
395
|
+
totalTokens: result.usageMetadata?.totalTokenCount || 0,
|
|
396
|
+
timestamp: Date.now()
|
|
397
|
+
};
|
|
398
|
+
if (result.usageMetadata && logger_default.level !== "silent") {
|
|
399
|
+
logger_default.debug(`API response metadata:`, {
|
|
400
|
+
modelVersion: result.modelVersion || "not-provided",
|
|
401
|
+
requestedModel: this.modelName,
|
|
402
|
+
promptTokens: result.usageMetadata.promptTokenCount,
|
|
403
|
+
responseTokens: result.usageMetadata.candidatesTokenCount,
|
|
404
|
+
totalTokens: result.usageMetadata.totalTokenCount
|
|
405
|
+
});
|
|
406
|
+
}
|
|
343
407
|
const modelResponse = result.text;
|
|
344
408
|
const extractedJSON = extractJSON(modelResponse);
|
|
345
409
|
if (extractedJSON?.data) {
|
|
@@ -357,6 +421,9 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
|
|
|
357
421
|
if (!this.chat) {
|
|
358
422
|
throw new Error("Chat session not initialized. Please call init() first.");
|
|
359
423
|
}
|
|
424
|
+
if (options.stateless) {
|
|
425
|
+
return await statelessMessage.call(this, sourcePayload, options, validatorFn);
|
|
426
|
+
}
|
|
360
427
|
const maxRetries = options.maxRetries ?? this.maxRetries;
|
|
361
428
|
const retryDelay = options.retryDelay ?? this.retryDelay;
|
|
362
429
|
const enableGroundingForMessage = options.enableGrounding ?? this.enableGrounding;
|
|
@@ -398,9 +465,13 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
|
|
|
398
465
|
} else {
|
|
399
466
|
throw new Error("Invalid source payload. Must be a JSON object or string.");
|
|
400
467
|
}
|
|
468
|
+
const messageOptions = {};
|
|
469
|
+
if (options.labels) {
|
|
470
|
+
messageOptions.labels = options.labels;
|
|
471
|
+
}
|
|
401
472
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
402
473
|
try {
|
|
403
|
-
const transformedPayload = attempt === 0 ? await this.rawMessage(lastPayload) : await this.rebuild(lastPayload, lastError.message);
|
|
474
|
+
const transformedPayload = attempt === 0 ? await this.rawMessage(lastPayload, messageOptions) : await this.rebuild(lastPayload, lastError.message);
|
|
404
475
|
lastPayload = transformedPayload;
|
|
405
476
|
if (validatorFn) {
|
|
406
477
|
await validatorFn(transformedPayload);
|
|
@@ -444,6 +515,17 @@ Respond with JSON only \u2013 no comments or explanations.
|
|
|
444
515
|
let result;
|
|
445
516
|
try {
|
|
446
517
|
result = await this.chat.sendMessage({ message: prompt });
|
|
518
|
+
this.lastResponseMetadata = {
|
|
519
|
+
modelVersion: result.modelVersion || null,
|
|
520
|
+
requestedModel: this.modelName,
|
|
521
|
+
promptTokens: result.usageMetadata?.promptTokenCount || 0,
|
|
522
|
+
responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
|
|
523
|
+
totalTokens: result.usageMetadata?.totalTokenCount || 0,
|
|
524
|
+
timestamp: Date.now()
|
|
525
|
+
};
|
|
526
|
+
if (result.usageMetadata && logger_default.level !== "silent") {
|
|
527
|
+
logger_default.debug(`Rebuild response metadata - tokens used:`, result.usageMetadata.totalTokenCount);
|
|
528
|
+
}
|
|
447
529
|
} catch (err) {
|
|
448
530
|
throw new Error(`Gemini call failed while repairing payload: ${err.message}`);
|
|
449
531
|
}
|
|
@@ -473,13 +555,36 @@ async function estimateTokenUsage(nextPayload) {
|
|
|
473
555
|
});
|
|
474
556
|
return resp;
|
|
475
557
|
}
|
|
558
|
+
var MODEL_PRICING = {
|
|
559
|
+
"gemini-2.5-flash": { input: 0.15, output: 0.6 },
|
|
560
|
+
"gemini-2.5-flash-lite": { input: 0.02, output: 0.1 },
|
|
561
|
+
"gemini-2.5-pro": { input: 2.5, output: 10 },
|
|
562
|
+
"gemini-3-pro": { input: 2, output: 12 },
|
|
563
|
+
"gemini-3-pro-preview": { input: 2, output: 12 },
|
|
564
|
+
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
565
|
+
"gemini-2.0-flash-lite": { input: 0.02, output: 0.1 }
|
|
566
|
+
};
|
|
567
|
+
async function estimateCost(nextPayload) {
|
|
568
|
+
const tokenInfo = await this.estimateTokenUsage(nextPayload);
|
|
569
|
+
const pricing = MODEL_PRICING[this.modelName] || { input: 0, output: 0 };
|
|
570
|
+
return {
|
|
571
|
+
totalTokens: tokenInfo.totalTokens,
|
|
572
|
+
model: this.modelName,
|
|
573
|
+
pricing,
|
|
574
|
+
estimatedInputCost: tokenInfo.totalTokens / 1e6 * pricing.input,
|
|
575
|
+
note: "Cost is for input tokens only; output cost depends on response length"
|
|
576
|
+
};
|
|
577
|
+
}
|
|
476
578
|
async function resetChat() {
|
|
477
579
|
if (this.chat) {
|
|
478
580
|
logger_default.debug("Resetting Gemini chat session...");
|
|
479
581
|
const chatOptions = {
|
|
480
582
|
model: this.modelName,
|
|
481
583
|
// @ts-ignore
|
|
482
|
-
config:
|
|
584
|
+
config: {
|
|
585
|
+
...this.chatConfig,
|
|
586
|
+
...Object.keys(this.labels).length > 0 && { labels: this.labels }
|
|
587
|
+
},
|
|
483
588
|
history: []
|
|
484
589
|
};
|
|
485
590
|
if (this.enableGrounding) {
|
|
@@ -510,6 +615,68 @@ async function updateSystemInstructions(newInstructions) {
|
|
|
510
615
|
logger_default.debug("Updating system instructions and reinitializing chat...");
|
|
511
616
|
await this.init(true);
|
|
512
617
|
}
|
|
618
|
+
async function clearConversation() {
|
|
619
|
+
if (!this.chat) {
|
|
620
|
+
logger_default.warn("Cannot clear conversation: chat not initialized.");
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const history = this.chat.getHistory();
|
|
624
|
+
const exampleHistory = history.slice(0, this.exampleCount || 0);
|
|
625
|
+
this.chat = await this.genAIClient.chats.create({
|
|
626
|
+
model: this.modelName,
|
|
627
|
+
// @ts-ignore
|
|
628
|
+
config: {
|
|
629
|
+
...this.chatConfig,
|
|
630
|
+
...Object.keys(this.labels).length > 0 && { labels: this.labels }
|
|
631
|
+
},
|
|
632
|
+
history: exampleHistory
|
|
633
|
+
});
|
|
634
|
+
logger_default.debug(`Conversation cleared. Preserved ${exampleHistory.length} example items.`);
|
|
635
|
+
}
|
|
636
|
+
async function statelessMessage(sourcePayload, options = {}, validatorFn = null) {
|
|
637
|
+
if (!this.chat) {
|
|
638
|
+
throw new Error("Chat session not initialized. Please call init() first.");
|
|
639
|
+
}
|
|
640
|
+
const payloadStr = typeof sourcePayload === "string" ? sourcePayload : JSON.stringify(sourcePayload, null, 2);
|
|
641
|
+
const contents = [];
|
|
642
|
+
if (this.exampleCount > 0) {
|
|
643
|
+
const history = this.chat.getHistory();
|
|
644
|
+
const exampleHistory = history.slice(0, this.exampleCount);
|
|
645
|
+
contents.push(...exampleHistory);
|
|
646
|
+
}
|
|
647
|
+
contents.push({ role: "user", parts: [{ text: payloadStr }] });
|
|
648
|
+
const mergedLabels = { ...this.labels, ...options.labels || {} };
|
|
649
|
+
const result = await this.genAIClient.models.generateContent({
|
|
650
|
+
model: this.modelName,
|
|
651
|
+
contents,
|
|
652
|
+
config: {
|
|
653
|
+
...this.chatConfig,
|
|
654
|
+
...Object.keys(mergedLabels).length > 0 && { labels: mergedLabels }
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
this.lastResponseMetadata = {
|
|
658
|
+
modelVersion: result.modelVersion || null,
|
|
659
|
+
requestedModel: this.modelName,
|
|
660
|
+
promptTokens: result.usageMetadata?.promptTokenCount || 0,
|
|
661
|
+
responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
|
|
662
|
+
totalTokens: result.usageMetadata?.totalTokenCount || 0,
|
|
663
|
+
timestamp: Date.now()
|
|
664
|
+
};
|
|
665
|
+
if (result.usageMetadata && logger_default.level !== "silent") {
|
|
666
|
+
logger_default.debug(`Stateless message metadata:`, {
|
|
667
|
+
modelVersion: result.modelVersion || "not-provided",
|
|
668
|
+
promptTokens: result.usageMetadata.promptTokenCount,
|
|
669
|
+
responseTokens: result.usageMetadata.candidatesTokenCount
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
const modelResponse = result.text;
|
|
673
|
+
const extractedJSON = extractJSON(modelResponse);
|
|
674
|
+
let transformedPayload = extractedJSON?.data ? extractedJSON.data : extractedJSON;
|
|
675
|
+
if (validatorFn) {
|
|
676
|
+
await validatorFn(transformedPayload);
|
|
677
|
+
}
|
|
678
|
+
return transformedPayload;
|
|
679
|
+
}
|
|
513
680
|
function attemptJSONRecovery(text, maxAttempts = 100) {
|
|
514
681
|
if (!text || typeof text !== "string") return null;
|
|
515
682
|
try {
|
package/index.js
CHANGED
|
@@ -57,7 +57,7 @@ const DEFAULT_THINKING_CONFIG = {
|
|
|
57
57
|
thinkingLevel: ThinkingLevel.MINIMAL
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const DEFAULT_MAX_OUTPUT_TOKENS =
|
|
60
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 50_000; // Default ceiling for output tokens
|
|
61
61
|
|
|
62
62
|
// Models that support thinking features (as of Dec 2024)
|
|
63
63
|
// Using regex patterns for more precise matching
|
|
@@ -112,6 +112,8 @@ class AITransformer {
|
|
|
112
112
|
this.onlyJSON = true; // always return JSON
|
|
113
113
|
this.asyncValidator = null; // for transformWithValidation
|
|
114
114
|
this.logLevel = 'info'; // default log level
|
|
115
|
+
this.lastResponseMetadata = null; // stores metadata from last API response
|
|
116
|
+
this.exampleCount = 0; // tracks number of example history items from seed()
|
|
115
117
|
AITransformFactory.call(this, options);
|
|
116
118
|
|
|
117
119
|
//external API
|
|
@@ -135,6 +137,8 @@ class AITransformer {
|
|
|
135
137
|
this.estimate = estimateTokenUsage.bind(this);
|
|
136
138
|
this.estimateTokenUsage = estimateTokenUsage.bind(this);
|
|
137
139
|
this.updateSystemInstructions = updateSystemInstructions.bind(this);
|
|
140
|
+
this.estimateCost = estimateCost.bind(this);
|
|
141
|
+
this.clearConversation = clearConversation.bind(this);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
|
|
@@ -180,8 +184,22 @@ function AITransformFactory(options = {}) {
|
|
|
180
184
|
log.level = 'info';
|
|
181
185
|
}
|
|
182
186
|
|
|
187
|
+
// Vertex AI configuration
|
|
188
|
+
this.vertexai = options.vertexai || false;
|
|
189
|
+
this.project = options.project || process.env.GOOGLE_CLOUD_PROJECT || null;
|
|
190
|
+
this.location = options.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
|
|
191
|
+
this.googleAuthOptions = options.googleAuthOptions || null;
|
|
192
|
+
|
|
193
|
+
// API Key (for Gemini API, not Vertex AI)
|
|
183
194
|
this.apiKey = options.apiKey !== undefined && options.apiKey !== null ? options.apiKey : GEMINI_API_KEY;
|
|
184
|
-
|
|
195
|
+
|
|
196
|
+
// Validate authentication - need either API key (for Gemini API) or Vertex AI config
|
|
197
|
+
if (!this.vertexai && !this.apiKey) {
|
|
198
|
+
throw new Error("Missing Gemini API key. Provide via options.apiKey or GEMINI_API_KEY env var. For Vertex AI, set vertexai: true with project and location.");
|
|
199
|
+
}
|
|
200
|
+
if (this.vertexai && !this.project) {
|
|
201
|
+
throw new Error("Vertex AI requires a project ID. Provide via options.project or GOOGLE_CLOUD_PROJECT env var.");
|
|
202
|
+
}
|
|
185
203
|
|
|
186
204
|
// Build chat config, making sure systemInstruction uses the custom instructions
|
|
187
205
|
this.chatConfig = {
|
|
@@ -270,6 +288,12 @@ function AITransformFactory(options = {}) {
|
|
|
270
288
|
this.enableGrounding = options.enableGrounding || false;
|
|
271
289
|
this.groundingConfig = options.groundingConfig || {};
|
|
272
290
|
|
|
291
|
+
// Billing labels for cost segmentation
|
|
292
|
+
this.labels = options.labels || {};
|
|
293
|
+
if (Object.keys(this.labels).length > 0 && log.level !== 'silent') {
|
|
294
|
+
log.debug(`Billing labels configured: ${JSON.stringify(this.labels)}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
273
297
|
if (this.promptKey === this.answerKey) {
|
|
274
298
|
throw new Error("Source and target keys cannot be the same. Please provide distinct keys.");
|
|
275
299
|
}
|
|
@@ -278,12 +302,33 @@ function AITransformFactory(options = {}) {
|
|
|
278
302
|
log.debug(`Creating AI Transformer with model: ${this.modelName}`);
|
|
279
303
|
log.debug(`Using keys - Source: "${this.promptKey}", Target: "${this.answerKey}", Context: "${this.contextKey}"`);
|
|
280
304
|
log.debug(`Max output tokens set to: ${this.chatConfig.maxOutputTokens}`);
|
|
281
|
-
// Log
|
|
282
|
-
|
|
305
|
+
// Log authentication method
|
|
306
|
+
if (this.vertexai) {
|
|
307
|
+
log.debug(`Using Vertex AI - Project: ${this.project}, Location: ${this.location}`);
|
|
308
|
+
if (this.googleAuthOptions?.keyFilename) {
|
|
309
|
+
log.debug(`Auth: Service account key file: ${this.googleAuthOptions.keyFilename}`);
|
|
310
|
+
} else if (this.googleAuthOptions?.credentials) {
|
|
311
|
+
log.debug(`Auth: Inline credentials provided`);
|
|
312
|
+
} else {
|
|
313
|
+
log.debug(`Auth: Application Default Credentials (ADC)`);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
log.debug(`Using Gemini API with key: ${this.apiKey.substring(0, 10)}...`);
|
|
317
|
+
}
|
|
283
318
|
log.debug(`Grounding ${this.enableGrounding ? 'ENABLED' : 'DISABLED'} (costs $35/1k queries)`);
|
|
284
319
|
}
|
|
285
320
|
|
|
286
|
-
|
|
321
|
+
// Initialize Google GenAI client with appropriate configuration
|
|
322
|
+
const clientOptions = this.vertexai
|
|
323
|
+
? {
|
|
324
|
+
vertexai: true,
|
|
325
|
+
project: this.project,
|
|
326
|
+
location: this.location,
|
|
327
|
+
...(this.googleAuthOptions && { googleAuthOptions: this.googleAuthOptions })
|
|
328
|
+
}
|
|
329
|
+
: { apiKey: this.apiKey };
|
|
330
|
+
|
|
331
|
+
const ai = new GoogleGenAI(clientOptions);
|
|
287
332
|
this.genAIClient = ai;
|
|
288
333
|
this.chat = null;
|
|
289
334
|
}
|
|
@@ -303,7 +348,10 @@ async function initChat(force = false) {
|
|
|
303
348
|
const chatOptions = {
|
|
304
349
|
model: this.modelName,
|
|
305
350
|
// @ts-ignore
|
|
306
|
-
config:
|
|
351
|
+
config: {
|
|
352
|
+
...this.chatConfig,
|
|
353
|
+
...(Object.keys(this.labels).length > 0 && { labels: this.labels })
|
|
354
|
+
},
|
|
307
355
|
history: [],
|
|
308
356
|
};
|
|
309
357
|
|
|
@@ -415,10 +463,15 @@ async function seedWithExamples(examples) {
|
|
|
415
463
|
this.chat = await this.genAIClient.chats.create({
|
|
416
464
|
model: this.modelName,
|
|
417
465
|
// @ts-ignore
|
|
418
|
-
config:
|
|
466
|
+
config: {
|
|
467
|
+
...this.chatConfig,
|
|
468
|
+
...(Object.keys(this.labels).length > 0 && { labels: this.labels })
|
|
469
|
+
},
|
|
419
470
|
history: [...currentHistory, ...historyToAdd],
|
|
420
471
|
});
|
|
421
472
|
|
|
473
|
+
// Track example count for clearConversation() and stateless messages
|
|
474
|
+
this.exampleCount = currentHistory.length + historyToAdd.length;
|
|
422
475
|
|
|
423
476
|
const newHistory = this.chat.getHistory();
|
|
424
477
|
log.debug(`Created new chat session with ${newHistory.length} examples.`);
|
|
@@ -436,9 +489,10 @@ async function seedWithExamples(examples) {
|
|
|
436
489
|
* No validation or retry logic.
|
|
437
490
|
* @this {ExportedAPI}
|
|
438
491
|
* @param {Object|string} sourcePayload - The source payload.
|
|
492
|
+
* @param {Object} [messageOptions] - Optional per-message options (e.g., labels).
|
|
439
493
|
* @returns {Promise<Object>} - The transformed payload.
|
|
440
494
|
*/
|
|
441
|
-
async function rawMessage(sourcePayload) {
|
|
495
|
+
async function rawMessage(sourcePayload, messageOptions = {}) {
|
|
442
496
|
if (!this.chat) {
|
|
443
497
|
throw new Error("Chat session not initialized.");
|
|
444
498
|
}
|
|
@@ -447,8 +501,40 @@ async function rawMessage(sourcePayload) {
|
|
|
447
501
|
? sourcePayload
|
|
448
502
|
: JSON.stringify(sourcePayload, null, 2);
|
|
449
503
|
|
|
504
|
+
// Merge instance labels with per-message labels (per-message takes precedence)
|
|
505
|
+
const mergedLabels = { ...this.labels, ...(messageOptions.labels || {}) };
|
|
506
|
+
const hasLabels = Object.keys(mergedLabels).length > 0;
|
|
507
|
+
|
|
450
508
|
try {
|
|
451
|
-
const
|
|
509
|
+
const sendParams = { message: actualPayload };
|
|
510
|
+
|
|
511
|
+
// Add config with labels if we have any
|
|
512
|
+
if (hasLabels) {
|
|
513
|
+
sendParams.config = { labels: mergedLabels };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const result = await this.chat.sendMessage(sendParams);
|
|
517
|
+
|
|
518
|
+
// Capture and log response metadata for model verification and debugging
|
|
519
|
+
this.lastResponseMetadata = {
|
|
520
|
+
modelVersion: result.modelVersion || null,
|
|
521
|
+
requestedModel: this.modelName,
|
|
522
|
+
promptTokens: result.usageMetadata?.promptTokenCount || 0,
|
|
523
|
+
responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
|
|
524
|
+
totalTokens: result.usageMetadata?.totalTokenCount || 0,
|
|
525
|
+
timestamp: Date.now()
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
if (result.usageMetadata && log.level !== 'silent') {
|
|
529
|
+
log.debug(`API response metadata:`, {
|
|
530
|
+
modelVersion: result.modelVersion || 'not-provided',
|
|
531
|
+
requestedModel: this.modelName,
|
|
532
|
+
promptTokens: result.usageMetadata.promptTokenCount,
|
|
533
|
+
responseTokens: result.usageMetadata.candidatesTokenCount,
|
|
534
|
+
totalTokens: result.usageMetadata.totalTokenCount
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
452
538
|
const modelResponse = result.text;
|
|
453
539
|
const extractedJSON = extractJSON(modelResponse); // Assuming extractJSON is defined
|
|
454
540
|
|
|
@@ -479,6 +565,12 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
|
|
|
479
565
|
if (!this.chat) {
|
|
480
566
|
throw new Error("Chat session not initialized. Please call init() first.");
|
|
481
567
|
}
|
|
568
|
+
|
|
569
|
+
// Handle stateless messages separately - they don't add to chat history
|
|
570
|
+
if (options.stateless) {
|
|
571
|
+
return await statelessMessage.call(this, sourcePayload, options, validatorFn);
|
|
572
|
+
}
|
|
573
|
+
|
|
482
574
|
const maxRetries = options.maxRetries ?? this.maxRetries;
|
|
483
575
|
const retryDelay = options.retryDelay ?? this.retryDelay;
|
|
484
576
|
|
|
@@ -542,11 +634,17 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
|
|
|
542
634
|
throw new Error("Invalid source payload. Must be a JSON object or string.");
|
|
543
635
|
}
|
|
544
636
|
|
|
637
|
+
// Extract per-message labels for passing to rawMessage
|
|
638
|
+
const messageOptions = {};
|
|
639
|
+
if (options.labels) {
|
|
640
|
+
messageOptions.labels = options.labels;
|
|
641
|
+
}
|
|
642
|
+
|
|
545
643
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
546
644
|
try {
|
|
547
645
|
// Step 1: Get the transformed payload
|
|
548
646
|
const transformedPayload = (attempt === 0)
|
|
549
|
-
? await this.rawMessage(lastPayload) // Use the new raw method
|
|
647
|
+
? await this.rawMessage(lastPayload, messageOptions) // Use the new raw method with per-message options
|
|
550
648
|
: await this.rebuild(lastPayload, lastError.message);
|
|
551
649
|
|
|
552
650
|
lastPayload = transformedPayload; // Always update lastPayload *before* validation
|
|
@@ -590,6 +688,7 @@ async function prepareAndValidateMessage(sourcePayload, options = {}, validatorF
|
|
|
590
688
|
|
|
591
689
|
/**
|
|
592
690
|
* Rebuilds a payload based on server error feedback
|
|
691
|
+
* @this {ExportedAPI}
|
|
593
692
|
* @param {Object} lastPayload - The payload that failed validation
|
|
594
693
|
* @param {string} serverError - The error message from the server
|
|
595
694
|
* @returns {Promise<Object>} - A new corrected payload
|
|
@@ -615,6 +714,20 @@ Respond with JSON only – no comments or explanations.
|
|
|
615
714
|
let result;
|
|
616
715
|
try {
|
|
617
716
|
result = await this.chat.sendMessage({ message: prompt });
|
|
717
|
+
|
|
718
|
+
// Capture and log response metadata for rebuild calls too
|
|
719
|
+
this.lastResponseMetadata = {
|
|
720
|
+
modelVersion: result.modelVersion || null,
|
|
721
|
+
requestedModel: this.modelName,
|
|
722
|
+
promptTokens: result.usageMetadata?.promptTokenCount || 0,
|
|
723
|
+
responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
|
|
724
|
+
totalTokens: result.usageMetadata?.totalTokenCount || 0,
|
|
725
|
+
timestamp: Date.now()
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
if (result.usageMetadata && log.level !== 'silent') {
|
|
729
|
+
log.debug(`Rebuild response metadata - tokens used:`, result.usageMetadata.totalTokenCount);
|
|
730
|
+
}
|
|
618
731
|
} catch (err) {
|
|
619
732
|
throw new Error(`Gemini call failed while repairing payload: ${err.message}`);
|
|
620
733
|
}
|
|
@@ -670,6 +783,37 @@ async function estimateTokenUsage(nextPayload) {
|
|
|
670
783
|
return resp; // includes totalTokens, possibly breakdown
|
|
671
784
|
}
|
|
672
785
|
|
|
786
|
+
// Model pricing per million tokens (as of Dec 2025)
|
|
787
|
+
// https://ai.google.dev/gemini-api/docs/pricing
|
|
788
|
+
const MODEL_PRICING = {
|
|
789
|
+
'gemini-2.5-flash': { input: 0.15, output: 0.60 },
|
|
790
|
+
'gemini-2.5-flash-lite': { input: 0.02, output: 0.10 },
|
|
791
|
+
'gemini-2.5-pro': { input: 2.50, output: 10.00 },
|
|
792
|
+
'gemini-3-pro': { input: 2.00, output: 12.00 },
|
|
793
|
+
'gemini-3-pro-preview': { input: 2.00, output: 12.00 },
|
|
794
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40 },
|
|
795
|
+
'gemini-2.0-flash-lite': { input: 0.02, output: 0.10 }
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Estimates the cost of sending a payload based on token count and model pricing.
|
|
800
|
+
* @this {ExportedAPI}
|
|
801
|
+
* @param {object|string} nextPayload - The next user message to be sent (object or string)
|
|
802
|
+
* @returns {Promise<Object>} - Cost estimation including tokens, model, pricing, and estimated input cost
|
|
803
|
+
*/
|
|
804
|
+
async function estimateCost(nextPayload) {
|
|
805
|
+
const tokenInfo = await this.estimateTokenUsage(nextPayload);
|
|
806
|
+
const pricing = MODEL_PRICING[this.modelName] || { input: 0, output: 0 };
|
|
807
|
+
|
|
808
|
+
return {
|
|
809
|
+
totalTokens: tokenInfo.totalTokens,
|
|
810
|
+
model: this.modelName,
|
|
811
|
+
pricing: pricing,
|
|
812
|
+
estimatedInputCost: (tokenInfo.totalTokens / 1_000_000) * pricing.input,
|
|
813
|
+
note: 'Cost is for input tokens only; output cost depends on response length'
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
673
817
|
|
|
674
818
|
/**
|
|
675
819
|
* Resets the current chat session, clearing all history and examples
|
|
@@ -684,7 +828,10 @@ async function resetChat() {
|
|
|
684
828
|
const chatOptions = {
|
|
685
829
|
model: this.modelName,
|
|
686
830
|
// @ts-ignore
|
|
687
|
-
config:
|
|
831
|
+
config: {
|
|
832
|
+
...this.chatConfig,
|
|
833
|
+
...(Object.keys(this.labels).length > 0 && { labels: this.labels })
|
|
834
|
+
},
|
|
688
835
|
history: [],
|
|
689
836
|
};
|
|
690
837
|
|
|
@@ -733,6 +880,110 @@ async function updateSystemInstructions(newInstructions) {
|
|
|
733
880
|
await this.init(true); // Force reinitialize with new instructions
|
|
734
881
|
}
|
|
735
882
|
|
|
883
|
+
/**
|
|
884
|
+
* Clears conversation history while preserving seeded examples.
|
|
885
|
+
* Useful for starting a fresh conversation within the same session
|
|
886
|
+
* without losing the few-shot learning examples.
|
|
887
|
+
* @this {ExportedAPI}
|
|
888
|
+
* @returns {Promise<void>}
|
|
889
|
+
*/
|
|
890
|
+
async function clearConversation() {
|
|
891
|
+
if (!this.chat) {
|
|
892
|
+
log.warn("Cannot clear conversation: chat not initialized.");
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const history = this.chat.getHistory();
|
|
897
|
+
const exampleHistory = history.slice(0, this.exampleCount || 0);
|
|
898
|
+
|
|
899
|
+
this.chat = await this.genAIClient.chats.create({
|
|
900
|
+
model: this.modelName,
|
|
901
|
+
// @ts-ignore
|
|
902
|
+
config: {
|
|
903
|
+
...this.chatConfig,
|
|
904
|
+
...(Object.keys(this.labels).length > 0 && { labels: this.labels })
|
|
905
|
+
},
|
|
906
|
+
history: exampleHistory,
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
log.debug(`Conversation cleared. Preserved ${exampleHistory.length} example items.`);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Sends a one-off message using generateContent (not chat).
|
|
914
|
+
* Does NOT affect chat history - useful for isolated requests.
|
|
915
|
+
* @this {ExportedAPI}
|
|
916
|
+
* @param {Object|string} sourcePayload - The source payload.
|
|
917
|
+
* @param {Object} [options] - Options including labels.
|
|
918
|
+
* @param {AsyncValidatorFunction|null} [validatorFn] - Optional validator.
|
|
919
|
+
* @returns {Promise<Object>} - The transformed payload.
|
|
920
|
+
*/
|
|
921
|
+
async function statelessMessage(sourcePayload, options = {}, validatorFn = null) {
|
|
922
|
+
if (!this.chat) {
|
|
923
|
+
throw new Error("Chat session not initialized. Please call init() first.");
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const payloadStr = typeof sourcePayload === 'string'
|
|
927
|
+
? sourcePayload
|
|
928
|
+
: JSON.stringify(sourcePayload, null, 2);
|
|
929
|
+
|
|
930
|
+
// Build contents including examples from current chat history
|
|
931
|
+
const contents = [];
|
|
932
|
+
|
|
933
|
+
// Include seeded examples if we have them
|
|
934
|
+
if (this.exampleCount > 0) {
|
|
935
|
+
const history = this.chat.getHistory();
|
|
936
|
+
const exampleHistory = history.slice(0, this.exampleCount);
|
|
937
|
+
contents.push(...exampleHistory);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Add the user message
|
|
941
|
+
contents.push({ role: 'user', parts: [{ text: payloadStr }] });
|
|
942
|
+
|
|
943
|
+
// Merge labels
|
|
944
|
+
const mergedLabels = { ...this.labels, ...(options.labels || {}) };
|
|
945
|
+
|
|
946
|
+
// Use generateContent instead of chat.sendMessage
|
|
947
|
+
const result = await this.genAIClient.models.generateContent({
|
|
948
|
+
model: this.modelName,
|
|
949
|
+
contents: contents,
|
|
950
|
+
config: {
|
|
951
|
+
...this.chatConfig,
|
|
952
|
+
...(Object.keys(mergedLabels).length > 0 && { labels: mergedLabels })
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// Capture and log response metadata
|
|
957
|
+
this.lastResponseMetadata = {
|
|
958
|
+
modelVersion: result.modelVersion || null,
|
|
959
|
+
requestedModel: this.modelName,
|
|
960
|
+
promptTokens: result.usageMetadata?.promptTokenCount || 0,
|
|
961
|
+
responseTokens: result.usageMetadata?.candidatesTokenCount || 0,
|
|
962
|
+
totalTokens: result.usageMetadata?.totalTokenCount || 0,
|
|
963
|
+
timestamp: Date.now()
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
if (result.usageMetadata && log.level !== 'silent') {
|
|
967
|
+
log.debug(`Stateless message metadata:`, {
|
|
968
|
+
modelVersion: result.modelVersion || 'not-provided',
|
|
969
|
+
promptTokens: result.usageMetadata.promptTokenCount,
|
|
970
|
+
responseTokens: result.usageMetadata.candidatesTokenCount
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const modelResponse = result.text;
|
|
975
|
+
const extractedJSON = extractJSON(modelResponse);
|
|
976
|
+
|
|
977
|
+
let transformedPayload = extractedJSON?.data ? extractedJSON.data : extractedJSON;
|
|
978
|
+
|
|
979
|
+
// Validate if a validator is provided
|
|
980
|
+
if (validatorFn) {
|
|
981
|
+
await validatorFn(transformedPayload);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return transformedPayload;
|
|
985
|
+
}
|
|
986
|
+
|
|
736
987
|
|
|
737
988
|
/*
|
|
738
989
|
----
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -26,9 +26,31 @@ export interface ChatConfig {
|
|
|
26
26
|
safetySettings?: SafetySetting[]; // Safety settings array
|
|
27
27
|
responseSchema?: Object; // Schema for validating model responses
|
|
28
28
|
thinkingConfig?: ThinkingConfig; // Thinking features configuration
|
|
29
|
+
labels?: Record<string, string>; // Labels for billing segmentation
|
|
30
|
+
tools?: any[]; // Tools configuration (e.g., grounding)
|
|
29
31
|
[key: string]: any; // Additional properties for flexibility
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/** Metadata from the last API response, useful for debugging and cost tracking */
|
|
35
|
+
export interface ResponseMetadata {
|
|
36
|
+
modelVersion: string | null; // The actual model version that responded
|
|
37
|
+
requestedModel: string; // The model that was requested
|
|
38
|
+
promptTokens: number; // Number of tokens in the prompt
|
|
39
|
+
responseTokens: number; // Number of tokens in the response
|
|
40
|
+
totalTokens: number; // Total tokens used
|
|
41
|
+
timestamp: number; // Timestamp of when the response was received
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Options for per-message configuration */
|
|
45
|
+
export interface MessageOptions {
|
|
46
|
+
labels?: Record<string, string>; // Per-message billing labels
|
|
47
|
+
stateless?: boolean; // If true, send message without affecting chat history
|
|
48
|
+
maxRetries?: number; // Override max retries for this message
|
|
49
|
+
retryDelay?: number; // Override retry delay for this message
|
|
50
|
+
enableGrounding?: boolean; // Override grounding setting for this message
|
|
51
|
+
groundingConfig?: Record<string, any>; // Override grounding config for this message
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
export interface AITransformerContext {
|
|
33
55
|
modelName?: string;
|
|
34
56
|
systemInstructions?: string;
|
|
@@ -45,15 +67,19 @@ export interface AITransformerContext {
|
|
|
45
67
|
maxRetries?: number;
|
|
46
68
|
retryDelay?: number;
|
|
47
69
|
init?: (force?: boolean) => Promise<void>; // Initialization function
|
|
48
|
-
seed?: () => Promise<void>; // Function to seed the transformer with examples
|
|
49
|
-
message?: (payload: Record<string, unknown
|
|
70
|
+
seed?: () => Promise<void>; // Function to seed the transformer with examples
|
|
71
|
+
message?: (payload: Record<string, unknown>, opts?: MessageOptions, validatorFn?: AsyncValidatorFunction | null) => Promise<Record<string, unknown>>; // Function to send messages to the model
|
|
50
72
|
rebuild?: (lastPayload: Record<string, unknown>, serverError: string) => Promise<Record<string, unknown>>; // Function to rebuild the transformer
|
|
51
|
-
rawMessage?: (payload: Record<string, unknown> | string) => Promise<Record<string, unknown>>; // Function to send raw messages to the model
|
|
73
|
+
rawMessage?: (payload: Record<string, unknown> | string, messageOptions?: { labels?: Record<string, string> }) => Promise<Record<string, unknown>>; // Function to send raw messages to the model
|
|
52
74
|
genAIClient?: GoogleGenAI; // Google GenAI client instance
|
|
53
75
|
onlyJSON?: boolean; // If true, only JSON responses are allowed
|
|
54
76
|
enableGrounding?: boolean; // Enable Google Search grounding (default: false, WARNING: costs $35/1k queries)
|
|
55
77
|
groundingConfig?: Record<string, any>; // Additional grounding configuration options
|
|
56
|
-
|
|
78
|
+
labels?: Record<string, string>; // Custom labels for billing segmentation (keys: 1-63 chars lowercase, values: max 63 chars)
|
|
79
|
+
estimateTokenUsage?: (nextPayload: Record<string, unknown> | string) => Promise<{ totalTokens: number; breakdown?: any }>;
|
|
80
|
+
lastResponseMetadata?: ResponseMetadata | null; // Metadata from the last API response
|
|
81
|
+
exampleCount?: number; // Number of example history items from seed()
|
|
82
|
+
clearConversation?: () => Promise<void>; // Clears conversation history while preserving examples
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
export interface TransformationExample {
|
|
@@ -71,13 +97,24 @@ export interface ExampleFileContent {
|
|
|
71
97
|
examples: TransformationExample[];
|
|
72
98
|
}
|
|
73
99
|
|
|
100
|
+
// Google Auth options for Vertex AI authentication
|
|
101
|
+
// See: https://github.com/googleapis/google-auth-library-nodejs/blob/main/src/auth/googleauth.ts
|
|
102
|
+
export interface GoogleAuthOptions {
|
|
103
|
+
keyFilename?: string; // Path to a .json, .pem, or .p12 key file
|
|
104
|
+
keyFile?: string; // Alias for keyFilename
|
|
105
|
+
credentials?: { client_email?: string; private_key?: string; [key: string]: any }; // Object containing client_email and private_key
|
|
106
|
+
scopes?: string | string[]; // Required scopes for the API request
|
|
107
|
+
projectId?: string; // Your project ID (alias for project)
|
|
108
|
+
universeDomain?: string; // The default service domain for a Cloud universe
|
|
109
|
+
}
|
|
110
|
+
|
|
74
111
|
export interface AITransformerOptions {
|
|
75
112
|
// ? https://ai.google.dev/gemini-api/docs/models
|
|
76
113
|
modelName?: string; // The Gemini model to use
|
|
77
114
|
systemInstructions?: string; // Custom system instructions for the model
|
|
78
115
|
chatConfig?: ChatConfig; // Configuration object for the chat session
|
|
79
116
|
thinkingConfig?: ThinkingConfig; // Thinking features configuration (defaults to thinkingBudget: 0, thinkingLevel: "MINIMAL")
|
|
80
|
-
maxOutputTokens?: number; // Maximum number of tokens that can be generated in the response (defaults to
|
|
117
|
+
maxOutputTokens?: number; // Maximum number of tokens that can be generated in the response (defaults to 50000)
|
|
81
118
|
examplesFile?: string; // Path to JSON file containing transformation examples
|
|
82
119
|
exampleData?: TransformationExample[]; // Inline examples to seed the transformer
|
|
83
120
|
sourceKey?: string; // Key name for source data in examples (alias for promptKey)
|
|
@@ -91,12 +128,20 @@ export interface AITransformerOptions {
|
|
|
91
128
|
retryDelay?: number; // Initial retry delay in milliseconds
|
|
92
129
|
// ? https://ai.google.dev/gemini-api/docs/structured-output
|
|
93
130
|
responseSchema?: Object; // Schema for validating model responses
|
|
94
|
-
apiKey?: string; // API key for Google GenAI
|
|
131
|
+
apiKey?: string; // API key for Google GenAI (Gemini API)
|
|
95
132
|
onlyJSON?: boolean; // If true, only JSON responses are allowed
|
|
96
133
|
asyncValidator?: AsyncValidatorFunction; // Optional async validator function for response validation
|
|
97
134
|
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'none'; // Log level for the logger (defaults to 'info', 'none' disables logging)
|
|
98
135
|
enableGrounding?: boolean; // Enable Google Search grounding (default: false, WARNING: costs $35/1k queries)
|
|
99
136
|
groundingConfig?: Record<string, any>; // Additional grounding configuration options
|
|
137
|
+
labels?: Record<string, string>; // Custom labels for billing segmentation
|
|
138
|
+
|
|
139
|
+
// Vertex AI Authentication Options
|
|
140
|
+
// Use these instead of apiKey for Vertex AI with service account authentication
|
|
141
|
+
vertexai?: boolean; // Set to true to use Vertex AI instead of Gemini API
|
|
142
|
+
project?: string; // Google Cloud project ID (required for Vertex AI)
|
|
143
|
+
location?: string; // Google Cloud location/region (e.g., 'us-central1') - required for Vertex AI
|
|
144
|
+
googleAuthOptions?: GoogleAuthOptions; // Authentication options for Vertex AI (keyFilename, credentials, etc.)
|
|
100
145
|
}
|
|
101
146
|
|
|
102
147
|
// Async validator function type
|
|
@@ -126,20 +171,40 @@ export declare class AITransformer {
|
|
|
126
171
|
logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'none';
|
|
127
172
|
enableGrounding: boolean;
|
|
128
173
|
groundingConfig: Record<string, any>;
|
|
129
|
-
|
|
174
|
+
labels: Record<string, string>;
|
|
175
|
+
/** Metadata from the last API response (model version, token counts, etc.) */
|
|
176
|
+
lastResponseMetadata: ResponseMetadata | null;
|
|
177
|
+
/** Number of history items that are seeded examples (used by clearConversation) */
|
|
178
|
+
exampleCount: number;
|
|
179
|
+
|
|
130
180
|
// Methods
|
|
131
181
|
init(force?: boolean): Promise<void>;
|
|
132
182
|
seed(examples?: TransformationExample[]): Promise<any>;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Send a message to the model.
|
|
185
|
+
* @param payload - The payload to transform
|
|
186
|
+
* @param opts - Options including { stateless: true } to send without affecting history
|
|
187
|
+
* @param validatorFn - Optional validator function
|
|
188
|
+
*/
|
|
189
|
+
message(payload: Record<string, unknown>, opts?: MessageOptions, validatorFn?: AsyncValidatorFunction | null): Promise<Record<string, unknown>>;
|
|
190
|
+
rawMessage(sourcePayload: Record<string, unknown> | string, messageOptions?: { labels?: Record<string, string> }): Promise<Record<string, unknown> | any>;
|
|
191
|
+
transformWithValidation(sourcePayload: Record<string, unknown>, validatorFn: AsyncValidatorFunction, options?: MessageOptions): Promise<Record<string, unknown>>;
|
|
192
|
+
messageAndValidate(sourcePayload: Record<string, unknown>, validatorFn: AsyncValidatorFunction, options?: MessageOptions): Promise<Record<string, unknown>>;
|
|
137
193
|
rebuild(lastPayload: Record<string, unknown>, serverError: string): Promise<Record<string, unknown>>;
|
|
138
194
|
reset(): Promise<void>;
|
|
139
195
|
getHistory(): Array<any>;
|
|
140
196
|
estimateTokenUsage(nextPayload: Record<string, unknown> | string): Promise<{ totalTokens: number; breakdown?: any }>;
|
|
141
197
|
estimate(nextPayload: Record<string, unknown> | string): Promise<{ totalTokens: number; breakdown?: any }>;
|
|
142
198
|
updateSystemInstructions(newInstructions: string): Promise<void>;
|
|
199
|
+
estimateCost(nextPayload: Record<string, unknown> | string): Promise<{
|
|
200
|
+
totalTokens: number;
|
|
201
|
+
model: string;
|
|
202
|
+
pricing: { input: number; output: number };
|
|
203
|
+
estimatedInputCost: number;
|
|
204
|
+
note: string;
|
|
205
|
+
}>;
|
|
206
|
+
/** Clears conversation history while preserving seeded examples */
|
|
207
|
+
clearConversation(): Promise<void>;
|
|
143
208
|
}
|
|
144
209
|
|
|
145
210
|
// Default export
|