@lov3kaizen/agentsea-cache 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 +323 -0
- package/dist/BaseMatchStrategy-1E1SHaUt.d.ts +60 -0
- package/dist/SemanticCache-vysguwUQ.d.ts +65 -0
- package/dist/SimilarityEngine-Cwv_mF9a.d.ts +41 -0
- package/dist/analytics/index.d.ts +123 -0
- package/dist/analytics/index.js +275 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/cache.types-DMuyQseO.d.ts +99 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +3301 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/agentsea/index.d.ts +103 -0
- package/dist/integrations/agentsea/index.js +201 -0
- package/dist/integrations/agentsea/index.js.map +1 -0
- package/dist/integrations/gateway/index.d.ts +98 -0
- package/dist/integrations/gateway/index.js +205 -0
- package/dist/integrations/gateway/index.js.map +1 -0
- package/dist/invalidation/index.d.ts +113 -0
- package/dist/invalidation/index.js +360 -0
- package/dist/invalidation/index.js.map +1 -0
- package/dist/store.types-BQy5Yyz9.d.ts +111 -0
- package/dist/stores/index.d.ts +138 -0
- package/dist/stores/index.js +1147 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/strategies/index.d.ts +36 -0
- package/dist/strategies/index.js +280 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/streaming/index.d.ts +206 -0
- package/dist/streaming/index.js +794 -0
- package/dist/streaming/index.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
import 'nanoid';
|
|
3
|
+
|
|
4
|
+
// src/stores/BaseCacheStore.ts
|
|
5
|
+
var BaseCacheStore = class {
|
|
6
|
+
/** Store configuration */
|
|
7
|
+
config;
|
|
8
|
+
/** Store metrics */
|
|
9
|
+
metrics = {
|
|
10
|
+
gets: 0,
|
|
11
|
+
sets: 0,
|
|
12
|
+
deletes: 0,
|
|
13
|
+
hits: 0,
|
|
14
|
+
misses: 0
|
|
15
|
+
};
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = {
|
|
18
|
+
namespace: config.namespace ?? "default",
|
|
19
|
+
...config
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the store namespace
|
|
24
|
+
*/
|
|
25
|
+
get namespace() {
|
|
26
|
+
return this.config.namespace ?? "default";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get store metrics
|
|
30
|
+
*/
|
|
31
|
+
getMetrics() {
|
|
32
|
+
return { ...this.metrics };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reset store metrics
|
|
36
|
+
*/
|
|
37
|
+
resetMetrics() {
|
|
38
|
+
this.metrics = {
|
|
39
|
+
gets: 0,
|
|
40
|
+
sets: 0,
|
|
41
|
+
deletes: 0,
|
|
42
|
+
hits: 0,
|
|
43
|
+
misses: 0
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Increment a metric counter
|
|
48
|
+
*/
|
|
49
|
+
incrementMetric(metric, amount = 1) {
|
|
50
|
+
if (typeof this.metrics[metric] === "number") {
|
|
51
|
+
this.metrics[metric] += amount;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function now() {
|
|
56
|
+
return Date.now();
|
|
57
|
+
}
|
|
58
|
+
function estimateEntrySize(entry) {
|
|
59
|
+
const vectorSize = (entry.embedding?.length ?? 0) * 4;
|
|
60
|
+
const messageSize = entry.request.messages.reduce(
|
|
61
|
+
(acc, m) => acc + (m.content?.length ?? 0) * 2,
|
|
62
|
+
0
|
|
63
|
+
);
|
|
64
|
+
const responseSize = (entry.response.content?.length ?? 0) * 2;
|
|
65
|
+
const overheadSize = 500;
|
|
66
|
+
return vectorSize + messageSize + responseSize + overheadSize;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/stores/MemoryCacheStore.ts
|
|
70
|
+
var DEFAULT_CONFIG = {
|
|
71
|
+
maxEntries: 1e4,
|
|
72
|
+
maxSizeBytes: 1024 * 1024 * 1024,
|
|
73
|
+
// 1GB
|
|
74
|
+
evictionPolicy: "lru"
|
|
75
|
+
};
|
|
76
|
+
var MemoryCacheStore = class extends BaseCacheStore {
|
|
77
|
+
storeType = "memory";
|
|
78
|
+
cache;
|
|
79
|
+
vectors = /* @__PURE__ */ new Map();
|
|
80
|
+
memoryConfig;
|
|
81
|
+
closed = false;
|
|
82
|
+
constructor(config = { type: "memory" }) {
|
|
83
|
+
super(config);
|
|
84
|
+
this.memoryConfig = { ...DEFAULT_CONFIG, ...config };
|
|
85
|
+
this.cache = new LRUCache({
|
|
86
|
+
max: this.memoryConfig.maxEntries ?? 1e4,
|
|
87
|
+
maxSize: this.memoryConfig.maxSizeBytes ?? 1024 * 1024 * 1024,
|
|
88
|
+
sizeCalculation: (entry) => estimateEntrySize(entry),
|
|
89
|
+
ttl: 0,
|
|
90
|
+
// TTL handled per-entry
|
|
91
|
+
updateAgeOnGet: true,
|
|
92
|
+
allowStale: false
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
get(key) {
|
|
96
|
+
this.incrementMetric("gets");
|
|
97
|
+
const entry = this.cache.get(key);
|
|
98
|
+
if (entry) {
|
|
99
|
+
this.incrementMetric("hits");
|
|
100
|
+
entry.metadata.accessedAt = now();
|
|
101
|
+
entry.metadata.accessCount++;
|
|
102
|
+
return Promise.resolve(entry);
|
|
103
|
+
}
|
|
104
|
+
this.incrementMetric("misses");
|
|
105
|
+
return Promise.resolve(void 0);
|
|
106
|
+
}
|
|
107
|
+
set(key, entry) {
|
|
108
|
+
const startTime = performance.now();
|
|
109
|
+
this.incrementMetric("sets");
|
|
110
|
+
const ttlMs = entry.metadata.ttl > 0 ? entry.metadata.ttl * 1e3 : void 0;
|
|
111
|
+
this.cache.set(key, entry, { ttl: ttlMs });
|
|
112
|
+
if (entry.embedding && entry.embedding.length > 0) {
|
|
113
|
+
this.vectors.set(key, {
|
|
114
|
+
id: entry.id,
|
|
115
|
+
vector: entry.embedding
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return Promise.resolve({
|
|
119
|
+
success: true,
|
|
120
|
+
id: entry.id,
|
|
121
|
+
durationMs: performance.now() - startTime
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
has(key) {
|
|
125
|
+
return Promise.resolve(this.cache.has(key));
|
|
126
|
+
}
|
|
127
|
+
delete(key) {
|
|
128
|
+
this.incrementMetric("deletes");
|
|
129
|
+
const existed = this.cache.has(key);
|
|
130
|
+
this.cache.delete(key);
|
|
131
|
+
this.vectors.delete(key);
|
|
132
|
+
return Promise.resolve(existed);
|
|
133
|
+
}
|
|
134
|
+
clear() {
|
|
135
|
+
this.cache.clear();
|
|
136
|
+
this.vectors.clear();
|
|
137
|
+
return Promise.resolve();
|
|
138
|
+
}
|
|
139
|
+
size() {
|
|
140
|
+
return Promise.resolve(this.cache.size);
|
|
141
|
+
}
|
|
142
|
+
keys() {
|
|
143
|
+
return Promise.resolve(Array.from(this.cache.keys()));
|
|
144
|
+
}
|
|
145
|
+
query(vector, options) {
|
|
146
|
+
const startTime = performance.now();
|
|
147
|
+
const topK = options?.topK ?? 10;
|
|
148
|
+
const minSimilarity = options?.minSimilarity ?? 0;
|
|
149
|
+
const results = [];
|
|
150
|
+
for (const [key, stored] of this.vectors) {
|
|
151
|
+
const entry = this.cache.get(key);
|
|
152
|
+
if (!entry) continue;
|
|
153
|
+
if (options?.namespace && entry.metadata.namespace !== options.namespace) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const similarity = this.cosineSimilarity(vector, stored.vector);
|
|
157
|
+
if (similarity >= minSimilarity) {
|
|
158
|
+
results.push({ ...entry, score: similarity });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
results.sort((a, b) => b.score - a.score);
|
|
162
|
+
return Promise.resolve({
|
|
163
|
+
entries: results.slice(0, topK),
|
|
164
|
+
durationMs: performance.now() - startTime
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
checkHealth() {
|
|
168
|
+
return Promise.resolve({
|
|
169
|
+
healthy: !this.closed,
|
|
170
|
+
latencyMs: 0,
|
|
171
|
+
lastCheck: now(),
|
|
172
|
+
error: this.closed ? "Store is closed" : void 0
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
close() {
|
|
176
|
+
this.closed = true;
|
|
177
|
+
this.cache.clear();
|
|
178
|
+
this.vectors.clear();
|
|
179
|
+
return Promise.resolve();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Compute cosine similarity between two vectors
|
|
183
|
+
*/
|
|
184
|
+
cosineSimilarity(a, b) {
|
|
185
|
+
if (a.length !== b.length) return 0;
|
|
186
|
+
let dotProduct = 0;
|
|
187
|
+
let normA = 0;
|
|
188
|
+
let normB = 0;
|
|
189
|
+
for (let i = 0; i < a.length; i++) {
|
|
190
|
+
dotProduct += a[i] * b[i];
|
|
191
|
+
normA += a[i] * a[i];
|
|
192
|
+
normB += b[i] * b[i];
|
|
193
|
+
}
|
|
194
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
195
|
+
if (denominator === 0) return 0;
|
|
196
|
+
return dotProduct / denominator;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get memory usage information
|
|
200
|
+
*/
|
|
201
|
+
getMemoryInfo() {
|
|
202
|
+
return {
|
|
203
|
+
entries: this.cache.size,
|
|
204
|
+
calculatedSize: this.cache.calculatedSize ?? 0,
|
|
205
|
+
maxSize: this.memoryConfig.maxSizeBytes ?? 0,
|
|
206
|
+
vectorCount: this.vectors.size
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Prune expired entries
|
|
211
|
+
*/
|
|
212
|
+
prune() {
|
|
213
|
+
this.cache.purgeStale();
|
|
214
|
+
let pruned = 0;
|
|
215
|
+
for (const key of this.vectors.keys()) {
|
|
216
|
+
if (!this.cache.has(key)) {
|
|
217
|
+
this.vectors.delete(key);
|
|
218
|
+
pruned++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return Promise.resolve(pruned);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
function createMemoryCacheStore(config) {
|
|
225
|
+
return new MemoryCacheStore({ type: "memory", ...config });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/similarity/metrics/SimilarityMetrics.ts
|
|
229
|
+
function cosineSimilarity(a, b) {
|
|
230
|
+
if (a.length !== b.length) return 0;
|
|
231
|
+
let dotProduct = 0;
|
|
232
|
+
let normA = 0;
|
|
233
|
+
let normB = 0;
|
|
234
|
+
for (let i = 0; i < a.length; i++) {
|
|
235
|
+
dotProduct += a[i] * b[i];
|
|
236
|
+
normA += a[i] * a[i];
|
|
237
|
+
normB += b[i] * b[i];
|
|
238
|
+
}
|
|
239
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
240
|
+
if (denominator === 0) return 0;
|
|
241
|
+
return dotProduct / denominator;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/stores/RedisCacheStore.ts
|
|
245
|
+
var DEFAULT_CONFIG2 = {
|
|
246
|
+
host: "localhost",
|
|
247
|
+
port: 6379,
|
|
248
|
+
db: 0,
|
|
249
|
+
keyPrefix: "llm-cache",
|
|
250
|
+
connectTimeout: 1e4
|
|
251
|
+
};
|
|
252
|
+
var RedisCacheStore = class extends BaseCacheStore {
|
|
253
|
+
storeType = "redis";
|
|
254
|
+
client = null;
|
|
255
|
+
redisConfig;
|
|
256
|
+
connected = false;
|
|
257
|
+
constructor(config) {
|
|
258
|
+
super(config);
|
|
259
|
+
this.redisConfig = { ...DEFAULT_CONFIG2, ...config };
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Connect to Redis
|
|
263
|
+
*/
|
|
264
|
+
async connect() {
|
|
265
|
+
if (this.connected) return;
|
|
266
|
+
try {
|
|
267
|
+
const { Redis } = await import('ioredis');
|
|
268
|
+
if (this.redisConfig.url) {
|
|
269
|
+
this.client = new Redis(this.redisConfig.url, {
|
|
270
|
+
connectTimeout: this.redisConfig.connectTimeout ?? 1e4,
|
|
271
|
+
lazyConnect: false,
|
|
272
|
+
tls: this.redisConfig.tls ? {} : void 0
|
|
273
|
+
});
|
|
274
|
+
} else {
|
|
275
|
+
this.client = new Redis({
|
|
276
|
+
host: this.redisConfig.host ?? "localhost",
|
|
277
|
+
port: this.redisConfig.port ?? 6379,
|
|
278
|
+
password: this.redisConfig.password,
|
|
279
|
+
db: this.redisConfig.db ?? 0,
|
|
280
|
+
connectTimeout: this.redisConfig.connectTimeout ?? 1e4,
|
|
281
|
+
tls: this.redisConfig.tls ? {} : void 0
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
await this.client.ping();
|
|
285
|
+
this.connected = true;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Failed to connect to Redis: ${error.message}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async ensureConnected() {
|
|
293
|
+
if (!this.connected || !this.client) {
|
|
294
|
+
await this.connect();
|
|
295
|
+
}
|
|
296
|
+
if (!this.client) {
|
|
297
|
+
throw new Error("Redis client not initialized");
|
|
298
|
+
}
|
|
299
|
+
return this.client;
|
|
300
|
+
}
|
|
301
|
+
prefixKey(key) {
|
|
302
|
+
const prefix = this.redisConfig.keyPrefix ?? "llm-cache";
|
|
303
|
+
return `${prefix}:${this.namespace}:${key}`;
|
|
304
|
+
}
|
|
305
|
+
async get(key) {
|
|
306
|
+
this.incrementMetric("gets");
|
|
307
|
+
const client = await this.ensureConnected();
|
|
308
|
+
const data = await client.get(this.prefixKey(key));
|
|
309
|
+
if (!data) {
|
|
310
|
+
this.incrementMetric("misses");
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
this.incrementMetric("hits");
|
|
314
|
+
try {
|
|
315
|
+
const entry = JSON.parse(data);
|
|
316
|
+
entry.metadata.accessedAt = now();
|
|
317
|
+
entry.metadata.accessCount++;
|
|
318
|
+
client.set(this.prefixKey(key), JSON.stringify(entry)).catch(() => {
|
|
319
|
+
});
|
|
320
|
+
return entry;
|
|
321
|
+
} catch {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async set(key, entry) {
|
|
326
|
+
const startTime = performance.now();
|
|
327
|
+
this.incrementMetric("sets");
|
|
328
|
+
const client = await this.ensureConnected();
|
|
329
|
+
await client.set(this.prefixKey(key), JSON.stringify(entry));
|
|
330
|
+
if (entry.metadata.ttl > 0) {
|
|
331
|
+
await client.expire(this.prefixKey(key), entry.metadata.ttl);
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
success: true,
|
|
335
|
+
id: entry.id,
|
|
336
|
+
durationMs: performance.now() - startTime
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async has(key) {
|
|
340
|
+
const client = await this.ensureConnected();
|
|
341
|
+
return await client.exists(this.prefixKey(key)) > 0;
|
|
342
|
+
}
|
|
343
|
+
async delete(key) {
|
|
344
|
+
this.incrementMetric("deletes");
|
|
345
|
+
const client = await this.ensureConnected();
|
|
346
|
+
return await client.del(this.prefixKey(key)) > 0;
|
|
347
|
+
}
|
|
348
|
+
async clear() {
|
|
349
|
+
const client = await this.ensureConnected();
|
|
350
|
+
const pattern = this.prefixKey("*");
|
|
351
|
+
const keys = await client.keys(pattern);
|
|
352
|
+
if (keys.length > 0) {
|
|
353
|
+
await client.del(...keys);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async size() {
|
|
357
|
+
const client = await this.ensureConnected();
|
|
358
|
+
const keys = await client.keys(this.prefixKey("*"));
|
|
359
|
+
return keys.length;
|
|
360
|
+
}
|
|
361
|
+
async keys() {
|
|
362
|
+
const client = await this.ensureConnected();
|
|
363
|
+
const keys = await client.keys(this.prefixKey("*"));
|
|
364
|
+
const prefix = this.prefixKey("");
|
|
365
|
+
return keys.map((k) => k.slice(prefix.length));
|
|
366
|
+
}
|
|
367
|
+
async query(vector, options) {
|
|
368
|
+
const startTime = performance.now();
|
|
369
|
+
const allKeys = await this.keys();
|
|
370
|
+
const entries = [];
|
|
371
|
+
const keysToProcess = allKeys.slice(0, 1e3);
|
|
372
|
+
for (const key of keysToProcess) {
|
|
373
|
+
const entry = await this.get(key);
|
|
374
|
+
if (entry?.embedding) {
|
|
375
|
+
const score = cosineSimilarity(vector, entry.embedding);
|
|
376
|
+
if (score >= (options?.minSimilarity ?? 0)) {
|
|
377
|
+
if (options?.namespace && entry.metadata.namespace !== options.namespace) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
entries.push({ ...entry, score });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
entries.sort((a, b) => b.score - a.score);
|
|
385
|
+
return {
|
|
386
|
+
entries: entries.slice(0, options?.topK ?? 10),
|
|
387
|
+
durationMs: performance.now() - startTime
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async checkHealth() {
|
|
391
|
+
const startTime = performance.now();
|
|
392
|
+
try {
|
|
393
|
+
const client = await this.ensureConnected();
|
|
394
|
+
await client.ping();
|
|
395
|
+
return {
|
|
396
|
+
healthy: true,
|
|
397
|
+
latencyMs: performance.now() - startTime,
|
|
398
|
+
lastCheck: now()
|
|
399
|
+
};
|
|
400
|
+
} catch (error) {
|
|
401
|
+
return {
|
|
402
|
+
healthy: false,
|
|
403
|
+
latencyMs: performance.now() - startTime,
|
|
404
|
+
lastCheck: now(),
|
|
405
|
+
error: error.message
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async close() {
|
|
410
|
+
if (this.client) {
|
|
411
|
+
await this.client.quit();
|
|
412
|
+
this.client = null;
|
|
413
|
+
this.connected = false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Check if connected to Redis
|
|
418
|
+
*/
|
|
419
|
+
isConnected() {
|
|
420
|
+
return this.connected;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
function createRedisCacheStore(config) {
|
|
424
|
+
return new RedisCacheStore(config);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/stores/SQLiteCacheStore.ts
|
|
428
|
+
var DEFAULT_CONFIG3 = {
|
|
429
|
+
dbPath: "cache.db",
|
|
430
|
+
inMemory: false,
|
|
431
|
+
enableVector: false
|
|
432
|
+
};
|
|
433
|
+
var SQLiteCacheStore = class extends BaseCacheStore {
|
|
434
|
+
storeType = "sqlite";
|
|
435
|
+
db = null;
|
|
436
|
+
sqliteConfig;
|
|
437
|
+
initialized = false;
|
|
438
|
+
constructor(config) {
|
|
439
|
+
super(config);
|
|
440
|
+
this.sqliteConfig = { ...DEFAULT_CONFIG3, ...config };
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Initialize the database
|
|
444
|
+
*/
|
|
445
|
+
async init() {
|
|
446
|
+
if (this.initialized) return;
|
|
447
|
+
try {
|
|
448
|
+
const BetterSqlite3 = (await import('better-sqlite3')).default;
|
|
449
|
+
const db = new BetterSqlite3(
|
|
450
|
+
this.sqliteConfig.inMemory ? ":memory:" : this.sqliteConfig.dbPath ?? "cache.db"
|
|
451
|
+
);
|
|
452
|
+
this.db = db;
|
|
453
|
+
db.exec(`
|
|
454
|
+
CREATE TABLE IF NOT EXISTS cache_entries (
|
|
455
|
+
key TEXT PRIMARY KEY,
|
|
456
|
+
id TEXT NOT NULL,
|
|
457
|
+
data TEXT NOT NULL,
|
|
458
|
+
embedding BLOB,
|
|
459
|
+
model TEXT NOT NULL,
|
|
460
|
+
namespace TEXT,
|
|
461
|
+
created_at INTEGER NOT NULL,
|
|
462
|
+
accessed_at INTEGER NOT NULL,
|
|
463
|
+
ttl INTEGER DEFAULT 0
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
CREATE INDEX IF NOT EXISTS idx_namespace ON cache_entries(namespace);
|
|
467
|
+
CREATE INDEX IF NOT EXISTS idx_model ON cache_entries(model);
|
|
468
|
+
CREATE INDEX IF NOT EXISTS idx_created_at ON cache_entries(created_at);
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_accessed_at ON cache_entries(accessed_at);
|
|
470
|
+
`);
|
|
471
|
+
this.initialized = true;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Failed to initialize SQLite database: ${error.message}`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
ensureInitialized() {
|
|
479
|
+
if (!this.initialized || !this.db) {
|
|
480
|
+
throw new Error("SQLite store not initialized. Call init() first.");
|
|
481
|
+
}
|
|
482
|
+
return this.db;
|
|
483
|
+
}
|
|
484
|
+
get(key) {
|
|
485
|
+
this.incrementMetric("gets");
|
|
486
|
+
const db = this.ensureInitialized();
|
|
487
|
+
const row = db.prepare("SELECT data FROM cache_entries WHERE key = ?").get(key);
|
|
488
|
+
if (!row) {
|
|
489
|
+
this.incrementMetric("misses");
|
|
490
|
+
return Promise.resolve(void 0);
|
|
491
|
+
}
|
|
492
|
+
this.incrementMetric("hits");
|
|
493
|
+
try {
|
|
494
|
+
const entry = JSON.parse(row.data);
|
|
495
|
+
entry.metadata.accessedAt = now();
|
|
496
|
+
entry.metadata.accessCount++;
|
|
497
|
+
db.prepare(
|
|
498
|
+
"UPDATE cache_entries SET accessed_at = ?, data = ? WHERE key = ?"
|
|
499
|
+
).run(now(), JSON.stringify(entry), key);
|
|
500
|
+
return Promise.resolve(entry);
|
|
501
|
+
} catch {
|
|
502
|
+
return Promise.resolve(void 0);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
set(key, entry) {
|
|
506
|
+
const startTime = performance.now();
|
|
507
|
+
this.incrementMetric("sets");
|
|
508
|
+
const db = this.ensureInitialized();
|
|
509
|
+
const embedding = entry.embedding ? Buffer.from(new Float32Array(entry.embedding).buffer) : null;
|
|
510
|
+
db.prepare(
|
|
511
|
+
`
|
|
512
|
+
INSERT OR REPLACE INTO cache_entries
|
|
513
|
+
(key, id, data, embedding, model, namespace, created_at, accessed_at, ttl)
|
|
514
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
515
|
+
`
|
|
516
|
+
).run(
|
|
517
|
+
key,
|
|
518
|
+
entry.id,
|
|
519
|
+
JSON.stringify(entry),
|
|
520
|
+
embedding,
|
|
521
|
+
entry.request.model,
|
|
522
|
+
entry.metadata.namespace ?? null,
|
|
523
|
+
entry.metadata.createdAt,
|
|
524
|
+
entry.metadata.accessedAt,
|
|
525
|
+
entry.metadata.ttl
|
|
526
|
+
);
|
|
527
|
+
return Promise.resolve({
|
|
528
|
+
success: true,
|
|
529
|
+
id: entry.id,
|
|
530
|
+
durationMs: performance.now() - startTime
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
has(key) {
|
|
534
|
+
const db = this.ensureInitialized();
|
|
535
|
+
const row = db.prepare("SELECT 1 FROM cache_entries WHERE key = ?").get(key);
|
|
536
|
+
return Promise.resolve(!!row);
|
|
537
|
+
}
|
|
538
|
+
delete(key) {
|
|
539
|
+
this.incrementMetric("deletes");
|
|
540
|
+
const db = this.ensureInitialized();
|
|
541
|
+
const result = db.prepare("DELETE FROM cache_entries WHERE key = ?").run(key);
|
|
542
|
+
return Promise.resolve(result.changes > 0);
|
|
543
|
+
}
|
|
544
|
+
clear() {
|
|
545
|
+
const db = this.ensureInitialized();
|
|
546
|
+
if (this.namespace === "default") {
|
|
547
|
+
db.prepare("DELETE FROM cache_entries").run();
|
|
548
|
+
} else {
|
|
549
|
+
db.prepare("DELETE FROM cache_entries WHERE namespace = ?").run(
|
|
550
|
+
this.namespace
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
return Promise.resolve();
|
|
554
|
+
}
|
|
555
|
+
size() {
|
|
556
|
+
const db = this.ensureInitialized();
|
|
557
|
+
const row = db.prepare("SELECT COUNT(*) as count FROM cache_entries").get();
|
|
558
|
+
return Promise.resolve(row.count);
|
|
559
|
+
}
|
|
560
|
+
keys() {
|
|
561
|
+
const db = this.ensureInitialized();
|
|
562
|
+
const rows = db.prepare("SELECT key FROM cache_entries").all();
|
|
563
|
+
return Promise.resolve(rows.map((r) => r.key));
|
|
564
|
+
}
|
|
565
|
+
query(vector, options) {
|
|
566
|
+
const startTime = performance.now();
|
|
567
|
+
const db = this.ensureInitialized();
|
|
568
|
+
let sql = `
|
|
569
|
+
SELECT key, data, embedding FROM cache_entries
|
|
570
|
+
WHERE embedding IS NOT NULL
|
|
571
|
+
`;
|
|
572
|
+
const params = [];
|
|
573
|
+
if (options?.namespace) {
|
|
574
|
+
sql += " AND namespace = ?";
|
|
575
|
+
params.push(options.namespace);
|
|
576
|
+
}
|
|
577
|
+
const rows = db.prepare(sql).all(...params);
|
|
578
|
+
const results = [];
|
|
579
|
+
for (const row of rows) {
|
|
580
|
+
const stored = new Float32Array(
|
|
581
|
+
row.embedding.buffer,
|
|
582
|
+
row.embedding.byteOffset,
|
|
583
|
+
row.embedding.length / 4
|
|
584
|
+
);
|
|
585
|
+
const similarity = cosineSimilarity(vector, Array.from(stored));
|
|
586
|
+
if (similarity >= (options?.minSimilarity ?? 0)) {
|
|
587
|
+
try {
|
|
588
|
+
const entry = JSON.parse(row.data);
|
|
589
|
+
results.push({ ...entry, score: similarity });
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
results.sort((a, b) => b.score - a.score);
|
|
595
|
+
return Promise.resolve({
|
|
596
|
+
entries: results.slice(0, options?.topK ?? 10),
|
|
597
|
+
durationMs: performance.now() - startTime
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
checkHealth() {
|
|
601
|
+
const startTime = performance.now();
|
|
602
|
+
try {
|
|
603
|
+
this.ensureInitialized();
|
|
604
|
+
return Promise.resolve({
|
|
605
|
+
healthy: true,
|
|
606
|
+
latencyMs: performance.now() - startTime,
|
|
607
|
+
lastCheck: now()
|
|
608
|
+
});
|
|
609
|
+
} catch (error) {
|
|
610
|
+
return Promise.resolve({
|
|
611
|
+
healthy: false,
|
|
612
|
+
latencyMs: performance.now() - startTime,
|
|
613
|
+
lastCheck: now(),
|
|
614
|
+
error: error.message
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
close() {
|
|
619
|
+
if (this.db) {
|
|
620
|
+
this.db.close();
|
|
621
|
+
this.db = null;
|
|
622
|
+
this.initialized = false;
|
|
623
|
+
}
|
|
624
|
+
return Promise.resolve();
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Prune expired entries
|
|
628
|
+
*/
|
|
629
|
+
pruneExpired() {
|
|
630
|
+
const db = this.ensureInitialized();
|
|
631
|
+
const currentTime = now();
|
|
632
|
+
const result = db.prepare(
|
|
633
|
+
`
|
|
634
|
+
DELETE FROM cache_entries
|
|
635
|
+
WHERE ttl > 0 AND (created_at + (ttl * 1000)) < ?
|
|
636
|
+
`
|
|
637
|
+
).run(currentTime);
|
|
638
|
+
return Promise.resolve(result.changes);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Get database file size (for non-memory databases)
|
|
642
|
+
*/
|
|
643
|
+
async getDbSize() {
|
|
644
|
+
if (this.sqliteConfig.inMemory) return null;
|
|
645
|
+
try {
|
|
646
|
+
const { statSync } = await import('fs');
|
|
647
|
+
const stats = statSync(this.sqliteConfig.dbPath ?? "cache.db");
|
|
648
|
+
return stats.size;
|
|
649
|
+
} catch {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Check if database is initialized
|
|
655
|
+
*/
|
|
656
|
+
isInitialized() {
|
|
657
|
+
return this.initialized;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
function createSQLiteCacheStore(config) {
|
|
661
|
+
return new SQLiteCacheStore(config);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/stores/TieredCacheStore.ts
|
|
665
|
+
var TieredCacheStore = class extends BaseCacheStore {
|
|
666
|
+
storeType = "tiered";
|
|
667
|
+
tiers;
|
|
668
|
+
accessCounts = /* @__PURE__ */ new Map();
|
|
669
|
+
constructor(config) {
|
|
670
|
+
super(config);
|
|
671
|
+
const validTiers = config.tiers.filter(
|
|
672
|
+
(t) => t.store !== void 0
|
|
673
|
+
);
|
|
674
|
+
if (validTiers.length === 0) {
|
|
675
|
+
throw new Error(
|
|
676
|
+
"TieredCacheStore requires at least one tier with a store"
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
this.tiers = validTiers.sort((a, b) => a.priority - b.priority);
|
|
680
|
+
}
|
|
681
|
+
async get(key) {
|
|
682
|
+
this.incrementMetric("gets");
|
|
683
|
+
for (let i = 0; i < this.tiers.length; i++) {
|
|
684
|
+
const tier = this.tiers[i];
|
|
685
|
+
const entry = await tier.store.get(key);
|
|
686
|
+
if (entry) {
|
|
687
|
+
this.incrementMetric("hits");
|
|
688
|
+
const accessCount = (this.accessCounts.get(key) ?? 0) + 1;
|
|
689
|
+
this.accessCounts.set(key, accessCount);
|
|
690
|
+
if (i > 0) {
|
|
691
|
+
await this.checkPromotion(key, entry, i, accessCount);
|
|
692
|
+
}
|
|
693
|
+
return entry;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
this.incrementMetric("misses");
|
|
697
|
+
return void 0;
|
|
698
|
+
}
|
|
699
|
+
async set(key, entry) {
|
|
700
|
+
const startTime = performance.now();
|
|
701
|
+
this.incrementMetric("sets");
|
|
702
|
+
const result = await this.tiers[0].store.set(key, entry);
|
|
703
|
+
this.accessCounts.set(key, 0);
|
|
704
|
+
await this.checkDemotion(0);
|
|
705
|
+
return {
|
|
706
|
+
...result,
|
|
707
|
+
durationMs: performance.now() - startTime
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
async has(key) {
|
|
711
|
+
for (const tier of this.tiers) {
|
|
712
|
+
if (await tier.store.has(key)) {
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
async delete(key) {
|
|
719
|
+
this.incrementMetric("deletes");
|
|
720
|
+
let deleted = false;
|
|
721
|
+
for (const tier of this.tiers) {
|
|
722
|
+
if (await tier.store.delete(key)) {
|
|
723
|
+
deleted = true;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
this.accessCounts.delete(key);
|
|
727
|
+
return deleted;
|
|
728
|
+
}
|
|
729
|
+
async clear() {
|
|
730
|
+
for (const tier of this.tiers) {
|
|
731
|
+
await tier.store.clear();
|
|
732
|
+
}
|
|
733
|
+
this.accessCounts.clear();
|
|
734
|
+
}
|
|
735
|
+
async size() {
|
|
736
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
737
|
+
for (const tier of this.tiers) {
|
|
738
|
+
const keys = await tier.store.keys();
|
|
739
|
+
keys.forEach((k) => allKeys.add(k));
|
|
740
|
+
}
|
|
741
|
+
return allKeys.size;
|
|
742
|
+
}
|
|
743
|
+
async keys() {
|
|
744
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
745
|
+
for (const tier of this.tiers) {
|
|
746
|
+
const keys = await tier.store.keys();
|
|
747
|
+
keys.forEach((k) => allKeys.add(k));
|
|
748
|
+
}
|
|
749
|
+
return Array.from(allKeys);
|
|
750
|
+
}
|
|
751
|
+
async query(vector, options) {
|
|
752
|
+
const startTime = performance.now();
|
|
753
|
+
const entriesMap = /* @__PURE__ */ new Map();
|
|
754
|
+
for (const tier of this.tiers) {
|
|
755
|
+
const result = await tier.store.query(vector, options);
|
|
756
|
+
for (const entry of result.entries) {
|
|
757
|
+
const existing = entriesMap.get(entry.key);
|
|
758
|
+
if (!existing || entry.score > existing.score) {
|
|
759
|
+
entriesMap.set(entry.key, entry);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const entries = Array.from(entriesMap.values()).sort((a, b) => b.score - a.score).slice(0, options?.topK ?? 10);
|
|
764
|
+
return {
|
|
765
|
+
entries,
|
|
766
|
+
durationMs: performance.now() - startTime
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
async checkHealth() {
|
|
770
|
+
const startTime = performance.now();
|
|
771
|
+
const tierHealths = [];
|
|
772
|
+
for (const tier of this.tiers) {
|
|
773
|
+
const health = await tier.store.checkHealth();
|
|
774
|
+
tierHealths.push({ name: tier.name, healthy: health.healthy });
|
|
775
|
+
}
|
|
776
|
+
const allHealthy = tierHealths.every((t) => t.healthy);
|
|
777
|
+
return {
|
|
778
|
+
healthy: allHealthy,
|
|
779
|
+
latencyMs: performance.now() - startTime,
|
|
780
|
+
lastCheck: now(),
|
|
781
|
+
error: allHealthy ? void 0 : `Unhealthy tiers: ${tierHealths.filter((t) => !t.healthy).map((t) => t.name).join(", ")}`
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
async close() {
|
|
785
|
+
for (const tier of this.tiers) {
|
|
786
|
+
await tier.store.close();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Get tier statistics
|
|
791
|
+
*/
|
|
792
|
+
async getTierStats() {
|
|
793
|
+
const stats = [];
|
|
794
|
+
for (const tier of this.tiers) {
|
|
795
|
+
stats.push({
|
|
796
|
+
name: tier.name,
|
|
797
|
+
priority: tier.priority,
|
|
798
|
+
size: await tier.store.size(),
|
|
799
|
+
maxSize: tier.maxSize
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return stats;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Manually promote an entry to a higher tier
|
|
806
|
+
*/
|
|
807
|
+
async promote(key, targetTierIndex = 0) {
|
|
808
|
+
for (let i = targetTierIndex + 1; i < this.tiers.length; i++) {
|
|
809
|
+
const entry = await this.tiers[i].store.get(key);
|
|
810
|
+
if (entry) {
|
|
811
|
+
await this.tiers[targetTierIndex].store.set(key, entry);
|
|
812
|
+
await this.tiers[i].store.delete(key);
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Manually demote an entry to a lower tier
|
|
820
|
+
*/
|
|
821
|
+
async demote(key, targetTierIndex) {
|
|
822
|
+
for (let i = 0; i < this.tiers.length - 1; i++) {
|
|
823
|
+
const entry = await this.tiers[i].store.get(key);
|
|
824
|
+
if (entry) {
|
|
825
|
+
const target = targetTierIndex ?? i + 1;
|
|
826
|
+
if (target >= this.tiers.length) return false;
|
|
827
|
+
await this.tiers[target].store.set(key, entry);
|
|
828
|
+
await this.tiers[i].store.delete(key);
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
async checkPromotion(key, entry, currentTierIndex, accessCount) {
|
|
835
|
+
for (let i = currentTierIndex - 1; i >= 0; i--) {
|
|
836
|
+
const tier = this.tiers[i];
|
|
837
|
+
const threshold = tier.promotionThreshold ?? 3;
|
|
838
|
+
if (accessCount >= threshold) {
|
|
839
|
+
await tier.store.set(key, entry);
|
|
840
|
+
await this.tiers[currentTierIndex].store.delete(key);
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async checkDemotion(tierIndex) {
|
|
846
|
+
const tier = this.tiers[tierIndex];
|
|
847
|
+
if (!tier.maxSize) return;
|
|
848
|
+
const size = await tier.store.size();
|
|
849
|
+
if (size <= tier.maxSize) return;
|
|
850
|
+
const demotionTarget = tier.demotionTarget ?? 0.9;
|
|
851
|
+
const targetSize = Math.floor(tier.maxSize * demotionTarget);
|
|
852
|
+
const toRemove = size - targetSize;
|
|
853
|
+
if (toRemove <= 0) return;
|
|
854
|
+
const keys = await tier.store.keys();
|
|
855
|
+
const keysByAccess = keys.map((k) => ({ key: k, count: this.accessCounts.get(k) ?? 0 })).sort((a, b) => a.count - b.count);
|
|
856
|
+
const nextTierIndex = tierIndex + 1;
|
|
857
|
+
if (nextTierIndex >= this.tiers.length) {
|
|
858
|
+
for (let i = 0; i < toRemove && i < keysByAccess.length; i++) {
|
|
859
|
+
await tier.store.delete(keysByAccess[i].key);
|
|
860
|
+
this.accessCounts.delete(keysByAccess[i].key);
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
for (let i = 0; i < toRemove && i < keysByAccess.length; i++) {
|
|
864
|
+
const key = keysByAccess[i].key;
|
|
865
|
+
const entry = await tier.store.get(key);
|
|
866
|
+
if (entry) {
|
|
867
|
+
await this.tiers[nextTierIndex].store.set(key, entry);
|
|
868
|
+
await tier.store.delete(key);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
function createTieredCacheStore(config) {
|
|
875
|
+
return new TieredCacheStore(config);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/stores/PineconeCacheStore.ts
|
|
879
|
+
var PineconeCacheStore = class extends BaseCacheStore {
|
|
880
|
+
storeType = "pinecone";
|
|
881
|
+
client = null;
|
|
882
|
+
index = null;
|
|
883
|
+
ns = null;
|
|
884
|
+
pineconeConfig;
|
|
885
|
+
connected = false;
|
|
886
|
+
constructor(config) {
|
|
887
|
+
super(config);
|
|
888
|
+
this.pineconeConfig = config;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Connect to Pinecone
|
|
892
|
+
*/
|
|
893
|
+
async connect() {
|
|
894
|
+
if (this.connected) return;
|
|
895
|
+
try {
|
|
896
|
+
const { Pinecone } = await import('@pinecone-database/pinecone');
|
|
897
|
+
this.client = new Pinecone({
|
|
898
|
+
apiKey: this.pineconeConfig.apiKey
|
|
899
|
+
});
|
|
900
|
+
this.index = this.client.Index(this.pineconeConfig.index);
|
|
901
|
+
this.ns = this.index.namespace(this.namespace);
|
|
902
|
+
this.connected = true;
|
|
903
|
+
} catch (error) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
`Failed to connect to Pinecone: ${error.message}`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
async ensureConnected() {
|
|
910
|
+
if (!this.connected || !this.ns) {
|
|
911
|
+
await this.connect();
|
|
912
|
+
}
|
|
913
|
+
if (!this.ns) {
|
|
914
|
+
throw new Error("Pinecone namespace not initialized");
|
|
915
|
+
}
|
|
916
|
+
return this.ns;
|
|
917
|
+
}
|
|
918
|
+
async get(key) {
|
|
919
|
+
this.incrementMetric("gets");
|
|
920
|
+
const ns = await this.ensureConnected();
|
|
921
|
+
try {
|
|
922
|
+
const result = await ns.fetch([key]);
|
|
923
|
+
if (!result.records[key]) {
|
|
924
|
+
this.incrementMetric("misses");
|
|
925
|
+
return void 0;
|
|
926
|
+
}
|
|
927
|
+
this.incrementMetric("hits");
|
|
928
|
+
const record = result.records[key];
|
|
929
|
+
const metadata = record.metadata;
|
|
930
|
+
const entry = JSON.parse(metadata.entryData);
|
|
931
|
+
entry.metadata.accessedAt = now();
|
|
932
|
+
entry.metadata.accessCount++;
|
|
933
|
+
this.updateAccessMetadata(key, record.values, metadata).catch(() => {
|
|
934
|
+
});
|
|
935
|
+
return entry;
|
|
936
|
+
} catch {
|
|
937
|
+
this.incrementMetric("misses");
|
|
938
|
+
return void 0;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async updateAccessMetadata(key, values, metadata) {
|
|
942
|
+
const ns = await this.ensureConnected();
|
|
943
|
+
const updatedEntry = JSON.parse(metadata.entryData);
|
|
944
|
+
updatedEntry.metadata.accessedAt = now();
|
|
945
|
+
updatedEntry.metadata.accessCount++;
|
|
946
|
+
await ns.upsert([
|
|
947
|
+
{
|
|
948
|
+
id: key,
|
|
949
|
+
values,
|
|
950
|
+
metadata: {
|
|
951
|
+
...metadata,
|
|
952
|
+
accessedAt: now(),
|
|
953
|
+
accessCount: metadata.accessCount + 1,
|
|
954
|
+
entryData: JSON.stringify(updatedEntry)
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
]);
|
|
958
|
+
}
|
|
959
|
+
async set(key, entry) {
|
|
960
|
+
const startTime = performance.now();
|
|
961
|
+
this.incrementMetric("sets");
|
|
962
|
+
const ns = await this.ensureConnected();
|
|
963
|
+
if (!entry.embedding || entry.embedding.length === 0) {
|
|
964
|
+
return {
|
|
965
|
+
success: false,
|
|
966
|
+
id: entry.id,
|
|
967
|
+
durationMs: performance.now() - startTime
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
const metadata = {
|
|
971
|
+
key,
|
|
972
|
+
model: entry.request.model,
|
|
973
|
+
content: entry.response.content.substring(0, 3e4),
|
|
974
|
+
// Pinecone metadata limit
|
|
975
|
+
createdAt: entry.metadata.createdAt,
|
|
976
|
+
accessedAt: entry.metadata.accessedAt,
|
|
977
|
+
accessCount: entry.metadata.accessCount,
|
|
978
|
+
hitCount: entry.metadata.hitCount,
|
|
979
|
+
ttl: entry.metadata.ttl,
|
|
980
|
+
namespace: entry.metadata.namespace ?? this.namespace,
|
|
981
|
+
tags: entry.metadata.tags ?? [],
|
|
982
|
+
entryData: JSON.stringify(entry)
|
|
983
|
+
};
|
|
984
|
+
try {
|
|
985
|
+
await ns.upsert([
|
|
986
|
+
{
|
|
987
|
+
id: key,
|
|
988
|
+
values: entry.embedding,
|
|
989
|
+
metadata
|
|
990
|
+
}
|
|
991
|
+
]);
|
|
992
|
+
return {
|
|
993
|
+
success: true,
|
|
994
|
+
id: entry.id,
|
|
995
|
+
durationMs: performance.now() - startTime
|
|
996
|
+
};
|
|
997
|
+
} catch (error) {
|
|
998
|
+
return {
|
|
999
|
+
success: false,
|
|
1000
|
+
id: entry.id,
|
|
1001
|
+
durationMs: performance.now() - startTime
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
async has(key) {
|
|
1006
|
+
const ns = await this.ensureConnected();
|
|
1007
|
+
try {
|
|
1008
|
+
const result = await ns.fetch([key]);
|
|
1009
|
+
return !!result.records[key];
|
|
1010
|
+
} catch {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async delete(key) {
|
|
1015
|
+
this.incrementMetric("deletes");
|
|
1016
|
+
const ns = await this.ensureConnected();
|
|
1017
|
+
try {
|
|
1018
|
+
await ns.deleteOne(key);
|
|
1019
|
+
return true;
|
|
1020
|
+
} catch {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
async clear() {
|
|
1025
|
+
const ns = await this.ensureConnected();
|
|
1026
|
+
await ns.deleteAll();
|
|
1027
|
+
}
|
|
1028
|
+
async size() {
|
|
1029
|
+
if (!this.index) {
|
|
1030
|
+
await this.connect();
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
const stats = await this.index.describeIndexStats();
|
|
1034
|
+
return stats.namespaces[this.namespace]?.recordCount ?? 0;
|
|
1035
|
+
} catch {
|
|
1036
|
+
return 0;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
async keys() {
|
|
1040
|
+
const ns = await this.ensureConnected();
|
|
1041
|
+
try {
|
|
1042
|
+
const result = await ns.listPaginated({ limit: 1e4 });
|
|
1043
|
+
return result.vectors.map((v) => v.id);
|
|
1044
|
+
} catch {
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async query(vector, options) {
|
|
1049
|
+
const startTime = performance.now();
|
|
1050
|
+
const ns = await this.ensureConnected();
|
|
1051
|
+
const queryOptions = {
|
|
1052
|
+
vector,
|
|
1053
|
+
topK: options?.topK ?? 10,
|
|
1054
|
+
includeMetadata: true,
|
|
1055
|
+
includeValues: options?.includeEmbedding ?? false
|
|
1056
|
+
};
|
|
1057
|
+
if (options?.filter) {
|
|
1058
|
+
queryOptions.filter = options.filter;
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const result = await ns.query(queryOptions);
|
|
1062
|
+
const entries = [];
|
|
1063
|
+
for (const match of result.matches) {
|
|
1064
|
+
if (options?.minSimilarity && match.score < options.minSimilarity) {
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
const metadata = match.metadata;
|
|
1068
|
+
if (metadata?.entryData) {
|
|
1069
|
+
try {
|
|
1070
|
+
const entry = JSON.parse(metadata.entryData);
|
|
1071
|
+
if (options?.includeEmbedding && match.values) {
|
|
1072
|
+
entry.embedding = match.values;
|
|
1073
|
+
}
|
|
1074
|
+
entries.push({
|
|
1075
|
+
...entry,
|
|
1076
|
+
score: match.score
|
|
1077
|
+
});
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
entries,
|
|
1084
|
+
durationMs: performance.now() - startTime
|
|
1085
|
+
};
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
return {
|
|
1088
|
+
entries: [],
|
|
1089
|
+
durationMs: performance.now() - startTime
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
async checkHealth() {
|
|
1094
|
+
const startTime = performance.now();
|
|
1095
|
+
try {
|
|
1096
|
+
if (!this.index) {
|
|
1097
|
+
await this.connect();
|
|
1098
|
+
}
|
|
1099
|
+
await this.index.describeIndexStats();
|
|
1100
|
+
return {
|
|
1101
|
+
healthy: true,
|
|
1102
|
+
latencyMs: performance.now() - startTime,
|
|
1103
|
+
lastCheck: now()
|
|
1104
|
+
};
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
return {
|
|
1107
|
+
healthy: false,
|
|
1108
|
+
latencyMs: performance.now() - startTime,
|
|
1109
|
+
lastCheck: now(),
|
|
1110
|
+
error: error.message
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
close() {
|
|
1115
|
+
this.client = null;
|
|
1116
|
+
this.index = null;
|
|
1117
|
+
this.ns = null;
|
|
1118
|
+
this.connected = false;
|
|
1119
|
+
return Promise.resolve();
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Check if connected to Pinecone
|
|
1123
|
+
*/
|
|
1124
|
+
isConnected() {
|
|
1125
|
+
return this.connected;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get index stats
|
|
1129
|
+
*/
|
|
1130
|
+
async getIndexStats() {
|
|
1131
|
+
if (!this.index) {
|
|
1132
|
+
await this.connect();
|
|
1133
|
+
}
|
|
1134
|
+
try {
|
|
1135
|
+
return await this.index.describeIndexStats();
|
|
1136
|
+
} catch {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
function createPineconeCacheStore(config) {
|
|
1142
|
+
return new PineconeCacheStore(config);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export { BaseCacheStore, MemoryCacheStore, PineconeCacheStore, RedisCacheStore, SQLiteCacheStore, TieredCacheStore, createMemoryCacheStore, createPineconeCacheStore, createRedisCacheStore, createSQLiteCacheStore, createTieredCacheStore };
|
|
1146
|
+
//# sourceMappingURL=index.js.map
|
|
1147
|
+
//# sourceMappingURL=index.js.map
|