agentic-flow 2.0.1-alpha.16 → 2.0.1-alpha.18
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/CHANGELOG.md +80 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/intelligence/EmbeddingCache.d.ts +105 -0
- package/dist/intelligence/EmbeddingCache.d.ts.map +1 -0
- package/dist/intelligence/EmbeddingCache.js +253 -0
- package/dist/intelligence/EmbeddingCache.js.map +1 -0
- package/dist/intelligence/EmbeddingService.d.ts +31 -1
- package/dist/intelligence/EmbeddingService.d.ts.map +1 -1
- package/dist/intelligence/EmbeddingService.js +86 -7
- package/dist/intelligence/EmbeddingService.js.map +1 -1
- package/dist/mcp/fastmcp/tools/hooks/intelligence-bridge.d.ts +121 -0
- package/dist/mcp/fastmcp/tools/hooks/intelligence-bridge.d.ts.map +1 -1
- package/dist/mcp/fastmcp/tools/hooks/intelligence-bridge.js +215 -0
- package/dist/mcp/fastmcp/tools/hooks/intelligence-bridge.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingCache - Persistent SQLite cache for embeddings
|
|
3
|
+
*
|
|
4
|
+
* Makes ONNX embeddings practical by caching across sessions:
|
|
5
|
+
* - First embed: ~400ms (ONNX inference)
|
|
6
|
+
* - Cached embed: ~0.1ms (SQLite lookup)
|
|
7
|
+
*
|
|
8
|
+
* Storage: ~/.agentic-flow/embedding-cache.db
|
|
9
|
+
*/
|
|
10
|
+
export interface CacheStats {
|
|
11
|
+
totalEntries: number;
|
|
12
|
+
hits: number;
|
|
13
|
+
misses: number;
|
|
14
|
+
hitRate: number;
|
|
15
|
+
dbSizeBytes: number;
|
|
16
|
+
oldestEntry: number;
|
|
17
|
+
newestEntry: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CacheConfig {
|
|
20
|
+
maxEntries?: number;
|
|
21
|
+
maxAgeDays?: number;
|
|
22
|
+
dbPath?: string;
|
|
23
|
+
dimension?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class EmbeddingCache {
|
|
26
|
+
private db;
|
|
27
|
+
private config;
|
|
28
|
+
private hits;
|
|
29
|
+
private misses;
|
|
30
|
+
private stmtGet;
|
|
31
|
+
private stmtInsert;
|
|
32
|
+
private stmtUpdateHits;
|
|
33
|
+
private stmtCount;
|
|
34
|
+
private stmtEvictOld;
|
|
35
|
+
private stmtEvictLRU;
|
|
36
|
+
constructor(config?: CacheConfig);
|
|
37
|
+
private initSchema;
|
|
38
|
+
private prepareStatements;
|
|
39
|
+
/**
|
|
40
|
+
* Generate hash key for text + model combination
|
|
41
|
+
*/
|
|
42
|
+
private hashKey;
|
|
43
|
+
/**
|
|
44
|
+
* Get embedding from cache
|
|
45
|
+
* Returns null if not found
|
|
46
|
+
*/
|
|
47
|
+
get(text: string, model?: string): Float32Array | null;
|
|
48
|
+
/**
|
|
49
|
+
* Store embedding in cache
|
|
50
|
+
*/
|
|
51
|
+
set(text: string, embedding: Float32Array, model?: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Check if text is cached
|
|
54
|
+
*/
|
|
55
|
+
has(text: string, model?: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Get multiple embeddings at once
|
|
58
|
+
* Returns map of text -> embedding (only cached ones)
|
|
59
|
+
*/
|
|
60
|
+
getMany(texts: string[], model?: string): Map<string, Float32Array>;
|
|
61
|
+
/**
|
|
62
|
+
* Store multiple embeddings at once
|
|
63
|
+
*/
|
|
64
|
+
setMany(entries: Array<{
|
|
65
|
+
text: string;
|
|
66
|
+
embedding: Float32Array;
|
|
67
|
+
}>, model?: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Evict old or LRU entries if over limit
|
|
70
|
+
*/
|
|
71
|
+
private maybeEvict;
|
|
72
|
+
/**
|
|
73
|
+
* Cleanup entries older than maxAgeDays
|
|
74
|
+
*/
|
|
75
|
+
private cleanupOldEntries;
|
|
76
|
+
/**
|
|
77
|
+
* Get cache statistics
|
|
78
|
+
*/
|
|
79
|
+
getStats(): CacheStats;
|
|
80
|
+
/**
|
|
81
|
+
* Clear all cached embeddings
|
|
82
|
+
*/
|
|
83
|
+
clear(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Clear embeddings for a specific model
|
|
86
|
+
*/
|
|
87
|
+
clearModel(model: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Vacuum database to reclaim space
|
|
90
|
+
*/
|
|
91
|
+
vacuum(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Close database connection
|
|
94
|
+
*/
|
|
95
|
+
close(): void;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get the singleton embedding cache
|
|
99
|
+
*/
|
|
100
|
+
export declare function getEmbeddingCache(config?: CacheConfig): EmbeddingCache;
|
|
101
|
+
/**
|
|
102
|
+
* Reset the cache singleton (for testing)
|
|
103
|
+
*/
|
|
104
|
+
export declare function resetEmbeddingCache(): void;
|
|
105
|
+
//# sourceMappingURL=EmbeddingCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbeddingCache.d.ts","sourceRoot":"","sources":["../../src/intelligence/EmbeddingCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,MAAM,CAAa;IAG3B,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,UAAU,CAAsB;IACxC,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,YAAY,CAAsB;gBAE9B,MAAM,GAAE,WAAgB;IAoBpC,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,iBAAiB;IA2BzB;;OAEG;IACH,OAAO,CAAC,OAAO;IAIf;;;OAGG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAkB,GAAG,YAAY,GAAG,IAAI;IAgBjE;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,GAAE,MAAkB,GAAG,IAAI;IAa3E;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAkB,GAAG,OAAO;IAMrD;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,GAAE,MAAkB,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IAa9E;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,YAAY,CAAA;KAAE,CAAC,EAAE,KAAK,GAAE,MAAkB,GAAG,IAAI;IAcnG;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,QAAQ,IAAI,UAAU;IA2BtB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAKD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,cAAc,CAKtE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingCache - Persistent SQLite cache for embeddings
|
|
3
|
+
*
|
|
4
|
+
* Makes ONNX embeddings practical by caching across sessions:
|
|
5
|
+
* - First embed: ~400ms (ONNX inference)
|
|
6
|
+
* - Cached embed: ~0.1ms (SQLite lookup)
|
|
7
|
+
*
|
|
8
|
+
* Storage: ~/.agentic-flow/embedding-cache.db
|
|
9
|
+
*/
|
|
10
|
+
import Database from 'better-sqlite3';
|
|
11
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { createHash } from 'crypto';
|
|
15
|
+
// Default config
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
maxEntries: 10000,
|
|
18
|
+
maxAgeDays: 30,
|
|
19
|
+
dbPath: join(homedir(), '.agentic-flow', 'embedding-cache.db'),
|
|
20
|
+
dimension: 384,
|
|
21
|
+
};
|
|
22
|
+
export class EmbeddingCache {
|
|
23
|
+
db;
|
|
24
|
+
config;
|
|
25
|
+
hits = 0;
|
|
26
|
+
misses = 0;
|
|
27
|
+
// Prepared statements for performance
|
|
28
|
+
stmtGet;
|
|
29
|
+
stmtInsert;
|
|
30
|
+
stmtUpdateHits;
|
|
31
|
+
stmtCount;
|
|
32
|
+
stmtEvictOld;
|
|
33
|
+
stmtEvictLRU;
|
|
34
|
+
constructor(config = {}) {
|
|
35
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
36
|
+
// Ensure directory exists
|
|
37
|
+
const dir = join(homedir(), '.agentic-flow');
|
|
38
|
+
if (!existsSync(dir)) {
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
// Open database with WAL mode for better concurrency
|
|
42
|
+
this.db = new Database(this.config.dbPath);
|
|
43
|
+
this.db.pragma('journal_mode = WAL');
|
|
44
|
+
this.db.pragma('synchronous = NORMAL');
|
|
45
|
+
this.db.pragma('cache_size = 10000');
|
|
46
|
+
this.initSchema();
|
|
47
|
+
this.prepareStatements();
|
|
48
|
+
this.cleanupOldEntries();
|
|
49
|
+
}
|
|
50
|
+
initSchema() {
|
|
51
|
+
this.db.exec(`
|
|
52
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
53
|
+
hash TEXT PRIMARY KEY,
|
|
54
|
+
text TEXT NOT NULL,
|
|
55
|
+
embedding BLOB NOT NULL,
|
|
56
|
+
dimension INTEGER NOT NULL,
|
|
57
|
+
model TEXT NOT NULL,
|
|
58
|
+
hits INTEGER DEFAULT 1,
|
|
59
|
+
created_at INTEGER NOT NULL,
|
|
60
|
+
last_accessed INTEGER NOT NULL
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_last_accessed ON embeddings(last_accessed);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_created_at ON embeddings(created_at);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_model ON embeddings(model);
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
prepareStatements() {
|
|
69
|
+
this.stmtGet = this.db.prepare(`
|
|
70
|
+
SELECT embedding, dimension FROM embeddings WHERE hash = ?
|
|
71
|
+
`);
|
|
72
|
+
this.stmtInsert = this.db.prepare(`
|
|
73
|
+
INSERT OR REPLACE INTO embeddings (hash, text, embedding, dimension, model, hits, created_at, last_accessed)
|
|
74
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)
|
|
75
|
+
`);
|
|
76
|
+
this.stmtUpdateHits = this.db.prepare(`
|
|
77
|
+
UPDATE embeddings SET hits = hits + 1, last_accessed = ? WHERE hash = ?
|
|
78
|
+
`);
|
|
79
|
+
this.stmtCount = this.db.prepare(`SELECT COUNT(*) as count FROM embeddings`);
|
|
80
|
+
this.stmtEvictOld = this.db.prepare(`
|
|
81
|
+
DELETE FROM embeddings WHERE created_at < ?
|
|
82
|
+
`);
|
|
83
|
+
this.stmtEvictLRU = this.db.prepare(`
|
|
84
|
+
DELETE FROM embeddings WHERE hash IN (
|
|
85
|
+
SELECT hash FROM embeddings ORDER BY last_accessed ASC LIMIT ?
|
|
86
|
+
)
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Generate hash key for text + model combination
|
|
91
|
+
*/
|
|
92
|
+
hashKey(text, model = 'default') {
|
|
93
|
+
return createHash('sha256').update(`${model}:${text}`).digest('hex').slice(0, 32);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get embedding from cache
|
|
97
|
+
* Returns null if not found
|
|
98
|
+
*/
|
|
99
|
+
get(text, model = 'default') {
|
|
100
|
+
const hash = this.hashKey(text, model);
|
|
101
|
+
const row = this.stmtGet.get(hash);
|
|
102
|
+
if (row) {
|
|
103
|
+
this.hits++;
|
|
104
|
+
// Update access time and hit count
|
|
105
|
+
this.stmtUpdateHits.run(Date.now(), hash);
|
|
106
|
+
// Convert Buffer back to Float32Array
|
|
107
|
+
return new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.dimension);
|
|
108
|
+
}
|
|
109
|
+
this.misses++;
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Store embedding in cache
|
|
114
|
+
*/
|
|
115
|
+
set(text, embedding, model = 'default') {
|
|
116
|
+
const hash = this.hashKey(text, model);
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
// Convert Float32Array to Buffer
|
|
119
|
+
const buffer = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
120
|
+
this.stmtInsert.run(hash, text, buffer, embedding.length, model, now, now);
|
|
121
|
+
// Check if we need to evict
|
|
122
|
+
this.maybeEvict();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if text is cached
|
|
126
|
+
*/
|
|
127
|
+
has(text, model = 'default') {
|
|
128
|
+
const hash = this.hashKey(text, model);
|
|
129
|
+
const row = this.stmtGet.get(hash);
|
|
130
|
+
return row !== undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get multiple embeddings at once
|
|
134
|
+
* Returns map of text -> embedding (only cached ones)
|
|
135
|
+
*/
|
|
136
|
+
getMany(texts, model = 'default') {
|
|
137
|
+
const result = new Map();
|
|
138
|
+
for (const text of texts) {
|
|
139
|
+
const embedding = this.get(text, model);
|
|
140
|
+
if (embedding) {
|
|
141
|
+
result.set(text, embedding);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Store multiple embeddings at once
|
|
148
|
+
*/
|
|
149
|
+
setMany(entries, model = 'default') {
|
|
150
|
+
const insertMany = this.db.transaction((items) => {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
for (const { text, embedding } of items) {
|
|
153
|
+
const hash = this.hashKey(text, model);
|
|
154
|
+
const buffer = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
155
|
+
this.stmtInsert.run(hash, text, buffer, embedding.length, model, now, now);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
insertMany(entries);
|
|
159
|
+
this.maybeEvict();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Evict old or LRU entries if over limit
|
|
163
|
+
*/
|
|
164
|
+
maybeEvict() {
|
|
165
|
+
const count = this.stmtCount.get().count;
|
|
166
|
+
if (count > this.config.maxEntries) {
|
|
167
|
+
// Evict 10% of entries (LRU)
|
|
168
|
+
const toEvict = Math.ceil(this.config.maxEntries * 0.1);
|
|
169
|
+
this.stmtEvictLRU.run(toEvict);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Cleanup entries older than maxAgeDays
|
|
174
|
+
*/
|
|
175
|
+
cleanupOldEntries() {
|
|
176
|
+
const cutoff = Date.now() - (this.config.maxAgeDays * 24 * 60 * 60 * 1000);
|
|
177
|
+
this.stmtEvictOld.run(cutoff);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get cache statistics
|
|
181
|
+
*/
|
|
182
|
+
getStats() {
|
|
183
|
+
const count = this.stmtCount.get().count;
|
|
184
|
+
const oldest = this.db.prepare(`SELECT MIN(created_at) as oldest FROM embeddings`).get();
|
|
185
|
+
const newest = this.db.prepare(`SELECT MAX(created_at) as newest FROM embeddings`).get();
|
|
186
|
+
// Get database file size
|
|
187
|
+
let dbSizeBytes = 0;
|
|
188
|
+
try {
|
|
189
|
+
const fs = require('fs');
|
|
190
|
+
const stats = fs.statSync(this.config.dbPath);
|
|
191
|
+
dbSizeBytes = stats.size;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Ignore
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
totalEntries: count,
|
|
198
|
+
hits: this.hits,
|
|
199
|
+
misses: this.misses,
|
|
200
|
+
hitRate: this.hits + this.misses > 0 ? this.hits / (this.hits + this.misses) : 0,
|
|
201
|
+
dbSizeBytes,
|
|
202
|
+
oldestEntry: oldest.oldest || 0,
|
|
203
|
+
newestEntry: newest.newest || 0,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Clear all cached embeddings
|
|
208
|
+
*/
|
|
209
|
+
clear() {
|
|
210
|
+
this.db.exec('DELETE FROM embeddings');
|
|
211
|
+
this.hits = 0;
|
|
212
|
+
this.misses = 0;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Clear embeddings for a specific model
|
|
216
|
+
*/
|
|
217
|
+
clearModel(model) {
|
|
218
|
+
this.db.prepare('DELETE FROM embeddings WHERE model = ?').run(model);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Vacuum database to reclaim space
|
|
222
|
+
*/
|
|
223
|
+
vacuum() {
|
|
224
|
+
this.db.exec('VACUUM');
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Close database connection
|
|
228
|
+
*/
|
|
229
|
+
close() {
|
|
230
|
+
this.db.close();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Singleton instance
|
|
234
|
+
let cacheInstance = null;
|
|
235
|
+
/**
|
|
236
|
+
* Get the singleton embedding cache
|
|
237
|
+
*/
|
|
238
|
+
export function getEmbeddingCache(config) {
|
|
239
|
+
if (!cacheInstance) {
|
|
240
|
+
cacheInstance = new EmbeddingCache(config);
|
|
241
|
+
}
|
|
242
|
+
return cacheInstance;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Reset the cache singleton (for testing)
|
|
246
|
+
*/
|
|
247
|
+
export function resetEmbeddingCache() {
|
|
248
|
+
if (cacheInstance) {
|
|
249
|
+
cacheInstance.close();
|
|
250
|
+
cacheInstance = null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=EmbeddingCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbeddingCache.js","sourceRoot":"","sources":["../../src/intelligence/EmbeddingCache.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * EmbeddingCache - Persistent SQLite cache for embeddings\n *\n * Makes ONNX embeddings practical by caching across sessions:\n * - First embed: ~400ms (ONNX inference)\n * - Cached embed: ~0.1ms (SQLite lookup)\n *\n * Storage: ~/.agentic-flow/embedding-cache.db\n */\n\nimport Database from 'better-sqlite3';\nimport { existsSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { createHash } from 'crypto';\n\nexport interface CacheStats {\n totalEntries: number;\n hits: number;\n misses: number;\n hitRate: number;\n dbSizeBytes: number;\n oldestEntry: number;\n newestEntry: number;\n}\n\nexport interface CacheConfig {\n maxEntries?: number; // Max cache entries (default: 10000)\n maxAgeDays?: number; // Max age before eviction (default: 30)\n dbPath?: string; // Custom database path\n dimension?: number; // Embedding dimension (default: 384)\n}\n\n// Default config\nconst DEFAULT_CONFIG: Required<CacheConfig> = {\n maxEntries: 10000,\n maxAgeDays: 30,\n dbPath: join(homedir(), '.agentic-flow', 'embedding-cache.db'),\n dimension: 384,\n};\n\nexport class EmbeddingCache {\n private db: Database.Database;\n private config: Required<CacheConfig>;\n private hits: number = 0;\n private misses: number = 0;\n\n // Prepared statements for performance\n private stmtGet!: Database.Statement;\n private stmtInsert!: Database.Statement;\n private stmtUpdateHits!: Database.Statement;\n private stmtCount!: Database.Statement;\n private stmtEvictOld!: Database.Statement;\n private stmtEvictLRU!: Database.Statement;\n\n constructor(config: CacheConfig = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n\n // Ensure directory exists\n const dir = join(homedir(), '.agentic-flow');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Open database with WAL mode for better concurrency\n this.db = new Database(this.config.dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('synchronous = NORMAL');\n this.db.pragma('cache_size = 10000');\n\n this.initSchema();\n this.prepareStatements();\n this.cleanupOldEntries();\n }\n\n private initSchema(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS embeddings (\n hash TEXT PRIMARY KEY,\n text TEXT NOT NULL,\n embedding BLOB NOT NULL,\n dimension INTEGER NOT NULL,\n model TEXT NOT NULL,\n hits INTEGER DEFAULT 1,\n created_at INTEGER NOT NULL,\n last_accessed INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_last_accessed ON embeddings(last_accessed);\n CREATE INDEX IF NOT EXISTS idx_created_at ON embeddings(created_at);\n CREATE INDEX IF NOT EXISTS idx_model ON embeddings(model);\n `);\n }\n\n private prepareStatements(): void {\n this.stmtGet = this.db.prepare(`\n SELECT embedding, dimension FROM embeddings WHERE hash = ?\n `);\n\n this.stmtInsert = this.db.prepare(`\n INSERT OR REPLACE INTO embeddings (hash, text, embedding, dimension, model, hits, created_at, last_accessed)\n VALUES (?, ?, ?, ?, ?, 1, ?, ?)\n `);\n\n this.stmtUpdateHits = this.db.prepare(`\n UPDATE embeddings SET hits = hits + 1, last_accessed = ? WHERE hash = ?\n `);\n\n this.stmtCount = this.db.prepare(`SELECT COUNT(*) as count FROM embeddings`);\n\n this.stmtEvictOld = this.db.prepare(`\n DELETE FROM embeddings WHERE created_at < ?\n `);\n\n this.stmtEvictLRU = this.db.prepare(`\n DELETE FROM embeddings WHERE hash IN (\n SELECT hash FROM embeddings ORDER BY last_accessed ASC LIMIT ?\n )\n `);\n }\n\n /**\n * Generate hash key for text + model combination\n */\n private hashKey(text: string, model: string = 'default'): string {\n return createHash('sha256').update(`${model}:${text}`).digest('hex').slice(0, 32);\n }\n\n /**\n * Get embedding from cache\n * Returns null if not found\n */\n get(text: string, model: string = 'default'): Float32Array | null {\n const hash = this.hashKey(text, model);\n const row = this.stmtGet.get(hash) as { embedding: Buffer; dimension: number } | undefined;\n\n if (row) {\n this.hits++;\n // Update access time and hit count\n this.stmtUpdateHits.run(Date.now(), hash);\n // Convert Buffer back to Float32Array\n return new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.dimension);\n }\n\n this.misses++;\n return null;\n }\n\n /**\n * Store embedding in cache\n */\n set(text: string, embedding: Float32Array, model: string = 'default'): void {\n const hash = this.hashKey(text, model);\n const now = Date.now();\n\n // Convert Float32Array to Buffer\n const buffer = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);\n\n this.stmtInsert.run(hash, text, buffer, embedding.length, model, now, now);\n\n // Check if we need to evict\n this.maybeEvict();\n }\n\n /**\n * Check if text is cached\n */\n has(text: string, model: string = 'default'): boolean {\n const hash = this.hashKey(text, model);\n const row = this.stmtGet.get(hash);\n return row !== undefined;\n }\n\n /**\n * Get multiple embeddings at once\n * Returns map of text -> embedding (only cached ones)\n */\n getMany(texts: string[], model: string = 'default'): Map<string, Float32Array> {\n const result = new Map<string, Float32Array>();\n\n for (const text of texts) {\n const embedding = this.get(text, model);\n if (embedding) {\n result.set(text, embedding);\n }\n }\n\n return result;\n }\n\n /**\n * Store multiple embeddings at once\n */\n setMany(entries: Array<{ text: string; embedding: Float32Array }>, model: string = 'default'): void {\n const insertMany = this.db.transaction((items: typeof entries) => {\n const now = Date.now();\n for (const { text, embedding } of items) {\n const hash = this.hashKey(text, model);\n const buffer = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);\n this.stmtInsert.run(hash, text, buffer, embedding.length, model, now, now);\n }\n });\n\n insertMany(entries);\n this.maybeEvict();\n }\n\n /**\n * Evict old or LRU entries if over limit\n */\n private maybeEvict(): void {\n const count = (this.stmtCount.get() as { count: number }).count;\n\n if (count > this.config.maxEntries) {\n // Evict 10% of entries (LRU)\n const toEvict = Math.ceil(this.config.maxEntries * 0.1);\n this.stmtEvictLRU.run(toEvict);\n }\n }\n\n /**\n * Cleanup entries older than maxAgeDays\n */\n private cleanupOldEntries(): void {\n const cutoff = Date.now() - (this.config.maxAgeDays * 24 * 60 * 60 * 1000);\n this.stmtEvictOld.run(cutoff);\n }\n\n /**\n * Get cache statistics\n */\n getStats(): CacheStats {\n const count = (this.stmtCount.get() as { count: number }).count;\n\n const oldest = this.db.prepare(`SELECT MIN(created_at) as oldest FROM embeddings`).get() as { oldest: number | null };\n const newest = this.db.prepare(`SELECT MAX(created_at) as newest FROM embeddings`).get() as { newest: number | null };\n\n // Get database file size\n let dbSizeBytes = 0;\n try {\n const fs = require('fs');\n const stats = fs.statSync(this.config.dbPath);\n dbSizeBytes = stats.size;\n } catch {\n // Ignore\n }\n\n return {\n totalEntries: count,\n hits: this.hits,\n misses: this.misses,\n hitRate: this.hits + this.misses > 0 ? this.hits / (this.hits + this.misses) : 0,\n dbSizeBytes,\n oldestEntry: oldest.oldest || 0,\n newestEntry: newest.newest || 0,\n };\n }\n\n /**\n * Clear all cached embeddings\n */\n clear(): void {\n this.db.exec('DELETE FROM embeddings');\n this.hits = 0;\n this.misses = 0;\n }\n\n /**\n * Clear embeddings for a specific model\n */\n clearModel(model: string): void {\n this.db.prepare('DELETE FROM embeddings WHERE model = ?').run(model);\n }\n\n /**\n * Vacuum database to reclaim space\n */\n vacuum(): void {\n this.db.exec('VACUUM');\n }\n\n /**\n * Close database connection\n */\n close(): void {\n this.db.close();\n }\n}\n\n// Singleton instance\nlet cacheInstance: EmbeddingCache | null = null;\n\n/**\n * Get the singleton embedding cache\n */\nexport function getEmbeddingCache(config?: CacheConfig): EmbeddingCache {\n if (!cacheInstance) {\n cacheInstance = new EmbeddingCache(config);\n }\n return cacheInstance;\n}\n\n/**\n * Reset the cache singleton (for testing)\n */\nexport function resetEmbeddingCache(): void {\n if (cacheInstance) {\n cacheInstance.close();\n cacheInstance = null;\n }\n}\n"]}
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
* - SIMD128 acceleration (6x faster)
|
|
6
6
|
* - Parallel worker threads (7 workers)
|
|
7
7
|
* - all-MiniLM-L6-v2 model (384 dimensions)
|
|
8
|
+
* - Persistent SQLite cache (0.1ms vs 400ms)
|
|
8
9
|
*
|
|
9
10
|
* Configure via:
|
|
10
11
|
* - AGENTIC_FLOW_EMBEDDINGS=simple|onnx|auto (default: auto)
|
|
11
12
|
* - AGENTIC_FLOW_EMBEDDING_MODEL=all-MiniLM-L6-v2 (default)
|
|
13
|
+
* - AGENTIC_FLOW_EMBEDDING_CACHE=true|false (default: true)
|
|
14
|
+
* - AGENTIC_FLOW_PERSISTENT_CACHE=true|false (default: true)
|
|
12
15
|
*/
|
|
13
16
|
export type EmbeddingBackend = 'simple' | 'onnx' | 'auto';
|
|
14
17
|
export interface EmbeddingStats {
|
|
@@ -23,6 +26,14 @@ export interface EmbeddingStats {
|
|
|
23
26
|
modelName?: string;
|
|
24
27
|
simdAvailable?: boolean;
|
|
25
28
|
parallelWorkers?: number;
|
|
29
|
+
persistentCache?: {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
entries: number;
|
|
32
|
+
hits: number;
|
|
33
|
+
misses: number;
|
|
34
|
+
hitRate: number;
|
|
35
|
+
dbSizeKB: number;
|
|
36
|
+
};
|
|
26
37
|
}
|
|
27
38
|
export interface SimilarityResult {
|
|
28
39
|
similarity: number;
|
|
@@ -51,6 +62,8 @@ export declare class EmbeddingService {
|
|
|
51
62
|
private cacheHits;
|
|
52
63
|
private cache;
|
|
53
64
|
private cacheEnabled;
|
|
65
|
+
private persistentCache;
|
|
66
|
+
private persistentCacheEnabled;
|
|
54
67
|
private corpus;
|
|
55
68
|
private constructor();
|
|
56
69
|
static getInstance(): EmbeddingService;
|
|
@@ -137,9 +150,26 @@ export declare class EmbeddingService {
|
|
|
137
150
|
*/
|
|
138
151
|
getStats(): EmbeddingStats;
|
|
139
152
|
/**
|
|
140
|
-
* Clear cache
|
|
153
|
+
* Clear in-memory cache
|
|
141
154
|
*/
|
|
142
155
|
clearCache(): void;
|
|
156
|
+
/**
|
|
157
|
+
* Clear persistent cache (SQLite)
|
|
158
|
+
*/
|
|
159
|
+
clearPersistentCache(): void;
|
|
160
|
+
/**
|
|
161
|
+
* Clear all caches (memory + persistent)
|
|
162
|
+
*/
|
|
163
|
+
clearAllCaches(): void;
|
|
164
|
+
/**
|
|
165
|
+
* Get persistent cache stats
|
|
166
|
+
*/
|
|
167
|
+
getPersistentCacheStats(): {
|
|
168
|
+
entries: number;
|
|
169
|
+
hits: number;
|
|
170
|
+
misses: number;
|
|
171
|
+
hitRate: number;
|
|
172
|
+
} | null;
|
|
143
173
|
/**
|
|
144
174
|
* Clear corpus
|
|
145
175
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbeddingService.d.ts","sourceRoot":"","sources":["../../src/intelligence/EmbeddingService.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"EmbeddingService.d.ts","sourceRoot":"","sources":["../../src/intelligence/EmbeddingService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,eAAe,CAAC,EAAE;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyFD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAiC;IAExD,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,cAAc,CAA8B;IAGpD,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,SAAS,CAAa;IAG9B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,YAAY,CAAU;IAG9B,OAAO,CAAC,eAAe,CAA+B;IACtD,OAAO,CAAC,sBAAsB,CAAU;IAGxC,OAAO,CAAC,MAAM,CAAkF;IAEhG,OAAO;IAoBP,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAOtC;;OAEG;YACW,cAAc;IAsB5B;;OAEG;IACH,UAAU,IAAI,gBAAgB;IAI9B;;OAEG;IACH,mBAAmB,IAAI,gBAAgB;IAIvC;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA0DhD;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAyC1D;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa/D;;;OAGG;IACG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAiB5D;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD;;;OAGG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB9E;;;OAGG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,GAAE,MAAY,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAoCzF;;;OAGG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EAAE,EACf,CAAC,GAAE,MAAU,EACb,aAAa,GAAE,MAAY,GAC1B,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IAyE7D;;;OAGG;IACI,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,GAAE,MAAW,GAAG,cAAc,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,YAAY,CAAA;KAAE,CAAC;IAerI;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAY,GAAG,YAAY;IAwB1D;;OAEG;IACH,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM;IAiB1D;;OAEG;IACH,QAAQ,IAAI,cAAc;IAkC1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAM5B;;OAEG;IACH,cAAc,IAAI,IAAI;IAOtB;;OAEG;IACH,uBAAuB,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAWpG;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;WACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQpC;AAGD,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAGD,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAE/D;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzE;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAElF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAY,GAAG,YAAY,CAEzE;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAE3E;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAE7F;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,GAAE,MAAY,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAExG;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,GAAE,MAAU,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC,CAE7H"}
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
* - SIMD128 acceleration (6x faster)
|
|
6
6
|
* - Parallel worker threads (7 workers)
|
|
7
7
|
* - all-MiniLM-L6-v2 model (384 dimensions)
|
|
8
|
+
* - Persistent SQLite cache (0.1ms vs 400ms)
|
|
8
9
|
*
|
|
9
10
|
* Configure via:
|
|
10
11
|
* - AGENTIC_FLOW_EMBEDDINGS=simple|onnx|auto (default: auto)
|
|
11
12
|
* - AGENTIC_FLOW_EMBEDDING_MODEL=all-MiniLM-L6-v2 (default)
|
|
13
|
+
* - AGENTIC_FLOW_EMBEDDING_CACHE=true|false (default: true)
|
|
14
|
+
* - AGENTIC_FLOW_PERSISTENT_CACHE=true|false (default: true)
|
|
12
15
|
*/
|
|
16
|
+
import { getEmbeddingCache } from './EmbeddingCache.js';
|
|
13
17
|
// ONNX availability cache
|
|
14
18
|
let onnxAvailable = null;
|
|
15
19
|
let ruvectorModule = null;
|
|
@@ -32,8 +36,8 @@ async function detectOnnx() {
|
|
|
32
36
|
return false;
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
|
-
// Simple LRU cache for embeddings
|
|
36
|
-
class
|
|
39
|
+
// Simple LRU cache for embeddings (in-memory, fast)
|
|
40
|
+
class LRUCache {
|
|
37
41
|
cache = new Map();
|
|
38
42
|
maxSize;
|
|
39
43
|
constructor(maxSize = 1000) {
|
|
@@ -78,9 +82,12 @@ export class EmbeddingService {
|
|
|
78
82
|
totalEmbeddings = 0;
|
|
79
83
|
totalLatencyMs = 0;
|
|
80
84
|
cacheHits = 0;
|
|
81
|
-
// Cache
|
|
85
|
+
// Cache (in-memory LRU)
|
|
82
86
|
cache;
|
|
83
87
|
cacheEnabled;
|
|
88
|
+
// Persistent cache (SQLite)
|
|
89
|
+
persistentCache = null;
|
|
90
|
+
persistentCacheEnabled;
|
|
84
91
|
// Corpus for search operations
|
|
85
92
|
corpus = { texts: [], embeddings: [] };
|
|
86
93
|
constructor() {
|
|
@@ -89,7 +96,18 @@ export class EmbeddingService {
|
|
|
89
96
|
this.modelName = process.env.AGENTIC_FLOW_EMBEDDING_MODEL || 'all-MiniLM-L6-v2';
|
|
90
97
|
this.dimension = 256; // Will be updated when ONNX loads (384)
|
|
91
98
|
this.cacheEnabled = process.env.AGENTIC_FLOW_EMBEDDING_CACHE !== 'false';
|
|
92
|
-
this.
|
|
99
|
+
this.persistentCacheEnabled = process.env.AGENTIC_FLOW_PERSISTENT_CACHE !== 'false';
|
|
100
|
+
this.cache = new LRUCache(1000);
|
|
101
|
+
// Initialize persistent cache
|
|
102
|
+
if (this.persistentCacheEnabled) {
|
|
103
|
+
try {
|
|
104
|
+
this.persistentCache = getEmbeddingCache({ dimension: 384 });
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn('[EmbeddingService] Persistent cache unavailable:', error);
|
|
108
|
+
this.persistentCacheEnabled = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
93
111
|
}
|
|
94
112
|
static getInstance() {
|
|
95
113
|
if (!EmbeddingService.instance) {
|
|
@@ -150,7 +168,7 @@ export class EmbeddingService {
|
|
|
150
168
|
*/
|
|
151
169
|
async embed(text) {
|
|
152
170
|
const startTime = performance.now();
|
|
153
|
-
// Check cache
|
|
171
|
+
// Check in-memory cache first (fastest)
|
|
154
172
|
if (this.cacheEnabled) {
|
|
155
173
|
const cached = this.cache.get(text);
|
|
156
174
|
if (cached) {
|
|
@@ -158,6 +176,18 @@ export class EmbeddingService {
|
|
|
158
176
|
return cached;
|
|
159
177
|
}
|
|
160
178
|
}
|
|
179
|
+
// Check persistent cache (SQLite, ~0.1ms)
|
|
180
|
+
if (this.persistentCache) {
|
|
181
|
+
const cached = this.persistentCache.get(text, this.modelName);
|
|
182
|
+
if (cached) {
|
|
183
|
+
this.cacheHits++;
|
|
184
|
+
// Also store in memory cache for faster subsequent access
|
|
185
|
+
if (this.cacheEnabled) {
|
|
186
|
+
this.cache.set(text, cached);
|
|
187
|
+
}
|
|
188
|
+
return cached;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
161
191
|
// Resolve backend (handles 'auto' mode)
|
|
162
192
|
const effectiveBackend = await this.resolveBackend();
|
|
163
193
|
let embedding;
|
|
@@ -177,10 +207,14 @@ export class EmbeddingService {
|
|
|
177
207
|
// Update stats
|
|
178
208
|
this.totalEmbeddings++;
|
|
179
209
|
this.totalLatencyMs += performance.now() - startTime;
|
|
180
|
-
// Cache result
|
|
210
|
+
// Cache result in memory
|
|
181
211
|
if (this.cacheEnabled) {
|
|
182
212
|
this.cache.set(text, embedding);
|
|
183
213
|
}
|
|
214
|
+
// Cache result persistently (for cross-session)
|
|
215
|
+
if (this.persistentCache && effectiveBackend === 'onnx') {
|
|
216
|
+
this.persistentCache.set(text, embedding, this.modelName);
|
|
217
|
+
}
|
|
184
218
|
return embedding;
|
|
185
219
|
}
|
|
186
220
|
/**
|
|
@@ -448,6 +482,19 @@ export class EmbeddingService {
|
|
|
448
482
|
getStats() {
|
|
449
483
|
const effective = this.effectiveBackend || this.backend;
|
|
450
484
|
const ruvectorStats = ruvectorModule?.getStats?.() || {};
|
|
485
|
+
// Get persistent cache stats
|
|
486
|
+
let persistentCacheStats;
|
|
487
|
+
if (this.persistentCache) {
|
|
488
|
+
const cacheStats = this.persistentCache.getStats();
|
|
489
|
+
persistentCacheStats = {
|
|
490
|
+
enabled: true,
|
|
491
|
+
entries: cacheStats.totalEntries,
|
|
492
|
+
hits: cacheStats.hits,
|
|
493
|
+
misses: cacheStats.misses,
|
|
494
|
+
hitRate: cacheStats.hitRate,
|
|
495
|
+
dbSizeKB: Math.round(cacheStats.dbSizeBytes / 1024),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
451
498
|
return {
|
|
452
499
|
backend: this.backend,
|
|
453
500
|
effectiveBackend: effective,
|
|
@@ -460,14 +507,46 @@ export class EmbeddingService {
|
|
|
460
507
|
modelName: effective === 'onnx' ? this.modelName : undefined,
|
|
461
508
|
simdAvailable: ruvectorStats.simdAvailable ?? onnxAvailable,
|
|
462
509
|
parallelWorkers: ruvectorStats.workerCount ?? undefined,
|
|
510
|
+
persistentCache: persistentCacheStats,
|
|
463
511
|
};
|
|
464
512
|
}
|
|
465
513
|
/**
|
|
466
|
-
* Clear cache
|
|
514
|
+
* Clear in-memory cache
|
|
467
515
|
*/
|
|
468
516
|
clearCache() {
|
|
469
517
|
this.cache.clear();
|
|
470
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Clear persistent cache (SQLite)
|
|
521
|
+
*/
|
|
522
|
+
clearPersistentCache() {
|
|
523
|
+
if (this.persistentCache) {
|
|
524
|
+
this.persistentCache.clear();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Clear all caches (memory + persistent)
|
|
529
|
+
*/
|
|
530
|
+
clearAllCaches() {
|
|
531
|
+
this.cache.clear();
|
|
532
|
+
if (this.persistentCache) {
|
|
533
|
+
this.persistentCache.clear();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get persistent cache stats
|
|
538
|
+
*/
|
|
539
|
+
getPersistentCacheStats() {
|
|
540
|
+
if (!this.persistentCache)
|
|
541
|
+
return null;
|
|
542
|
+
const stats = this.persistentCache.getStats();
|
|
543
|
+
return {
|
|
544
|
+
entries: stats.totalEntries,
|
|
545
|
+
hits: stats.hits,
|
|
546
|
+
misses: stats.misses,
|
|
547
|
+
hitRate: stats.hitRate,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
471
550
|
/**
|
|
472
551
|
* Clear corpus
|
|
473
552
|
*/
|