cozo-memory 1.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.
Files changed (49) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +533 -0
  3. package/dist/api_bridge.js +266 -0
  4. package/dist/benchmark-gpu-cpu.js +188 -0
  5. package/dist/benchmark-heavy.js +230 -0
  6. package/dist/benchmark.js +160 -0
  7. package/dist/clear-cache.js +29 -0
  8. package/dist/db-service.js +228 -0
  9. package/dist/download-model.js +48 -0
  10. package/dist/embedding-service.js +249 -0
  11. package/dist/full-system-test.js +45 -0
  12. package/dist/hybrid-search.js +337 -0
  13. package/dist/index.js +3106 -0
  14. package/dist/inference-engine.js +348 -0
  15. package/dist/memory-service.js +215 -0
  16. package/dist/test-advanced-filters.js +64 -0
  17. package/dist/test-advanced-search.js +82 -0
  18. package/dist/test-advanced-time.js +47 -0
  19. package/dist/test-embedding.js +22 -0
  20. package/dist/test-filter-expr.js +84 -0
  21. package/dist/test-fts.js +58 -0
  22. package/dist/test-functions.js +25 -0
  23. package/dist/test-gpu-check.js +16 -0
  24. package/dist/test-graph-algs-final.js +76 -0
  25. package/dist/test-graph-filters.js +88 -0
  26. package/dist/test-graph-rag.js +124 -0
  27. package/dist/test-graph-walking.js +138 -0
  28. package/dist/test-index.js +35 -0
  29. package/dist/test-int-filter.js +48 -0
  30. package/dist/test-integration.js +69 -0
  31. package/dist/test-lower.js +35 -0
  32. package/dist/test-lsh.js +67 -0
  33. package/dist/test-mcp-tool.js +40 -0
  34. package/dist/test-pagerank.js +31 -0
  35. package/dist/test-semantic-walk.js +145 -0
  36. package/dist/test-time-filter.js +66 -0
  37. package/dist/test-time-functions.js +38 -0
  38. package/dist/test-triggers.js +60 -0
  39. package/dist/test-ts-ort.js +48 -0
  40. package/dist/test-validity-access.js +35 -0
  41. package/dist/test-validity-body.js +42 -0
  42. package/dist/test-validity-decomp.js +37 -0
  43. package/dist/test-validity-extraction.js +45 -0
  44. package/dist/test-validity-json.js +35 -0
  45. package/dist/test-validity.js +38 -0
  46. package/dist/types.js +3 -0
  47. package/dist/verify-gpu.js +30 -0
  48. package/dist/verify_transaction_tool.js +46 -0
  49. package/package.json +75 -0
