@aitytech/agentkits-memory 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -149
- package/assets/agentkits-memory-add-memory.png +0 -0
- package/assets/agentkits-memory-memory-detail.png +0 -0
- package/assets/agentkits-memory-memory-list.png +0 -0
- package/assets/logo.svg +24 -0
- package/dist/better-sqlite3-backend.d.ts +192 -0
- package/dist/better-sqlite3-backend.d.ts.map +1 -0
- package/dist/better-sqlite3-backend.js +801 -0
- package/dist/better-sqlite3-backend.js.map +1 -0
- package/dist/cli/save.js +0 -0
- package/dist/cli/setup.d.ts +6 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +289 -42
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/viewer.js +25 -56
- package/dist/cli/viewer.js.map +1 -1
- package/dist/cli/web-viewer.d.ts +14 -0
- package/dist/cli/web-viewer.d.ts.map +1 -0
- package/dist/cli/web-viewer.js +1769 -0
- package/dist/cli/web-viewer.js.map +1 -0
- package/dist/embeddings/embedding-cache.d.ts +131 -0
- package/dist/embeddings/embedding-cache.d.ts.map +1 -0
- package/dist/embeddings/embedding-cache.js +217 -0
- package/dist/embeddings/embedding-cache.js.map +1 -0
- package/dist/embeddings/index.d.ts +11 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +11 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/local-embeddings.d.ts +140 -0
- package/dist/embeddings/local-embeddings.d.ts.map +1 -0
- package/dist/embeddings/local-embeddings.js +293 -0
- package/dist/embeddings/local-embeddings.js.map +1 -0
- package/dist/hooks/context.d.ts +6 -1
- package/dist/hooks/context.d.ts.map +1 -1
- package/dist/hooks/context.js +12 -2
- package/dist/hooks/context.js.map +1 -1
- package/dist/hooks/observation.d.ts +6 -1
- package/dist/hooks/observation.d.ts.map +1 -1
- package/dist/hooks/observation.js +12 -2
- package/dist/hooks/observation.js.map +1 -1
- package/dist/hooks/service.d.ts +1 -6
- package/dist/hooks/service.d.ts.map +1 -1
- package/dist/hooks/service.js +33 -85
- package/dist/hooks/service.js.map +1 -1
- package/dist/hooks/session-init.d.ts +6 -1
- package/dist/hooks/session-init.d.ts.map +1 -1
- package/dist/hooks/session-init.js +12 -2
- package/dist/hooks/session-init.js.map +1 -1
- package/dist/hooks/summarize.d.ts +6 -1
- package/dist/hooks/summarize.d.ts.map +1 -1
- package/dist/hooks/summarize.js +12 -2
- package/dist/hooks/summarize.js.map +1 -1
- package/dist/index.d.ts +10 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +172 -94
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +17 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/migration.js +3 -3
- package/dist/migration.js.map +1 -1
- package/dist/search/hybrid-search.d.ts +262 -0
- package/dist/search/hybrid-search.d.ts.map +1 -0
- package/dist/search/hybrid-search.js +688 -0
- package/dist/search/hybrid-search.js.map +1 -0
- package/dist/search/index.d.ts +13 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +13 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/token-economics.d.ts +161 -0
- package/dist/search/token-economics.d.ts.map +1 -0
- package/dist/search/token-economics.js +239 -0
- package/dist/search/token-economics.js.map +1 -0
- package/dist/types.d.ts +0 -68
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +23 -8
- package/src/__tests__/better-sqlite3-backend.test.ts +1466 -0
- package/src/__tests__/cache-manager.test.ts +499 -0
- package/src/__tests__/embedding-integration.test.ts +481 -0
- package/src/__tests__/hnsw-index.test.ts +727 -0
- package/src/__tests__/index.test.ts +432 -0
- package/src/better-sqlite3-backend.ts +1000 -0
- package/src/cli/setup.ts +358 -47
- package/src/cli/viewer.ts +28 -63
- package/src/cli/web-viewer.ts +1956 -0
- package/src/embeddings/__tests__/embedding-cache.test.ts +269 -0
- package/src/embeddings/__tests__/local-embeddings.test.ts +495 -0
- package/src/embeddings/embedding-cache.ts +318 -0
- package/src/embeddings/index.ts +20 -0
- package/src/embeddings/local-embeddings.ts +419 -0
- package/src/hooks/__tests__/handlers.test.ts +58 -17
- package/src/hooks/__tests__/integration.test.ts +77 -26
- package/src/hooks/context.ts +13 -2
- package/src/hooks/observation.ts +13 -2
- package/src/hooks/service.ts +39 -100
- package/src/hooks/session-init.ts +13 -2
- package/src/hooks/summarize.ts +13 -2
- package/src/index.ts +210 -116
- package/src/mcp/server.ts +20 -3
- package/src/search/__tests__/hybrid-search.test.ts +669 -0
- package/src/search/__tests__/token-economics.test.ts +276 -0
- package/src/search/hybrid-search.ts +968 -0
- package/src/search/index.ts +29 -0
- package/src/search/token-economics.ts +367 -0
- package/src/types.ts +0 -96
- package/src/__tests__/sqljs-backend.test.ts +0 -410
- package/src/migration.ts +0 -574
- package/src/sql.js.d.ts +0 -70
- package/src/sqljs-backend.ts +0 -789
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent Embedding Cache
|
|
3
|
+
*
|
|
4
|
+
* SQLite-backed cache for embeddings to avoid re-computing
|
|
5
|
+
* embeddings for the same text content.
|
|
6
|
+
*
|
|
7
|
+
* @module @aitytech/agentkits-memory/embeddings
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import type { Database as BetterDatabase } from 'better-sqlite3';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Embedding cache configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface PersistentEmbeddingCacheConfig {
|
|
17
|
+
/** Maximum number of entries (default: 10000) */
|
|
18
|
+
maxSize?: number;
|
|
19
|
+
|
|
20
|
+
/** TTL in milliseconds (default: 7 days) */
|
|
21
|
+
ttlMs?: number;
|
|
22
|
+
|
|
23
|
+
/** Vector dimensions (default: 384) */
|
|
24
|
+
dimensions?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cached embedding entry
|
|
29
|
+
*/
|
|
30
|
+
export interface CachedEmbedding {
|
|
31
|
+
/** Content hash (primary key) */
|
|
32
|
+
hash: string;
|
|
33
|
+
|
|
34
|
+
/** The embedding vector */
|
|
35
|
+
embedding: Float32Array;
|
|
36
|
+
|
|
37
|
+
/** When the entry was created */
|
|
38
|
+
createdAt: number;
|
|
39
|
+
|
|
40
|
+
/** When the entry expires */
|
|
41
|
+
expiresAt: number;
|
|
42
|
+
|
|
43
|
+
/** Access count */
|
|
44
|
+
accessCount: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Cache statistics
|
|
49
|
+
*/
|
|
50
|
+
export interface PersistentEmbeddingCacheStats {
|
|
51
|
+
/** Total entries in cache */
|
|
52
|
+
size: number;
|
|
53
|
+
|
|
54
|
+
/** Cache hits */
|
|
55
|
+
hits: number;
|
|
56
|
+
|
|
57
|
+
/** Cache misses */
|
|
58
|
+
misses: number;
|
|
59
|
+
|
|
60
|
+
/** Hit rate (0-1) */
|
|
61
|
+
hitRate: number;
|
|
62
|
+
|
|
63
|
+
/** Total bytes used */
|
|
64
|
+
bytesUsed: number;
|
|
65
|
+
|
|
66
|
+
/** Oldest entry age in ms */
|
|
67
|
+
oldestEntryAge: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Persistent Embedding Cache using SQLite
|
|
72
|
+
*/
|
|
73
|
+
export class PersistentEmbeddingCache {
|
|
74
|
+
private db: BetterDatabase;
|
|
75
|
+
private config: Required<PersistentEmbeddingCacheConfig>;
|
|
76
|
+
private stats = {
|
|
77
|
+
hits: 0,
|
|
78
|
+
misses: 0,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
constructor(db: BetterDatabase, config: PersistentEmbeddingCacheConfig = {}) {
|
|
82
|
+
this.db = db;
|
|
83
|
+
this.config = {
|
|
84
|
+
maxSize: config.maxSize || 10000,
|
|
85
|
+
ttlMs: config.ttlMs || 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
86
|
+
dimensions: config.dimensions || 384,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.initializeSchema();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Initialize the cache table
|
|
94
|
+
*/
|
|
95
|
+
private initializeSchema(): void {
|
|
96
|
+
this.db.exec(`
|
|
97
|
+
CREATE TABLE IF NOT EXISTS embedding_cache (
|
|
98
|
+
hash TEXT PRIMARY KEY,
|
|
99
|
+
embedding BLOB NOT NULL,
|
|
100
|
+
created_at INTEGER NOT NULL,
|
|
101
|
+
expires_at INTEGER NOT NULL,
|
|
102
|
+
access_count INTEGER DEFAULT 1,
|
|
103
|
+
last_accessed_at INTEGER NOT NULL
|
|
104
|
+
)
|
|
105
|
+
`);
|
|
106
|
+
|
|
107
|
+
this.db.exec(`
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_cache_expires
|
|
109
|
+
ON embedding_cache(expires_at)
|
|
110
|
+
`);
|
|
111
|
+
|
|
112
|
+
this.db.exec(`
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_cache_accessed
|
|
114
|
+
ON embedding_cache(last_accessed_at)
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Hash content for cache key
|
|
120
|
+
*/
|
|
121
|
+
private hashContent(content: string): string {
|
|
122
|
+
return createHash('sha256').update(content).digest('hex').substring(0, 32);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Convert Float32Array to Buffer for storage
|
|
127
|
+
*/
|
|
128
|
+
private toBuffer(embedding: Float32Array): Buffer {
|
|
129
|
+
return Buffer.from(embedding.buffer);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convert Buffer back to Float32Array
|
|
134
|
+
*/
|
|
135
|
+
private fromBuffer(buffer: Buffer): Float32Array {
|
|
136
|
+
return new Float32Array(buffer.buffer.slice(
|
|
137
|
+
buffer.byteOffset,
|
|
138
|
+
buffer.byteOffset + buffer.byteLength
|
|
139
|
+
));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get embedding from cache
|
|
144
|
+
*/
|
|
145
|
+
get(content: string): Float32Array | null {
|
|
146
|
+
const hash = this.hashContent(content);
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
|
|
149
|
+
const row = this.db.prepare(`
|
|
150
|
+
SELECT embedding, expires_at
|
|
151
|
+
FROM embedding_cache
|
|
152
|
+
WHERE hash = ? AND expires_at > ?
|
|
153
|
+
`).get(hash, now) as { embedding: Buffer; expires_at: number } | undefined;
|
|
154
|
+
|
|
155
|
+
if (row) {
|
|
156
|
+
// Update access stats
|
|
157
|
+
this.db.prepare(`
|
|
158
|
+
UPDATE embedding_cache
|
|
159
|
+
SET access_count = access_count + 1,
|
|
160
|
+
last_accessed_at = ?
|
|
161
|
+
WHERE hash = ?
|
|
162
|
+
`).run(now, hash);
|
|
163
|
+
|
|
164
|
+
this.stats.hits++;
|
|
165
|
+
return this.fromBuffer(row.embedding);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.stats.misses++;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Store embedding in cache
|
|
174
|
+
*/
|
|
175
|
+
set(content: string, embedding: Float32Array): void {
|
|
176
|
+
const hash = this.hashContent(content);
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
const expiresAt = now + this.config.ttlMs;
|
|
179
|
+
|
|
180
|
+
// Check if we need to evict entries
|
|
181
|
+
this.evictIfNeeded();
|
|
182
|
+
|
|
183
|
+
// Insert or replace
|
|
184
|
+
this.db.prepare(`
|
|
185
|
+
INSERT OR REPLACE INTO embedding_cache
|
|
186
|
+
(hash, embedding, created_at, expires_at, access_count, last_accessed_at)
|
|
187
|
+
VALUES (?, ?, ?, ?, 1, ?)
|
|
188
|
+
`).run(hash, this.toBuffer(embedding), now, expiresAt, now);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if content is in cache
|
|
193
|
+
*/
|
|
194
|
+
has(content: string): boolean {
|
|
195
|
+
const hash = this.hashContent(content);
|
|
196
|
+
const now = Date.now();
|
|
197
|
+
|
|
198
|
+
const row = this.db.prepare(`
|
|
199
|
+
SELECT 1 FROM embedding_cache
|
|
200
|
+
WHERE hash = ? AND expires_at > ?
|
|
201
|
+
`).get(hash, now);
|
|
202
|
+
|
|
203
|
+
return !!row;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Delete expired entries
|
|
208
|
+
*/
|
|
209
|
+
evictExpired(): number {
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
const result = this.db.prepare(`DELETE FROM embedding_cache WHERE expires_at <= ?`).run(now);
|
|
212
|
+
return result.changes;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Evict oldest entries if over capacity
|
|
217
|
+
*/
|
|
218
|
+
private evictIfNeeded(): void {
|
|
219
|
+
const countRow = this.db.prepare(`SELECT COUNT(*) as count FROM embedding_cache`).get() as { count: number };
|
|
220
|
+
|
|
221
|
+
if (countRow.count >= this.config.maxSize) {
|
|
222
|
+
// Delete oldest 10% of entries
|
|
223
|
+
const deleteCount = Math.max(1, Math.floor(this.config.maxSize * 0.1));
|
|
224
|
+
this.db.prepare(`
|
|
225
|
+
DELETE FROM embedding_cache
|
|
226
|
+
WHERE hash IN (
|
|
227
|
+
SELECT hash FROM embedding_cache
|
|
228
|
+
ORDER BY last_accessed_at ASC
|
|
229
|
+
LIMIT ?
|
|
230
|
+
)
|
|
231
|
+
`).run(deleteCount);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Clear all cache entries
|
|
237
|
+
*/
|
|
238
|
+
clear(): void {
|
|
239
|
+
this.db.exec(`DELETE FROM embedding_cache`);
|
|
240
|
+
this.stats.hits = 0;
|
|
241
|
+
this.stats.misses = 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get cache statistics
|
|
246
|
+
*/
|
|
247
|
+
getStats(): PersistentEmbeddingCacheStats {
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
|
|
250
|
+
// Get size and bytes
|
|
251
|
+
const result = this.db.prepare(`
|
|
252
|
+
SELECT
|
|
253
|
+
COUNT(*) as size,
|
|
254
|
+
COALESCE(SUM(LENGTH(embedding)), 0) as bytes,
|
|
255
|
+
MIN(created_at) as oldest
|
|
256
|
+
FROM embedding_cache
|
|
257
|
+
WHERE expires_at > ?
|
|
258
|
+
`).get(now) as {
|
|
259
|
+
size: number;
|
|
260
|
+
bytes: number;
|
|
261
|
+
oldest: number | null;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const totalRequests = this.stats.hits + this.stats.misses;
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
size: result.size,
|
|
268
|
+
hits: this.stats.hits,
|
|
269
|
+
misses: this.stats.misses,
|
|
270
|
+
hitRate: totalRequests > 0 ? this.stats.hits / totalRequests : 0,
|
|
271
|
+
bytesUsed: result.bytes,
|
|
272
|
+
oldestEntryAge: result.oldest ? now - result.oldest : 0,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get all cached embeddings (for warm-starting HNSW index)
|
|
278
|
+
*/
|
|
279
|
+
getAllEmbeddings(): Array<{ hash: string; embedding: Float32Array }> {
|
|
280
|
+
const now = Date.now();
|
|
281
|
+
|
|
282
|
+
const rows = this.db.prepare(`
|
|
283
|
+
SELECT hash, embedding
|
|
284
|
+
FROM embedding_cache
|
|
285
|
+
WHERE expires_at > ?
|
|
286
|
+
`).all(now) as { hash: string; embedding: Buffer }[];
|
|
287
|
+
|
|
288
|
+
return rows.map(row => ({
|
|
289
|
+
hash: row.hash,
|
|
290
|
+
embedding: this.fromBuffer(row.embedding),
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Create a persistent embedding cache
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```typescript
|
|
300
|
+
* import Database from 'better-sqlite3';
|
|
301
|
+
* import { createPersistentEmbeddingCache } from '@aitytech/agentkits-memory/embeddings';
|
|
302
|
+
*
|
|
303
|
+
* const db = new Database(':memory:');
|
|
304
|
+
*
|
|
305
|
+
* const cache = createPersistentEmbeddingCache(db, {
|
|
306
|
+
* maxSize: 10000,
|
|
307
|
+
* ttlMs: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
308
|
+
* });
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export function createPersistentEmbeddingCache(
|
|
312
|
+
db: BetterDatabase,
|
|
313
|
+
config?: PersistentEmbeddingCacheConfig
|
|
314
|
+
): PersistentEmbeddingCache {
|
|
315
|
+
return new PersistentEmbeddingCache(db, config);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export default PersistentEmbeddingCache;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Embeddings Module
|
|
3
|
+
*
|
|
4
|
+
* 100% offline text embeddings for semantic search.
|
|
5
|
+
* Uses Transformers.js (WASM) - no external API calls.
|
|
6
|
+
*
|
|
7
|
+
* @module @aitytech/agentkits-memory/embeddings
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
LocalEmbeddingsService,
|
|
12
|
+
createLocalEmbeddings,
|
|
13
|
+
createEmbeddingGenerator,
|
|
14
|
+
type LocalEmbeddingsConfig,
|
|
15
|
+
type EmbeddingProvider,
|
|
16
|
+
type EmbeddingResult,
|
|
17
|
+
type EmbeddingsStats,
|
|
18
|
+
} from './local-embeddings.js';
|
|
19
|
+
|
|
20
|
+
export { PersistentEmbeddingCache, createPersistentEmbeddingCache } from './embedding-cache.js';
|