ak-gemini 2.0.3 → 2.0.4
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/embedding.js +181 -0
- package/package.json +2 -1
package/embedding.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Embedding class — Generate vector embeddings via Google's embedding models.
|
|
3
|
+
*
|
|
4
|
+
* Extends BaseGemini for auth/client reuse but overrides init() to skip chat session
|
|
5
|
+
* creation (embeddings don't use chat). Follows the Message class pattern.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```javascript
|
|
9
|
+
* import { Embedding } from 'ak-gemini';
|
|
10
|
+
*
|
|
11
|
+
* const embedder = new Embedding({ apiKey: 'your-key' });
|
|
12
|
+
* const result = await embedder.embed('Hello world');
|
|
13
|
+
* console.log(result.values); // [0.012, -0.034, ...]
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import BaseGemini from './base.js';
|
|
18
|
+
import log from './logger.js';
|
|
19
|
+
|
|
20
|
+
export default class Embedding extends BaseGemini {
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {import('./types.d.ts').EmbeddingOptions} [options={}]
|
|
24
|
+
*/
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
// Embeddings use a different model family — default to gemini-embedding-001
|
|
27
|
+
if (options.modelName === undefined) {
|
|
28
|
+
options = { ...options, modelName: 'gemini-embedding-001' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// No system prompt for embeddings
|
|
32
|
+
if (options.systemPrompt === undefined) {
|
|
33
|
+
options = { ...options, systemPrompt: null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
super(options);
|
|
37
|
+
|
|
38
|
+
this.taskType = options.taskType || null;
|
|
39
|
+
this.title = options.title || null;
|
|
40
|
+
this.outputDimensionality = options.outputDimensionality || null;
|
|
41
|
+
this.autoTruncate = options.autoTruncate ?? true;
|
|
42
|
+
|
|
43
|
+
log.debug(`Embedding created with model: ${this.modelName}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the Embedding client.
|
|
48
|
+
* Override: validates API connection only, NO chat session (stateless).
|
|
49
|
+
* @param {boolean} [force=false]
|
|
50
|
+
* @returns {Promise<void>}
|
|
51
|
+
*/
|
|
52
|
+
async init(force = false) {
|
|
53
|
+
if (this._initialized && !force) return;
|
|
54
|
+
|
|
55
|
+
log.debug(`Initializing ${this.constructor.name} with model: ${this.modelName}...`);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await this.genAIClient.models.list();
|
|
59
|
+
log.debug(`${this.constructor.name}: API connection successful.`);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
throw new Error(`${this.constructor.name} initialization failed: ${e.message}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this._initialized = true;
|
|
65
|
+
log.debug(`${this.constructor.name}: Initialized (stateless mode).`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Builds the config object for embedContent calls.
|
|
70
|
+
* @param {Object} [overrides={}] - Per-call config overrides
|
|
71
|
+
* @returns {Object} The config object
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
_buildConfig(overrides = {}) {
|
|
75
|
+
const config = {};
|
|
76
|
+
const taskType = overrides.taskType || this.taskType;
|
|
77
|
+
const title = overrides.title || this.title;
|
|
78
|
+
const dims = overrides.outputDimensionality || this.outputDimensionality;
|
|
79
|
+
|
|
80
|
+
if (taskType) config.taskType = taskType;
|
|
81
|
+
if (title) config.title = title;
|
|
82
|
+
if (dims) config.outputDimensionality = dims;
|
|
83
|
+
|
|
84
|
+
return config;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Embed a single text string.
|
|
89
|
+
* @param {string} text - The text to embed
|
|
90
|
+
* @param {Object} [config={}] - Per-call config overrides
|
|
91
|
+
* @param {string} [config.taskType] - Override task type
|
|
92
|
+
* @param {string} [config.title] - Override title
|
|
93
|
+
* @param {number} [config.outputDimensionality] - Override dimensions
|
|
94
|
+
|
|
95
|
+
* @returns {Promise<import('./types.d.ts').EmbeddingResult>} The embedding result
|
|
96
|
+
*/
|
|
97
|
+
async embed(text, config = {}) {
|
|
98
|
+
if (!this._initialized) await this.init();
|
|
99
|
+
|
|
100
|
+
const result = await this.genAIClient.models.embedContent({
|
|
101
|
+
model: this.modelName,
|
|
102
|
+
contents: text,
|
|
103
|
+
config: this._buildConfig(config)
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return result.embeddings[0];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Embed multiple text strings in a single API call.
|
|
111
|
+
* @param {string[]} texts - Array of texts to embed
|
|
112
|
+
* @param {Object} [config={}] - Per-call config overrides
|
|
113
|
+
* @param {string} [config.taskType] - Override task type
|
|
114
|
+
* @param {string} [config.title] - Override title
|
|
115
|
+
* @param {number} [config.outputDimensionality] - Override dimensions
|
|
116
|
+
|
|
117
|
+
* @returns {Promise<import('./types.d.ts').EmbeddingResult[]>} Array of embedding results
|
|
118
|
+
*/
|
|
119
|
+
async embedBatch(texts, config = {}) {
|
|
120
|
+
if (!this._initialized) await this.init();
|
|
121
|
+
|
|
122
|
+
const result = await this.genAIClient.models.embedContent({
|
|
123
|
+
model: this.modelName,
|
|
124
|
+
contents: texts,
|
|
125
|
+
config: this._buildConfig(config)
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return result.embeddings;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Compute cosine similarity between two embedding vectors.
|
|
133
|
+
* Pure math — no API call.
|
|
134
|
+
* @param {number[]} a - First embedding vector
|
|
135
|
+
* @param {number[]} b - Second embedding vector
|
|
136
|
+
* @returns {number} Cosine similarity between -1 and 1
|
|
137
|
+
*/
|
|
138
|
+
similarity(a, b) {
|
|
139
|
+
if (!a || !b || a.length !== b.length) {
|
|
140
|
+
throw new Error('Vectors must be non-null and have the same length');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let dot = 0;
|
|
144
|
+
let magA = 0;
|
|
145
|
+
let magB = 0;
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < a.length; i++) {
|
|
148
|
+
dot += a[i] * b[i];
|
|
149
|
+
magA += a[i] * a[i];
|
|
150
|
+
magB += b[i] * b[i];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const magnitude = Math.sqrt(magA) * Math.sqrt(magB);
|
|
154
|
+
if (magnitude === 0) return 0;
|
|
155
|
+
|
|
156
|
+
return dot / magnitude;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── No-ops (embeddings don't use chat sessions) ──
|
|
160
|
+
|
|
161
|
+
/** @returns {any[]} Always returns empty array */
|
|
162
|
+
getHistory() { return []; }
|
|
163
|
+
|
|
164
|
+
/** No-op for Embedding */
|
|
165
|
+
async clearHistory() {}
|
|
166
|
+
|
|
167
|
+
/** No-op for Embedding */
|
|
168
|
+
async seed() {
|
|
169
|
+
log.warn('Embedding.seed() is a no-op — embeddings do not support few-shot examples.');
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {any} _nextPayload
|
|
175
|
+
* @throws {Error} Embedding does not support token estimation
|
|
176
|
+
* @returns {Promise<{inputTokens: number}>}
|
|
177
|
+
*/
|
|
178
|
+
async estimate(_nextPayload) {
|
|
179
|
+
throw new Error('Embedding does not support token estimation. Use embed() directly.');
|
|
180
|
+
}
|
|
181
|
+
}
|
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... everything",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.4",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"tool-agent.js",
|
|
15
15
|
"code-agent.js",
|
|
16
16
|
"rag-agent.js",
|
|
17
|
+
"embedding.js",
|
|
17
18
|
"json-helpers.js",
|
|
18
19
|
"types.d.ts",
|
|
19
20
|
"logger.js",
|