@lov3kaizen/agentsea-embeddings 0.5.1
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/LICENSE +21 -0
- package/README.md +475 -0
- package/dist/caching/index.d.mts +286 -0
- package/dist/caching/index.d.ts +286 -0
- package/dist/caching/index.js +1005 -0
- package/dist/caching/index.mjs +27 -0
- package/dist/chunk-3KM32UQK.mjs +207 -0
- package/dist/chunk-DJAURHAS.mjs +1117 -0
- package/dist/chunk-NBHIRTJT.mjs +895 -0
- package/dist/chunk-QAITLJ2E.mjs +259 -0
- package/dist/chunk-TER262ST.mjs +877 -0
- package/dist/chunk-VPSMDBHH.mjs +957 -0
- package/dist/chunking/index.d.mts +1 -0
- package/dist/chunking/index.d.ts +1 -0
- package/dist/chunking/index.js +1408 -0
- package/dist/chunking/index.mjs +37 -0
- package/dist/embedding.types-CCgPVxt1.d.mts +102 -0
- package/dist/embedding.types-CCgPVxt1.d.ts +102 -0
- package/dist/index-CeG6God2.d.mts +297 -0
- package/dist/index-DMaQRn2w.d.mts +172 -0
- package/dist/index-DMaQRn2w.d.ts +172 -0
- package/dist/index-DWddsKRi.d.ts +297 -0
- package/dist/index.d.mts +647 -0
- package/dist/index.d.ts +647 -0
- package/dist/index.js +5259 -0
- package/dist/index.mjs +1028 -0
- package/dist/providers/index.d.mts +2 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1235 -0
- package/dist/providers/index.mjs +32 -0
- package/dist/stores/index.d.mts +298 -0
- package/dist/stores/index.d.ts +298 -0
- package/dist/stores/index.js +1178 -0
- package/dist/stores/index.mjs +26 -0
- package/package.json +102 -0
|
@@ -0,0 +1,1235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/providers/index.ts
|
|
21
|
+
var providers_exports = {};
|
|
22
|
+
__export(providers_exports, {
|
|
23
|
+
BaseProvider: () => BaseProvider,
|
|
24
|
+
CohereProvider: () => CohereProvider,
|
|
25
|
+
HuggingFaceProvider: () => HuggingFaceProvider,
|
|
26
|
+
LocalProvider: () => LocalProvider,
|
|
27
|
+
OpenAIProvider: () => OpenAIProvider,
|
|
28
|
+
VoyageProvider: () => VoyageProvider,
|
|
29
|
+
createCohereProvider: () => createCohereProvider,
|
|
30
|
+
createHuggingFaceProvider: () => createHuggingFaceProvider,
|
|
31
|
+
createLocalProvider: () => createLocalProvider,
|
|
32
|
+
createMockProvider: () => createMockProvider,
|
|
33
|
+
createOpenAIProvider: () => createOpenAIProvider,
|
|
34
|
+
createRandomProvider: () => createRandomProvider,
|
|
35
|
+
createVoyageProvider: () => createVoyageProvider
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(providers_exports);
|
|
38
|
+
|
|
39
|
+
// src/core/EmbeddingModel.ts
|
|
40
|
+
var EmbeddingModel = class {
|
|
41
|
+
/**
|
|
42
|
+
* Get model dimensions
|
|
43
|
+
*/
|
|
44
|
+
get dimensions() {
|
|
45
|
+
return this.info.dimensions;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get max tokens
|
|
49
|
+
*/
|
|
50
|
+
get maxTokens() {
|
|
51
|
+
return this.info.maxTokens;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get max batch size
|
|
55
|
+
*/
|
|
56
|
+
get maxBatchSize() {
|
|
57
|
+
return this.info.maxBatchSize;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get model name
|
|
61
|
+
*/
|
|
62
|
+
get name() {
|
|
63
|
+
return this.info.name;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get provider name
|
|
67
|
+
*/
|
|
68
|
+
get provider() {
|
|
69
|
+
return this.info.provider;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Count tokens in text (default implementation)
|
|
73
|
+
* Subclasses should override for accurate counting
|
|
74
|
+
*/
|
|
75
|
+
countTokens(text) {
|
|
76
|
+
return Math.ceil(text.length / 4);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if text exceeds max tokens
|
|
80
|
+
*/
|
|
81
|
+
exceedsMaxTokens(text) {
|
|
82
|
+
return this.countTokens(text) > this.maxTokens;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Truncate text to max tokens
|
|
86
|
+
*/
|
|
87
|
+
truncateToMaxTokens(text) {
|
|
88
|
+
const tokens = this.countTokens(text);
|
|
89
|
+
if (tokens <= this.maxTokens) {
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
const ratio = this.maxTokens / tokens;
|
|
93
|
+
const targetLength = Math.floor(text.length * ratio * 0.95);
|
|
94
|
+
return text.slice(0, targetLength);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Calculate similarity between two vectors
|
|
98
|
+
*/
|
|
99
|
+
static cosineSimilarity(a, b) {
|
|
100
|
+
if (a.length !== b.length) {
|
|
101
|
+
throw new Error(`Vector dimensions mismatch: ${a.length} vs ${b.length}`);
|
|
102
|
+
}
|
|
103
|
+
let dotProduct = 0;
|
|
104
|
+
let normA = 0;
|
|
105
|
+
let normB = 0;
|
|
106
|
+
for (let i = 0; i < a.length; i++) {
|
|
107
|
+
dotProduct += a[i] * b[i];
|
|
108
|
+
normA += a[i] * a[i];
|
|
109
|
+
normB += b[i] * b[i];
|
|
110
|
+
}
|
|
111
|
+
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
112
|
+
if (magnitude === 0) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
return dotProduct / magnitude;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Calculate Euclidean distance between two vectors
|
|
119
|
+
*/
|
|
120
|
+
static euclideanDistance(a, b) {
|
|
121
|
+
if (a.length !== b.length) {
|
|
122
|
+
throw new Error(`Vector dimensions mismatch: ${a.length} vs ${b.length}`);
|
|
123
|
+
}
|
|
124
|
+
let sum = 0;
|
|
125
|
+
for (let i = 0; i < a.length; i++) {
|
|
126
|
+
const diff = a[i] - b[i];
|
|
127
|
+
sum += diff * diff;
|
|
128
|
+
}
|
|
129
|
+
return Math.sqrt(sum);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Calculate dot product of two vectors
|
|
133
|
+
*/
|
|
134
|
+
static dotProduct(a, b) {
|
|
135
|
+
if (a.length !== b.length) {
|
|
136
|
+
throw new Error(`Vector dimensions mismatch: ${a.length} vs ${b.length}`);
|
|
137
|
+
}
|
|
138
|
+
let result = 0;
|
|
139
|
+
for (let i = 0; i < a.length; i++) {
|
|
140
|
+
result += a[i] * b[i];
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Normalize a vector to unit length
|
|
146
|
+
*/
|
|
147
|
+
static normalize(vector) {
|
|
148
|
+
let norm = 0;
|
|
149
|
+
for (let i = 0; i < vector.length; i++) {
|
|
150
|
+
norm += vector[i] * vector[i];
|
|
151
|
+
}
|
|
152
|
+
norm = Math.sqrt(norm);
|
|
153
|
+
if (norm === 0) {
|
|
154
|
+
return vector.slice();
|
|
155
|
+
}
|
|
156
|
+
return vector.map((v) => v / norm);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Average multiple vectors
|
|
160
|
+
*/
|
|
161
|
+
static average(vectors) {
|
|
162
|
+
if (vectors.length === 0) {
|
|
163
|
+
throw new Error("Cannot average empty array of vectors");
|
|
164
|
+
}
|
|
165
|
+
const dimensions = vectors[0].length;
|
|
166
|
+
const result = new Array(dimensions).fill(0);
|
|
167
|
+
for (const vector of vectors) {
|
|
168
|
+
if (vector.length !== dimensions) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Vector dimensions mismatch: expected ${dimensions}, got ${vector.length}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
for (let i = 0; i < dimensions; i++) {
|
|
174
|
+
result[i] += vector[i];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (let i = 0; i < dimensions; i++) {
|
|
178
|
+
result[i] /= vectors.length;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Weighted average of vectors
|
|
184
|
+
*/
|
|
185
|
+
static weightedAverage(vectors, weights) {
|
|
186
|
+
if (vectors.length === 0) {
|
|
187
|
+
throw new Error("Cannot average empty array of vectors");
|
|
188
|
+
}
|
|
189
|
+
if (vectors.length !== weights.length) {
|
|
190
|
+
throw new Error("Vectors and weights arrays must have same length");
|
|
191
|
+
}
|
|
192
|
+
const dimensions = vectors[0].length;
|
|
193
|
+
const result = new Array(dimensions).fill(0);
|
|
194
|
+
let totalWeight = 0;
|
|
195
|
+
for (let j = 0; j < vectors.length; j++) {
|
|
196
|
+
const vector = vectors[j];
|
|
197
|
+
const weight = weights[j];
|
|
198
|
+
totalWeight += weight;
|
|
199
|
+
if (vector.length !== dimensions) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Vector dimensions mismatch: expected ${dimensions}, got ${vector.length}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
for (let i = 0; i < dimensions; i++) {
|
|
205
|
+
result[i] += vector[i] * weight;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (totalWeight === 0) {
|
|
209
|
+
throw new Error("Total weight cannot be zero");
|
|
210
|
+
}
|
|
211
|
+
for (let i = 0; i < dimensions; i++) {
|
|
212
|
+
result[i] /= totalWeight;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
var ModelRegistry = class {
|
|
218
|
+
models = /* @__PURE__ */ new Map();
|
|
219
|
+
defaultModel = null;
|
|
220
|
+
/**
|
|
221
|
+
* Register a model
|
|
222
|
+
*/
|
|
223
|
+
register(model, isDefault = false) {
|
|
224
|
+
const key = `${model.provider}:${model.name}`;
|
|
225
|
+
this.models.set(key, model);
|
|
226
|
+
if (isDefault || this.defaultModel === null) {
|
|
227
|
+
this.defaultModel = key;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get a model by provider and name
|
|
232
|
+
*/
|
|
233
|
+
get(provider, name) {
|
|
234
|
+
return this.models.get(`${provider}:${name}`);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get model by key
|
|
238
|
+
*/
|
|
239
|
+
getByKey(key) {
|
|
240
|
+
return this.models.get(key);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the default model
|
|
244
|
+
*/
|
|
245
|
+
getDefault() {
|
|
246
|
+
if (this.defaultModel === null) {
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
return this.models.get(this.defaultModel);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Set default model
|
|
253
|
+
*/
|
|
254
|
+
setDefault(provider, name) {
|
|
255
|
+
const key = `${provider}:${name}`;
|
|
256
|
+
if (!this.models.has(key)) {
|
|
257
|
+
throw new Error(`Model ${key} not found in registry`);
|
|
258
|
+
}
|
|
259
|
+
this.defaultModel = key;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* List all registered models
|
|
263
|
+
*/
|
|
264
|
+
list() {
|
|
265
|
+
return Array.from(this.models.values()).map((m) => m.info);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Check if a model is registered
|
|
269
|
+
*/
|
|
270
|
+
has(provider, name) {
|
|
271
|
+
return this.models.has(`${provider}:${name}`);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Remove a model
|
|
275
|
+
*/
|
|
276
|
+
remove(provider, name) {
|
|
277
|
+
const key = `${provider}:${name}`;
|
|
278
|
+
if (this.defaultModel === key) {
|
|
279
|
+
this.defaultModel = null;
|
|
280
|
+
}
|
|
281
|
+
return this.models.delete(key);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Clear all models
|
|
285
|
+
*/
|
|
286
|
+
clear() {
|
|
287
|
+
this.models.clear();
|
|
288
|
+
this.defaultModel = null;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var modelRegistry = new ModelRegistry();
|
|
292
|
+
|
|
293
|
+
// src/core/utils.ts
|
|
294
|
+
function batch(items, batchSize) {
|
|
295
|
+
const batches = [];
|
|
296
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
297
|
+
batches.push(items.slice(i, i + batchSize));
|
|
298
|
+
}
|
|
299
|
+
return batches;
|
|
300
|
+
}
|
|
301
|
+
async function withConcurrency(items, fn, concurrency) {
|
|
302
|
+
const results = new Array(items.length);
|
|
303
|
+
let currentIndex = 0;
|
|
304
|
+
async function worker() {
|
|
305
|
+
while (currentIndex < items.length) {
|
|
306
|
+
const index = currentIndex++;
|
|
307
|
+
results[index] = await fn(items[index], index);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const workers = Array.from(
|
|
311
|
+
{ length: Math.min(concurrency, items.length) },
|
|
312
|
+
() => worker()
|
|
313
|
+
);
|
|
314
|
+
await Promise.all(workers);
|
|
315
|
+
return results;
|
|
316
|
+
}
|
|
317
|
+
async function retry(fn, options = {}) {
|
|
318
|
+
const {
|
|
319
|
+
maxRetries = 3,
|
|
320
|
+
initialDelay = 1e3,
|
|
321
|
+
maxDelay = 3e4,
|
|
322
|
+
backoffMultiplier = 2,
|
|
323
|
+
retryCondition = () => true
|
|
324
|
+
} = options;
|
|
325
|
+
let lastError;
|
|
326
|
+
let delay = initialDelay;
|
|
327
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
328
|
+
try {
|
|
329
|
+
return await fn();
|
|
330
|
+
} catch (error) {
|
|
331
|
+
lastError = error;
|
|
332
|
+
if (attempt === maxRetries || !retryCondition(lastError)) {
|
|
333
|
+
throw lastError;
|
|
334
|
+
}
|
|
335
|
+
await sleep(delay);
|
|
336
|
+
delay = Math.min(delay * backoffMultiplier, maxDelay);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
throw lastError;
|
|
340
|
+
}
|
|
341
|
+
function sleep(ms) {
|
|
342
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
343
|
+
}
|
|
344
|
+
async function measureTime(fn) {
|
|
345
|
+
const start = performance.now();
|
|
346
|
+
const result = await fn();
|
|
347
|
+
const durationMs = performance.now() - start;
|
|
348
|
+
return { result, durationMs };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/providers/BaseProvider.ts
|
|
352
|
+
var BaseProvider = class extends EmbeddingModel {
|
|
353
|
+
config;
|
|
354
|
+
metrics;
|
|
355
|
+
health;
|
|
356
|
+
latencies = [];
|
|
357
|
+
maxLatencySamples = 1e3;
|
|
358
|
+
constructor(config) {
|
|
359
|
+
super();
|
|
360
|
+
this.config = {
|
|
361
|
+
timeout: 3e4,
|
|
362
|
+
maxRetries: 3,
|
|
363
|
+
retryDelay: 1e3,
|
|
364
|
+
...config
|
|
365
|
+
};
|
|
366
|
+
this.metrics = this.createInitialMetrics();
|
|
367
|
+
this.health = {
|
|
368
|
+
healthy: true,
|
|
369
|
+
latencyMs: 0,
|
|
370
|
+
lastCheck: Date.now()
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
createInitialMetrics() {
|
|
374
|
+
return {
|
|
375
|
+
provider: this.config.type,
|
|
376
|
+
totalRequests: 0,
|
|
377
|
+
successfulRequests: 0,
|
|
378
|
+
failedRequests: 0,
|
|
379
|
+
totalTokens: 0,
|
|
380
|
+
avgLatencyMs: 0,
|
|
381
|
+
p50LatencyMs: 0,
|
|
382
|
+
p95LatencyMs: 0,
|
|
383
|
+
p99LatencyMs: 0,
|
|
384
|
+
errorRate: 0,
|
|
385
|
+
rateLimitHits: 0,
|
|
386
|
+
estimatedCostUSD: 0
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Generate embedding for a single text
|
|
391
|
+
*/
|
|
392
|
+
async embed(text, options) {
|
|
393
|
+
const result = await this.embedBatch([text], options);
|
|
394
|
+
return result.results[0];
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Generate embeddings for multiple texts
|
|
398
|
+
*/
|
|
399
|
+
async embedBatch(texts, options) {
|
|
400
|
+
const startTime = performance.now();
|
|
401
|
+
const maxBatchSize = this.info.maxBatchSize;
|
|
402
|
+
const concurrency = options?.concurrency ?? 5;
|
|
403
|
+
const results = [];
|
|
404
|
+
let totalTokens = 0;
|
|
405
|
+
let failures = 0;
|
|
406
|
+
const batches = batch(texts, maxBatchSize);
|
|
407
|
+
const processBatch = async (batchTexts) => {
|
|
408
|
+
this.metrics.totalRequests++;
|
|
409
|
+
try {
|
|
410
|
+
const { result, durationMs } = await measureTime(
|
|
411
|
+
() => retry(() => this.doEmbed(batchTexts, options), {
|
|
412
|
+
maxRetries: this.config.maxRetries,
|
|
413
|
+
initialDelay: this.config.retryDelay,
|
|
414
|
+
retryCondition: (error) => this.isRetryable(error)
|
|
415
|
+
})
|
|
416
|
+
);
|
|
417
|
+
this.recordLatency(durationMs);
|
|
418
|
+
this.metrics.successfulRequests++;
|
|
419
|
+
this.metrics.totalTokens += result.tokenCount;
|
|
420
|
+
totalTokens += result.tokenCount;
|
|
421
|
+
return batchTexts.map((text, i) => ({
|
|
422
|
+
vector: result.vectors[i],
|
|
423
|
+
text,
|
|
424
|
+
tokenCount: Math.ceil(result.tokenCount / batchTexts.length),
|
|
425
|
+
cached: false,
|
|
426
|
+
model: this.info.name,
|
|
427
|
+
dimensions: this.info.dimensions,
|
|
428
|
+
latencyMs: durationMs / batchTexts.length
|
|
429
|
+
}));
|
|
430
|
+
} catch (error) {
|
|
431
|
+
this.metrics.failedRequests++;
|
|
432
|
+
this.health.healthy = false;
|
|
433
|
+
this.health.error = error.message;
|
|
434
|
+
if (options?.continueOnError) {
|
|
435
|
+
failures += batchTexts.length;
|
|
436
|
+
return [];
|
|
437
|
+
}
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
const batchResults = await withConcurrency(
|
|
442
|
+
batches,
|
|
443
|
+
processBatch,
|
|
444
|
+
concurrency
|
|
445
|
+
);
|
|
446
|
+
for (const batchResult of batchResults) {
|
|
447
|
+
results.push(...batchResult);
|
|
448
|
+
}
|
|
449
|
+
const totalLatencyMs = performance.now() - startTime;
|
|
450
|
+
this.updateMetrics();
|
|
451
|
+
return {
|
|
452
|
+
results,
|
|
453
|
+
totalTokens,
|
|
454
|
+
totalLatencyMs,
|
|
455
|
+
cacheHits: 0,
|
|
456
|
+
cacheMisses: texts.length,
|
|
457
|
+
failures
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Check if error is retryable
|
|
462
|
+
*/
|
|
463
|
+
isRetryable(error) {
|
|
464
|
+
const message = error.message.toLowerCase();
|
|
465
|
+
return message.includes("rate limit") || message.includes("timeout") || message.includes("network") || message.includes("econnreset") || message.includes("502") || message.includes("503") || message.includes("504");
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Record latency sample
|
|
469
|
+
*/
|
|
470
|
+
recordLatency(latencyMs) {
|
|
471
|
+
this.latencies.push(latencyMs);
|
|
472
|
+
if (this.latencies.length > this.maxLatencySamples) {
|
|
473
|
+
this.latencies.shift();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Calculate percentile from latencies
|
|
478
|
+
*/
|
|
479
|
+
calculatePercentile(p) {
|
|
480
|
+
if (this.latencies.length === 0) return 0;
|
|
481
|
+
const sorted = [...this.latencies].sort((a, b) => a - b);
|
|
482
|
+
const index = Math.ceil(p / 100 * sorted.length) - 1;
|
|
483
|
+
return sorted[Math.max(0, index)];
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Update metrics
|
|
487
|
+
*/
|
|
488
|
+
updateMetrics() {
|
|
489
|
+
const total = this.metrics.totalRequests;
|
|
490
|
+
if (total > 0) {
|
|
491
|
+
this.metrics.errorRate = this.metrics.failedRequests / total;
|
|
492
|
+
this.metrics.avgLatencyMs = this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length || 0;
|
|
493
|
+
this.metrics.p50LatencyMs = this.calculatePercentile(50);
|
|
494
|
+
this.metrics.p95LatencyMs = this.calculatePercentile(95);
|
|
495
|
+
this.metrics.p99LatencyMs = this.calculatePercentile(99);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Get provider metrics
|
|
500
|
+
*/
|
|
501
|
+
getMetrics() {
|
|
502
|
+
return { ...this.metrics };
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Get provider health
|
|
506
|
+
*/
|
|
507
|
+
getHealth() {
|
|
508
|
+
return { ...this.health };
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Check provider health
|
|
512
|
+
*/
|
|
513
|
+
async checkHealth() {
|
|
514
|
+
try {
|
|
515
|
+
const { durationMs } = await measureTime(
|
|
516
|
+
() => this.doEmbed(["health check"])
|
|
517
|
+
);
|
|
518
|
+
this.health = {
|
|
519
|
+
healthy: true,
|
|
520
|
+
latencyMs: durationMs,
|
|
521
|
+
lastCheck: Date.now()
|
|
522
|
+
};
|
|
523
|
+
} catch (error) {
|
|
524
|
+
this.health = {
|
|
525
|
+
healthy: false,
|
|
526
|
+
latencyMs: 0,
|
|
527
|
+
lastCheck: Date.now(),
|
|
528
|
+
error: error.message
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
return this.health;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Reset metrics
|
|
535
|
+
*/
|
|
536
|
+
resetMetrics() {
|
|
537
|
+
this.metrics = this.createInitialMetrics();
|
|
538
|
+
this.latencies = [];
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/providers/OpenAIProvider.ts
|
|
543
|
+
var OPENAI_MODELS = {
|
|
544
|
+
"text-embedding-3-small": {
|
|
545
|
+
name: "text-embedding-3-small",
|
|
546
|
+
provider: "openai",
|
|
547
|
+
dimensions: 1536,
|
|
548
|
+
maxTokens: 8191,
|
|
549
|
+
maxBatchSize: 2048,
|
|
550
|
+
costPer1K: 2e-5,
|
|
551
|
+
description: "Smaller, faster, cheaper embedding model"
|
|
552
|
+
},
|
|
553
|
+
"text-embedding-3-large": {
|
|
554
|
+
name: "text-embedding-3-large",
|
|
555
|
+
provider: "openai",
|
|
556
|
+
dimensions: 3072,
|
|
557
|
+
maxTokens: 8191,
|
|
558
|
+
maxBatchSize: 2048,
|
|
559
|
+
costPer1K: 13e-5,
|
|
560
|
+
description: "Larger, more powerful embedding model"
|
|
561
|
+
},
|
|
562
|
+
"text-embedding-ada-002": {
|
|
563
|
+
name: "text-embedding-ada-002",
|
|
564
|
+
provider: "openai",
|
|
565
|
+
dimensions: 1536,
|
|
566
|
+
maxTokens: 8191,
|
|
567
|
+
maxBatchSize: 2048,
|
|
568
|
+
costPer1K: 1e-4,
|
|
569
|
+
description: "Legacy embedding model"
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var OpenAIProvider = class extends BaseProvider {
|
|
573
|
+
modelInfo;
|
|
574
|
+
apiKey;
|
|
575
|
+
baseUrl;
|
|
576
|
+
organization;
|
|
577
|
+
constructor(config) {
|
|
578
|
+
super({ ...config, type: "openai" });
|
|
579
|
+
if (!config.apiKey) {
|
|
580
|
+
throw new Error("OpenAI API key is required");
|
|
581
|
+
}
|
|
582
|
+
this.apiKey = config.apiKey;
|
|
583
|
+
this.baseUrl = config.baseUrl ?? "https://api.openai.com/v1";
|
|
584
|
+
this.organization = config.organization;
|
|
585
|
+
const modelName = config.model ?? "text-embedding-3-small";
|
|
586
|
+
const modelConfig = OPENAI_MODELS[modelName];
|
|
587
|
+
if (!modelConfig) {
|
|
588
|
+
this.modelInfo = {
|
|
589
|
+
name: modelName,
|
|
590
|
+
provider: "openai",
|
|
591
|
+
dimensions: config.dimensions ?? 1536,
|
|
592
|
+
maxTokens: 8191,
|
|
593
|
+
maxBatchSize: 2048,
|
|
594
|
+
costPer1K: 1e-4
|
|
595
|
+
};
|
|
596
|
+
} else {
|
|
597
|
+
this.modelInfo = {
|
|
598
|
+
...modelConfig,
|
|
599
|
+
// Allow dimension override for text-embedding-3 models
|
|
600
|
+
dimensions: config.dimensions ?? modelConfig.dimensions
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
get info() {
|
|
605
|
+
return this.modelInfo;
|
|
606
|
+
}
|
|
607
|
+
async doEmbed(texts, options) {
|
|
608
|
+
const headers = {
|
|
609
|
+
"Content-Type": "application/json",
|
|
610
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
611
|
+
};
|
|
612
|
+
if (this.organization) {
|
|
613
|
+
headers["OpenAI-Organization"] = this.organization;
|
|
614
|
+
}
|
|
615
|
+
const body = {
|
|
616
|
+
model: options?.model ?? this.modelInfo.name,
|
|
617
|
+
input: texts
|
|
618
|
+
};
|
|
619
|
+
if (this.modelInfo.name.startsWith("text-embedding-3")) {
|
|
620
|
+
const config2 = this.config;
|
|
621
|
+
if (config2.dimensions) {
|
|
622
|
+
body.dimensions = config2.dimensions;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const config = this.config;
|
|
626
|
+
if (config.encodingFormat) {
|
|
627
|
+
body.encoding_format = config.encodingFormat;
|
|
628
|
+
}
|
|
629
|
+
if (options?.user) {
|
|
630
|
+
body.user = options.user;
|
|
631
|
+
}
|
|
632
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers,
|
|
635
|
+
body: JSON.stringify(body),
|
|
636
|
+
signal: this.config.timeout ? AbortSignal.timeout(this.config.timeout) : void 0
|
|
637
|
+
});
|
|
638
|
+
if (!response.ok) {
|
|
639
|
+
const error = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
640
|
+
const errorMessage = error.error?.message ?? response.statusText;
|
|
641
|
+
if (response.status === 429) {
|
|
642
|
+
this.metrics.rateLimitHits++;
|
|
643
|
+
}
|
|
644
|
+
throw new Error(`OpenAI API error: ${errorMessage} (${response.status})`);
|
|
645
|
+
}
|
|
646
|
+
const data = await response.json();
|
|
647
|
+
const embeddings = data.data.sort((a, b) => a.index - b.index);
|
|
648
|
+
const vectors = embeddings.map((e) => e.embedding);
|
|
649
|
+
const tokenCount = data.usage?.total_tokens ?? 0;
|
|
650
|
+
this.metrics.estimatedCostUSD += tokenCount / 1e3 * (this.modelInfo.costPer1K ?? 0);
|
|
651
|
+
return { vectors, tokenCount };
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Count tokens using tiktoken approximation
|
|
655
|
+
*/
|
|
656
|
+
countTokens(text) {
|
|
657
|
+
const words = text.split(/\s+/);
|
|
658
|
+
let tokens = 0;
|
|
659
|
+
for (const word of words) {
|
|
660
|
+
tokens += Math.ceil(word.length / 4) + 1;
|
|
661
|
+
}
|
|
662
|
+
return Math.max(1, tokens);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
function createOpenAIProvider(config) {
|
|
666
|
+
return new OpenAIProvider(config);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/providers/CohereProvider.ts
|
|
670
|
+
var COHERE_MODELS = {
|
|
671
|
+
"embed-english-v3.0": {
|
|
672
|
+
name: "embed-english-v3.0",
|
|
673
|
+
provider: "cohere",
|
|
674
|
+
dimensions: 1024,
|
|
675
|
+
maxTokens: 512,
|
|
676
|
+
maxBatchSize: 96,
|
|
677
|
+
costPer1K: 1e-4,
|
|
678
|
+
description: "English embedding model v3"
|
|
679
|
+
},
|
|
680
|
+
"embed-multilingual-v3.0": {
|
|
681
|
+
name: "embed-multilingual-v3.0",
|
|
682
|
+
provider: "cohere",
|
|
683
|
+
dimensions: 1024,
|
|
684
|
+
maxTokens: 512,
|
|
685
|
+
maxBatchSize: 96,
|
|
686
|
+
costPer1K: 1e-4,
|
|
687
|
+
description: "Multilingual embedding model v3"
|
|
688
|
+
},
|
|
689
|
+
"embed-english-light-v3.0": {
|
|
690
|
+
name: "embed-english-light-v3.0",
|
|
691
|
+
provider: "cohere",
|
|
692
|
+
dimensions: 384,
|
|
693
|
+
maxTokens: 512,
|
|
694
|
+
maxBatchSize: 96,
|
|
695
|
+
costPer1K: 1e-4,
|
|
696
|
+
description: "Lightweight English embedding model v3"
|
|
697
|
+
},
|
|
698
|
+
"embed-multilingual-light-v3.0": {
|
|
699
|
+
name: "embed-multilingual-light-v3.0",
|
|
700
|
+
provider: "cohere",
|
|
701
|
+
dimensions: 384,
|
|
702
|
+
maxTokens: 512,
|
|
703
|
+
maxBatchSize: 96,
|
|
704
|
+
costPer1K: 1e-4,
|
|
705
|
+
description: "Lightweight multilingual embedding model v3"
|
|
706
|
+
},
|
|
707
|
+
"embed-english-v2.0": {
|
|
708
|
+
name: "embed-english-v2.0",
|
|
709
|
+
provider: "cohere",
|
|
710
|
+
dimensions: 4096,
|
|
711
|
+
maxTokens: 512,
|
|
712
|
+
maxBatchSize: 96,
|
|
713
|
+
costPer1K: 1e-4,
|
|
714
|
+
description: "Legacy English embedding model v2"
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
var CohereProvider = class extends BaseProvider {
|
|
718
|
+
modelInfo;
|
|
719
|
+
apiKey;
|
|
720
|
+
baseUrl;
|
|
721
|
+
inputType;
|
|
722
|
+
truncate;
|
|
723
|
+
constructor(config) {
|
|
724
|
+
super({ ...config, type: "cohere" });
|
|
725
|
+
if (!config.apiKey) {
|
|
726
|
+
throw new Error("Cohere API key is required");
|
|
727
|
+
}
|
|
728
|
+
this.apiKey = config.apiKey;
|
|
729
|
+
this.baseUrl = config.baseUrl ?? "https://api.cohere.ai/v1";
|
|
730
|
+
this.inputType = config.inputType ?? "search_document";
|
|
731
|
+
this.truncate = config.truncate ?? "END";
|
|
732
|
+
const modelName = config.model ?? "embed-english-v3.0";
|
|
733
|
+
const modelConfig = COHERE_MODELS[modelName];
|
|
734
|
+
if (!modelConfig) {
|
|
735
|
+
this.modelInfo = {
|
|
736
|
+
name: modelName,
|
|
737
|
+
provider: "cohere",
|
|
738
|
+
dimensions: 1024,
|
|
739
|
+
maxTokens: 512,
|
|
740
|
+
maxBatchSize: 96,
|
|
741
|
+
costPer1K: 1e-4
|
|
742
|
+
};
|
|
743
|
+
} else {
|
|
744
|
+
this.modelInfo = { ...modelConfig };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
get info() {
|
|
748
|
+
return this.modelInfo;
|
|
749
|
+
}
|
|
750
|
+
async doEmbed(texts, options) {
|
|
751
|
+
const headers = {
|
|
752
|
+
"Content-Type": "application/json",
|
|
753
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
754
|
+
"Request-Source": "agentsea-embeddings"
|
|
755
|
+
};
|
|
756
|
+
const body = {
|
|
757
|
+
model: options?.model ?? this.modelInfo.name,
|
|
758
|
+
texts,
|
|
759
|
+
input_type: this.inputType,
|
|
760
|
+
truncate: this.truncate
|
|
761
|
+
};
|
|
762
|
+
const response = await fetch(`${this.baseUrl}/embed`, {
|
|
763
|
+
method: "POST",
|
|
764
|
+
headers,
|
|
765
|
+
body: JSON.stringify(body),
|
|
766
|
+
signal: this.config.timeout ? AbortSignal.timeout(this.config.timeout) : void 0
|
|
767
|
+
});
|
|
768
|
+
if (!response.ok) {
|
|
769
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
770
|
+
const errorMessage = error.message ?? response.statusText;
|
|
771
|
+
if (response.status === 429) {
|
|
772
|
+
this.metrics.rateLimitHits++;
|
|
773
|
+
}
|
|
774
|
+
throw new Error(`Cohere API error: ${errorMessage} (${response.status})`);
|
|
775
|
+
}
|
|
776
|
+
const data = await response.json();
|
|
777
|
+
const vectors = data.embeddings;
|
|
778
|
+
const tokenCount = texts.reduce(
|
|
779
|
+
(sum, text) => sum + this.countTokens(text),
|
|
780
|
+
0
|
|
781
|
+
);
|
|
782
|
+
this.metrics.estimatedCostUSD += tokenCount / 1e3 * (this.modelInfo.costPer1K ?? 0);
|
|
783
|
+
return { vectors, tokenCount };
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Set input type for embeddings
|
|
787
|
+
*/
|
|
788
|
+
setInputType(inputType) {
|
|
789
|
+
this.inputType = inputType;
|
|
790
|
+
return this;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Count tokens (approximation)
|
|
794
|
+
*/
|
|
795
|
+
countTokens(text) {
|
|
796
|
+
return Math.ceil(text.length / 4);
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
function createCohereProvider(config) {
|
|
800
|
+
return new CohereProvider(config);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/providers/VoyageProvider.ts
|
|
804
|
+
var VOYAGE_MODELS = {
|
|
805
|
+
"voyage-3": {
|
|
806
|
+
name: "voyage-3",
|
|
807
|
+
provider: "voyage",
|
|
808
|
+
dimensions: 1024,
|
|
809
|
+
maxTokens: 32e3,
|
|
810
|
+
maxBatchSize: 128,
|
|
811
|
+
costPer1K: 6e-5,
|
|
812
|
+
description: "Latest general-purpose embedding model"
|
|
813
|
+
},
|
|
814
|
+
"voyage-3-lite": {
|
|
815
|
+
name: "voyage-3-lite",
|
|
816
|
+
provider: "voyage",
|
|
817
|
+
dimensions: 512,
|
|
818
|
+
maxTokens: 32e3,
|
|
819
|
+
maxBatchSize: 128,
|
|
820
|
+
costPer1K: 2e-5,
|
|
821
|
+
description: "Lightweight general-purpose model"
|
|
822
|
+
},
|
|
823
|
+
"voyage-code-3": {
|
|
824
|
+
name: "voyage-code-3",
|
|
825
|
+
provider: "voyage",
|
|
826
|
+
dimensions: 1024,
|
|
827
|
+
maxTokens: 32e3,
|
|
828
|
+
maxBatchSize: 128,
|
|
829
|
+
costPer1K: 6e-5,
|
|
830
|
+
description: "Optimized for code retrieval"
|
|
831
|
+
},
|
|
832
|
+
"voyage-finance-2": {
|
|
833
|
+
name: "voyage-finance-2",
|
|
834
|
+
provider: "voyage",
|
|
835
|
+
dimensions: 1024,
|
|
836
|
+
maxTokens: 32e3,
|
|
837
|
+
maxBatchSize: 128,
|
|
838
|
+
costPer1K: 12e-5,
|
|
839
|
+
description: "Optimized for finance domain"
|
|
840
|
+
},
|
|
841
|
+
"voyage-law-2": {
|
|
842
|
+
name: "voyage-law-2",
|
|
843
|
+
provider: "voyage",
|
|
844
|
+
dimensions: 1024,
|
|
845
|
+
maxTokens: 32e3,
|
|
846
|
+
maxBatchSize: 128,
|
|
847
|
+
costPer1K: 12e-5,
|
|
848
|
+
description: "Optimized for legal domain"
|
|
849
|
+
},
|
|
850
|
+
"voyage-multilingual-2": {
|
|
851
|
+
name: "voyage-multilingual-2",
|
|
852
|
+
provider: "voyage",
|
|
853
|
+
dimensions: 1024,
|
|
854
|
+
maxTokens: 32e3,
|
|
855
|
+
maxBatchSize: 128,
|
|
856
|
+
costPer1K: 12e-5,
|
|
857
|
+
description: "Multilingual embedding model"
|
|
858
|
+
},
|
|
859
|
+
"voyage-2": {
|
|
860
|
+
name: "voyage-2",
|
|
861
|
+
provider: "voyage",
|
|
862
|
+
dimensions: 1024,
|
|
863
|
+
maxTokens: 4e3,
|
|
864
|
+
maxBatchSize: 128,
|
|
865
|
+
costPer1K: 1e-4,
|
|
866
|
+
description: "Previous generation model"
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
var VoyageProvider = class extends BaseProvider {
|
|
870
|
+
modelInfo;
|
|
871
|
+
apiKey;
|
|
872
|
+
baseUrl;
|
|
873
|
+
inputType;
|
|
874
|
+
truncation;
|
|
875
|
+
constructor(config) {
|
|
876
|
+
super({ ...config, type: "voyage" });
|
|
877
|
+
if (!config.apiKey) {
|
|
878
|
+
throw new Error("Voyage AI API key is required");
|
|
879
|
+
}
|
|
880
|
+
this.apiKey = config.apiKey;
|
|
881
|
+
this.baseUrl = config.baseUrl ?? "https://api.voyageai.com/v1";
|
|
882
|
+
this.inputType = config.inputType ?? "document";
|
|
883
|
+
this.truncation = config.truncation ?? true;
|
|
884
|
+
const modelName = config.model ?? "voyage-3";
|
|
885
|
+
const modelConfig = VOYAGE_MODELS[modelName];
|
|
886
|
+
if (!modelConfig) {
|
|
887
|
+
this.modelInfo = {
|
|
888
|
+
name: modelName,
|
|
889
|
+
provider: "voyage",
|
|
890
|
+
dimensions: 1024,
|
|
891
|
+
maxTokens: 32e3,
|
|
892
|
+
maxBatchSize: 128,
|
|
893
|
+
costPer1K: 1e-4
|
|
894
|
+
};
|
|
895
|
+
} else {
|
|
896
|
+
this.modelInfo = { ...modelConfig };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
get info() {
|
|
900
|
+
return this.modelInfo;
|
|
901
|
+
}
|
|
902
|
+
async doEmbed(texts, options) {
|
|
903
|
+
const headers = {
|
|
904
|
+
"Content-Type": "application/json",
|
|
905
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
906
|
+
};
|
|
907
|
+
const body = {
|
|
908
|
+
model: options?.model ?? this.modelInfo.name,
|
|
909
|
+
input: texts,
|
|
910
|
+
input_type: this.inputType,
|
|
911
|
+
truncation: this.truncation
|
|
912
|
+
};
|
|
913
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
914
|
+
method: "POST",
|
|
915
|
+
headers,
|
|
916
|
+
body: JSON.stringify(body),
|
|
917
|
+
signal: this.config.timeout ? AbortSignal.timeout(this.config.timeout) : void 0
|
|
918
|
+
});
|
|
919
|
+
if (!response.ok) {
|
|
920
|
+
const error = await response.json().catch(() => ({ detail: response.statusText }));
|
|
921
|
+
const errorMessage = error.detail ?? response.statusText;
|
|
922
|
+
if (response.status === 429) {
|
|
923
|
+
this.metrics.rateLimitHits++;
|
|
924
|
+
}
|
|
925
|
+
throw new Error(
|
|
926
|
+
`Voyage AI API error: ${errorMessage} (${response.status})`
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
const data = await response.json();
|
|
930
|
+
const vectors = data.data.map((d) => d.embedding);
|
|
931
|
+
const tokenCount = data.usage?.total_tokens ?? texts.reduce((sum, text) => sum + this.countTokens(text), 0);
|
|
932
|
+
this.metrics.estimatedCostUSD += tokenCount / 1e3 * (this.modelInfo.costPer1K ?? 0);
|
|
933
|
+
return { vectors, tokenCount };
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Set input type for embeddings
|
|
937
|
+
*/
|
|
938
|
+
setInputType(inputType) {
|
|
939
|
+
this.inputType = inputType;
|
|
940
|
+
return this;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Count tokens (approximation)
|
|
944
|
+
*/
|
|
945
|
+
countTokens(text) {
|
|
946
|
+
return Math.ceil(text.length / 4);
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
function createVoyageProvider(config) {
|
|
950
|
+
return new VoyageProvider(config);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/providers/LocalProvider.ts
|
|
954
|
+
var LocalProvider = class extends BaseProvider {
|
|
955
|
+
modelInfo;
|
|
956
|
+
embedFn = null;
|
|
957
|
+
normalize;
|
|
958
|
+
batchSize;
|
|
959
|
+
constructor(config) {
|
|
960
|
+
super({ ...config, type: "local" });
|
|
961
|
+
if (!config.embedFn && !config.modelPath) {
|
|
962
|
+
throw new Error(
|
|
963
|
+
"Either embedFn or modelPath is required for local provider"
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
this.embedFn = config.embedFn ?? null;
|
|
967
|
+
this.normalize = config.normalize ?? true;
|
|
968
|
+
this.batchSize = config.batchSize ?? 32;
|
|
969
|
+
this.modelInfo = {
|
|
970
|
+
name: config.name ?? config.modelPath ?? "local-model",
|
|
971
|
+
provider: "local",
|
|
972
|
+
dimensions: config.dimensions,
|
|
973
|
+
maxTokens: config.maxTokens ?? 512,
|
|
974
|
+
maxBatchSize: config.maxBatchSize ?? 32,
|
|
975
|
+
costPer1K: 0,
|
|
976
|
+
// Local models have no API cost
|
|
977
|
+
description: "Local embedding model"
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
get info() {
|
|
981
|
+
return this.modelInfo;
|
|
982
|
+
}
|
|
983
|
+
async doEmbed(texts, options) {
|
|
984
|
+
if (!this.embedFn) {
|
|
985
|
+
throw new Error("No embedding function configured");
|
|
986
|
+
}
|
|
987
|
+
let vectors = await this.embedFn(texts, options);
|
|
988
|
+
if (this.normalize) {
|
|
989
|
+
vectors = vectors.map((v) => EmbeddingModel.normalize(v));
|
|
990
|
+
}
|
|
991
|
+
const tokenCount = texts.reduce(
|
|
992
|
+
(sum, text) => sum + this.countTokens(text),
|
|
993
|
+
0
|
|
994
|
+
);
|
|
995
|
+
return { vectors, tokenCount };
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Set the embedding function
|
|
999
|
+
*/
|
|
1000
|
+
setEmbedFunction(fn) {
|
|
1001
|
+
this.embedFn = fn;
|
|
1002
|
+
return this;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Count tokens (simple approximation for local models)
|
|
1006
|
+
*/
|
|
1007
|
+
countTokens(text) {
|
|
1008
|
+
return text.split(/\s+/).length;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
function createLocalProvider(config) {
|
|
1012
|
+
return new LocalProvider(config);
|
|
1013
|
+
}
|
|
1014
|
+
function createMockProvider(config) {
|
|
1015
|
+
const delay = config.delay ?? 10;
|
|
1016
|
+
return new LocalProvider({
|
|
1017
|
+
type: "local",
|
|
1018
|
+
dimensions: config.dimensions,
|
|
1019
|
+
name: config.name ?? "mock-model",
|
|
1020
|
+
embedFn: (texts) => {
|
|
1021
|
+
return new Promise((resolve) => setTimeout(resolve, delay)).then(() => {
|
|
1022
|
+
return texts.map((text) => {
|
|
1023
|
+
const hash = text.split("").reduce((acc, char) => {
|
|
1024
|
+
return (acc << 5) - acc + char.charCodeAt(0);
|
|
1025
|
+
}, 0);
|
|
1026
|
+
const vector = [];
|
|
1027
|
+
let seed = Math.abs(hash);
|
|
1028
|
+
for (let i = 0; i < config.dimensions; i++) {
|
|
1029
|
+
seed = seed * 1103515245 + 12345 & 2147483647;
|
|
1030
|
+
vector.push(seed / 2147483647 * 2 - 1);
|
|
1031
|
+
}
|
|
1032
|
+
const norm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
|
|
1033
|
+
return vector.map((v) => v / norm);
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
function createRandomProvider(config) {
|
|
1040
|
+
return new LocalProvider({
|
|
1041
|
+
type: "local",
|
|
1042
|
+
dimensions: config.dimensions,
|
|
1043
|
+
name: config.name ?? "random-model",
|
|
1044
|
+
embedFn: (texts) => {
|
|
1045
|
+
return Promise.resolve(
|
|
1046
|
+
texts.map(() => {
|
|
1047
|
+
const vector = [];
|
|
1048
|
+
for (let i = 0; i < config.dimensions; i++) {
|
|
1049
|
+
vector.push(Math.random() * 2 - 1);
|
|
1050
|
+
}
|
|
1051
|
+
const norm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
|
|
1052
|
+
return vector.map((v) => v / norm);
|
|
1053
|
+
})
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/providers/HuggingFaceProvider.ts
|
|
1060
|
+
var HUGGINGFACE_MODELS = {
|
|
1061
|
+
"sentence-transformers/all-MiniLM-L6-v2": {
|
|
1062
|
+
dimensions: 384,
|
|
1063
|
+
maxTokens: 256,
|
|
1064
|
+
description: "Lightweight sentence transformer"
|
|
1065
|
+
},
|
|
1066
|
+
"sentence-transformers/all-mpnet-base-v2": {
|
|
1067
|
+
dimensions: 768,
|
|
1068
|
+
maxTokens: 384,
|
|
1069
|
+
description: "High quality sentence transformer"
|
|
1070
|
+
},
|
|
1071
|
+
"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2": {
|
|
1072
|
+
dimensions: 384,
|
|
1073
|
+
maxTokens: 128,
|
|
1074
|
+
description: "Multilingual sentence transformer"
|
|
1075
|
+
},
|
|
1076
|
+
"BAAI/bge-small-en-v1.5": {
|
|
1077
|
+
dimensions: 384,
|
|
1078
|
+
maxTokens: 512,
|
|
1079
|
+
description: "BGE small English model"
|
|
1080
|
+
},
|
|
1081
|
+
"BAAI/bge-base-en-v1.5": {
|
|
1082
|
+
dimensions: 768,
|
|
1083
|
+
maxTokens: 512,
|
|
1084
|
+
description: "BGE base English model"
|
|
1085
|
+
},
|
|
1086
|
+
"BAAI/bge-large-en-v1.5": {
|
|
1087
|
+
dimensions: 1024,
|
|
1088
|
+
maxTokens: 512,
|
|
1089
|
+
description: "BGE large English model"
|
|
1090
|
+
},
|
|
1091
|
+
"thenlper/gte-small": {
|
|
1092
|
+
dimensions: 384,
|
|
1093
|
+
maxTokens: 512,
|
|
1094
|
+
description: "GTE small model"
|
|
1095
|
+
},
|
|
1096
|
+
"thenlper/gte-base": {
|
|
1097
|
+
dimensions: 768,
|
|
1098
|
+
maxTokens: 512,
|
|
1099
|
+
description: "GTE base model"
|
|
1100
|
+
},
|
|
1101
|
+
"thenlper/gte-large": {
|
|
1102
|
+
dimensions: 1024,
|
|
1103
|
+
maxTokens: 512,
|
|
1104
|
+
description: "GTE large model"
|
|
1105
|
+
},
|
|
1106
|
+
"intfloat/e5-small-v2": {
|
|
1107
|
+
dimensions: 384,
|
|
1108
|
+
maxTokens: 512,
|
|
1109
|
+
description: "E5 small v2 model"
|
|
1110
|
+
},
|
|
1111
|
+
"intfloat/e5-base-v2": {
|
|
1112
|
+
dimensions: 768,
|
|
1113
|
+
maxTokens: 512,
|
|
1114
|
+
description: "E5 base v2 model"
|
|
1115
|
+
},
|
|
1116
|
+
"intfloat/e5-large-v2": {
|
|
1117
|
+
dimensions: 1024,
|
|
1118
|
+
maxTokens: 512,
|
|
1119
|
+
description: "E5 large v2 model"
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
var HuggingFaceProvider = class extends BaseProvider {
|
|
1123
|
+
modelInfo;
|
|
1124
|
+
apiKey;
|
|
1125
|
+
baseUrl;
|
|
1126
|
+
waitForModel;
|
|
1127
|
+
constructor(config) {
|
|
1128
|
+
super({ ...config, type: "huggingface" });
|
|
1129
|
+
if (!config.apiKey) {
|
|
1130
|
+
throw new Error("HuggingFace API key is required");
|
|
1131
|
+
}
|
|
1132
|
+
this.apiKey = config.apiKey;
|
|
1133
|
+
this.waitForModel = config.waitForModel ?? true;
|
|
1134
|
+
const modelName = config.model ?? "sentence-transformers/all-MiniLM-L6-v2";
|
|
1135
|
+
const knownConfig = HUGGINGFACE_MODELS[modelName];
|
|
1136
|
+
this.baseUrl = config.baseUrl ?? `https://api-inference.huggingface.co/pipeline/feature-extraction/${modelName}`;
|
|
1137
|
+
this.modelInfo = {
|
|
1138
|
+
name: modelName,
|
|
1139
|
+
provider: "huggingface",
|
|
1140
|
+
dimensions: knownConfig?.dimensions ?? 768,
|
|
1141
|
+
maxTokens: knownConfig?.maxTokens ?? 512,
|
|
1142
|
+
maxBatchSize: 32,
|
|
1143
|
+
// HF inference API handles batching
|
|
1144
|
+
costPer1K: 0,
|
|
1145
|
+
// Free tier available
|
|
1146
|
+
description: knownConfig?.description ?? "HuggingFace model"
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
get info() {
|
|
1150
|
+
return this.modelInfo;
|
|
1151
|
+
}
|
|
1152
|
+
async doEmbed(texts, _options) {
|
|
1153
|
+
const headers = {
|
|
1154
|
+
"Content-Type": "application/json",
|
|
1155
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
1156
|
+
};
|
|
1157
|
+
const body = {
|
|
1158
|
+
inputs: texts,
|
|
1159
|
+
options: {
|
|
1160
|
+
wait_for_model: this.waitForModel
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
const response = await fetch(this.baseUrl, {
|
|
1164
|
+
method: "POST",
|
|
1165
|
+
headers,
|
|
1166
|
+
body: JSON.stringify(body),
|
|
1167
|
+
signal: this.config.timeout ? AbortSignal.timeout(this.config.timeout) : void 0
|
|
1168
|
+
});
|
|
1169
|
+
if (!response.ok) {
|
|
1170
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
1171
|
+
const errorMessage = error.error ?? response.statusText;
|
|
1172
|
+
if (response.status === 429) {
|
|
1173
|
+
this.metrics.rateLimitHits++;
|
|
1174
|
+
}
|
|
1175
|
+
throw new Error(
|
|
1176
|
+
`HuggingFace API error: ${errorMessage} (${response.status})`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
const data = await response.json();
|
|
1180
|
+
let vectors;
|
|
1181
|
+
if (Array.isArray(data) && Array.isArray(data[0])) {
|
|
1182
|
+
if (typeof data[0][0] === "number") {
|
|
1183
|
+
vectors = data;
|
|
1184
|
+
} else {
|
|
1185
|
+
vectors = data.map((tokenEmbeddings) => {
|
|
1186
|
+
const dims = tokenEmbeddings[0]?.length ?? this.modelInfo.dimensions;
|
|
1187
|
+
const mean = new Array(dims).fill(0);
|
|
1188
|
+
for (const embedding of tokenEmbeddings) {
|
|
1189
|
+
for (let i = 0; i < dims; i++) {
|
|
1190
|
+
mean[i] += embedding[i];
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return mean.map((v) => v / tokenEmbeddings.length);
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
} else {
|
|
1197
|
+
vectors = [data];
|
|
1198
|
+
}
|
|
1199
|
+
const tokenCount = texts.reduce(
|
|
1200
|
+
(sum, text) => sum + this.countTokens(text),
|
|
1201
|
+
0
|
|
1202
|
+
);
|
|
1203
|
+
return { vectors, tokenCount };
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Count tokens (approximation based on wordpiece)
|
|
1207
|
+
*/
|
|
1208
|
+
countTokens(text) {
|
|
1209
|
+
const words = text.split(/\s+/);
|
|
1210
|
+
let tokens = 0;
|
|
1211
|
+
for (const word of words) {
|
|
1212
|
+
tokens += Math.ceil(word.length / 5) + 1;
|
|
1213
|
+
}
|
|
1214
|
+
return Math.max(1, tokens);
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
function createHuggingFaceProvider(config) {
|
|
1218
|
+
return new HuggingFaceProvider(config);
|
|
1219
|
+
}
|
|
1220
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1221
|
+
0 && (module.exports = {
|
|
1222
|
+
BaseProvider,
|
|
1223
|
+
CohereProvider,
|
|
1224
|
+
HuggingFaceProvider,
|
|
1225
|
+
LocalProvider,
|
|
1226
|
+
OpenAIProvider,
|
|
1227
|
+
VoyageProvider,
|
|
1228
|
+
createCohereProvider,
|
|
1229
|
+
createHuggingFaceProvider,
|
|
1230
|
+
createLocalProvider,
|
|
1231
|
+
createMockProvider,
|
|
1232
|
+
createOpenAIProvider,
|
|
1233
|
+
createRandomProvider,
|
|
1234
|
+
createVoyageProvider
|
|
1235
|
+
});
|