@yamo/memory-mesh 2.3.2 → 3.0.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/bin/memory_mesh.js +1 -1
- package/lib/llm/client.d.ts +111 -0
- package/lib/llm/client.js +299 -357
- package/lib/llm/client.ts +413 -0
- package/lib/llm/index.d.ts +17 -0
- package/lib/llm/index.js +15 -8
- package/lib/llm/index.ts +19 -0
- package/lib/memory/adapters/client.d.ts +183 -0
- package/lib/memory/adapters/client.js +518 -0
- package/lib/memory/adapters/client.ts +678 -0
- package/lib/memory/adapters/config.d.ts +137 -0
- package/lib/memory/adapters/config.js +189 -0
- package/lib/memory/adapters/config.ts +259 -0
- package/lib/memory/adapters/errors.d.ts +76 -0
- package/lib/memory/adapters/errors.js +128 -0
- package/lib/memory/adapters/errors.ts +166 -0
- package/lib/memory/context-manager.d.ts +44 -0
- package/lib/memory/context-manager.js +344 -0
- package/lib/memory/context-manager.ts +432 -0
- package/lib/memory/embeddings/factory.d.ts +59 -0
- package/lib/memory/embeddings/factory.js +148 -0
- package/lib/{embeddings/factory.js → memory/embeddings/factory.ts} +69 -28
- package/lib/memory/embeddings/index.d.ts +2 -0
- package/lib/memory/embeddings/index.js +2 -0
- package/lib/memory/embeddings/index.ts +2 -0
- package/lib/memory/embeddings/service.d.ts +164 -0
- package/lib/memory/embeddings/service.js +515 -0
- package/lib/{embeddings/service.js → memory/embeddings/service.ts} +223 -156
- package/lib/memory/index.d.ts +9 -0
- package/lib/memory/index.js +9 -1
- package/lib/memory/index.ts +20 -0
- package/lib/memory/memory-mesh.d.ts +274 -0
- package/lib/memory/memory-mesh.js +1469 -678
- package/lib/memory/memory-mesh.ts +1803 -0
- package/lib/memory/memory-translator.d.ts +19 -0
- package/lib/memory/memory-translator.js +125 -0
- package/lib/memory/memory-translator.ts +158 -0
- package/lib/memory/schema.d.ts +111 -0
- package/lib/memory/schema.js +183 -0
- package/lib/memory/schema.ts +267 -0
- package/lib/memory/scorer.d.ts +26 -0
- package/lib/memory/scorer.js +77 -0
- package/lib/memory/scorer.ts +95 -0
- package/lib/memory/search/index.d.ts +1 -0
- package/lib/memory/search/index.js +1 -0
- package/lib/memory/search/index.ts +1 -0
- package/lib/memory/search/keyword-search.d.ts +62 -0
- package/lib/memory/search/keyword-search.js +135 -0
- package/lib/{search/keyword-search.js → memory/search/keyword-search.ts} +66 -36
- package/lib/scrubber/config/defaults.d.ts +53 -0
- package/lib/scrubber/config/defaults.js +49 -57
- package/lib/scrubber/config/defaults.ts +117 -0
- package/lib/scrubber/index.d.ts +6 -0
- package/lib/scrubber/index.js +3 -23
- package/lib/scrubber/index.ts +7 -0
- package/lib/scrubber/scrubber.d.ts +61 -0
- package/lib/scrubber/scrubber.js +99 -121
- package/lib/scrubber/scrubber.ts +168 -0
- package/lib/scrubber/stages/chunker.d.ts +13 -0
- package/lib/scrubber/stages/metadata-annotator.d.ts +18 -0
- package/lib/scrubber/stages/normalizer.d.ts +13 -0
- package/lib/scrubber/stages/semantic-filter.d.ts +13 -0
- package/lib/scrubber/stages/structural-cleaner.d.ts +13 -0
- package/lib/scrubber/stages/validator.d.ts +18 -0
- package/lib/scrubber/telemetry.d.ts +36 -0
- package/lib/scrubber/telemetry.js +53 -58
- package/lib/scrubber/telemetry.ts +99 -0
- package/lib/utils/logger.d.ts +29 -0
- package/lib/utils/logger.js +64 -0
- package/lib/utils/logger.ts +85 -0
- package/lib/utils/skill-metadata.d.ts +32 -0
- package/lib/utils/skill-metadata.js +132 -0
- package/lib/utils/skill-metadata.ts +147 -0
- package/lib/yamo/emitter.d.ts +73 -0
- package/lib/yamo/emitter.js +78 -143
- package/lib/yamo/emitter.ts +249 -0
- package/lib/yamo/schema.d.ts +58 -0
- package/lib/yamo/schema.js +81 -108
- package/lib/yamo/schema.ts +165 -0
- package/package.json +11 -8
- package/index.d.ts +0 -111
- package/lib/embeddings/index.js +0 -2
- package/lib/index.js +0 -6
- package/lib/lancedb/client.js +0 -633
- package/lib/lancedb/config.js +0 -215
- package/lib/lancedb/errors.js +0 -144
- package/lib/lancedb/index.js +0 -4
- package/lib/lancedb/schema.js +0 -217
- package/lib/scrubber/errors/scrubber-error.js +0 -43
- package/lib/scrubber/stages/chunker.js +0 -103
- package/lib/scrubber/stages/metadata-annotator.js +0 -74
- package/lib/scrubber/stages/normalizer.js +0 -59
- package/lib/scrubber/stages/semantic-filter.js +0 -61
- package/lib/scrubber/stages/structural-cleaner.js +0 -82
- package/lib/scrubber/stages/validator.js +0 -66
- package/lib/scrubber/utils/hash.js +0 -39
- package/lib/scrubber/utils/html-parser.js +0 -45
- package/lib/scrubber/utils/pattern-matcher.js +0 -63
- package/lib/scrubber/utils/token-counter.js +0 -31
- package/lib/search/index.js +0 -1
- package/lib/utils/index.js +0 -1
- package/lib/yamo/index.js +0 -15
|
@@ -10,38 +10,93 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import crypto from "crypto";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
import { ConfigurationError, EmbeddingError } from "../adapters/errors.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Service configuration interface
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface ServiceConfig {
|
|
20
|
+
modelType?: "local" | "ollama" | "openai" | "cohere";
|
|
21
|
+
modelName?: string;
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
dimension?: number;
|
|
24
|
+
batchSize?: number;
|
|
25
|
+
normalize?: boolean;
|
|
26
|
+
cacheMaxSize?: number;
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
priority?: number; // Used by factory
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ServiceStats {
|
|
32
|
+
modelType: string;
|
|
33
|
+
modelName: string;
|
|
34
|
+
dimension: number;
|
|
35
|
+
initialized: boolean;
|
|
36
|
+
totalEmbeddings: number;
|
|
37
|
+
cacheHits: number;
|
|
38
|
+
cacheMisses: number;
|
|
39
|
+
cacheSize: number;
|
|
40
|
+
cacheMaxSize: number;
|
|
41
|
+
cacheHitRate: number;
|
|
42
|
+
batchCount: number;
|
|
43
|
+
batchSize: number;
|
|
44
|
+
normalize: boolean;
|
|
45
|
+
}
|
|
19
46
|
|
|
20
47
|
/**
|
|
21
48
|
* EmbeddingService provides a unified interface for generating text embeddings
|
|
22
49
|
* using multiple backend providers (local ONNX models or cloud APIs).
|
|
23
50
|
*/
|
|
24
|
-
class EmbeddingService {
|
|
51
|
+
export class EmbeddingService {
|
|
52
|
+
modelType: string;
|
|
53
|
+
modelName: string;
|
|
54
|
+
baseUrl: string;
|
|
55
|
+
dimension: number;
|
|
56
|
+
batchSize: number;
|
|
57
|
+
normalize: boolean;
|
|
58
|
+
apiKey?: string;
|
|
59
|
+
model: any;
|
|
60
|
+
cache: Map<string, number[]>;
|
|
61
|
+
cacheMaxSize: number;
|
|
62
|
+
initialized: boolean;
|
|
63
|
+
stats: {
|
|
64
|
+
totalEmbeddings: number;
|
|
65
|
+
cacheHits: number;
|
|
66
|
+
cacheMisses: number;
|
|
67
|
+
batchCount: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
25
70
|
/**
|
|
26
71
|
* Create a new EmbeddingService instance
|
|
27
72
|
* @param {Object} [config={}] - Configuration options
|
|
28
|
-
* @param {string} [config.modelType] - Type of model ('local', 'ollama', 'openai', 'cohere')
|
|
29
|
-
* @param {string} [config.modelName] - Name of the model to use
|
|
30
|
-
* @param {string} [config.baseUrl] - Base URL for Ollama (default: http://localhost:11434)
|
|
31
|
-
* @param {number} [config.dimension] - Embedding dimension
|
|
32
|
-
* @param {number} [config.batchSize] - Maximum batch size for processing
|
|
33
|
-
* @param {boolean} [config.normalize] - Whether to normalize embeddings to unit length
|
|
34
|
-
* @param {number} [config.cacheMaxSize] - Maximum size of LRU cache
|
|
35
|
-
* @param {string} [config.apiKey] - API key for cloud providers
|
|
36
73
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
constructor(config: ServiceConfig = {}) {
|
|
75
|
+
this.modelType =
|
|
76
|
+
(config && config.modelType) ||
|
|
77
|
+
process.env.EMBEDDING_MODEL_TYPE ||
|
|
78
|
+
"local";
|
|
79
|
+
this.modelName =
|
|
80
|
+
(config && config.modelName) ||
|
|
81
|
+
process.env.EMBEDDING_MODEL_NAME ||
|
|
82
|
+
"Xenova/all-MiniLM-L6-v2";
|
|
83
|
+
this.baseUrl =
|
|
84
|
+
(config && config.baseUrl) ||
|
|
85
|
+
process.env.OLLAMA_BASE_URL ||
|
|
86
|
+
process.env.EMBEDDING_BASE_URL ||
|
|
87
|
+
"http://localhost:11434";
|
|
88
|
+
this.dimension =
|
|
89
|
+
(config && config.dimension) ||
|
|
90
|
+
parseInt(process.env.EMBEDDING_DIMENSION || "384") ||
|
|
91
|
+
384;
|
|
92
|
+
this.batchSize =
|
|
93
|
+
(config && config.batchSize) ||
|
|
94
|
+
parseInt(process.env.EMBEDDING_BATCH_SIZE || "32") ||
|
|
95
|
+
32;
|
|
96
|
+
this.normalize =
|
|
97
|
+
config && config.normalize !== undefined
|
|
98
|
+
? config.normalize
|
|
99
|
+
: process.env.EMBEDDING_NORMALIZE !== "false";
|
|
45
100
|
|
|
46
101
|
this.apiKey = (config && config.apiKey) || process.env.EMBEDDING_API_KEY;
|
|
47
102
|
|
|
@@ -55,7 +110,7 @@ class EmbeddingService {
|
|
|
55
110
|
totalEmbeddings: 0,
|
|
56
111
|
cacheHits: 0,
|
|
57
112
|
cacheMisses: 0,
|
|
58
|
-
batchCount: 0
|
|
113
|
+
batchCount: 0,
|
|
59
114
|
};
|
|
60
115
|
}
|
|
61
116
|
|
|
@@ -63,25 +118,25 @@ class EmbeddingService {
|
|
|
63
118
|
* Initialize the embedding model
|
|
64
119
|
* Loads the model based on modelType (local, ollama, openai, cohere)
|
|
65
120
|
*/
|
|
66
|
-
async init() {
|
|
121
|
+
async init(): Promise<void> {
|
|
67
122
|
try {
|
|
68
123
|
switch (this.modelType) {
|
|
69
|
-
case
|
|
124
|
+
case "local":
|
|
70
125
|
await this._initLocalModel();
|
|
71
126
|
break;
|
|
72
|
-
case
|
|
73
|
-
|
|
127
|
+
case "ollama":
|
|
128
|
+
this._initOllama();
|
|
74
129
|
break;
|
|
75
|
-
case
|
|
130
|
+
case "openai":
|
|
76
131
|
await this._initOpenAI();
|
|
77
132
|
break;
|
|
78
|
-
case
|
|
133
|
+
case "cohere":
|
|
79
134
|
await this._initCohere();
|
|
80
135
|
break;
|
|
81
136
|
default:
|
|
82
137
|
throw new ConfigurationError(
|
|
83
138
|
`Unknown model type: ${this.modelType}. Must be 'local', 'ollama', 'openai', or 'cohere'`,
|
|
84
|
-
{ modelType: this.modelType }
|
|
139
|
+
{ modelType: this.modelType },
|
|
85
140
|
);
|
|
86
141
|
}
|
|
87
142
|
|
|
@@ -93,7 +148,11 @@ class EmbeddingService {
|
|
|
93
148
|
const message = error instanceof Error ? error.message : String(error);
|
|
94
149
|
throw new EmbeddingError(
|
|
95
150
|
`Failed to initialize embedding service: ${message}`,
|
|
96
|
-
{
|
|
151
|
+
{
|
|
152
|
+
modelType: this.modelType,
|
|
153
|
+
modelName: this.modelName,
|
|
154
|
+
originalError: message,
|
|
155
|
+
},
|
|
97
156
|
);
|
|
98
157
|
}
|
|
99
158
|
}
|
|
@@ -104,19 +163,21 @@ class EmbeddingService {
|
|
|
104
163
|
* @param {Object} options - Options for embedding generation
|
|
105
164
|
* @returns {Promise<number[]>} Embedding vector
|
|
106
165
|
*/
|
|
107
|
-
async embed(text,
|
|
166
|
+
async embed(text: string, _options: any = {}): Promise<number[]> {
|
|
108
167
|
if (!this.initialized) {
|
|
109
168
|
throw new EmbeddingError(
|
|
110
|
-
|
|
111
|
-
{
|
|
169
|
+
"Embedding service not initialized. Call init() first.",
|
|
170
|
+
{
|
|
171
|
+
modelType: this.modelType,
|
|
172
|
+
},
|
|
112
173
|
);
|
|
113
174
|
}
|
|
114
175
|
|
|
115
|
-
if (!text || typeof text !==
|
|
116
|
-
throw new EmbeddingError(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
176
|
+
if (!text || typeof text !== "string") {
|
|
177
|
+
throw new EmbeddingError("Text must be a non-empty string", {
|
|
178
|
+
text,
|
|
179
|
+
textType: typeof text,
|
|
180
|
+
});
|
|
120
181
|
}
|
|
121
182
|
|
|
122
183
|
// Check cache
|
|
@@ -128,26 +189,25 @@ class EmbeddingService {
|
|
|
128
189
|
}
|
|
129
190
|
|
|
130
191
|
// Generate embedding
|
|
131
|
-
let embedding;
|
|
192
|
+
let embedding: number[];
|
|
132
193
|
try {
|
|
133
194
|
switch (this.modelType) {
|
|
134
|
-
case
|
|
195
|
+
case "local":
|
|
135
196
|
embedding = await this._embedLocal(text);
|
|
136
197
|
break;
|
|
137
|
-
case
|
|
198
|
+
case "ollama":
|
|
138
199
|
embedding = await this._embedOllama(text);
|
|
139
200
|
break;
|
|
140
|
-
case
|
|
201
|
+
case "openai":
|
|
141
202
|
embedding = await this._embedOpenAI(text);
|
|
142
203
|
break;
|
|
143
|
-
case
|
|
204
|
+
case "cohere":
|
|
144
205
|
embedding = await this._embedCohere(text);
|
|
145
206
|
break;
|
|
146
207
|
default:
|
|
147
|
-
throw new EmbeddingError(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
);
|
|
208
|
+
throw new EmbeddingError(`Unknown model type: ${this.modelType}`, {
|
|
209
|
+
modelType: this.modelType,
|
|
210
|
+
});
|
|
151
211
|
}
|
|
152
212
|
|
|
153
213
|
// Normalize if enabled
|
|
@@ -167,10 +227,10 @@ class EmbeddingService {
|
|
|
167
227
|
throw error;
|
|
168
228
|
}
|
|
169
229
|
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
-
throw new EmbeddingError(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
230
|
+
throw new EmbeddingError(`Failed to generate embedding: ${message}`, {
|
|
231
|
+
modelType: this.modelType,
|
|
232
|
+
text: text.substring(0, 100),
|
|
233
|
+
});
|
|
174
234
|
}
|
|
175
235
|
}
|
|
176
236
|
|
|
@@ -180,19 +240,20 @@ class EmbeddingService {
|
|
|
180
240
|
* @param {Object} options - Options for embedding generation
|
|
181
241
|
* @returns {Promise<number[][]>} Array of embedding vectors
|
|
182
242
|
*/
|
|
183
|
-
async embedBatch(texts,
|
|
243
|
+
async embedBatch(texts: string[], _options: any = {}): Promise<number[][]> {
|
|
184
244
|
if (!this.initialized) {
|
|
185
245
|
throw new EmbeddingError(
|
|
186
|
-
|
|
187
|
-
{
|
|
246
|
+
"Embedding service not initialized. Call init() first.",
|
|
247
|
+
{
|
|
248
|
+
modelType: this.modelType,
|
|
249
|
+
},
|
|
188
250
|
);
|
|
189
251
|
}
|
|
190
252
|
|
|
191
253
|
if (!Array.isArray(texts)) {
|
|
192
|
-
throw new EmbeddingError(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
);
|
|
254
|
+
throw new EmbeddingError("Texts must be an array", {
|
|
255
|
+
textsType: typeof texts,
|
|
256
|
+
});
|
|
196
257
|
}
|
|
197
258
|
|
|
198
259
|
if (texts.length === 0) {
|
|
@@ -200,15 +261,18 @@ class EmbeddingService {
|
|
|
200
261
|
}
|
|
201
262
|
|
|
202
263
|
try {
|
|
203
|
-
const embeddings = [];
|
|
264
|
+
const embeddings: number[][] = [];
|
|
204
265
|
|
|
205
266
|
// Process in batches
|
|
206
267
|
for (let i = 0; i < texts.length; i += this.batchSize) {
|
|
207
|
-
const batch = texts.slice(
|
|
268
|
+
const batch = texts.slice(
|
|
269
|
+
i,
|
|
270
|
+
Math.min(i + this.batchSize, texts.length),
|
|
271
|
+
);
|
|
208
272
|
|
|
209
273
|
// Generate embeddings for batch
|
|
210
274
|
const batchEmbeddings = await Promise.all(
|
|
211
|
-
batch.map(text => this.embed(text
|
|
275
|
+
batch.map((text) => this.embed(text)),
|
|
212
276
|
);
|
|
213
277
|
|
|
214
278
|
embeddings.push(...batchEmbeddings);
|
|
@@ -223,7 +287,10 @@ class EmbeddingService {
|
|
|
223
287
|
const message = error instanceof Error ? error.message : String(error);
|
|
224
288
|
throw new EmbeddingError(
|
|
225
289
|
`Failed to generate batch embeddings: ${message}`,
|
|
226
|
-
{
|
|
290
|
+
{
|
|
291
|
+
modelType: this.modelType,
|
|
292
|
+
batchSize: texts.length,
|
|
293
|
+
},
|
|
227
294
|
);
|
|
228
295
|
}
|
|
229
296
|
}
|
|
@@ -232,36 +299,31 @@ class EmbeddingService {
|
|
|
232
299
|
* Initialize local ONNX model using Xenova/Transformers.js
|
|
233
300
|
* @private
|
|
234
301
|
*/
|
|
235
|
-
async _initLocalModel() {
|
|
302
|
+
async _initLocalModel(): Promise<void> {
|
|
236
303
|
try {
|
|
237
304
|
// Dynamic import to allow optional dependency
|
|
238
|
-
const { pipeline } = await import("@xenova/transformers");
|
|
305
|
+
const { pipeline } = (await import("@xenova/transformers")) as any;
|
|
239
306
|
|
|
240
307
|
// Load feature extraction pipeline
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
progress_callback: (progress) => {
|
|
248
|
-
// Optional: Log model download progress
|
|
249
|
-
if (progress.status === 'downloading') {
|
|
250
|
-
// Silently handle progress
|
|
251
|
-
}
|
|
308
|
+
this.model = await pipeline("feature-extraction", this.modelName, {
|
|
309
|
+
quantized: true,
|
|
310
|
+
progress_callback: (progress: any) => {
|
|
311
|
+
// Optional: Log model download progress
|
|
312
|
+
if (progress.status === "downloading") {
|
|
313
|
+
// Silently handle progress
|
|
252
314
|
}
|
|
253
|
-
}
|
|
254
|
-
);
|
|
315
|
+
},
|
|
316
|
+
});
|
|
255
317
|
|
|
256
318
|
// Update dimension based on model (384 for all-MiniLM-L6-v2)
|
|
257
|
-
if (this.modelName.includes(
|
|
319
|
+
if (this.modelName.includes("all-MiniLM-L6-v2")) {
|
|
258
320
|
this.dimension = 384;
|
|
259
321
|
}
|
|
260
322
|
} catch (error) {
|
|
261
323
|
const message = error instanceof Error ? error.message : String(error);
|
|
262
324
|
throw new ConfigurationError(
|
|
263
325
|
`Failed to load local model: ${message}. Make sure @xenova/transformers is installed.`,
|
|
264
|
-
{ modelName: this.modelName, error: message }
|
|
326
|
+
{ modelName: this.modelName, error: message },
|
|
265
327
|
);
|
|
266
328
|
}
|
|
267
329
|
}
|
|
@@ -271,20 +333,20 @@ class EmbeddingService {
|
|
|
271
333
|
* Ollama runs locally and doesn't require authentication
|
|
272
334
|
* @private
|
|
273
335
|
*/
|
|
274
|
-
|
|
336
|
+
_initOllama(): void {
|
|
275
337
|
// Ollama doesn't require initialization - it's a local HTTP API
|
|
276
338
|
// Store the base URL for use in _embedOllama
|
|
277
339
|
this.model = {
|
|
278
340
|
baseUrl: this.baseUrl,
|
|
279
|
-
modelName: this.modelName ||
|
|
341
|
+
modelName: this.modelName || "nomic-embed-text",
|
|
280
342
|
};
|
|
281
343
|
|
|
282
344
|
// Set default dimension for common Ollama embedding models
|
|
283
|
-
if (this.modelName.includes(
|
|
345
|
+
if (this.modelName.includes("nomic-embed-text")) {
|
|
284
346
|
this.dimension = 768;
|
|
285
|
-
} else if (this.modelName.includes(
|
|
347
|
+
} else if (this.modelName.includes("mxbai-embed")) {
|
|
286
348
|
this.dimension = 1024;
|
|
287
|
-
} else if (this.modelName.includes(
|
|
349
|
+
} else if (this.modelName.includes("all-MiniLM")) {
|
|
288
350
|
this.dimension = 384;
|
|
289
351
|
}
|
|
290
352
|
}
|
|
@@ -293,29 +355,28 @@ class EmbeddingService {
|
|
|
293
355
|
* Initialize OpenAI client
|
|
294
356
|
* @private
|
|
295
357
|
*/
|
|
296
|
-
async _initOpenAI() {
|
|
358
|
+
async _initOpenAI(): Promise<void> {
|
|
297
359
|
if (!this.apiKey) {
|
|
298
360
|
throw new ConfigurationError(
|
|
299
|
-
|
|
300
|
-
{ modelType:
|
|
361
|
+
"OpenAI API key is required. Set EMBEDDING_API_KEY environment variable or pass apiKey in config.",
|
|
362
|
+
{ modelType: "openai" },
|
|
301
363
|
);
|
|
302
364
|
}
|
|
303
365
|
|
|
304
366
|
try {
|
|
305
|
-
// Dynamic import to allow optional dependency
|
|
306
|
-
const { OpenAI } = await import("openai");
|
|
307
|
-
// @ts-ignore - OpenAI constructor
|
|
367
|
+
// Dynamic import to allow optional dependency (openai may not be installed)
|
|
368
|
+
const { OpenAI } = await import("openai" as any);
|
|
308
369
|
this.model = new OpenAI({ apiKey: this.apiKey });
|
|
309
370
|
|
|
310
371
|
// Update dimension for OpenAI models
|
|
311
|
-
if (this.modelName.includes(
|
|
372
|
+
if (this.modelName.includes("text-embedding-ada-002")) {
|
|
312
373
|
this.dimension = 1536;
|
|
313
374
|
}
|
|
314
375
|
} catch (error) {
|
|
315
376
|
const message = error instanceof Error ? error.message : String(error);
|
|
316
377
|
throw new ConfigurationError(
|
|
317
378
|
`Failed to initialize OpenAI client: ${message}. Make sure openai package is installed.`,
|
|
318
|
-
{ error: message }
|
|
379
|
+
{ error: message },
|
|
319
380
|
);
|
|
320
381
|
}
|
|
321
382
|
}
|
|
@@ -324,29 +385,28 @@ class EmbeddingService {
|
|
|
324
385
|
* Initialize Cohere client
|
|
325
386
|
* @private
|
|
326
387
|
*/
|
|
327
|
-
async _initCohere() {
|
|
388
|
+
async _initCohere(): Promise<void> {
|
|
328
389
|
if (!this.apiKey) {
|
|
329
390
|
throw new ConfigurationError(
|
|
330
|
-
|
|
331
|
-
{ modelType:
|
|
391
|
+
"Cohere API key is required. Set EMBEDDING_API_KEY environment variable or pass apiKey in config.",
|
|
392
|
+
{ modelType: "cohere" },
|
|
332
393
|
);
|
|
333
394
|
}
|
|
334
395
|
|
|
335
396
|
try {
|
|
336
|
-
// Dynamic import to allow optional dependency
|
|
337
|
-
const cohere = await import("cohere-ai");
|
|
338
|
-
// @ts-ignore - Cohere constructor
|
|
397
|
+
// Dynamic import to allow optional dependency (cohere-ai may not be installed)
|
|
398
|
+
const cohere = await import("cohere-ai" as any);
|
|
339
399
|
this.model = new cohere.CohereClient({ token: this.apiKey });
|
|
340
400
|
|
|
341
401
|
// Update dimension for Cohere models
|
|
342
|
-
if (this.modelName.includes(
|
|
402
|
+
if (this.modelName.includes("embed-english-v3.0")) {
|
|
343
403
|
this.dimension = 1024;
|
|
344
404
|
}
|
|
345
405
|
} catch (error) {
|
|
346
406
|
const message = error instanceof Error ? error.message : String(error);
|
|
347
407
|
throw new ConfigurationError(
|
|
348
408
|
`Failed to initialize Cohere client: ${message}. Make sure cohere-ai package is installed.`,
|
|
349
|
-
{ error: message }
|
|
409
|
+
{ error: message },
|
|
350
410
|
);
|
|
351
411
|
}
|
|
352
412
|
}
|
|
@@ -357,24 +417,28 @@ class EmbeddingService {
|
|
|
357
417
|
* @returns {Promise<number[]>} Embedding vector
|
|
358
418
|
* @private
|
|
359
419
|
*/
|
|
360
|
-
async _embedLocal(text) {
|
|
361
|
-
if (!this.model)
|
|
420
|
+
async _embedLocal(text: string): Promise<number[]> {
|
|
421
|
+
if (!this.model) {
|
|
422
|
+
throw new EmbeddingError("Model not initialized");
|
|
423
|
+
}
|
|
362
424
|
try {
|
|
363
|
-
//
|
|
425
|
+
// Local model call
|
|
364
426
|
const output = await this.model(text, {
|
|
365
|
-
pooling:
|
|
366
|
-
normalize: false
|
|
427
|
+
pooling: "mean",
|
|
428
|
+
normalize: false,
|
|
367
429
|
});
|
|
368
430
|
|
|
369
431
|
// Convert from tensor to array
|
|
370
432
|
const embedding = Array.from(output.data);
|
|
371
|
-
|
|
372
|
-
return embedding;
|
|
433
|
+
return embedding as number[];
|
|
373
434
|
} catch (error) {
|
|
374
435
|
const message = error instanceof Error ? error.message : String(error);
|
|
375
436
|
throw new EmbeddingError(
|
|
376
437
|
`Failed to generate local embedding: ${message}`,
|
|
377
|
-
{
|
|
438
|
+
{
|
|
439
|
+
modelName: this.modelName,
|
|
440
|
+
text: text.substring(0, 100),
|
|
441
|
+
},
|
|
378
442
|
);
|
|
379
443
|
}
|
|
380
444
|
}
|
|
@@ -385,30 +449,30 @@ class EmbeddingService {
|
|
|
385
449
|
* @returns {Promise<number[]>} Embedding vector
|
|
386
450
|
* @private
|
|
387
451
|
*/
|
|
388
|
-
async _embedOllama(text) {
|
|
389
|
-
if (!this.model)
|
|
452
|
+
async _embedOllama(text: string): Promise<number[]> {
|
|
453
|
+
if (!this.model) {
|
|
454
|
+
throw new EmbeddingError("Model not initialized");
|
|
455
|
+
}
|
|
390
456
|
try {
|
|
391
|
-
// @ts-ignore - Ollama model object
|
|
392
457
|
const baseUrl = this.model.baseUrl;
|
|
393
|
-
// @ts-ignore
|
|
394
458
|
const modelName = this.model.modelName;
|
|
395
459
|
|
|
396
460
|
const response = await fetch(`${baseUrl}/api/embeddings`, {
|
|
397
|
-
method:
|
|
461
|
+
method: "POST",
|
|
398
462
|
headers: {
|
|
399
|
-
|
|
463
|
+
"Content-Type": "application/json",
|
|
400
464
|
},
|
|
401
465
|
body: JSON.stringify({
|
|
402
466
|
model: modelName,
|
|
403
|
-
prompt: text
|
|
404
|
-
})
|
|
467
|
+
prompt: text,
|
|
468
|
+
}),
|
|
405
469
|
});
|
|
406
470
|
|
|
407
471
|
if (!response.ok) {
|
|
408
472
|
const errorText = await response.text();
|
|
409
473
|
throw new EmbeddingError(
|
|
410
474
|
`Ollama API error: ${response.status} ${response.statusText} - ${errorText}`,
|
|
411
|
-
{ baseUrl: baseUrl, modelName: modelName }
|
|
475
|
+
{ baseUrl: baseUrl, modelName: modelName },
|
|
412
476
|
);
|
|
413
477
|
}
|
|
414
478
|
|
|
@@ -416,8 +480,10 @@ class EmbeddingService {
|
|
|
416
480
|
|
|
417
481
|
if (!data.embedding) {
|
|
418
482
|
throw new EmbeddingError(
|
|
419
|
-
|
|
420
|
-
{
|
|
483
|
+
"Invalid response from Ollama API: missing embedding field",
|
|
484
|
+
{
|
|
485
|
+
response: data,
|
|
486
|
+
},
|
|
421
487
|
);
|
|
422
488
|
}
|
|
423
489
|
|
|
@@ -427,13 +493,11 @@ class EmbeddingService {
|
|
|
427
493
|
throw error;
|
|
428
494
|
}
|
|
429
495
|
const message = error instanceof Error ? error.message : String(error);
|
|
430
|
-
// @ts-ignore
|
|
431
496
|
const baseUrl = this.model?.baseUrl;
|
|
432
|
-
// @ts-ignore
|
|
433
497
|
const modelName = this.model?.modelName;
|
|
434
498
|
throw new EmbeddingError(
|
|
435
499
|
`Failed to generate Ollama embedding: ${message}. Make sure Ollama is running and the model is available.`,
|
|
436
|
-
{ baseUrl, modelName, error: message }
|
|
500
|
+
{ baseUrl, modelName, error: message },
|
|
437
501
|
);
|
|
438
502
|
}
|
|
439
503
|
}
|
|
@@ -444,13 +508,14 @@ class EmbeddingService {
|
|
|
444
508
|
* @returns {Promise<number[]>} Embedding vector
|
|
445
509
|
* @private
|
|
446
510
|
*/
|
|
447
|
-
async _embedOpenAI(text) {
|
|
448
|
-
if (!this.model)
|
|
511
|
+
async _embedOpenAI(text: string): Promise<number[]> {
|
|
512
|
+
if (!this.model) {
|
|
513
|
+
throw new EmbeddingError("Model not initialized");
|
|
514
|
+
}
|
|
449
515
|
try {
|
|
450
|
-
// @ts-ignore - OpenAI client
|
|
451
516
|
const response = await this.model.embeddings.create({
|
|
452
517
|
model: this.modelName,
|
|
453
|
-
input: text
|
|
518
|
+
input: text,
|
|
454
519
|
});
|
|
455
520
|
|
|
456
521
|
const embedding = response.data[0].embedding;
|
|
@@ -459,7 +524,10 @@ class EmbeddingService {
|
|
|
459
524
|
const message = error instanceof Error ? error.message : String(error);
|
|
460
525
|
throw new EmbeddingError(
|
|
461
526
|
`Failed to generate OpenAI embedding: ${message}`,
|
|
462
|
-
{
|
|
527
|
+
{
|
|
528
|
+
modelName: this.modelName,
|
|
529
|
+
error: message,
|
|
530
|
+
},
|
|
463
531
|
);
|
|
464
532
|
}
|
|
465
533
|
}
|
|
@@ -470,14 +538,15 @@ class EmbeddingService {
|
|
|
470
538
|
* @returns {Promise<number[]>} Embedding vector
|
|
471
539
|
* @private
|
|
472
540
|
*/
|
|
473
|
-
async _embedCohere(text) {
|
|
474
|
-
if (!this.model)
|
|
541
|
+
async _embedCohere(text: string): Promise<number[]> {
|
|
542
|
+
if (!this.model) {
|
|
543
|
+
throw new EmbeddingError("Model not initialized");
|
|
544
|
+
}
|
|
475
545
|
try {
|
|
476
|
-
// @ts-ignore - Cohere client
|
|
477
546
|
const response = await this.model.embed({
|
|
478
547
|
model: this.modelName,
|
|
479
548
|
texts: [text],
|
|
480
|
-
inputType:
|
|
549
|
+
inputType: "search_document",
|
|
481
550
|
});
|
|
482
551
|
|
|
483
552
|
const embedding = response.embeddings[0];
|
|
@@ -486,7 +555,10 @@ class EmbeddingService {
|
|
|
486
555
|
const message = error instanceof Error ? error.message : String(error);
|
|
487
556
|
throw new EmbeddingError(
|
|
488
557
|
`Failed to generate Cohere embedding: ${message}`,
|
|
489
|
-
{
|
|
558
|
+
{
|
|
559
|
+
modelName: this.modelName,
|
|
560
|
+
error: message,
|
|
561
|
+
},
|
|
490
562
|
);
|
|
491
563
|
}
|
|
492
564
|
}
|
|
@@ -497,10 +569,10 @@ class EmbeddingService {
|
|
|
497
569
|
* @returns {number[]} Normalized vector
|
|
498
570
|
* @private
|
|
499
571
|
*/
|
|
500
|
-
_normalize(vector) {
|
|
572
|
+
_normalize(vector: number[]): number[] {
|
|
501
573
|
// Calculate magnitude
|
|
502
574
|
const magnitude = Math.sqrt(
|
|
503
|
-
vector.reduce((sum, val) => sum + val * val, 0)
|
|
575
|
+
vector.reduce((sum, val) => sum + val * val, 0),
|
|
504
576
|
);
|
|
505
577
|
|
|
506
578
|
// Avoid division by zero
|
|
@@ -509,7 +581,7 @@ class EmbeddingService {
|
|
|
509
581
|
}
|
|
510
582
|
|
|
511
583
|
// Normalize
|
|
512
|
-
return vector.map(val => val / magnitude);
|
|
584
|
+
return vector.map((val) => val / magnitude);
|
|
513
585
|
}
|
|
514
586
|
|
|
515
587
|
/**
|
|
@@ -518,24 +590,17 @@ class EmbeddingService {
|
|
|
518
590
|
* @returns {string} Cache key
|
|
519
591
|
* @private
|
|
520
592
|
*/
|
|
521
|
-
_getCacheKey(text) {
|
|
522
|
-
return crypto
|
|
523
|
-
.createHash('md5')
|
|
524
|
-
.update(text)
|
|
525
|
-
.digest('hex');
|
|
593
|
+
_getCacheKey(text: string): string {
|
|
594
|
+
return crypto.createHash("md5").update(text).digest("hex");
|
|
526
595
|
}
|
|
527
596
|
|
|
528
|
-
|
|
529
|
-
* Set cache value with LRU eviction
|
|
530
|
-
* @param {string} key - Cache key
|
|
531
|
-
* @param {number[]} value - Embedding vector
|
|
532
|
-
* @private
|
|
533
|
-
*/
|
|
534
|
-
_setCache(key, value) {
|
|
597
|
+
_setCache(key: string, value: number[]): void {
|
|
535
598
|
// Evict oldest if at capacity
|
|
536
599
|
if (this.cache.size >= this.cacheMaxSize) {
|
|
537
600
|
const firstKey = this.cache.keys().next().value;
|
|
538
|
-
|
|
601
|
+
if (firstKey !== undefined) {
|
|
602
|
+
this.cache.delete(firstKey);
|
|
603
|
+
}
|
|
539
604
|
}
|
|
540
605
|
|
|
541
606
|
this.cache.set(key, value);
|
|
@@ -545,7 +610,7 @@ class EmbeddingService {
|
|
|
545
610
|
* Get service statistics
|
|
546
611
|
* @returns {Object} Statistics object
|
|
547
612
|
*/
|
|
548
|
-
getStats() {
|
|
613
|
+
getStats(): ServiceStats {
|
|
549
614
|
return {
|
|
550
615
|
modelType: this.modelType,
|
|
551
616
|
modelName: this.modelName,
|
|
@@ -556,29 +621,31 @@ class EmbeddingService {
|
|
|
556
621
|
cacheMisses: this.stats.cacheMisses,
|
|
557
622
|
cacheSize: this.cache.size,
|
|
558
623
|
cacheMaxSize: this.cacheMaxSize,
|
|
559
|
-
cacheHitRate:
|
|
624
|
+
cacheHitRate:
|
|
625
|
+
this.stats.cacheHits /
|
|
626
|
+
(this.stats.cacheHits + this.stats.cacheMisses) || 0,
|
|
560
627
|
batchCount: this.stats.batchCount,
|
|
561
628
|
batchSize: this.batchSize,
|
|
562
|
-
normalize: this.normalize
|
|
629
|
+
normalize: this.normalize,
|
|
563
630
|
};
|
|
564
631
|
}
|
|
565
632
|
|
|
566
633
|
/**
|
|
567
634
|
* Clear the embedding cache
|
|
568
635
|
*/
|
|
569
|
-
clearCache() {
|
|
636
|
+
clearCache(): void {
|
|
570
637
|
this.cache.clear();
|
|
571
638
|
}
|
|
572
639
|
|
|
573
640
|
/**
|
|
574
641
|
* Reset statistics
|
|
575
642
|
*/
|
|
576
|
-
resetStats() {
|
|
643
|
+
resetStats(): void {
|
|
577
644
|
this.stats = {
|
|
578
645
|
totalEmbeddings: 0,
|
|
579
646
|
cacheHits: 0,
|
|
580
647
|
cacheMisses: 0,
|
|
581
|
-
batchCount: 0
|
|
648
|
+
batchCount: 0,
|
|
582
649
|
};
|
|
583
650
|
}
|
|
584
651
|
}
|