@@ -0,0 +1,348 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InferenceEngine = void 0;
4
+ class InferenceEngine {
5
+ db;
6
+ embeddingService;
7
+ constructor(db, embeddingService) {
8
+ this.db = db;
9
+ this.embeddingService = embeddingService;
10
+ }
11
+ /**
12
+ * Infers relations for a specific entity based on various strategies
13
+ */
14
+ async inferRelations(entityId) {
15
+ const results = [];
16
+ // 1. Co-occurrence in Observations (same entity mentioned in texts)
17
+ const coOccurrences = await this.findCoOccurrences(entityId);
18
+ results.push(...coOccurrences);
19
+ // 2. Vector Proximity (Semantic Similarity)
20
+ const similar = await this.findSimilarEntities(entityId);
21
+ results.push(...similar);
22
+ // 3. Transitive Relations (A -> B, B -> C => A -> C)
23
+ const transitive = await this.findTransitiveRelations(entityId);
24
+ results.push(...transitive);
25
+ const expertise = await this.findTransitiveExpertise(entityId);
26
+ results.push(...expertise);
27
+ const custom = await this.applyCustomRules(entityId);
28
+ results.push(...custom);
29
+ return results;
30
+ }
31
+ async inferImplicitRelations(entityId) {
32
+ const expertise = await this.findTransitiveExpertise(entityId);
33
+ const custom = await this.applyCustomRules(entityId);
34
+ return [...expertise, ...custom];
35
+ }
36
+ async findCoOccurrences(entityId) {
37
+ try {
38
+ // Search for entities whose names appear in the observations of the target entity
39
+ // Or entities that reference the same observation (if we had multi-entity observations)
40
+ // Here: Search for entities whose name appears in the text of the observations of entityId
41
+ const query = `
42
+ ?[other_id, other_name, confidence, reason] :=
43
+ *observation{entity_id: $target, text, @ "NOW"},
44
+ *entity{id: other_id, name: other_name, @ "NOW"},
45
+ other_id != $target,
46
+ str_includes(text, other_name),
47
+ confidence = 0.7,
48
+ reason = concat('Mention of ', other_name, ' in observations')
49
+ `;
50
+ const res = await this.db.run(query, { target: entityId });
51
+ return res.rows.map((row) => ({
52
+ from_id: entityId,
53
+ to_id: row[0],
54
+ relation_type: "related_to",
55
+ confidence: row[2],
56
+ reason: row[3]
57
+ }));
58
+ }
59
+ catch (e) {
60
+ console.error("Inference: Co-occurrence failed", e);
61
+ return [];
62
+ }
63
+ }
64
+ async findSimilarEntities(entityId) {
65
+ try {
66
+ // Get embedding of the target entity
67
+ const entityRes = await this.db.run('?[embedding] := *entity{id: $id, embedding, @ "NOW"}', { id: entityId });
68
+ if (entityRes.rows.length === 0)
69
+ return [];
70
+ const embedding = entityRes.rows[0][0];
71
+ // Search semantically similar entities via HNSW
72
+ const query = `
73
+ ?[other_id, other_name, confidence, reason] :=
74
+ ~entity:semantic { id: other_id, embedding |
75
+ query: vec($emb),
76
+ k: 5,
77
+ ef: 64,
78
+ bind_distance: dist
79
+ },
80
+ *entity{id: other_id, name: other_name, @ "NOW"},
81
+ other_id != $id,
82
+ dist < 0.2,
83
+ confidence = (1.0 - dist) * 0.9,
84
+ reason = 'Semantic Similarity'
85
+ `;
86
+ const res = await this.db.run(query, { id: entityId, emb: embedding });
87
+ return res.rows.map((row) => ({
88
+ from_id: entityId,
89
+ to_id: row[0],
90
+ relation_type: "similar_to",
91
+ confidence: row[2],
92
+ reason: row[3]
93
+ }));
94
+ }
95
+ catch (e) {
96
+ console.error("Inference: Similarity failed", e);
97
+ return [];
98
+ }
99
+ }
100
+ async findTransitiveRelations(entityId) {
101
+ try {
102
+ // Finds A -> B -> C and proposes A -> C
103
+ const query = `
104
+ ?[target_id, target_name, confidence, reason] :=
105
+ *relationship{from_id: $id, to_id: mid_id, relation_type: r1, @ "NOW"},
106
+ *relationship{from_id: mid_id, to_id: target_id, relation_type: r2, @ "NOW"},
107
+ *entity{id: target_id, name: target_name, @ "NOW"},
108
+ target_id != $id,
109
+ confidence = 0.5,
110
+ reason = concat('Indirect connection via ', r1, ' and ', r2)
111
+ `;
112
+ const res = await this.db.run(query, { id: entityId });
113
+ return res.rows.map((row) => ({
114
+ from_id: entityId,
115
+ to_id: row[0],
116
+ relation_type: "potentially_related",
117
+ confidence: row[2],
118
+ reason: row[3]
119
+ }));
120
+ }
121
+ catch (e) {
122
+ console.error("Inference: Transitive failed", e);
123
+ return [];
124
+ }
125
+ }
126
+ async findTransitiveExpertise(entityId) {
127
+ try {
128
+ const query = `
129
+ ?[tech_id, tech_name, tech_type, confidence, reason] :=
130
+ *entity{id: $id, type: 'Person', @ "NOW"},
131
+ *relationship{from_id: $id, to_id: proj_id, relation_type: 'works_on', @ "NOW"},
132
+ *relationship{from_id: proj_id, to_id: tech_id, relation_type: 'uses_tech', @ "NOW"},
133
+ *entity{id: tech_id, name: tech_name, type: tech_type, @ "NOW"},
134
+ confidence = 0.7,
135
+ reason = concat('Transitive expertise via works_on -> uses_tech: ', tech_name)
136
+ `;
137
+ const res = await this.db.run(query, { id: entityId });
138
+ const bestByTarget = new Map();
139
+ for (const row of res.rows) {
140
+ const to_id = row[0];
141
+ const confidence = Number(row[3]);
142
+ const candidate = {
143
+ from_id: entityId,
144
+ to_id,
145
+ relation_type: "expert_in",
146
+ confidence,
147
+ reason: row[4],
148
+ target_name: row[1],
149
+ target_type: row[2],
150
+ };
151
+ const current = bestByTarget.get(to_id);
152
+ if (!current || confidence > current.confidence)
153
+ bestByTarget.set(to_id, candidate);
154
+ }
155
+ return Array.from(bestByTarget.values());
156
+ }
157
+ catch (e) {
158
+ console.error("Inference: Transitive expertise failed", e);
159
+ return [];
160
+ }
161
+ }
162
+ /**
163
+ * Performs a recursive "Graph Walk" that follows both explicit relations and
164
+ * semantic similarity. This enables finding paths like:
165
+ * A -> (explicit) -> B -> (semantically similar) -> C -> (explicit) -> D
166
+ *
167
+ * @param startEntityId The starting entity
168
+ * @param maxDepth Maximum depth of the search (Default: 3)
169
+ * @param minSimilarity Minimum similarity for semantic jumps (0.0 - 1.0, Default: 0.7)
170
+ */
171
+ async semanticGraphWalk(startEntityId, maxDepth = 3, minSimilarity = 0.7) {
172
+ try {
173
+ // Get embedding of the start entity for the first semantic jump
174
+ const entityRes = await this.db.run('?[embedding] := *entity{id: $id, embedding, @ "NOW"}', { id: startEntityId });
175
+ if (entityRes.rows.length === 0)
176
+ return [];
177
+ const startEmbedding = entityRes.rows[0][0];
178
+ // Recursive Datalog query
179
+ // We avoid complex aggregation of strings in Datalog as this can cause errors.
180
+ // Instead, we implicitly group by 'type' as well and filter later in JS.
181
+ const query = `
182
+ # 1. Start point
183
+ path[id, depth, score, type] :=
184
+ id = $startId,
185
+ depth = 0,
186
+ score = 1.0,
187
+ type = 'start'
188
+
189
+ # 2. Recursion: Follow explicit relations
190
+ path[next_id, new_depth, new_score, new_type] :=
191
+ path[curr_id, depth, score, curr_type],
192
+ depth < $maxDepth,
193
+ *relationship{from_id: curr_id, to_id: next_id, relation_type, strength, @ "NOW"},
194
+ new_depth = depth + 1,
195
+ new_score = score * strength,
196
+ new_type = if(curr_type == 'start', 'explicit', if(curr_type == 'explicit', 'explicit', 'mixed'))
197
+
198
+ # 3. Recursion: Follow semantic similarity (via HNSW Index)
199
+ path[next_id, new_depth, new_score, new_type] :=
200
+ path[curr_id, depth, score, curr_type],
201
+ depth < $maxDepth,
202
+ *entity{id: curr_id, embedding: curr_emb, @ "NOW"}, # Load embedding
203
+ # Search for the K nearest neighbors to the current embedding
204
+ ~entity:semantic { id: next_id |
205
+ query: curr_emb,
206
+ k: 5,
207
+ ef: 20,
208
+ bind_distance: dist
209
+ },
210
+ next_id != curr_id, # No self-reference
211
+ sim = 1.0 - dist,
212
+ sim >= $minSim,
213
+ new_depth = depth + 1,
214
+ new_score = score * sim * 0.8, # Penalize semantic jumps slightly (damping)
215
+ new_type = if(curr_type == 'start', 'semantic', if(curr_type == 'semantic', 'semantic', 'mixed'))
216
+
217
+ # Aggregate result (Grouping by ID and Type)
218
+ ?[id, min_depth, max_score, type] :=
219
+ path[id, d, s, type],
220
+ id != $startId,
221
+ min_depth = min(d),
222
+ max_score = max(s)
223
+ :limit 100
224
+ `;
225
+ const res = await this.db.run(query, {
226
+ startId: startEntityId,
227
+ maxDepth: maxDepth,
228
+ minSim: minSimilarity
229
+ });
230
+ // Post-processing in JS: Select best path type per ID
231
+ const bestPaths = new Map();
232
+ for (const row of res.rows) {
233
+ const [id, depth, score, type] = row;
234
+ // Cozo sometimes returns arrays or raw values, ensure we have Strings/Numbers
235
+ const cleanId = String(id);
236
+ const cleanDepth = Number(depth);
237
+ const cleanScore = Number(score);
238
+ const cleanType = String(type);
239
+ if (!bestPaths.has(cleanId) || cleanScore > bestPaths.get(cleanId).path_score) {
240
+ bestPaths.set(cleanId, {
241
+ entity_id: cleanId,
242
+ distance: cleanDepth,
243
+ path_score: cleanScore,
244
+ path_type: cleanType
245
+ });
246
+ }
247
+ }
248
+ return Array.from(bestPaths.values());
249
+ }
250
+ catch (e) {
251
+ console.error("Semantic Graph Walk Failed:", e.message);
252
+ return [];
253
+ }
254
+ }
255
+ /**
256
+ * Analyzes the cluster structure directly on the HNSW graph (Layer 0).
257
+ * This is extremely fast because no vector calculations (K-Means) are necessary,
258
+ * but the already indexed neighborhood topology is used.
259
+ */
260
+ async analyzeHnswClusters() {
261
+ try {
262
+ const query = `
263
+ # 1. Extract edges from HNSW Layer 0 (Base Layer)
264
+ # We use unweighted edges (weight=1.0) for pure topology analysis
265
+ hnsw_edges[from, to, weight] :=
266
+ *entity:semantic{layer: 0, fr_id: from, to_id: to},
267
+ from != to,
268
+ weight = 1.0
269
+
270
+ # 2. Run Label Propagation on the HNSW graph
271
+ # This finds natural density clusters in the vector space
272
+ communities[cluster_id, node_id] <~ LabelPropagation(hnsw_edges[from, to, weight])
273
+
274
+ # 3. Get names for the nodes
275
+ cluster_nodes[cluster_id, name] :=
276
+ communities[cluster_id, node_id],
277
+ *entity{id: node_id, name, @ "NOW"}
278
+
279
+ # 4. Aggregate results: size and examples per cluster
280
+ # We get the raw data and aggregate in JS to avoid string aggregation problems
281
+ ?[cluster_id, name] :=
282
+ cluster_nodes[cluster_id, name]
283
+ `;
284
+ const res = await this.db.run(query);
285
+ // Post-Processing: Aggregation in JS
286
+ const clusterMap = new Map();
287
+ for (const row of res.rows) {
288
+ const clusterId = Number(row[0]);
289
+ const name = row[1];
290
+ if (!clusterMap.has(clusterId)) {
291
+ clusterMap.set(clusterId, { size: 0, examples: [] });
292
+ }
293
+ const entry = clusterMap.get(clusterId);
294
+ entry.size++;
295
+ if (entry.examples.length < 5) {
296
+ entry.examples.push(name);
297
+ }
298
+ }
299
+ const clusters = Array.from(clusterMap.entries()).map(([id, data]) => ({
300
+ cluster_id: id,
301
+ size: data.size,
302
+ examples: data.examples
303
+ }));
304
+ // Sort by cluster size descending
305
+ return clusters.sort((a, b) => b.size - a.size);
306
+ }
307
+ catch (e) {
308
+ console.error("HNSW Cluster Analysis Failed:", e.message);
309
+ return [];
310
+ }
311
+ }
312
+ async applyCustomRules(entityId) {
313
+ try {
314
+ const rulesRes = await this.db.run('?[id, name, datalog] := *inference_rule{id, name, datalog}');
315
+ if (rulesRes.rows.length === 0)
316
+ return [];
317
+ const results = [];
318
+ for (const row of rulesRes.rows) {
319
+ const ruleId = String(row[0]);
320
+ const ruleName = String(row[1]);
321
+ const datalog = String(row[2]);
322
+ try {
323
+ const res = await this.db.run(datalog, { id: entityId });
324
+ for (const r of res.rows) {
325
+ if (!r || r.length < 5)
326
+ continue;
327
+ results.push({
328
+ from_id: String(r[0]),
329
+ to_id: String(r[1]),
330
+ relation_type: String(r[2]),
331
+ confidence: Number(r[3]),
332
+ reason: String(r[4]),
333
+ });
334
+ }
335
+ }
336
+ catch (e) {
337
+ console.error("Inference: Custom rule failed", ruleId, ruleName, e);
338
+ }
339
+ }
340
+ return results;
341
+ }
342
+ catch (e) {
343
+ console.error("Inference: Custom rules load failed", e);
344
+ return [];
345
+ }
346
+ }
347
+ }
348
+ exports.InferenceEngine = InferenceEngine;
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryService = void 0;
4
+ const uuid_1 = require("uuid");
5
+ class MemoryService {
6
+ db;
7
+ embeddings;
8
+ constructor(db, embeddings) {
9
+ this.db = db;
10
+ this.embeddings = embeddings;
11
+ }
12
+ async createEntity(name, type, metadata = {}) {
13
+ const id = (0, uuid_1.v4)();
14
+ const nameEmbedding = await this.embeddings.embed(name);
15
+ const contentText = metadata.description ? name + '. ' + metadata.description : name;
16
+ const contentEmbedding = await this.embeddings.embed(contentText);
17
+ const entity = {
18
+ id: id,
19
+ name: name,
20
+ type: type,
21
+ embedding: contentEmbedding,
22
+ name_embedding: nameEmbedding,
23
+ metadata: metadata,
24
+ created_at: Date.now(),
25
+ };
26
+ await this.db.createEntity(entity);
27
+ return entity;
28
+ }
29
+ async getEntity(id, asOf) {
30
+ return this.db.getEntity(id, asOf);
31
+ }
32
+ async updateEntity(id, updates) {
33
+ await this.db.updateEntity(id, updates);
34
+ }
35
+ async deleteEntity(id) {
36
+ await this.db.deleteEntity(id);
37
+ }
38
+ async addObservation(entityId, text, metadata = {}) {
39
+ const id = (0, uuid_1.v4)();
40
+ const embedding = await this.embeddings.embed(text);
41
+ const observation = {
42
+ id: id,
43
+ entity_id: entityId,
44
+ text: text,
45
+ embedding: embedding,
46
+ metadata: metadata,
47
+ created_at: Date.now(),
48
+ };
49
+ await this.db.addObservation(observation);
50
+ return observation;
51
+ }
52
+ async getObservations(entityId) {
53
+ return this.db.getObservationsForEntity(entityId);
54
+ }
55
+ async createRelation(fromId, toId, relationType, strength = 1.0, metadata = {}) {
56
+ if (fromId === toId) {
57
+ throw new Error('Self-references are not allowed');
58
+ }
59
+ const relation = {
60
+ from_id: fromId,
61
+ to_id: toId,
62
+ relation_type: relationType,
63
+ strength: strength,
64
+ metadata: metadata,
65
+ created_at: Date.now(),
66
+ };
67
+ await this.db.createRelation(relation);
68
+ return relation;
69
+ }
70
+ async getRelations(fromId, toId) {
71
+ return this.db.getRelations(fromId, toId);
72
+ }
73
+ async search(query, limit = 10, entityTypes) {
74
+ const queryEmbedding = await this.embeddings.embed(query);
75
+ const vectorResults = await this.db.vectorSearchEntity(queryEmbedding, limit * 2);
76
+ const keywordResults = await this.db.fullTextSearchEntity(query, limit * 2);
77
+ const combined = this.reciprocalRankFusion(vectorResults, keywordResults, limit);
78
+ if (entityTypes && entityTypes.length > 0) {
79
+ return combined.filter(r => entityTypes.includes(r.entity.type));
80
+ }
81
+ return combined;
82
+ }
83
+ reciprocalRankFusion(vectorResults, keywordResults, limit, k = 60) {
84
+ const scores = new Map();
85
+ vectorResults.forEach((row, index) => {
86
+ const id = row[0];
87
+ const rank = index + 1;
88
+ const rrfScore = 1 / (k + rank);
89
+ if (!scores.has(id)) {
90
+ scores.set(id, {
91
+ entity: {
92
+ id: row[0],
93
+ name: row[1],
94
+ type: row[2],
95
+ metadata: row[3],
96
+ },
97
+ score: 0,
98
+ sources: new Set(),
99
+ });
100
+ }
101
+ const entry = scores.get(id);
102
+ if (entry) {
103
+ entry.score += rrfScore;
104
+ entry.sources.add('vector');
105
+ }
106
+ });
107
+ keywordResults.forEach((row, index) => {
108
+ const id = row[0];
109
+ const rank = index + 1;
110
+ const rrfScore = 1 / (k + rank);
111
+ if (!scores.has(id)) {
112
+ scores.set(id, {
113
+ entity: {
114
+ id: row[0],
115
+ name: row[1],
116
+ type: row[2],
117
+ metadata: row[3],
118
+ },
119
+ score: 0,
120
+ sources: new Set(),
121
+ });
122
+ }
123
+ const entry = scores.get(id);
124
+ if (entry) {
125
+ entry.score += rrfScore;
126
+ entry.sources.add('keyword');
127
+ }
128
+ });
129
+ const results = Array.from(scores.entries()).map(([id, data]) => ({
130
+ entity: data.entity,
131
+ score: data.score,
132
+ source: data.sources.has('vector') && data.sources.has('keyword')
133
+ ? 'vector'
134
+ : (data.sources.has('vector') ? 'vector' : 'keyword'),
135
+ }));
136
+ results.sort((a, b) => b.score - a.score);
137
+ return results.slice(0, limit);
138
+ }
139
+ async getContext(query, contextWindow = 20) {
140
+ const searchResults = await this.search(query, contextWindow);
141
+ const entitiesWithObservations = await Promise.all(searchResults.map(async (result) => {
142
+ const observations = await this.getObservations(result.entity.id);
143
+ const relations = await this.getRelations(result.entity.id);
144
+ return {
145
+ ...result,
146
+ observations: observations.slice(0, 5),
147
+ relations: relations.slice(0, 5),
148
+ };
149
+ }));
150
+ return {
151
+ query: query,
152
+ entities: entitiesWithObservations,
153
+ total_entities: searchResults.length,
154
+ };
155
+ }
156
+ async health() {
157
+ const stats = await this.db.getStats();
158
+ const cacheStats = this.embeddings.getCacheStats();
159
+ return {
160
+ status: 'healthy',
161
+ database: stats,
162
+ cache: cacheStats,
163
+ timestamp: new Date().toISOString(),
164
+ };
165
+ }
166
+ async createSnapshot(metadata = {}) {
167
+ const snapshotId = (0, uuid_1.v4)();
168
+ const stats = await this.db.getStats();
169
+ console.error('[MemoryService] Snapshot created:', snapshotId, stats);
170
+ return snapshotId;
171
+ }
172
+ async ingestFile(content, format, entityName, entityType = 'Document', chunking = 'paragraphs') {
173
+ const searchResults = await this.search(entityName, 1);
174
+ let entity;
175
+ if (searchResults.length > 0 && searchResults[0].entity.name.toLowerCase() === entityName.toLowerCase()) {
176
+ entity = searchResults[0].entity;
177
+ }
178
+ else {
179
+ entity = await this.createEntity(entityName, entityType, { format: format });
180
+ }
181
+ let chunks = [];
182
+ if (format === 'markdown' && chunking === 'paragraphs') {
183
+ chunks = content.split(/\n\s*\n/).filter((c) => c.trim().length > 0);
184
+ }
185
+ else if (format === 'json') {
186
+ try {
187
+ const data = JSON.parse(content);
188
+ chunks = [JSON.stringify(data, null, 2)];
189
+ }
190
+ catch (e) {
191
+ chunks = [content];
192
+ }
193
+ }
194
+ else {
195
+ chunks = [content];
196
+ }
197
+ const observations = [];
198
+ const maxChunks = Math.min(chunks.length, 50);
199
+ for (let i = 0; i < maxChunks; i++) {
200
+ const chunk = chunks[i];
201
+ const obs = await this.addObservation(entity.id, chunk, {
202
+ chunk_index: i,
203
+ total_chunks: chunks.length,
204
+ });
205
+ observations.push(obs);
206
+ }
207
+ return {
208
+ entity_id: entity.id,
209
+ entity_name: entity.name,
210
+ chunks_processed: observations.length,
211
+ total_chunks: chunks.length,
212
+ };
213
+ }
214
+ }
215
+ exports.MemoryService = MemoryService;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const cozo_node_1 = require("cozo-node");
7
+ const hybrid_search_1 = require("./hybrid-search");
8
+ const embedding_service_1 = require("./embedding-service");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ async function testAdvancedSearchFilters() {
11
+ const dbPath = "test-advanced-filters.db";
12
+ if (fs_1.default.existsSync(dbPath)) {
13
+ try {
14
+ fs_1.default.unlinkSync(dbPath);
15
+ }
16
+ catch (e) { }
17
+ }
18
+ const db = new cozo_node_1.CozoDb("sqlite", dbPath);
19
+ const embeddingService = new embedding_service_1.EmbeddingService();
20
+ // Setup schema
21
+ const EMBEDDING_DIM = 1024;
22
+ await db.run(`{:create entity {id: String, created_at: Validity => name: String, type: String, embedding: <F32; ${EMBEDDING_DIM}>, metadata: Json}}`);
23
+ await db.run(`{::hnsw create entity:semantic {dim: ${EMBEDDING_DIM}, m: 16, dtype: F32, fields: [embedding], distance: Cosine, ef_construction: 200}}`);
24
+ await db.run(`{:create entity_rank {entity_id: String => pagerank: Float}}`);
25
+ const hybridSearch = new hybrid_search_1.HybridSearch(db, embeddingService);
26
+ // Insert some data
27
+ const now = Date.now();
28
+ const vec = new Array(EMBEDDING_DIM).fill(0.1);
29
+ const data = [
30
+ ['id1', [now, true], 'Alice', 'Person', vec, { role: 'Developer', level: 5 }],
31
+ ['id2', [now, true], 'Bob', 'Person', vec, { role: 'Manager', level: 8 }],
32
+ ['id3', [now, true], 'Project X', 'Project', vec, { status: 'active' }]
33
+ ];
34
+ await db.run(`?[id, created_at, name, type, embedding, metadata] <- $data :put entity {id, created_at => name, type, embedding, metadata}`, { data });
35
+ await db.run(`?[entity_id, pagerank] <- [['id1', 0.5], ['id2', 0.5], ['id3', 0.5]] :put entity_rank {entity_id => pagerank}`);
36
+ console.log("--- Testing Filter: Entity Type 'Person' ---");
37
+ const res1 = await hybridSearch.advancedSearch({
38
+ query: "test",
39
+ filters: { entityTypes: ["Person"] }
40
+ });
41
+ console.log("Results 1 (Person):", res1.map(r => r.id));
42
+ console.log("\n--- Testing Filter: Metadata role='Manager' ---");
43
+ const res2 = await hybridSearch.advancedSearch({
44
+ query: "test",
45
+ filters: { metadata: { role: "Manager" } }
46
+ });
47
+ console.log("Results 2 (Manager):", res2.map(r => r.id));
48
+ console.log("\n--- Testing Filter: Metadata level=5 ---");
49
+ const res3 = await hybridSearch.advancedSearch({
50
+ query: "test",
51
+ filters: { metadata: { level: 5 } }
52
+ });
53
+ console.log("Results 3 (Level 5):", res3.map(r => r.id));
54
+ console.log("\n--- Testing Multi-Filter: Type 'Person' + role='Developer' ---");
55
+ const res4 = await hybridSearch.advancedSearch({
56
+ query: "test",
57
+ filters: {
58
+ entityTypes: ["Person"],
59
+ metadata: { role: "Developer" }
60
+ }
61
+ });
62
+ console.log("Results 4 (Dev Person):", res4.map(r => r.id));
63
+ }
64
+ testAdvancedSearchFilters();