@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.
- package/LICENSE +21 -0
- package/README.md +718 -0
- package/dist/KnowledgeGraphManager.d.ts +215 -0
- package/dist/KnowledgeGraphManager.js +910 -0
- package/dist/KnowledgeGraphManager.js.map +1 -0
- package/dist/callToolHandler.d.ts +5 -0
- package/dist/callToolHandler.js +26 -0
- package/dist/callToolHandler.js.map +1 -0
- package/dist/cli/neo4j-setup.d.ts +52 -0
- package/dist/cli/neo4j-setup.js +258 -0
- package/dist/cli/neo4j-setup.js.map +1 -0
- package/dist/config/paths.d.ts +13 -0
- package/dist/config/paths.js +41 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/storage.d.ts +35 -0
- package/dist/config/storage.js +52 -0
- package/dist/config/storage.js.map +1 -0
- package/dist/embeddings/DefaultEmbeddingService.d.ts +64 -0
- package/dist/embeddings/DefaultEmbeddingService.js +139 -0
- package/dist/embeddings/DefaultEmbeddingService.js.map +1 -0
- package/dist/embeddings/EmbeddingJobManager.d.ts +212 -0
- package/dist/embeddings/EmbeddingJobManager.js +545 -0
- package/dist/embeddings/EmbeddingJobManager.js.map +1 -0
- package/dist/embeddings/EmbeddingService.d.ts +96 -0
- package/dist/embeddings/EmbeddingService.js +44 -0
- package/dist/embeddings/EmbeddingService.js.map +1 -0
- package/dist/embeddings/EmbeddingServiceFactory.d.ts +72 -0
- package/dist/embeddings/EmbeddingServiceFactory.js +147 -0
- package/dist/embeddings/EmbeddingServiceFactory.js.map +1 -0
- package/dist/embeddings/OpenAIEmbeddingService.d.ts +73 -0
- package/dist/embeddings/OpenAIEmbeddingService.js +195 -0
- package/dist/embeddings/OpenAIEmbeddingService.js.map +1 -0
- package/dist/embeddings/config.d.ts +83 -0
- package/dist/embeddings/config.js +65 -0
- package/dist/embeddings/config.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +220 -0
- package/dist/index.js.map +1 -0
- package/dist/server/handlers/callToolHandler.d.ts +20 -0
- package/dist/server/handlers/callToolHandler.js +505 -0
- package/dist/server/handlers/callToolHandler.js.map +1 -0
- package/dist/server/handlers/listToolsHandler.d.ts +7 -0
- package/dist/server/handlers/listToolsHandler.js +511 -0
- package/dist/server/handlers/listToolsHandler.js.map +1 -0
- package/dist/server/handlers/toolHandlers/addObservations.d.ts +12 -0
- package/dist/server/handlers/toolHandlers/addObservations.js +99 -0
- package/dist/server/handlers/toolHandlers/addObservations.js.map +1 -0
- package/dist/server/handlers/toolHandlers/createEntities.d.ts +12 -0
- package/dist/server/handlers/toolHandlers/createEntities.js +20 -0
- package/dist/server/handlers/toolHandlers/createEntities.js.map +1 -0
- package/dist/server/handlers/toolHandlers/createRelations.d.ts +12 -0
- package/dist/server/handlers/toolHandlers/createRelations.js +20 -0
- package/dist/server/handlers/toolHandlers/createRelations.js.map +1 -0
- package/dist/server/handlers/toolHandlers/deleteEntities.d.ts +12 -0
- package/dist/server/handlers/toolHandlers/deleteEntities.js +20 -0
- package/dist/server/handlers/toolHandlers/deleteEntities.js.map +1 -0
- package/dist/server/handlers/toolHandlers/index.d.ts +8 -0
- package/dist/server/handlers/toolHandlers/index.js +9 -0
- package/dist/server/handlers/toolHandlers/index.js.map +1 -0
- package/dist/server/handlers/toolHandlers/readGraph.d.ts +12 -0
- package/dist/server/handlers/toolHandlers/readGraph.js +20 -0
- package/dist/server/handlers/toolHandlers/readGraph.js.map +1 -0
- package/dist/server/setup.d.ts +8 -0
- package/dist/server/setup.js +48 -0
- package/dist/server/setup.js.map +1 -0
- package/dist/storage/FileStorageProvider.d.ts +125 -0
- package/dist/storage/FileStorageProvider.js +322 -0
- package/dist/storage/FileStorageProvider.js.map +1 -0
- package/dist/storage/SearchResultCache.d.ts +102 -0
- package/dist/storage/SearchResultCache.js +258 -0
- package/dist/storage/SearchResultCache.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +171 -0
- package/dist/storage/StorageProvider.js +46 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/StorageProviderFactory.d.ts +63 -0
- package/dist/storage/StorageProviderFactory.js +113 -0
- package/dist/storage/StorageProviderFactory.js.map +1 -0
- package/dist/storage/VectorStoreFactory.d.ts +43 -0
- package/dist/storage/VectorStoreFactory.js +41 -0
- package/dist/storage/VectorStoreFactory.js.map +1 -0
- package/dist/storage/neo4j/Neo4jConfig.d.ts +37 -0
- package/dist/storage/neo4j/Neo4jConfig.js +13 -0
- package/dist/storage/neo4j/Neo4jConfig.js.map +1 -0
- package/dist/storage/neo4j/Neo4jConnectionManager.d.ts +40 -0
- package/dist/storage/neo4j/Neo4jConnectionManager.js +58 -0
- package/dist/storage/neo4j/Neo4jConnectionManager.js.map +1 -0
- package/dist/storage/neo4j/Neo4jSchemaManager.d.ts +74 -0
- package/dist/storage/neo4j/Neo4jSchemaManager.js +224 -0
- package/dist/storage/neo4j/Neo4jSchemaManager.js.map +1 -0
- package/dist/storage/neo4j/Neo4jStorageProvider.d.ts +225 -0
- package/dist/storage/neo4j/Neo4jStorageProvider.js +1900 -0
- package/dist/storage/neo4j/Neo4jStorageProvider.js.map +1 -0
- package/dist/storage/neo4j/Neo4jVectorStore.d.ts +80 -0
- package/dist/storage/neo4j/Neo4jVectorStore.js +396 -0
- package/dist/storage/neo4j/Neo4jVectorStore.js.map +1 -0
- package/dist/types/entity-embedding.d.ts +156 -0
- package/dist/types/entity-embedding.js +2 -0
- package/dist/types/entity-embedding.js.map +1 -0
- package/dist/types/relation.d.ts +77 -0
- package/dist/types/relation.js +93 -0
- package/dist/types/relation.js.map +1 -0
- package/dist/types/temporalEntity.d.ts +55 -0
- package/dist/types/temporalEntity.js +66 -0
- package/dist/types/temporalEntity.js.map +1 -0
- package/dist/types/temporalRelation.d.ts +60 -0
- package/dist/types/temporalRelation.js +89 -0
- package/dist/types/temporalRelation.js.map +1 -0
- package/dist/types/vector-index.d.ts +48 -0
- package/dist/types/vector-index.js +2 -0
- package/dist/types/vector-index.js.map +1 -0
- package/dist/types/vector-store.d.ts +16 -0
- package/dist/types/vector-store.js +2 -0
- package/dist/types/vector-store.js.map +1 -0
- package/dist/utils/fs.d.ts +2 -0
- package/dist/utils/fs.js +3 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/logger.js.map +1 -0
- 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
|