@henrychong-ai/mcp-neo4j-knowledge-graph 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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +718 -0
  3. package/dist/KnowledgeGraphManager.d.ts +215 -0
  4. package/dist/KnowledgeGraphManager.js +910 -0
  5. package/dist/KnowledgeGraphManager.js.map +1 -0
  6. package/dist/callToolHandler.d.ts +5 -0
  7. package/dist/callToolHandler.js +26 -0
  8. package/dist/callToolHandler.js.map +1 -0
  9. package/dist/cli/neo4j-setup.d.ts +52 -0
  10. package/dist/cli/neo4j-setup.js +258 -0
  11. package/dist/cli/neo4j-setup.js.map +1 -0
  12. package/dist/config/paths.d.ts +13 -0
  13. package/dist/config/paths.js +41 -0
  14. package/dist/config/paths.js.map +1 -0
  15. package/dist/config/storage.d.ts +35 -0
  16. package/dist/config/storage.js +52 -0
  17. package/dist/config/storage.js.map +1 -0
  18. package/dist/embeddings/DefaultEmbeddingService.d.ts +64 -0
  19. package/dist/embeddings/DefaultEmbeddingService.js +139 -0
  20. package/dist/embeddings/DefaultEmbeddingService.js.map +1 -0
  21. package/dist/embeddings/EmbeddingJobManager.d.ts +212 -0
  22. package/dist/embeddings/EmbeddingJobManager.js +545 -0
  23. package/dist/embeddings/EmbeddingJobManager.js.map +1 -0
  24. package/dist/embeddings/EmbeddingService.d.ts +96 -0
  25. package/dist/embeddings/EmbeddingService.js +44 -0
  26. package/dist/embeddings/EmbeddingService.js.map +1 -0
  27. package/dist/embeddings/EmbeddingServiceFactory.d.ts +72 -0
  28. package/dist/embeddings/EmbeddingServiceFactory.js +147 -0
  29. package/dist/embeddings/EmbeddingServiceFactory.js.map +1 -0
  30. package/dist/embeddings/OpenAIEmbeddingService.d.ts +73 -0
  31. package/dist/embeddings/OpenAIEmbeddingService.js +195 -0
  32. package/dist/embeddings/OpenAIEmbeddingService.js.map +1 -0
  33. package/dist/embeddings/config.d.ts +83 -0
  34. package/dist/embeddings/config.js +65 -0
  35. package/dist/embeddings/config.js.map +1 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +220 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/server/handlers/callToolHandler.d.ts +20 -0
  40. package/dist/server/handlers/callToolHandler.js +505 -0
  41. package/dist/server/handlers/callToolHandler.js.map +1 -0
  42. package/dist/server/handlers/listToolsHandler.d.ts +7 -0
  43. package/dist/server/handlers/listToolsHandler.js +511 -0
  44. package/dist/server/handlers/listToolsHandler.js.map +1 -0
  45. package/dist/server/handlers/toolHandlers/addObservations.d.ts +12 -0
  46. package/dist/server/handlers/toolHandlers/addObservations.js +99 -0
  47. package/dist/server/handlers/toolHandlers/addObservations.js.map +1 -0
  48. package/dist/server/handlers/toolHandlers/createEntities.d.ts +12 -0
  49. package/dist/server/handlers/toolHandlers/createEntities.js +20 -0
  50. package/dist/server/handlers/toolHandlers/createEntities.js.map +1 -0
  51. package/dist/server/handlers/toolHandlers/createRelations.d.ts +12 -0
  52. package/dist/server/handlers/toolHandlers/createRelations.js +20 -0
  53. package/dist/server/handlers/toolHandlers/createRelations.js.map +1 -0
  54. package/dist/server/handlers/toolHandlers/deleteEntities.d.ts +12 -0
  55. package/dist/server/handlers/toolHandlers/deleteEntities.js +20 -0
  56. package/dist/server/handlers/toolHandlers/deleteEntities.js.map +1 -0
  57. package/dist/server/handlers/toolHandlers/index.d.ts +8 -0
  58. package/dist/server/handlers/toolHandlers/index.js +9 -0
  59. package/dist/server/handlers/toolHandlers/index.js.map +1 -0
  60. package/dist/server/handlers/toolHandlers/readGraph.d.ts +12 -0
  61. package/dist/server/handlers/toolHandlers/readGraph.js +20 -0
  62. package/dist/server/handlers/toolHandlers/readGraph.js.map +1 -0
  63. package/dist/server/setup.d.ts +8 -0
  64. package/dist/server/setup.js +48 -0
  65. package/dist/server/setup.js.map +1 -0
  66. package/dist/storage/FileStorageProvider.d.ts +125 -0
  67. package/dist/storage/FileStorageProvider.js +322 -0
  68. package/dist/storage/FileStorageProvider.js.map +1 -0
  69. package/dist/storage/SearchResultCache.d.ts +102 -0
  70. package/dist/storage/SearchResultCache.js +258 -0
  71. package/dist/storage/SearchResultCache.js.map +1 -0
  72. package/dist/storage/StorageProvider.d.ts +171 -0
  73. package/dist/storage/StorageProvider.js +46 -0
  74. package/dist/storage/StorageProvider.js.map +1 -0
  75. package/dist/storage/StorageProviderFactory.d.ts +63 -0
  76. package/dist/storage/StorageProviderFactory.js +113 -0
  77. package/dist/storage/StorageProviderFactory.js.map +1 -0
  78. package/dist/storage/VectorStoreFactory.d.ts +43 -0
  79. package/dist/storage/VectorStoreFactory.js +41 -0
  80. package/dist/storage/VectorStoreFactory.js.map +1 -0
  81. package/dist/storage/neo4j/Neo4jConfig.d.ts +37 -0
  82. package/dist/storage/neo4j/Neo4jConfig.js +13 -0
  83. package/dist/storage/neo4j/Neo4jConfig.js.map +1 -0
  84. package/dist/storage/neo4j/Neo4jConnectionManager.d.ts +40 -0
  85. package/dist/storage/neo4j/Neo4jConnectionManager.js +58 -0
  86. package/dist/storage/neo4j/Neo4jConnectionManager.js.map +1 -0
  87. package/dist/storage/neo4j/Neo4jSchemaManager.d.ts +74 -0
  88. package/dist/storage/neo4j/Neo4jSchemaManager.js +224 -0
  89. package/dist/storage/neo4j/Neo4jSchemaManager.js.map +1 -0
  90. package/dist/storage/neo4j/Neo4jStorageProvider.d.ts +225 -0
  91. package/dist/storage/neo4j/Neo4jStorageProvider.js +1900 -0
  92. package/dist/storage/neo4j/Neo4jStorageProvider.js.map +1 -0
  93. package/dist/storage/neo4j/Neo4jVectorStore.d.ts +80 -0
  94. package/dist/storage/neo4j/Neo4jVectorStore.js +396 -0
  95. package/dist/storage/neo4j/Neo4jVectorStore.js.map +1 -0
  96. package/dist/types/entity-embedding.d.ts +156 -0
  97. package/dist/types/entity-embedding.js +2 -0
  98. package/dist/types/entity-embedding.js.map +1 -0
  99. package/dist/types/relation.d.ts +77 -0
  100. package/dist/types/relation.js +93 -0
  101. package/dist/types/relation.js.map +1 -0
  102. package/dist/types/temporalEntity.d.ts +55 -0
  103. package/dist/types/temporalEntity.js +66 -0
  104. package/dist/types/temporalEntity.js.map +1 -0
  105. package/dist/types/temporalRelation.d.ts +60 -0
  106. package/dist/types/temporalRelation.js +89 -0
  107. package/dist/types/temporalRelation.js.map +1 -0
  108. package/dist/types/vector-index.d.ts +48 -0
  109. package/dist/types/vector-index.js +2 -0
  110. package/dist/types/vector-index.js.map +1 -0
  111. package/dist/types/vector-store.d.ts +16 -0
  112. package/dist/types/vector-store.js +2 -0
  113. package/dist/types/vector-store.js.map +1 -0
  114. package/dist/utils/fs.d.ts +2 -0
  115. package/dist/utils/fs.js +3 -0
  116. package/dist/utils/fs.js.map +1 -0
  117. package/dist/utils/logger.d.ts +10 -0
  118. package/dist/utils/logger.js +35 -0
  119. package/dist/utils/logger.js.map +1 -0
  120. package/package.json +85 -0
