cozo-memory 1.1.7 → 1.2.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.
@@ -322,6 +322,11 @@ class AdaptiveGraphRetrieval {
322
322
  * Main adaptive retrieval method
323
323
  */
324
324
  async retrieve(query, limit = 10) {
325
+ // Validate limit to prevent errors
326
+ if (limit <= 0) {
327
+ console.error('[AdaptiveRetrieval] Invalid limit value:', limit, '- must be positive. Defaulting to 10.');
328
+ limit = 10;
329
+ }
325
330
  // 1. Classify query complexity
326
331
  const complexity = this.classifyQueryComplexity(query);
327
332
  console.error(`[AdaptiveRetrieval] Query complexity: ${complexity}`);
@@ -338,6 +343,11 @@ class AdaptiveGraphRetrieval {
338
343
  }
339
344
  // ==================== Strategy Implementations ====================
340
345
  async vectorSearch(embedding, limit) {
346
+ // Validate limit
347
+ if (limit <= 0) {
348
+ console.error('[AdaptiveRetrieval] Invalid limit in vectorSearch:', limit);
349
+ return [];
350
+ }
341
351
  const result = await this.db.run(`
342
352
  ?[id, name, type, score] :=
343
353
  ~entity:semantic{id | query: vec($embedding), k: $limit, ef: 100, bind_distance: dist},
@@ -74,10 +74,23 @@ class DynamicFusionSearch {
74
74
  */
75
75
  async search(query, config = {}) {
76
76
  const startTime = Date.now();
77
+ // Merge config with defaults first
78
+ const fullConfig = this.mergeConfig(config);
79
+ // Validate topK values to prevent errors
80
+ if (fullConfig.vector && fullConfig.vector.topK <= 0) {
81
+ console.error('[DynamicFusion] Invalid vector.topK:', fullConfig.vector.topK, '- must be positive. Defaulting to 20.');
82
+ fullConfig.vector.topK = 20;
83
+ }
84
+ if (fullConfig.sparse && fullConfig.sparse.topK <= 0) {
85
+ console.error('[DynamicFusion] Invalid sparse.topK:', fullConfig.sparse.topK, '- must be positive. Defaulting to 20.');
86
+ fullConfig.sparse.topK = 20;
87
+ }
88
+ if (fullConfig.fts && fullConfig.fts.topK <= 0) {
89
+ console.error('[DynamicFusion] Invalid fts.topK:', fullConfig.fts.topK, '- must be positive. Defaulting to 20.');
90
+ fullConfig.fts.topK = 20;
91
+ }
77
92
  // Get adaptive weights based on query classification
78
93
  const adaptiveWeights = await this.adaptiveQueryFusion.getAdaptiveWeights(query);
79
- // Merge config with defaults first, then apply adaptive weights
80
- const fullConfig = this.mergeConfig(config);
81
94
  // Override weights with adaptive values
82
95
  fullConfig.vector.weight = adaptiveWeights.vector;
83
96
  fullConfig.sparse.weight = adaptiveWeights.sparse;
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.HybridSearch = void 0;
7
7
  const crypto_1 = __importDefault(require("crypto"));
8
8
  const reranker_service_1 = require("./reranker-service");
9
+ const logger_1 = require("./logger");
10
+ const performance_monitor_1 = require("./performance-monitor");
9
11
  const SEMANTIC_CACHE_THRESHOLD = 0.95;
10
12
  class HybridSearch {
11
13
  db;
@@ -138,22 +140,30 @@ class HybridSearch {
138
140
  }
139
141
  }
140
142
  async advancedSearch(options) {
141
- console.error("[HybridSearch] Starting advancedSearch with options:", JSON.stringify(options, null, 2));
143
+ logger_1.logger.debug('HybridSearch', 'Starting advancedSearch', { query: options.query, limit: options.limit });
142
144
  const { query, limit = 10, filters, graphConstraints, vectorParams } = options;
145
+ // Validate limit to prevent infinite loops
146
+ if (limit <= 0) {
147
+ logger_1.logger.warn('HybridSearch', `Invalid limit value: ${limit} - must be positive. Defaulting to 10.`);
148
+ options.limit = 10;
149
+ }
150
+ const endTimer = performance_monitor_1.perfMonitor.startTimer('advancedSearch');
143
151
  let queryEmbedding;
144
152
  try {
145
153
  queryEmbedding = await this.embeddingService.embed(query);
146
154
  }
147
155
  catch (e) {
148
- console.error("[HybridSearch] Embedding failed", e);
156
+ logger_1.logger.error('HybridSearch', 'Embedding failed', e);
157
+ endTimer();
149
158
  throw e;
150
159
  }
151
160
  const cachedResults = await this.tryCacheLookup(options, queryEmbedding);
152
161
  if (cachedResults !== null) {
153
- console.error("[HybridSearch] Cache hit for advancedSearch");
162
+ logger_1.logger.debug('HybridSearch', 'Cache hit for advancedSearch');
163
+ endTimer();
154
164
  return cachedResults;
155
165
  }
156
- console.error("[HybridSearch] Cache miss, executing Datalog query...");
166
+ logger_1.logger.trace('HybridSearch', 'Cache miss, executing Datalog query...');
157
167
  let topk = limit * 2;
158
168
  const hasFilters = (filters?.metadata && Object.keys(filters.metadata).length > 0) ||
159
169
  (filters?.entityTypes && filters.entityTypes.length > 0);
@@ -204,7 +214,7 @@ class HybridSearch {
204
214
  semanticCall += `, filter: ${hnswFilters.join(" && ")}`;
205
215
  }
206
216
  semanticCall += `}`;
207
- let bodyConstraints = [semanticCall, `*entity{id, name, type, metadata, created_at, @ "NOW"}`];
217
+ let bodyConstraints = [semanticCall, `*entity{id, name, type, metadata, created_at}`];
208
218
  if (metaJoins.length > 0) {
209
219
  bodyConstraints.push(...metaJoins);
210
220
  }
@@ -229,13 +239,13 @@ class HybridSearch {
229
239
  }
230
240
  const helperRules = [
231
241
  `rank_val[id, r] := *entity_rank{entity_id: id, pagerank: r}`,
232
- `rank_val[id, r] := *entity{id, @ "NOW"}, not *entity_rank{entity_id: id}, r = 0.0`
242
+ `rank_val[id, r] := *entity{id}, not *entity_rank{entity_id: id}, r = 0.0`
233
243
  ];
234
244
  if (graphConstraints?.requiredRelations && graphConstraints.requiredRelations.length > 0) {
235
- helperRules.push(`rel_match[id, rel_type] := *relationship{from_id: id, relation_type: rel_type, @ "NOW"}`, `rel_match[id, rel_type] := *relationship{to_id: id, relation_type: rel_type, @ "NOW"}`);
245
+ helperRules.push(`rel_match[id, rel_type] := *relationship{from_id: id, relation_type: rel_type}`, `rel_match[id, rel_type] := *relationship{to_id: id, relation_type: rel_type}`);
236
246
  }
237
247
  if (graphConstraints?.targetEntityIds && graphConstraints.targetEntityIds.length > 0) {
238
- helperRules.push(`target_match[id, target_id] := *relationship{from_id: id, to_id: target_id, @ "NOW"}`, `target_match[id, target_id] := *relationship{to_id: id, from_id: target_id, @ "NOW"}`);
248
+ helperRules.push(`target_match[id, target_id] := *relationship{from_id: id, to_id: target_id}`, `target_match[id, target_id] := *relationship{to_id: id, from_id: target_id}`);
239
249
  }
240
250
  const datalogQuery = [
241
251
  ...helperRules,
@@ -278,11 +288,16 @@ class HybridSearch {
278
288
  return rerankedResults;
279
289
  }
280
290
  await this.updateCache(options, queryEmbedding, finalResults);
291
+ endTimer();
281
292
  return finalResults;
282
293
  }
283
294
  catch (e) {
284
- console.error("[HybridSearch] Error in advancedSearch:", e.message);
285
- return this.search(options);
295
+ logger_1.logger.error('HybridSearch', 'Error in advancedSearch', e.message);
296
+ performance_monitor_1.perfMonitor.recordMetric('advancedSearch', 0, true);
297
+ endTimer();
298
+ // Prevent infinite recursion by returning empty results instead of calling search()
299
+ logger_1.logger.warn('HybridSearch', 'Returning empty results to prevent infinite loop');
300
+ return [];
286
301
  }
287
302
  }
288
303
  async search(options) {
@@ -308,8 +323,14 @@ class HybridSearch {
308
323
  });
309
324
  }
310
325
  async graphRag(options) {
311
- console.error("[HybridSearch] Starting graphRag with options:", JSON.stringify(options, null, 2));
326
+ logger_1.logger.debug('HybridSearch', 'Starting graphRag', { query: options.query, limit: options.limit });
312
327
  const { query, limit = 5, filters, graphConstraints } = options;
328
+ // Validate limit to prevent infinite loops
329
+ if (limit <= 0) {
330
+ logger_1.logger.warn('HybridSearch', `Invalid limit value: ${limit} - must be positive. Defaulting to 5.`);
331
+ options.limit = 5;
332
+ }
333
+ const endTimer = performance_monitor_1.perfMonitor.startTimer('graphRag');
313
334
  const maxDepth = graphConstraints?.maxDepth || 2;
314
335
  const queryEmbedding = await this.embeddingService.embed(query);
315
336
  const topk = limit * 2;
@@ -350,7 +371,7 @@ class HybridSearch {
350
371
  // 4. Calculate a combined score based on vector distance, graph distance, and PageRank
351
372
  const datalogQuery = `
352
373
  rank_val[id, r] := *entity_rank{entity_id: id, pagerank: r}
353
- rank_val[id, r] := *entity{id, @ "NOW"}, not *entity_rank{entity_id: id}, r = 0.0
374
+ rank_val[id, r] := *entity{id}, not *entity_rank{entity_id: id}, r = 0.0
354
375
 
355
376
  seeds[id, score] := ${seedConstraints.join(", ")}, score = 1.0 - dist
356
377
 
@@ -360,7 +381,7 @@ class HybridSearch {
360
381
 
361
382
  result_entities[id, final_score, depth] := path[seed_id, id, depth], seeds[seed_id, seed_score], rank_val[id, pr], final_score = seed_score * (1.0 - 0.2 * depth)
362
383
 
363
- ?[id, name, type, metadata, created_at, score, source, text] := result_entities[id, score, depth], *entity{id, name, type, metadata, created_at, @ "NOW"}, source = 'graph_rag_entity', text = ''
384
+ ?[id, name, type, metadata, created_at, score, source, text] := result_entities[id, score, depth], *entity{id, name, type, metadata, created_at}, source = 'graph_rag_entity', text = ''
364
385
 
365
386
  :sort -score
366
387
  :limit $limit
@@ -396,19 +417,31 @@ class HybridSearch {
396
417
  }
397
418
  const decayedResults = this.applyTimeDecay(searchResults);
398
419
  if (options.rerank) {
399
- return await this.applyReranking(options.query, decayedResults);
420
+ const reranked = await this.applyReranking(options.query, decayedResults);
421
+ endTimer();
422
+ return reranked;
400
423
  }
424
+ endTimer();
401
425
  return decayedResults;
402
426
  }
403
427
  catch (e) {
404
- console.error("[HybridSearch] Error in graphRag:", e.message);
405
- // Fallback to normal search on error
406
- return this.search(options);
428
+ logger_1.logger.error('HybridSearch', 'Error in graphRag', e.message);
429
+ performance_monitor_1.perfMonitor.recordMetric('graphRag', 0, true);
430
+ endTimer();
431
+ // Prevent infinite recursion by returning empty results
432
+ logger_1.logger.warn('HybridSearch', 'Returning empty results to prevent infinite loop');
433
+ return [];
407
434
  }
408
435
  }
409
436
  async agenticRetrieve(options) {
410
- console.error("[HybridSearch] Starting agenticRetrieve with query:", options.query);
437
+ logger_1.logger.debug('HybridSearch', 'Starting agenticRetrieve', { query: options.query });
411
438
  const { query, routingModel = "demyagent-4b-i1:Q6_K" } = options;
439
+ // Validate limit to prevent infinite loops
440
+ if (options.limit !== undefined && options.limit <= 0) {
441
+ logger_1.logger.warn('HybridSearch', `Invalid limit value: ${options.limit} - must be positive. Defaulting to 10.`);
442
+ options.limit = 10;
443
+ }
444
+ const endTimer = performance_monitor_1.perfMonitor.startTimer('agenticRetrieve');
412
445
  const systemPrompt = `You are a Routing Agent for an advanced Memory/RAG system.
413
446
  Your job is to analyze the user's query and decide which search strategy is the most appropriate.
414
447
  Available strategies:
package/dist/logger.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * Centralized Logging System for CozoDB Memory
4
+ *
5
+ * Supports different log levels and can be configured via environment variables
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.logger = exports.LogLevel = void 0;
9
+ var LogLevel;
10
+ (function (LogLevel) {
11
+ LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
12
+ LogLevel[LogLevel["WARN"] = 1] = "WARN";
13
+ LogLevel[LogLevel["INFO"] = 2] = "INFO";
14
+ LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
15
+ LogLevel[LogLevel["TRACE"] = 4] = "TRACE";
16
+ })(LogLevel || (exports.LogLevel = LogLevel = {}));
17
+ class Logger {
18
+ level;
19
+ prefix;
20
+ constructor(prefix = '[CozoDB]') {
21
+ this.prefix = prefix;
22
+ // Read from environment variable, default to INFO
23
+ const envLevel = process.env.LOG_LEVEL?.toUpperCase();
24
+ this.level = LogLevel[envLevel] ?? LogLevel.INFO;
25
+ }
26
+ setLevel(level) {
27
+ this.level = level;
28
+ }
29
+ error(component, message, ...args) {
30
+ if (this.level >= LogLevel.ERROR) {
31
+ console.error(`${this.prefix}[${component}] ERROR:`, message, ...args);
32
+ }
33
+ }
34
+ warn(component, message, ...args) {
35
+ if (this.level >= LogLevel.WARN) {
36
+ console.warn(`${this.prefix}[${component}] WARN:`, message, ...args);
37
+ }
38
+ }
39
+ info(component, message, ...args) {
40
+ if (this.level >= LogLevel.INFO) {
41
+ console.error(`${this.prefix}[${component}] INFO:`, message, ...args);
42
+ }
43
+ }
44
+ debug(component, message, ...args) {
45
+ if (this.level >= LogLevel.DEBUG) {
46
+ console.error(`${this.prefix}[${component}] DEBUG:`, message, ...args);
47
+ }
48
+ }
49
+ trace(component, message, ...args) {
50
+ if (this.level >= LogLevel.TRACE) {
51
+ console.error(`${this.prefix}[${component}] TRACE:`, message, ...args);
52
+ }
53
+ }
54
+ }
55
+ // Singleton instance
56
+ exports.logger = new Logger();
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Migration Script: Replace console.error with logger
4
+ *
5
+ * This script helps migrate from console.error to the centralized logger
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ const fs = __importStar(require("fs"));
42
+ const filesToMigrate = [
43
+ 'src/memory-service.ts',
44
+ 'src/db-service.ts',
45
+ 'src/embedding-service.ts',
46
+ 'src/inference-engine.ts',
47
+ 'src/dynamic-fusion.ts',
48
+ 'src/adaptive-retrieval.ts',
49
+ 'src/adaptive-query-fusion.ts',
50
+ 'src/reranker-service.ts',
51
+ 'src/export-import-service.ts',
52
+ 'src/janitor-service.ts'
53
+ ];
54
+ // Mapping of console.error patterns to logger calls
55
+ const migrations = [
56
+ {
57
+ pattern: /console\.error\(\s*\[([^\]]+)\]\s+([^,]+),/g,
58
+ replacement: "logger.error('$1', $2,"
59
+ },
60
+ {
61
+ pattern: /console\.error\(\s*\[([^\]]+)\]\s+([^)]+)\)/g,
62
+ replacement: "logger.error('$1', $2)"
63
+ },
64
+ {
65
+ pattern: /console\.warn\(\s*\[([^\]]+)\]\s+([^)]+)\)/g,
66
+ replacement: "logger.warn('$1', $2)"
67
+ }
68
+ ];
69
+ function migrateFile(filePath) {
70
+ if (!fs.existsSync(filePath)) {
71
+ console.log(`Skipping ${filePath} - file not found`);
72
+ return;
73
+ }
74
+ let content = fs.readFileSync(filePath, 'utf-8');
75
+ let modified = false;
76
+ // Check if logger is already imported
77
+ if (!content.includes("import { logger }")) {
78
+ // Find the last import statement
79
+ const importRegex = /^import .+ from .+;$/gm;
80
+ const imports = content.match(importRegex);
81
+ if (imports && imports.length > 0) {
82
+ const lastImport = imports[imports.length - 1];
83
+ const lastImportIndex = content.lastIndexOf(lastImport);
84
+ content = content.slice(0, lastImportIndex + lastImport.length) +
85
+ "\nimport { logger } from './logger';" +
86
+ content.slice(lastImportIndex + lastImport.length);
87
+ modified = true;
88
+ }
89
+ }
90
+ // Apply migrations
91
+ for (const migration of migrations) {
92
+ if (migration.pattern.test(content)) {
93
+ content = content.replace(migration.pattern, migration.replacement);
94
+ modified = true;
95
+ }
96
+ }
97
+ if (modified) {
98
+ fs.writeFileSync(filePath, content, 'utf-8');
99
+ console.log(`✓ Migrated ${filePath}`);
100
+ }
101
+ else {
102
+ console.log(`- No changes needed for ${filePath}`);
103
+ }
104
+ }
105
+ console.log('Starting logging migration...\n');
106
+ for (const file of filesToMigrate) {
107
+ migrateFile(file);
108
+ }
109
+ console.log('\nMigration complete!');
110
+ console.log('\nNext steps:');
111
+ console.log('1. Review the changes');
112
+ console.log('2. Run: npm run build');
113
+ console.log('3. Test with: LOG_LEVEL=DEBUG npm start');
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /**
3
+ * Performance Monitoring System
4
+ *
5
+ * Tracks operation latencies, throughput, and resource usage
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.perfMonitor = exports.PerformanceMonitor = void 0;
9
+ const logger_1 = require("./logger");
10
+ class PerformanceMonitor {
11
+ metrics;
12
+ maxSamples;
13
+ constructor(maxSamples = 1000) {
14
+ this.metrics = new Map();
15
+ this.maxSamples = maxSamples;
16
+ }
17
+ /**
18
+ * Start timing an operation
19
+ */
20
+ startTimer(operation) {
21
+ const startTime = Date.now();
22
+ return () => {
23
+ const duration = Date.now() - startTime;
24
+ this.recordMetric(operation, duration);
25
+ };
26
+ }
27
+ /**
28
+ * Record a metric manually
29
+ */
30
+ recordMetric(operation, duration, isError = false) {
31
+ let metric = this.metrics.get(operation);
32
+ if (!metric) {
33
+ metric = {
34
+ times: [],
35
+ errors: 0,
36
+ lastExecuted: Date.now()
37
+ };
38
+ this.metrics.set(operation, metric);
39
+ }
40
+ metric.times.push(duration);
41
+ metric.lastExecuted = Date.now();
42
+ if (isError) {
43
+ metric.errors++;
44
+ }
45
+ // Keep only last N samples
46
+ if (metric.times.length > this.maxSamples) {
47
+ metric.times.shift();
48
+ }
49
+ }
50
+ /**
51
+ * Get metrics for a specific operation
52
+ */
53
+ getMetrics(operation) {
54
+ const metric = this.metrics.get(operation);
55
+ if (!metric || metric.times.length === 0) {
56
+ return null;
57
+ }
58
+ const sorted = [...metric.times].sort((a, b) => a - b);
59
+ const count = sorted.length;
60
+ const totalTime = sorted.reduce((sum, t) => sum + t, 0);
61
+ return {
62
+ operation,
63
+ count,
64
+ totalTime,
65
+ avgTime: totalTime / count,
66
+ minTime: sorted[0],
67
+ maxTime: sorted[count - 1],
68
+ p50: sorted[Math.floor(count * 0.5)],
69
+ p95: sorted[Math.floor(count * 0.95)],
70
+ p99: sorted[Math.floor(count * 0.99)],
71
+ errors: metric.errors,
72
+ lastExecuted: metric.lastExecuted
73
+ };
74
+ }
75
+ /**
76
+ * Get all metrics
77
+ */
78
+ getAllMetrics() {
79
+ const results = [];
80
+ for (const operation of this.metrics.keys()) {
81
+ const metric = this.getMetrics(operation);
82
+ if (metric) {
83
+ results.push(metric);
84
+ }
85
+ }
86
+ return results.sort((a, b) => b.count - a.count);
87
+ }
88
+ /**
89
+ * Log performance summary
90
+ */
91
+ logSummary() {
92
+ const metrics = this.getAllMetrics();
93
+ logger_1.logger.info('PerformanceMonitor', '=== Performance Summary ===');
94
+ for (const m of metrics) {
95
+ logger_1.logger.info('PerformanceMonitor', `${m.operation}: ${m.count} calls, avg=${m.avgTime.toFixed(2)}ms, ` +
96
+ `p95=${m.p95.toFixed(2)}ms, errors=${m.errors}`);
97
+ }
98
+ }
99
+ /**
100
+ * Reset all metrics
101
+ */
102
+ reset() {
103
+ this.metrics.clear();
104
+ }
105
+ }
106
+ exports.PerformanceMonitor = PerformanceMonitor;
107
+ // Singleton instance
108
+ exports.perfMonitor = new PerformanceMonitor();