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.
Files changed (2) hide show
  1. package/embedding.js +181 -0
  2. 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.3",
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",