@@ -0,0 +1,910 @@
1
+ import { fs } from './utils/fs.js';
2
+ import { VectorStoreFactory, } from './storage/VectorStoreFactory.js';
3
+ import { logger } from './utils/logger.js';
4
+ // Type guard functions
5
+ function hasSearchVectors(provider) {
6
+ return ('searchVectors' in provider &&
7
+ typeof provider.searchVectors === 'function');
8
+ }
9
+ function hasSemanticSearch(provider) {
10
+ return ('semanticSearch' in provider &&
11
+ typeof provider.semanticSearch === 'function');
12
+ }
13
+ // Check if a provider has an updateRelation method that returns a Relation
14
+ function hasUpdateRelation(provider) {
15
+ return ('updateRelation' in provider &&
16
+ typeof provider.updateRelation === 'function');
17
+ }
18
+ // Re-export the Relation interface for backward compatibility
19
+ export { Relation } from './types/relation.js';
20
+ // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
21
+ export class KnowledgeGraphManager {
22
+ constructor(options) {
23
+ this.memoryFilePath = '';
24
+ // Expose the fs module for testing
25
+ this.fsModule = fs;
26
+ this.storageProvider = options?.storageProvider;
27
+ this.embeddingJobManager = options?.embeddingJobManager;
28
+ // If no storage provider is given, log a deprecation warning
29
+ if (!this.storageProvider) {
30
+ logger.warn('WARNING: Using deprecated file-based storage. This will be removed in a future version. Please use a StorageProvider implementation instead.');
31
+ }
32
+ // If memoryFilePath is provided, use it (for backward compatibility)
33
+ if (options?.memoryFilePath) {
34
+ this.memoryFilePath = options.memoryFilePath;
35
+ }
36
+ else if (process.env.MEMORY_FILE_PATH) {
37
+ this.memoryFilePath = process.env.MEMORY_FILE_PATH;
38
+ }
39
+ // Initialize vector store if options provided
40
+ if (options?.vectorStoreOptions) {
41
+ this.initializeVectorStore(options.vectorStoreOptions).catch((err) => logger.error('Failed to initialize vector store during construction', err));
42
+ }
43
+ }
44
+ /**
45
+ * Initialize the vector store with the given options
46
+ *
47
+ * @param options - Options for the vector store
48
+ */
49
+ async initializeVectorStore(options) {
50
+ try {
51
+ // Set the initialize immediately flag to true
52
+ const factoryOptions = {
53
+ ...options,
54
+ initializeImmediately: true,
55
+ };
56
+ // Create and initialize the vector store
57
+ this.vectorStore = await VectorStoreFactory.createVectorStore(factoryOptions);
58
+ logger.info('Vector store initialized successfully');
59
+ }
60
+ catch (error) {
61
+ logger.error('Failed to initialize vector store', error);
62
+ throw error;
63
+ }
64
+ }
65
+ /**
66
+ * Ensure vector store is initialized
67
+ *
68
+ * @returns Promise that resolves when the vector store is initialized
69
+ */
70
+ async ensureVectorStore() {
71
+ if (!this.vectorStore) {
72
+ // If vectorStore is not yet initialized but we have options from the storage provider,
73
+ // try to initialize it
74
+ if (this.storageProvider && 'vectorStoreOptions' in this.storageProvider) {
75
+ await this.initializeVectorStore(this.storageProvider
76
+ .vectorStoreOptions);
77
+ // If still undefined after initialization attempt, throw error
78
+ if (!this.vectorStore) {
79
+ throw new Error('Failed to initialize vector store');
80
+ }
81
+ }
82
+ else {
83
+ throw new Error('Vector store is not initialized and no options are available');
84
+ }
85
+ }
86
+ return this.vectorStore;
87
+ }
88
+ /**
89
+ * Update an entity's embedding in both the storage provider and vector store
90
+ *
91
+ * @param entityName - Name of the entity
92
+ * @param embedding - The embedding to store
93
+ * @private
94
+ */
95
+ async updateEntityEmbedding(entityName, embedding) {
96
+ // First, ensure we have the entity data
97
+ if (!this.storageProvider || typeof this.storageProvider.getEntity !== 'function') {
98
+ throw new Error('Storage provider is required to update entity embeddings');
99
+ }
100
+ const entity = await this.storageProvider.getEntity(entityName);
101
+ if (!entity) {
102
+ throw new Error(`Entity ${entityName} not found`);
103
+ }
104
+ // Update the storage provider
105
+ if (this.storageProvider && typeof this.storageProvider.updateEntityEmbedding === 'function') {
106
+ await this.storageProvider.updateEntityEmbedding(entityName, embedding);
107
+ }
108
+ // Update the vector store - ensure it's initialized first
109
+ try {
110
+ const vectorStore = await this.ensureVectorStore();
111
+ // Add metadata for filtering
112
+ const metadata = {
113
+ name: entityName,
114
+ entityType: entity.entityType,
115
+ };
116
+ await vectorStore.addVector(entityName, embedding.vector, metadata);
117
+ logger.debug(`Updated vector for entity ${entityName} in vector store`);
118
+ }
119
+ catch (error) {
120
+ logger.error(`Failed to update vector for entity ${entityName}`, error);
121
+ throw error;
122
+ }
123
+ }
124
+ /**
125
+ * Load the knowledge graph from storage
126
+ * @deprecated Direct file-based storage is deprecated. Use a StorageProvider implementation instead.
127
+ * @private
128
+ */
129
+ async loadGraph() {
130
+ if (this.storageProvider) {
131
+ return this.storageProvider.loadGraph();
132
+ }
133
+ // Fallback to file-based implementation
134
+ try {
135
+ // If no memory file path is set, return empty graph
136
+ if (!this.memoryFilePath) {
137
+ logger.warn('No memory file path set, returning empty graph');
138
+ return { entities: [], relations: [] };
139
+ }
140
+ // Check if file exists before reading
141
+ try {
142
+ await this.fsModule.access(this.memoryFilePath);
143
+ }
144
+ catch {
145
+ // If file doesn't exist, create empty graph
146
+ return { entities: [], relations: [] };
147
+ }
148
+ const fileContents = await this.fsModule.readFile(this.memoryFilePath, 'utf-8');
149
+ if (!fileContents || fileContents.trim() === '') {
150
+ return { entities: [], relations: [] };
151
+ }
152
+ // Try to parse it as a single entity or relation
153
+ try {
154
+ const parsedItem = JSON.parse(fileContents);
155
+ // If it's a test object with a type field
156
+ if (parsedItem.type === 'entity') {
157
+ const { type: _, ...entity } = parsedItem;
158
+ return {
159
+ entities: [entity],
160
+ relations: [],
161
+ };
162
+ }
163
+ else if (parsedItem.type === 'relation') {
164
+ const { type: _, ...relation } = parsedItem;
165
+ return {
166
+ entities: [],
167
+ relations: [relation],
168
+ };
169
+ }
170
+ // If it's a complete graph object with entities and relations arrays,
171
+ // just return it directly - this helps with certain test scenarios
172
+ if (parsedItem.entities || parsedItem.relations) {
173
+ return {
174
+ entities: parsedItem.entities || [],
175
+ relations: parsedItem.relations || [],
176
+ };
177
+ }
178
+ }
179
+ catch (e) {
180
+ logger.error('Error parsing complete file content', e);
181
+ }
182
+ // Try to parse it as newline-delimited JSON
183
+ const lines = fileContents.split('\n').filter((line) => line.trim() !== '');
184
+ const entities = [];
185
+ const relations = [];
186
+ for (const line of lines) {
187
+ try {
188
+ const item = JSON.parse(line);
189
+ if (item.type === 'entity') {
190
+ const { type: _, ...entity } = item; // Remove the type property
191
+ entities.push(entity);
192
+ }
193
+ else if (item.type === 'relation') {
194
+ const { type: _, ...relation } = item; // Remove the type property
195
+ relations.push(relation);
196
+ }
197
+ }
198
+ catch (e) {
199
+ logger.error('Error parsing line', { line, error: e });
200
+ }
201
+ }
202
+ return { entities, relations };
203
+ }
204
+ catch (error) {
205
+ // If error has code 'ENOENT', return empty graph (file not found)
206
+ if (error?.code === 'ENOENT') {
207
+ return { entities: [], relations: [] };
208
+ }
209
+ logger.error('Error loading graph from file', error);
210
+ throw error;
211
+ }
212
+ }
213
+ /**
214
+ * Save the knowledge graph to storage
215
+ * @deprecated Direct file-based storage is deprecated. Use a StorageProvider implementation instead.
216
+ * @private
217
+ */
218
+ async saveGraph(graph) {
219
+ if (this.storageProvider) {
220
+ return this.storageProvider.saveGraph(graph);
221
+ }
222
+ // Fallback to file-based implementation
223
+ try {
224
+ // If no memory file path is set, log warning and return
225
+ if (!this.memoryFilePath) {
226
+ logger.warn('No memory file path set, cannot save graph');
227
+ return;
228
+ }
229
+ // Convert entities and relations to JSON lines with type field
230
+ // Use newlines for better readability and append
231
+ const lines = [];
232
+ // Add entities
233
+ for (const entity of graph.entities) {
234
+ // Create a copy without entityType to avoid duplication
235
+ const { entityType, ...entityWithoutType } = entity;
236
+ lines.push(JSON.stringify({ entityType, ...entityWithoutType }));
237
+ }
238
+ // Add relations
239
+ for (const relation of graph.relations) {
240
+ // Create a copy without relationType to avoid duplication
241
+ const { relationType, ...relationWithoutType } = relation;
242
+ lines.push(JSON.stringify({ relationType, ...relationWithoutType }));
243
+ }
244
+ // Write to file
245
+ await this.fsModule.writeFile(this.memoryFilePath, lines.join('\n'));
246
+ }
247
+ catch (error) {
248
+ logger.error('Error saving graph to file', error);
249
+ throw error;
250
+ }
251
+ }
252
+ async createEntities(entities) {
253
+ // If no entities to create, load graph, save it unchanged and return empty array early
254
+ if (!entities || entities.length === 0) {
255
+ if (!this.storageProvider) {
256
+ const graph = await this.loadGraph();
257
+ await this.saveGraph(graph);
258
+ }
259
+ return [];
260
+ }
261
+ // Filter entities to only include those we need to create
262
+ const graph = await this.loadGraph();
263
+ const entitiesMap = new Map();
264
+ // Add existing entities to the map
265
+ for (const entity of graph.entities) {
266
+ entitiesMap.set(entity.name, entity);
267
+ }
268
+ // Process new entities
269
+ let entitiesArray = [...graph.entities];
270
+ const newEntities = [];
271
+ for (const entity of entities) {
272
+ // Check if entity already exists
273
+ if (entitiesMap.has(entity.name)) {
274
+ // Update existing entity by merging observations
275
+ const existingEntity = entitiesMap.get(entity.name);
276
+ const updatedObservations = new Set([
277
+ ...existingEntity.observations,
278
+ ...entity.observations,
279
+ ]);
280
+ existingEntity.observations = Array.from(updatedObservations);
281
+ // Update the entity in our map and array
282
+ entitiesMap.set(entity.name, existingEntity);
283
+ entitiesArray = entitiesArray.map((e) => (e.name === entity.name ? existingEntity : e));
284
+ }
285
+ else {
286
+ // Add new entity
287
+ entitiesMap.set(entity.name, entity);
288
+ entitiesArray.push(entity);
289
+ newEntities.push(entity);
290
+ }
291
+ }
292
+ // Update the graph with our modified entities
293
+ graph.entities = entitiesArray;
294
+ // Save the graph regardless of whether we have new entities
295
+ if (!this.storageProvider) {
296
+ await this.saveGraph(graph);
297
+ }
298
+ // If no new entities, just return empty array
299
+ if (newEntities.length === 0) {
300
+ return [];
301
+ }
302
+ let createdEntities = [];
303
+ if (this.storageProvider) {
304
+ // Use storage provider for creating entities
305
+ createdEntities = await this.storageProvider.createEntities(newEntities);
306
+ // Add entities with existing embeddings to vector store
307
+ for (const entity of createdEntities) {
308
+ if (entity.embedding && entity.embedding.vector) {
309
+ try {
310
+ const vectorStore = await this.ensureVectorStore().catch(() => undefined);
311
+ if (vectorStore) {
312
+ // Add metadata for filtering
313
+ const metadata = {
314
+ name: entity.name,
315
+ entityType: entity.entityType,
316
+ };
317
+ await vectorStore.addVector(entity.name, entity.embedding.vector, metadata);
318
+ logger.debug(`Added vector for entity ${entity.name} to vector store`);
319
+ }
320
+ }
321
+ catch (error) {
322
+ logger.error(`Failed to add vector for entity ${entity.name} to vector store`, error);
323
+ // Continue with scheduling embedding job
324
+ }
325
+ }
326
+ }
327
+ // Schedule embedding jobs if manager is provided
328
+ if (this.embeddingJobManager) {
329
+ for (const entity of createdEntities) {
330
+ await this.embeddingJobManager.scheduleEntityEmbedding(entity.name, 1);
331
+ }
332
+ }
333
+ }
334
+ else {
335
+ // No storage provider, so use the entities we've already added to the graph
336
+ // Add entities with existing embeddings to vector store
337
+ for (const entity of newEntities) {
338
+ if (entity.embedding && entity.embedding.vector) {
339
+ try {
340
+ const vectorStore = await this.ensureVectorStore().catch(() => undefined);
341
+ if (vectorStore) {
342
+ // Add metadata for filtering
343
+ const metadata = {
344
+ name: entity.name,
345
+ entityType: entity.entityType,
346
+ };
347
+ await vectorStore.addVector(entity.name, entity.embedding.vector, metadata);
348
+ logger.debug(`Added vector for entity ${entity.name} to vector store`);
349
+ }
350
+ }
351
+ catch (error) {
352
+ logger.error(`Failed to add vector for entity ${entity.name} to vector store`, error);
353
+ // Continue with scheduling embedding job
354
+ }
355
+ }
356
+ }
357
+ if (this.embeddingJobManager) {
358
+ for (const entity of newEntities) {
359
+ await this.embeddingJobManager.scheduleEntityEmbedding(entity.name, 1);
360
+ }
361
+ }
362
+ createdEntities = newEntities;
363
+ }
364
+ return createdEntities;
365
+ }
366
+ async createRelations(relations) {
367
+ if (!relations || relations.length === 0) {
368
+ if (!this.storageProvider) {
369
+ // In test mode, still call loadGraph/saveGraph for empty relations
370
+ // This ensures mockWriteFile is called in tests
371
+ const graph = await this.loadGraph();
372
+ await this.saveGraph(graph);
373
+ }
374
+ return [];
375
+ }
376
+ if (this.storageProvider) {
377
+ // Use storage provider for creating relations
378
+ const createdRelations = await this.storageProvider.createRelations(relations);
379
+ return createdRelations;
380
+ }
381
+ else {
382
+ // Fallback to file-based implementation
383
+ const graph = await this.loadGraph();
384
+ // Get the entities that exist in the graph
385
+ const entityNames = new Set(graph.entities.map((e) => e.name));
386
+ // Verify all entities in the relations exist
387
+ for (const relation of relations) {
388
+ if (!entityNames.has(relation.from)) {
389
+ throw new Error(`"From" entity with name ${relation.from} does not exist.`);
390
+ }
391
+ if (!entityNames.has(relation.to)) {
392
+ throw new Error(`"To" entity with name ${relation.to} does not exist.`);
393
+ }
394
+ }
395
+ // Filter out relations that already exist
396
+ const existingRelations = new Set();
397
+ for (const r of graph.relations) {
398
+ const key = `${r.from}|${r.relationType}|${r.to}`;
399
+ existingRelations.add(key);
400
+ }
401
+ const newRelations = relations.filter((r) => {
402
+ const key = `${r.from}|${r.relationType}|${r.to}`;
403
+ return !existingRelations.has(key);
404
+ });
405
+ // If no new relations to create, return empty array
406
+ if (newRelations.length === 0) {
407
+ // Still save the graph to ensure mockWriteFile is called in tests
408
+ await this.saveGraph(graph);
409
+ return [];
410
+ }
411
+ // Fallback to file-based implementation
412
+ graph.relations = [...graph.relations, ...newRelations];
413
+ await this.saveGraph(graph);
414
+ return newRelations;
415
+ }
416
+ }
417
+ async deleteEntities(entityNames) {
418
+ if (!entityNames || entityNames.length === 0) {
419
+ return;
420
+ }
421
+ if (this.storageProvider) {
422
+ // Use storage provider for deleting entities
423
+ await this.storageProvider.deleteEntities(entityNames);
424
+ }
425
+ else {
426
+ // Fallback to file-based implementation
427
+ const graph = await this.loadGraph();
428
+ // Remove the entities
429
+ const entitiesToKeep = graph.entities.filter((e) => !entityNames.includes(e.name));
430
+ // Remove relations involving the deleted entities
431
+ const relationsToKeep = graph.relations.filter((r) => !entityNames.includes(r.from) && !entityNames.includes(r.to));
432
+ // Update the graph
433
+ graph.entities = entitiesToKeep;
434
+ graph.relations = relationsToKeep;
435
+ await this.saveGraph(graph);
436
+ }
437
+ // Remove entities from vector store if available
438
+ try {
439
+ // Ensure vector store is available
440
+ const vectorStore = await this.ensureVectorStore().catch(() => undefined);
441
+ if (vectorStore) {
442
+ for (const entityName of entityNames) {
443
+ try {
444
+ await vectorStore.removeVector(entityName);
445
+ logger.debug(`Removed vector for entity ${entityName} from vector store`);
446
+ }
447
+ catch (error) {
448
+ logger.error(`Failed to remove vector for entity ${entityName}`, error);
449
+ // Don't throw here, continue with the next entity
450
+ }
451
+ }
452
+ }
453
+ }
454
+ catch (error) {
455
+ logger.error('Failed to remove vectors from vector store', error);
456
+ // Continue even if vector store operations fail
457
+ }
458
+ }
459
+ async deleteObservations(deletions) {
460
+ if (!deletions || deletions.length === 0) {
461
+ return;
462
+ }
463
+ if (this.storageProvider) {
464
+ // Use storage provider for deleting observations
465
+ await this.storageProvider.deleteObservations(deletions);
466
+ // Schedule re-embedding for affected entities if manager is provided
467
+ if (this.embeddingJobManager) {
468
+ for (const deletion of deletions) {
469
+ await this.embeddingJobManager.scheduleEntityEmbedding(deletion.entityName, 1);
470
+ }
471
+ }
472
+ }
473
+ else {
474
+ // Fallback to file-based implementation
475
+ const graph = await this.loadGraph();
476
+ // Process each deletion
477
+ for (const deletion of deletions) {
478
+ const entity = graph.entities.find((e) => e.name === deletion.entityName);
479
+ if (entity) {
480
+ // Remove the observations
481
+ entity.observations = entity.observations.filter((obs) => !deletion.observations.includes(obs));
482
+ }
483
+ }
484
+ await this.saveGraph(graph);
485
+ // Schedule re-embedding for affected entities if manager is provided
486
+ if (this.embeddingJobManager) {
487
+ for (const deletion of deletions) {
488
+ await this.embeddingJobManager.scheduleEntityEmbedding(deletion.entityName, 1);
489
+ }
490
+ }
491
+ }
492
+ }
493
+ async deleteRelations(relations) {
494
+ if (!relations || relations.length === 0) {
495
+ return;
496
+ }
497
+ if (this.storageProvider) {
498
+ // Use storage provider for deleting relations
499
+ await this.storageProvider.deleteRelations(relations);
500
+ }
501
+ else {
502
+ // Fallback to file-based implementation
503
+ const graph = await this.loadGraph();
504
+ // Filter out relations that match the ones to delete
505
+ graph.relations = graph.relations.filter((r) => {
506
+ // Check if this relation matches any in the deletion list
507
+ return !relations.some((delRel) => r.from === delRel.from && r.relationType === delRel.relationType && r.to === delRel.to);
508
+ });
509
+ await this.saveGraph(graph);
510
+ }
511
+ }
512
+ async searchNodes(query) {
513
+ if (this.storageProvider) {
514
+ return this.storageProvider.searchNodes(query);
515
+ }
516
+ // Fallback to file-based implementation
517
+ const graph = await this.loadGraph();
518
+ const lowercaseQuery = query.toLowerCase();
519
+ // Filter entities based on name match
520
+ const filteredEntities = graph.entities.filter((e) => e.name.toLowerCase().includes(lowercaseQuery));
521
+ // Get relations where either the source or target entity matches the query
522
+ const filteredRelations = graph.relations.filter((r) => r.from.toLowerCase().includes(lowercaseQuery) || r.to.toLowerCase().includes(lowercaseQuery));
523
+ return {
524
+ entities: filteredEntities,
525
+ relations: filteredRelations,
526
+ };
527
+ }
528
+ async openNodes(names) {
529
+ if (this.storageProvider) {
530
+ return this.storageProvider.openNodes(names);
531
+ }
532
+ // Fallback to file-based implementation
533
+ const graph = await this.loadGraph();
534
+ // Filter entities by name
535
+ const filteredEntities = graph.entities.filter((e) => names.includes(e.name));
536
+ // Get relations connected to these entities
537
+ const filteredRelations = graph.relations.filter((r) => names.includes(r.from) || names.includes(r.to));
538
+ return {
539
+ entities: filteredEntities,
540
+ relations: filteredRelations,
541
+ };
542
+ }
543
+ /**
544
+ * Add observations to entities
545
+ * @param observations Array of observation objects
546
+ * @returns Promise resolving to array of added observations
547
+ */
548
+ async addObservations(observations) {
549
+ if (!observations || observations.length === 0) {
550
+ return [];
551
+ }
552
+ // Extract only the fields needed by storage providers
553
+ // Keep the simplified format for compatibility with existing storage providers
554
+ const simplifiedObservations = observations.map((obs) => ({
555
+ entityName: obs.entityName,
556
+ contents: obs.contents,
557
+ }));
558
+ if (this.storageProvider) {
559
+ // Use storage provider for adding observations
560
+ const results = await this.storageProvider.addObservations(simplifiedObservations);
561
+ // Schedule re-embedding for affected entities if manager is provided
562
+ if (this.embeddingJobManager) {
563
+ for (const result of results) {
564
+ if (result.addedObservations.length > 0) {
565
+ await this.embeddingJobManager.scheduleEntityEmbedding(result.entityName, 1);
566
+ }
567
+ }
568
+ }
569
+ return results;
570
+ }
571
+ else {
572
+ // Fallback to file-based implementation
573
+ const graph = await this.loadGraph();
574
+ // Check if all entity names exist first
575
+ const entityNames = new Set(graph.entities.map((e) => e.name));
576
+ for (const obs of simplifiedObservations) {
577
+ if (!entityNames.has(obs.entityName)) {
578
+ throw new Error(`Entity with name ${obs.entityName} does not exist.`);
579
+ }
580
+ }
581
+ const results = [];
582
+ // Process each observation addition
583
+ for (const obs of simplifiedObservations) {
584
+ const entity = graph.entities.find((e) => e.name === obs.entityName);
585
+ if (entity) {
586
+ // Create a set of existing observations for deduplication
587
+ const existingObsSet = new Set(entity.observations);
588
+ const addedObservations = [];
589
+ // Add new observations
590
+ for (const content of obs.contents) {
591
+ if (!existingObsSet.has(content)) {
592
+ entity.observations.push(content);
593
+ existingObsSet.add(content);
594
+ addedObservations.push(content);
595
+ }
596
+ }
597
+ results.push({
598
+ entityName: obs.entityName,
599
+ addedObservations,
600
+ });
601
+ }
602
+ }
603
+ await this.saveGraph(graph);
604
+ // Schedule re-embedding for affected entities if manager is provided
605
+ if (this.embeddingJobManager) {
606
+ for (const result of results) {
607
+ if (result.addedObservations.length > 0) {
608
+ await this.embeddingJobManager.scheduleEntityEmbedding(result.entityName, 1);
609
+ }
610
+ }
611
+ }
612
+ return results;
613
+ }
614
+ }
615
+ /**
616
+ * Find entities that are semantically similar to the query
617
+ * @param query The query text to search for
618
+ * @param options Search options including limit and threshold
619
+ * @returns Promise resolving to an array of matches with scores
620
+ */
621
+ async findSimilarEntities(query, options = {}) {
622
+ if (!this.embeddingJobManager) {
623
+ throw new Error('Embedding job manager is required for semantic search');
624
+ }
625
+ const embeddingService = this.embeddingJobManager['embeddingService'];
626
+ if (!embeddingService) {
627
+ throw new Error('Embedding service not available');
628
+ }
629
+ // Generate embedding for the query
630
+ const embedding = await embeddingService.generateEmbedding(query);
631
+ // If we have a vector store, use it directly
632
+ try {
633
+ // Ensure vector store is available
634
+ const vectorStore = await this.ensureVectorStore().catch(() => undefined);
635
+ if (vectorStore) {
636
+ const limit = options.limit || 10;
637
+ const minSimilarity = options.threshold || 0.7;
638
+ // Search the vector store
639
+ const results = await vectorStore.search(embedding, {
640
+ limit,
641
+ minSimilarity,
642
+ });
643
+ // Convert to the expected format
644
+ return results.map((result) => ({
645
+ name: result.id.toString(),
646
+ score: result.similarity,
647
+ }));
648
+ }
649
+ }
650
+ catch (error) {
651
+ logger.error('Failed to search vector store', error);
652
+ // Fall through to other methods
653
+ }
654
+ // If we have a vector search method in the storage provider, use it
655
+ if (this.storageProvider && hasSearchVectors(this.storageProvider)) {
656
+ return this.storageProvider.searchVectors(embedding, options.limit || 10, options.threshold || 0.7);
657
+ }
658
+ // Otherwise, return an empty result
659
+ return [];
660
+ }
661
+ /**
662
+ * Read the entire knowledge graph
663
+ *
664
+ * This is an alias for loadGraph() for backward compatibility
665
+ * @returns The knowledge graph
666
+ */
667
+ async readGraph() {
668
+ return this.loadGraph();
669
+ }
670
+ /**
671
+ * Search the knowledge graph with various options
672
+ *
673
+ * @param query The search query string
674
+ * @param options Search options
675
+ * @returns Promise resolving to a knowledge graph with search results
676
+ */
677
+ async search(query, options = {}) {
678
+ // If hybridSearch is true, always set semanticSearch to true as well
679
+ if (options.hybridSearch) {
680
+ options = { ...options, semanticSearch: true };
681
+ }
682
+ // Check if semantic search is requested
683
+ if (options.semanticSearch || options.hybridSearch) {
684
+ // Check if we have a storage provider with semanticSearch method
685
+ if (this.storageProvider && hasSemanticSearch(this.storageProvider)) {
686
+ try {
687
+ // Generate query vector if we have an embedding service
688
+ if (this.embeddingJobManager) {
689
+ const embeddingService = this.embeddingJobManager['embeddingService'];
690
+ if (embeddingService) {
691
+ const queryVector = await embeddingService.generateEmbedding(query);
692
+ return this.storageProvider.semanticSearch(query, {
693
+ ...options,
694
+ queryVector,
695
+ });
696
+ }
697
+ }
698
+ // Fall back to text search if no embedding service
699
+ return this.storageProvider.searchNodes(query);
700
+ }
701
+ catch (error) {
702
+ logger.error('Provider semanticSearch failed, falling back to basic search', error);
703
+ return this.storageProvider.searchNodes(query);
704
+ }
705
+ }
706
+ else if (this.storageProvider) {
707
+ // Fall back to searchNodes if semanticSearch is not available in the provider
708
+ return this.storageProvider.searchNodes(query);
709
+ }
710
+ // If no storage provider or its semanticSearch is not available, try internal semantic search
711
+ if (this.embeddingJobManager) {
712
+ try {
713
+ // Try to use semantic search
714
+ const results = await this.semanticSearch(query, {
715
+ hybridSearch: options.hybridSearch || false,
716
+ limit: options.limit || 10,
717
+ threshold: options.threshold || options.minSimilarity || 0.5,
718
+ entityTypes: options.entityTypes || [],
719
+ facets: options.facets || [],
720
+ offset: options.offset || 0,
721
+ });
722
+ return results;
723
+ }
724
+ catch (error) {
725
+ // Log error but fall back to basic search
726
+ logger.error('Semantic search failed, falling back to basic search', error);
727
+ // Explicitly call searchNodes if available in the provider
728
+ if (this.storageProvider) {
729
+ return this.storageProvider.searchNodes(query);
730
+ }
731
+ }
732
+ }
733
+ else {
734
+ logger.warn('Semantic search requested but no embedding capability available');
735
+ }
736
+ }
737
+ // Use basic search
738
+ return this.searchNodes(query);
739
+ }
740
+ /**
741
+ * Perform semantic search on the knowledge graph
742
+ *
743
+ * @param query The search query string
744
+ * @param options Search options
745
+ * @returns Promise resolving to a knowledge graph with semantic search results
746
+ */
747
+ async semanticSearch(query, options = {}) {
748
+ // Find similar entities using vector similarity
749
+ const similarEntities = await this.findSimilarEntities(query, {
750
+ limit: options.limit || 10,
751
+ threshold: options.threshold || 0.5,
752
+ });
753
+ if (!similarEntities.length) {
754
+ return { entities: [], relations: [] };
755
+ }
756
+ // Get full entity details
757
+ const entityNames = similarEntities.map((e) => e.name);
758
+ const graph = await this.openNodes(entityNames);
759
+ // Add scores to entities for client use
760
+ const scoredEntities = graph.entities.map((entity) => {
761
+ const matchScore = similarEntities.find((e) => e.name === entity.name)?.score || 0;
762
+ return {
763
+ ...entity,
764
+ score: matchScore,
765
+ };
766
+ });
767
+ // Sort by score descending
768
+ scoredEntities.sort((a, b) => {
769
+ const scoreA = 'score' in a ? a.score : 0;
770
+ const scoreB = 'score' in b ? b.score : 0;
771
+ return scoreB - scoreA;
772
+ });
773
+ return {
774
+ entities: scoredEntities,
775
+ relations: graph.relations,
776
+ total: similarEntities.length,
777
+ };
778
+ }
779
+ /**
780
+ * Get a specific relation by its from, to, and type identifiers
781
+ *
782
+ * @param from The name of the entity where the relation starts
783
+ * @param to The name of the entity where the relation ends
784
+ * @param relationType The type of the relation
785
+ * @returns The relation or null if not found
786
+ */
787
+ async getRelation(from, to, relationType) {
788
+ if (this.storageProvider && typeof this.storageProvider.getRelation === 'function') {
789
+ return this.storageProvider.getRelation(from, to, relationType);
790
+ }
791
+ // Fallback implementation
792
+ const graph = await this.loadGraph();
793
+ const relation = graph.relations.find((r) => r.from === from && r.to === to && r.relationType === relationType);
794
+ return relation || null;
795
+ }
796
+ /**
797
+ * Update a relation with new properties
798
+ *
799
+ * @param relation The relation to update
800
+ * @returns The updated relation
801
+ */
802
+ async updateRelation(relation) {
803
+ if (this.storageProvider && hasUpdateRelation(this.storageProvider)) {
804
+ // Cast to the extended interface to access the method
805
+ const provider = this.storageProvider;
806
+ return provider.updateRelation(relation);
807
+ }
808
+ // Fallback implementation
809
+ const graph = await this.loadGraph();
810
+ // Find the relation to update
811
+ const index = graph.relations.findIndex((r) => r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType);
812
+ if (index === -1) {
813
+ throw new Error(`Relation from '${relation.from}' to '${relation.to}' of type '${relation.relationType}' not found`);
814
+ }
815
+ // Update the relation
816
+ graph.relations[index] = relation;
817
+ // Save the updated graph
818
+ await this.saveGraph(graph);
819
+ return relation;
820
+ }
821
+ /**
822
+ * Update an entity with new properties
823
+ *
824
+ * @param entityName The name of the entity to update
825
+ * @param updates Properties to update
826
+ * @returns The updated entity
827
+ */
828
+ async updateEntity(entityName, updates) {
829
+ if (this.storageProvider &&
830
+ 'updateEntity' in this.storageProvider &&
831
+ typeof this.storageProvider.updateEntity === 'function') {
832
+ const result = await this.storageProvider.updateEntity(entityName, updates);
833
+ // Schedule embedding generation if observations were updated
834
+ if (this.embeddingJobManager && updates.observations) {
835
+ await this.embeddingJobManager.scheduleEntityEmbedding(entityName, 2);
836
+ }
837
+ return result;
838
+ }
839
+ // Fallback implementation
840
+ const graph = await this.loadGraph();
841
+ // Find the entity to update
842
+ const index = graph.entities.findIndex((e) => e.name === entityName);
843
+ if (index === -1) {
844
+ throw new Error(`Entity with name ${entityName} not found`);
845
+ }
846
+ // Update the entity
847
+ const updatedEntity = {
848
+ ...graph.entities[index],
849
+ ...updates,
850
+ };
851
+ graph.entities[index] = updatedEntity;
852
+ // Save the updated graph
853
+ await this.saveGraph(graph);
854
+ // Schedule embedding generation if observations were updated
855
+ if (this.embeddingJobManager && updates.observations) {
856
+ await this.embeddingJobManager.scheduleEntityEmbedding(entityName, 2);
857
+ }
858
+ return updatedEntity;
859
+ }
860
+ /**
861
+ * Get a version of the graph with confidences decayed based on time
862
+ *
863
+ * @returns Graph with decayed confidences
864
+ */
865
+ async getDecayedGraph() {
866
+ if (!this.storageProvider || typeof this.storageProvider.getDecayedGraph !== 'function') {
867
+ throw new Error('Storage provider does not support decay operations');
868
+ }
869
+ return this.storageProvider.getDecayedGraph();
870
+ }
871
+ /**
872
+ * Get the history of an entity
873
+ *
874
+ * @param entityName The name of the entity to retrieve history for
875
+ * @returns Array of entity versions
876
+ */
877
+ async getEntityHistory(entityName) {
878
+ if (!this.storageProvider || typeof this.storageProvider.getEntityHistory !== 'function') {
879
+ throw new Error('Storage provider does not support entity history operations');
880
+ }
881
+ return this.storageProvider.getEntityHistory(entityName);
882
+ }
883
+ /**
884
+ * Get the history of a relation
885
+ *
886
+ * @param from The name of the entity where the relation starts
887
+ * @param to The name of the entity where the relation ends
888
+ * @param relationType The type of the relation
889
+ * @returns Array of relation versions
890
+ */
891
+ async getRelationHistory(from, to, relationType) {
892
+ if (!this.storageProvider || typeof this.storageProvider.getRelationHistory !== 'function') {
893
+ throw new Error('Storage provider does not support relation history operations');
894
+ }
895
+ return this.storageProvider.getRelationHistory(from, to, relationType);
896
+ }
897
+ /**
898
+ * Get the state of the knowledge graph at a specific point in time
899
+ *
900
+ * @param timestamp The timestamp (in milliseconds since epoch) to query the graph at
901
+ * @returns The knowledge graph as it existed at the specified time
902
+ */
903
+ async getGraphAtTime(timestamp) {
904
+ if (!this.storageProvider || typeof this.storageProvider.getGraphAtTime !== 'function') {
905
+ throw new Error('Storage provider does not support temporal graph operations');
906
+ }
907
+ return this.storageProvider.getGraphAtTime(timestamp);
908
+ }
909
+ }
910
+ //# sourceMappingURL=KnowledgeGraphManager.js.map