@cmdoss/memwal-sdk 0.6.2 → 0.8.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/ARCHITECTURE.md +547 -547
- package/BENCHMARKS.md +238 -238
- package/README.md +310 -181
- package/dist/ai-sdk/tools.d.ts +2 -2
- package/dist/ai-sdk/tools.js +2 -2
- package/dist/client/ClientMemoryManager.js +2 -2
- package/dist/client/ClientMemoryManager.js.map +1 -1
- package/dist/client/PersonalDataWallet.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.d.ts +29 -1
- package/dist/client/SimplePDWClient.d.ts.map +1 -1
- package/dist/client/SimplePDWClient.js +45 -13
- package/dist/client/SimplePDWClient.js.map +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.d.ts +1 -1
- package/dist/client/namespaces/EmbeddingsNamespace.js +1 -1
- package/dist/client/namespaces/MemoryNamespace.d.ts +31 -0
- package/dist/client/namespaces/MemoryNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/MemoryNamespace.js +272 -39
- package/dist/client/namespaces/MemoryNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/AINamespace.d.ts +2 -2
- package/dist/client/namespaces/consolidated/AINamespace.js +2 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts +12 -2
- package/dist/client/namespaces/consolidated/BlockchainNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js +62 -4
- package/dist/client/namespaces/consolidated/BlockchainNamespace.js.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts +67 -2
- package/dist/client/namespaces/consolidated/StorageNamespace.d.ts.map +1 -1
- package/dist/client/namespaces/consolidated/StorageNamespace.js +549 -16
- package/dist/client/namespaces/consolidated/StorageNamespace.js.map +1 -1
- package/dist/config/ConfigurationHelper.js +61 -61
- package/dist/config/defaults.js +2 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/graph/GraphService.js +21 -21
- package/dist/graph/GraphService.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.d.ts +9 -5
- package/dist/infrastructure/seal/EncryptionService.d.ts.map +1 -1
- package/dist/infrastructure/seal/EncryptionService.js +37 -15
- package/dist/infrastructure/seal/EncryptionService.js.map +1 -1
- package/dist/infrastructure/seal/SealService.d.ts +13 -5
- package/dist/infrastructure/seal/SealService.d.ts.map +1 -1
- package/dist/infrastructure/seal/SealService.js +36 -34
- package/dist/infrastructure/seal/SealService.js.map +1 -1
- package/dist/langchain/createPDWRAG.js +30 -30
- package/dist/retrieval/MemoryDecryptionPipeline.d.ts.map +1 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js +2 -1
- package/dist/retrieval/MemoryDecryptionPipeline.js.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.d.ts +31 -0
- package/dist/retrieval/MemoryRetrievalService.d.ts.map +1 -1
- package/dist/retrieval/MemoryRetrievalService.js +44 -4
- package/dist/retrieval/MemoryRetrievalService.js.map +1 -1
- package/dist/services/CapabilityService.d.ts.map +1 -1
- package/dist/services/CapabilityService.js +30 -14
- package/dist/services/CapabilityService.js.map +1 -1
- package/dist/services/CrossContextPermissionService.d.ts.map +1 -1
- package/dist/services/CrossContextPermissionService.js +9 -7
- package/dist/services/CrossContextPermissionService.js.map +1 -1
- package/dist/services/EmbeddingService.d.ts +28 -1
- package/dist/services/EmbeddingService.d.ts.map +1 -1
- package/dist/services/EmbeddingService.js +54 -0
- package/dist/services/EmbeddingService.js.map +1 -1
- package/dist/services/EncryptionService.d.ts.map +1 -1
- package/dist/services/EncryptionService.js +6 -5
- package/dist/services/EncryptionService.js.map +1 -1
- package/dist/services/GeminiAIService.js +309 -309
- package/dist/services/IndexManager.d.ts +5 -1
- package/dist/services/IndexManager.d.ts.map +1 -1
- package/dist/services/IndexManager.js +17 -40
- package/dist/services/IndexManager.js.map +1 -1
- package/dist/services/QueryService.js +1 -1
- package/dist/services/QueryService.js.map +1 -1
- package/dist/services/StorageService.d.ts +11 -0
- package/dist/services/StorageService.d.ts.map +1 -1
- package/dist/services/StorageService.js +73 -10
- package/dist/services/StorageService.js.map +1 -1
- package/dist/services/TransactionService.d.ts +20 -0
- package/dist/services/TransactionService.d.ts.map +1 -1
- package/dist/services/TransactionService.js +43 -0
- package/dist/services/TransactionService.js.map +1 -1
- package/dist/services/ViewService.js +2 -2
- package/dist/services/ViewService.js.map +1 -1
- package/dist/services/storage/QuiltBatchManager.d.ts +101 -1
- package/dist/services/storage/QuiltBatchManager.d.ts.map +1 -1
- package/dist/services/storage/QuiltBatchManager.js +410 -20
- package/dist/services/storage/QuiltBatchManager.js.map +1 -1
- package/dist/services/storage/index.d.ts +1 -1
- package/dist/services/storage/index.d.ts.map +1 -1
- package/dist/services/storage/index.js.map +1 -1
- package/dist/utils/LRUCache.d.ts +106 -0
- package/dist/utils/LRUCache.d.ts.map +1 -0
- package/dist/utils/LRUCache.js +281 -0
- package/dist/utils/LRUCache.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/memoryIndexOnChain.d.ts +212 -0
- package/dist/utils/memoryIndexOnChain.d.ts.map +1 -0
- package/dist/utils/memoryIndexOnChain.js +312 -0
- package/dist/utils/memoryIndexOnChain.js.map +1 -0
- package/dist/utils/rebuildIndexNode.d.ts +29 -0
- package/dist/utils/rebuildIndexNode.d.ts.map +1 -1
- package/dist/utils/rebuildIndexNode.js +366 -98
- package/dist/utils/rebuildIndexNode.js.map +1 -1
- package/dist/vector/HnswWasmService.d.ts +20 -5
- package/dist/vector/HnswWasmService.d.ts.map +1 -1
- package/dist/vector/HnswWasmService.js +73 -40
- package/dist/vector/HnswWasmService.js.map +1 -1
- package/dist/vector/IHnswService.d.ts +10 -1
- package/dist/vector/IHnswService.d.ts.map +1 -1
- package/dist/vector/IHnswService.js.map +1 -1
- package/dist/vector/NodeHnswService.d.ts +16 -0
- package/dist/vector/NodeHnswService.d.ts.map +1 -1
- package/dist/vector/NodeHnswService.js +84 -5
- package/dist/vector/NodeHnswService.js.map +1 -1
- package/dist/vector/createHnswService.d.ts +1 -1
- package/dist/vector/createHnswService.js +1 -1
- package/dist/vector/index.d.ts +1 -1
- package/dist/vector/index.js +1 -1
- package/package.json +157 -157
- package/src/access/PermissionService.ts +635 -635
- package/src/aggregation/AggregationService.ts +389 -389
- package/src/ai-sdk/PDWVectorStore.ts +715 -715
- package/src/ai-sdk/index.ts +65 -65
- package/src/ai-sdk/tools.ts +460 -460
- package/src/ai-sdk/types.ts +404 -404
- package/src/batch/BatchManager.ts +597 -597
- package/src/batch/BatchingService.ts +429 -429
- package/src/batch/MemoryProcessingCache.ts +492 -492
- package/src/batch/index.ts +30 -30
- package/src/browser.ts +200 -200
- package/src/client/ClientMemoryManager.ts +987 -987
- package/src/client/PersonalDataWallet.ts +345 -345
- package/src/client/SimplePDWClient.ts +1289 -1222
- package/src/client/factory.ts +154 -154
- package/src/client/namespaces/AnalyticsNamespace.ts +377 -377
- package/src/client/namespaces/BatchNamespace.ts +356 -356
- package/src/client/namespaces/CacheNamespace.ts +123 -123
- package/src/client/namespaces/CapabilityNamespace.ts +217 -217
- package/src/client/namespaces/ClassifyNamespace.ts +169 -169
- package/src/client/namespaces/ContextNamespace.ts +297 -297
- package/src/client/namespaces/EmbeddingsNamespace.ts +99 -99
- package/src/client/namespaces/EncryptionNamespace.ts +221 -221
- package/src/client/namespaces/GraphNamespace.ts +468 -468
- package/src/client/namespaces/IndexNamespace.ts +361 -361
- package/src/client/namespaces/MemoryNamespace.ts +1422 -1135
- package/src/client/namespaces/PermissionsNamespace.ts +254 -254
- package/src/client/namespaces/PipelineNamespace.ts +220 -220
- package/src/client/namespaces/SearchNamespace.ts +1049 -1049
- package/src/client/namespaces/StorageNamespace.ts +458 -458
- package/src/client/namespaces/TxNamespace.ts +260 -260
- package/src/client/namespaces/WalletNamespace.ts +243 -243
- package/src/client/namespaces/consolidated/AINamespace.ts +449 -449
- package/src/client/namespaces/consolidated/BlockchainNamespace.ts +607 -546
- package/src/client/namespaces/consolidated/SecurityNamespace.ts +648 -648
- package/src/client/namespaces/consolidated/StorageNamespace.ts +1141 -497
- package/src/client/namespaces/consolidated/index.ts +39 -39
- package/src/client/signers/KeypairSigner.ts +108 -108
- package/src/client/signers/UnifiedSigner.ts +110 -110
- package/src/client/signers/WalletAdapterSigner.ts +159 -159
- package/src/client/signers/index.ts +26 -26
- package/src/config/ConfigurationHelper.ts +412 -412
- package/src/config/defaults.ts +51 -51
- package/src/config/index.ts +8 -8
- package/src/config/validation.ts +70 -70
- package/src/core/index.ts +14 -14
- package/src/core/interfaces/IService.ts +307 -307
- package/src/core/interfaces/index.ts +8 -8
- package/src/core/types/capability.ts +297 -297
- package/src/core/types/index.ts +870 -870
- package/src/core/types/wallet.ts +270 -270
- package/src/core/types.ts +9 -9
- package/src/core/wallet.ts +222 -222
- package/src/embedding/index.ts +19 -19
- package/src/embedding/types.ts +357 -357
- package/src/errors/index.ts +602 -602
- package/src/errors/recovery.ts +461 -461
- package/src/errors/validation.ts +567 -567
- package/src/generated/pdw/capability.ts +319 -319
- package/src/graph/GraphService.ts +887 -887
- package/src/graph/KnowledgeGraphManager.ts +728 -728
- package/src/graph/index.ts +25 -25
- package/src/index.ts +498 -474
- package/src/infrastructure/index.ts +22 -22
- package/src/infrastructure/seal/EncryptionService.ts +628 -603
- package/src/infrastructure/seal/SealService.ts +613 -615
- package/src/infrastructure/seal/index.ts +9 -9
- package/src/infrastructure/sui/BlockchainManager.ts +627 -627
- package/src/infrastructure/sui/SuiService.ts +888 -888
- package/src/infrastructure/sui/index.ts +9 -9
- package/src/infrastructure/walrus/StorageManager.ts +604 -604
- package/src/infrastructure/walrus/WalrusStorageService.ts +612 -612
- package/src/infrastructure/walrus/index.ts +9 -9
- package/src/langchain/PDWEmbeddings.ts +145 -145
- package/src/langchain/PDWVectorStore.ts +456 -456
- package/src/langchain/createPDWRAG.ts +303 -303
- package/src/langchain/index.ts +47 -47
- package/src/permissions/ConsentRepository.browser.ts +249 -249
- package/src/permissions/ConsentRepository.ts +364 -364
- package/src/pipeline/MemoryPipeline.ts +862 -862
- package/src/pipeline/PipelineManager.ts +683 -683
- package/src/pipeline/index.ts +26 -26
- package/src/retrieval/AdvancedSearchService.ts +629 -629
- package/src/retrieval/MemoryAnalyticsService.ts +711 -711
- package/src/retrieval/MemoryDecryptionPipeline.ts +825 -824
- package/src/retrieval/MemoryRetrievalService.ts +904 -830
- package/src/retrieval/index.ts +42 -42
- package/src/services/BatchService.ts +352 -352
- package/src/services/CapabilityService.ts +464 -448
- package/src/services/ClassifierService.ts +465 -465
- package/src/services/CrossContextPermissionService.ts +486 -484
- package/src/services/EmbeddingService.ts +771 -706
- package/src/services/EncryptionService.ts +712 -711
- package/src/services/GeminiAIService.ts +753 -753
- package/src/services/IndexManager.ts +977 -1004
- package/src/services/MemoryIndexService.ts +1003 -1003
- package/src/services/MemoryService.ts +369 -369
- package/src/services/QueryService.ts +890 -890
- package/src/services/StorageService.ts +1182 -1111
- package/src/services/TransactionService.ts +838 -790
- package/src/services/VectorService.ts +462 -462
- package/src/services/ViewService.ts +484 -484
- package/src/services/index.ts +25 -25
- package/src/services/storage/BlobAttributesManager.ts +333 -333
- package/src/services/storage/KnowledgeGraphManager.ts +425 -425
- package/src/services/storage/MemorySearchManager.ts +387 -387
- package/src/services/storage/QuiltBatchManager.ts +1130 -660
- package/src/services/storage/WalrusMetadataManager.ts +268 -268
- package/src/services/storage/WalrusStorageManager.ts +287 -287
- package/src/services/storage/index.ts +57 -52
- package/src/types/index.ts +13 -13
- package/src/utils/LRUCache.ts +378 -0
- package/src/utils/index.ts +76 -68
- package/src/utils/memoryIndexOnChain.ts +507 -0
- package/src/utils/rebuildIndex.ts +290 -290
- package/src/utils/rebuildIndexNode.ts +771 -424
- package/src/vector/BrowserHnswIndexService.ts +758 -758
- package/src/vector/HnswWasmService.ts +731 -679
- package/src/vector/IHnswService.ts +233 -224
- package/src/vector/NodeHnswService.ts +833 -735
- package/src/vector/VectorManager.ts +478 -478
- package/src/vector/createHnswService.ts +135 -135
- package/src/vector/index.ts +56 -56
- package/src/wallet/ContextWalletService.ts +656 -656
- package/src/wallet/MainWalletService.ts +317 -317
|
@@ -1,729 +1,729 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KnowledgeGraphManager - Integration layer for memory-graph processing
|
|
3
|
-
*
|
|
4
|
-
* Orchestrates knowledge graph updates as memories are processed,
|
|
5
|
-
* provides intelligent graph queries, and manages graph persistence.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { GraphService, KnowledgeGraph, Entity, Relationship, GraphExtractionResult } from './GraphService';
|
|
9
|
-
import { Memory, ProcessedMemory } from '../embedding/types';
|
|
10
|
-
|
|
11
|
-
export interface GraphMemoryMapping {
|
|
12
|
-
memoryId: string;
|
|
13
|
-
entityIds: string[];
|
|
14
|
-
relationshipIds: string[];
|
|
15
|
-
extractionDate: Date;
|
|
16
|
-
confidence: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface GraphUpdateResult {
|
|
20
|
-
success: boolean;
|
|
21
|
-
entitiesAdded: number;
|
|
22
|
-
relationshipsAdded: number;
|
|
23
|
-
entitiesUpdated: number;
|
|
24
|
-
relationshipsUpdated: number;
|
|
25
|
-
processingTimeMs: number;
|
|
26
|
-
extractionResult?: GraphExtractionResult;
|
|
27
|
-
error?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface GraphSearchQuery {
|
|
31
|
-
keywords?: string[];
|
|
32
|
-
entityTypes?: string[];
|
|
33
|
-
relationshipTypes?: string[];
|
|
34
|
-
memoryIds?: string[];
|
|
35
|
-
dateRange?: {
|
|
36
|
-
start: Date;
|
|
37
|
-
end: Date;
|
|
38
|
-
};
|
|
39
|
-
similarToMemory?: string;
|
|
40
|
-
maxResults?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface GraphSearchResult {
|
|
44
|
-
entities: Entity[];
|
|
45
|
-
relationships: Relationship[];
|
|
46
|
-
relatedMemories: string[];
|
|
47
|
-
searchPaths?: Array<{
|
|
48
|
-
score: number;
|
|
49
|
-
entities: string[];
|
|
50
|
-
relationships: string[];
|
|
51
|
-
}>;
|
|
52
|
-
totalResults: number;
|
|
53
|
-
queryTimeMs: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface KnowledgeGraphStats {
|
|
57
|
-
totalGraphs: number;
|
|
58
|
-
totalEntities: number;
|
|
59
|
-
totalRelationships: number;
|
|
60
|
-
averageConnections: number;
|
|
61
|
-
topEntityTypes: Array<{ type: string; count: number }>;
|
|
62
|
-
topRelationshipTypes: Array<{ type: string; count: number }>;
|
|
63
|
-
memoryMappings: number;
|
|
64
|
-
lastUpdate: Date;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* High-level knowledge graph manager integrating with memory processing
|
|
69
|
-
*/
|
|
70
|
-
export class KnowledgeGraphManager {
|
|
71
|
-
private graphService: GraphService;
|
|
72
|
-
private memoryMappings = new Map<string, GraphMemoryMapping[]>(); // userId -> mappings
|
|
73
|
-
private graphCache = new Map<string, { graph: KnowledgeGraph; lastUpdated: Date }>();
|
|
74
|
-
|
|
75
|
-
private stats = {
|
|
76
|
-
totalUpdates: 0,
|
|
77
|
-
successfulUpdates: 0,
|
|
78
|
-
failedUpdates: 0,
|
|
79
|
-
averageProcessingTime: 0,
|
|
80
|
-
totalEntitiesCreated: 0,
|
|
81
|
-
totalRelationshipsCreated: 0
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
constructor(graphService?: GraphService) {
|
|
85
|
-
this.graphService = graphService || new GraphService();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ==================== MEMORY INTEGRATION ====================
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Process memory and update knowledge graph
|
|
92
|
-
*/
|
|
93
|
-
async processMemoryForGraph(
|
|
94
|
-
memory: ProcessedMemory,
|
|
95
|
-
userIdParam?: string,
|
|
96
|
-
options: {
|
|
97
|
-
forceReprocess?: boolean;
|
|
98
|
-
skipCache?: boolean;
|
|
99
|
-
confidenceThreshold?: number;
|
|
100
|
-
} = {}
|
|
101
|
-
): Promise<GraphUpdateResult> {
|
|
102
|
-
const startTime = Date.now();
|
|
103
|
-
this.stats.totalUpdates++;
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// Extract userId from memory or use provided parameter
|
|
107
|
-
const userId = userIdParam || memory.userId;
|
|
108
|
-
if (!userId) {
|
|
109
|
-
return {
|
|
110
|
-
success: false,
|
|
111
|
-
entitiesAdded: 0,
|
|
112
|
-
relationshipsAdded: 0,
|
|
113
|
-
entitiesUpdated: 0,
|
|
114
|
-
relationshipsUpdated: 0,
|
|
115
|
-
processingTimeMs: Date.now() - startTime,
|
|
116
|
-
error: 'No userId provided'
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Check if already processed
|
|
121
|
-
if (!options.forceReprocess && this.isMemoryProcessed(userId, memory.id)) {
|
|
122
|
-
return {
|
|
123
|
-
success: true,
|
|
124
|
-
entitiesAdded: 0,
|
|
125
|
-
relationshipsAdded: 0,
|
|
126
|
-
entitiesUpdated: 0,
|
|
127
|
-
relationshipsUpdated: 0,
|
|
128
|
-
processingTimeMs: Date.now() - startTime
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Extract entities and relationships from memory content
|
|
133
|
-
const extractionResult = await this.graphService.extractEntitiesAndRelationships(
|
|
134
|
-
memory.content,
|
|
135
|
-
memory.id,
|
|
136
|
-
{ confidenceThreshold: options.confidenceThreshold }
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
// Check if extraction returned valid result
|
|
140
|
-
if (!extractionResult) {
|
|
141
|
-
return {
|
|
142
|
-
success: false,
|
|
143
|
-
entitiesAdded: 0,
|
|
144
|
-
relationshipsAdded: 0,
|
|
145
|
-
entitiesUpdated: 0,
|
|
146
|
-
relationshipsUpdated: 0,
|
|
147
|
-
processingTimeMs: Date.now() - startTime,
|
|
148
|
-
error: 'Extraction returned no result'
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Skip if extraction failed or confidence too low
|
|
153
|
-
const minConfidence = options.confidenceThreshold || 0.3;
|
|
154
|
-
if ((extractionResult.confidence || 0) < minConfidence) {
|
|
155
|
-
console.log(`Skipping memory ${memory.id} due to low confidence: ${extractionResult.confidence}`);
|
|
156
|
-
return {
|
|
157
|
-
success: false,
|
|
158
|
-
entitiesAdded: 0,
|
|
159
|
-
relationshipsAdded: 0,
|
|
160
|
-
entitiesUpdated: 0,
|
|
161
|
-
relationshipsUpdated: 0,
|
|
162
|
-
processingTimeMs: Date.now() - startTime,
|
|
163
|
-
error: 'Extraction confidence below threshold'
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Get or create user's knowledge graph
|
|
168
|
-
let graph = await this.getUserGraph(userId);
|
|
169
|
-
if (!graph) {
|
|
170
|
-
graph = this.graphService.createGraph(userId);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Count entities/relationships before update
|
|
174
|
-
const entitiesBefore = graph.entities.length;
|
|
175
|
-
const relationshipsBefore = graph.relationships.length;
|
|
176
|
-
|
|
177
|
-
// Add extracted data to graph
|
|
178
|
-
const updatedGraph = this.graphService.addToGraph(
|
|
179
|
-
graph,
|
|
180
|
-
extractionResult.entities,
|
|
181
|
-
extractionResult.relationships,
|
|
182
|
-
memory.id
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
// Calculate changes
|
|
186
|
-
const entitiesAdded = updatedGraph.entities.length - entitiesBefore;
|
|
187
|
-
const relationshipsAdded = updatedGraph.relationships.length - relationshipsBefore;
|
|
188
|
-
const entitiesUpdated = entitiesAdded > 0 ? 0 : extractionResult.entities.length; // Estimate
|
|
189
|
-
const relationshipsUpdated = relationshipsAdded > 0 ? 0 : extractionResult.relationships.length; // Estimate
|
|
190
|
-
|
|
191
|
-
// Update cached graph
|
|
192
|
-
this.updateUserGraph(userId, updatedGraph);
|
|
193
|
-
|
|
194
|
-
// Track memory mapping
|
|
195
|
-
this.addMemoryMapping(userId, {
|
|
196
|
-
memoryId: memory.id,
|
|
197
|
-
entityIds: extractionResult.entities.map(e => e.id),
|
|
198
|
-
relationshipIds: extractionResult.relationships.map(r =>
|
|
199
|
-
r.id || this.generateRelationshipId(r)
|
|
200
|
-
),
|
|
201
|
-
extractionDate: new Date(),
|
|
202
|
-
confidence: extractionResult.confidence
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Update statistics
|
|
206
|
-
this.stats.successfulUpdates++;
|
|
207
|
-
this.stats.totalEntitiesCreated += entitiesAdded;
|
|
208
|
-
this.stats.totalRelationshipsCreated += relationshipsAdded;
|
|
209
|
-
this.updateAverageProcessingTime(Date.now() - startTime);
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
success: true,
|
|
213
|
-
entitiesAdded,
|
|
214
|
-
relationshipsAdded,
|
|
215
|
-
entitiesUpdated,
|
|
216
|
-
relationshipsUpdated,
|
|
217
|
-
processingTimeMs: Date.now() - startTime,
|
|
218
|
-
extractionResult
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
} catch (error) {
|
|
222
|
-
if (process.env.NODE_ENV === 'development') {
|
|
223
|
-
console.error('Error processing memory for graph:', error);
|
|
224
|
-
}
|
|
225
|
-
this.stats.failedUpdates++;
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
success: false,
|
|
229
|
-
entitiesAdded: 0,
|
|
230
|
-
relationshipsAdded: 0,
|
|
231
|
-
entitiesUpdated: 0,
|
|
232
|
-
relationshipsUpdated: 0,
|
|
233
|
-
processingTimeMs: Date.now() - startTime,
|
|
234
|
-
error: (error as Error).message
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Process multiple memories for graph updates (alias for compatibility)
|
|
241
|
-
*/
|
|
242
|
-
async processBatchMemoriesForGraph(
|
|
243
|
-
userId: string,
|
|
244
|
-
memories: Memory[],
|
|
245
|
-
options: {
|
|
246
|
-
batchSize?: number;
|
|
247
|
-
delayMs?: number;
|
|
248
|
-
onProgress?: (completed: number, total: number) => void;
|
|
249
|
-
} = {}
|
|
250
|
-
): Promise<GraphUpdateResult[]> {
|
|
251
|
-
// Convert Memory to ProcessedMemory
|
|
252
|
-
const processedMemories = memories.map(m => ({
|
|
253
|
-
...m,
|
|
254
|
-
userId: m.userId || userId,
|
|
255
|
-
category: m.category || 'general',
|
|
256
|
-
createdAt: m.createdAt || (m.metadata?.createdAt as Date) || new Date()
|
|
257
|
-
})) as ProcessedMemory[];
|
|
258
|
-
|
|
259
|
-
return this.processMemoriesForGraphBatch(processedMemories, userId, options);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Process multiple memories for graph updates
|
|
264
|
-
*/
|
|
265
|
-
async processMemoriesForGraphBatch(
|
|
266
|
-
memories: ProcessedMemory[],
|
|
267
|
-
userId: string,
|
|
268
|
-
options: {
|
|
269
|
-
batchSize?: number;
|
|
270
|
-
delayMs?: number;
|
|
271
|
-
onProgress?: (completed: number, total: number) => void;
|
|
272
|
-
} = {}
|
|
273
|
-
): Promise<GraphUpdateResult[]> {
|
|
274
|
-
const batchSize = options.batchSize || 5;
|
|
275
|
-
const delayMs = options.delayMs || 1000;
|
|
276
|
-
const results: GraphUpdateResult[] = [];
|
|
277
|
-
|
|
278
|
-
for (let i = 0; i < memories.length; i += batchSize) {
|
|
279
|
-
const batch = memories.slice(i, i + batchSize);
|
|
280
|
-
|
|
281
|
-
const batchPromises = batch.map(memory =>
|
|
282
|
-
this.processMemoryForGraph(memory, userId)
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const batchResults = await Promise.all(batchPromises);
|
|
286
|
-
results.push(...batchResults);
|
|
287
|
-
|
|
288
|
-
// Progress callback
|
|
289
|
-
if (options.onProgress) {
|
|
290
|
-
options.onProgress(i + batch.length, memories.length);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Delay between batches
|
|
294
|
-
if (i + batchSize < memories.length) {
|
|
295
|
-
await this.delay(delayMs);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return results;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ==================== GRAPH QUERIES ====================
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Search knowledge graph with complex queries
|
|
306
|
-
*/
|
|
307
|
-
async searchGraph(
|
|
308
|
-
userId: string,
|
|
309
|
-
query: GraphSearchQuery
|
|
310
|
-
): Promise<GraphSearchResult> {
|
|
311
|
-
const startTime = Date.now();
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
const graph = await this.getUserGraph(userId);
|
|
315
|
-
if (!graph) {
|
|
316
|
-
return {
|
|
317
|
-
entities: [],
|
|
318
|
-
relationships: [],
|
|
319
|
-
relatedMemories: [],
|
|
320
|
-
totalResults: 0,
|
|
321
|
-
queryTimeMs: Date.now() - startTime
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
let entities = graph.entities;
|
|
326
|
-
let relationships = graph.relationships;
|
|
327
|
-
|
|
328
|
-
// Filter by entity types
|
|
329
|
-
if (query.entityTypes && query.entityTypes.length > 0) {
|
|
330
|
-
entities = entities.filter(e => query.entityTypes!.includes(e.type));
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Filter by relationship types
|
|
334
|
-
if (query.relationshipTypes && query.relationshipTypes.length > 0) {
|
|
335
|
-
relationships = relationships.filter(r =>
|
|
336
|
-
query.relationshipTypes!.includes(r.type || r.label)
|
|
337
|
-
);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Keyword search
|
|
341
|
-
if (query.keywords && query.keywords.length > 0) {
|
|
342
|
-
const keywords = query.keywords.map(k => k.toLowerCase());
|
|
343
|
-
|
|
344
|
-
entities = entities.filter(e =>
|
|
345
|
-
keywords.some(keyword =>
|
|
346
|
-
e.label.toLowerCase().includes(keyword) ||
|
|
347
|
-
JSON.stringify(e.properties || {}).toLowerCase().includes(keyword)
|
|
348
|
-
)
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
relationships = relationships.filter(r =>
|
|
352
|
-
keywords.some(keyword =>
|
|
353
|
-
r.label.toLowerCase().includes(keyword) ||
|
|
354
|
-
JSON.stringify(r.properties || {}).toLowerCase().includes(keyword)
|
|
355
|
-
)
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Filter by memory IDs
|
|
360
|
-
if (query.memoryIds && query.memoryIds.length > 0) {
|
|
361
|
-
entities = entities.filter(e =>
|
|
362
|
-
e.sourceMemoryIds &&
|
|
363
|
-
e.sourceMemoryIds.some(memId => query.memoryIds!.includes(memId))
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
relationships = relationships.filter(r =>
|
|
367
|
-
r.sourceMemoryIds &&
|
|
368
|
-
r.sourceMemoryIds.some(memId => query.memoryIds!.includes(memId))
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Date range filter
|
|
373
|
-
if (query.dateRange) {
|
|
374
|
-
const { start, end } = query.dateRange;
|
|
375
|
-
|
|
376
|
-
entities = entities.filter(e =>
|
|
377
|
-
e.createdAt && e.createdAt >= start && e.createdAt <= end
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
relationships = relationships.filter(r =>
|
|
381
|
-
r.createdAt && r.createdAt >= start && r.createdAt <= end
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Apply result limit
|
|
386
|
-
if (query.maxResults) {
|
|
387
|
-
entities = entities.slice(0, Math.floor(query.maxResults / 2));
|
|
388
|
-
relationships = relationships.slice(0, Math.floor(query.maxResults / 2));
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Collect related memories
|
|
392
|
-
const relatedMemories = new Set<string>();
|
|
393
|
-
entities.forEach(e => e.sourceMemoryIds?.forEach(id => relatedMemories.add(id)));
|
|
394
|
-
relationships.forEach(r => r.sourceMemoryIds?.forEach(id => relatedMemories.add(id)));
|
|
395
|
-
|
|
396
|
-
// Find search paths if similarity query
|
|
397
|
-
let searchPaths: Array<{ score: number; entities: string[]; relationships: string[] }> = [];
|
|
398
|
-
if (query.similarToMemory) {
|
|
399
|
-
// Find entities from the reference memory
|
|
400
|
-
const referenceEntities = entities.filter(e =>
|
|
401
|
-
e.sourceMemoryIds?.includes(query.similarToMemory!)
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
if (referenceEntities.length > 0) {
|
|
405
|
-
const relatedResult = this.graphService.findRelatedEntities(
|
|
406
|
-
graph,
|
|
407
|
-
referenceEntities.map(e => e.id),
|
|
408
|
-
{ maxHops: 2, includeWeights: true }
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
searchPaths = relatedResult.paths || [];
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
return {
|
|
416
|
-
entities,
|
|
417
|
-
relationships,
|
|
418
|
-
relatedMemories: Array.from(relatedMemories),
|
|
419
|
-
searchPaths,
|
|
420
|
-
totalResults: entities.length + relationships.length,
|
|
421
|
-
queryTimeMs: Date.now() - startTime
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
} catch (error) {
|
|
425
|
-
if (process.env.NODE_ENV === 'development') {
|
|
426
|
-
console.error('Error searching graph:', error);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return {
|
|
430
|
-
entities: [],
|
|
431
|
-
relationships: [],
|
|
432
|
-
relatedMemories: [],
|
|
433
|
-
totalResults: 0,
|
|
434
|
-
queryTimeMs: Date.now() - startTime
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Find memories connected to a specific entity or concept
|
|
441
|
-
*/
|
|
442
|
-
async findMemoriesRelatedToEntity(
|
|
443
|
-
userId: string,
|
|
444
|
-
entityId: string,
|
|
445
|
-
options: {
|
|
446
|
-
maxHops?: number;
|
|
447
|
-
includeRelationships?: boolean;
|
|
448
|
-
} = {}
|
|
449
|
-
): Promise<{
|
|
450
|
-
memories: string[];
|
|
451
|
-
connectedEntities: Entity[];
|
|
452
|
-
pathways: Array<{ memory: string; entities: string[]; score: number }>;
|
|
453
|
-
}> {
|
|
454
|
-
try {
|
|
455
|
-
const graph = await this.getUserGraph(userId);
|
|
456
|
-
if (!graph) {
|
|
457
|
-
return { memories: [], connectedEntities: [], pathways: [] };
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Find related entities through graph traversal
|
|
461
|
-
const relatedResult = this.graphService.findRelatedEntities(
|
|
462
|
-
graph,
|
|
463
|
-
[entityId],
|
|
464
|
-
{
|
|
465
|
-
maxHops: options.maxHops || 2,
|
|
466
|
-
includeWeights: true
|
|
467
|
-
}
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
// Collect memories from all related entities
|
|
471
|
-
const memories = new Set<string>();
|
|
472
|
-
const pathways: Array<{ memory: string; entities: string[]; score: number }> = [];
|
|
473
|
-
|
|
474
|
-
for (const entity of relatedResult.entities) {
|
|
475
|
-
if (entity.sourceMemoryIds) {
|
|
476
|
-
for (const memoryId of entity.sourceMemoryIds) {
|
|
477
|
-
memories.add(memoryId);
|
|
478
|
-
|
|
479
|
-
// Track pathway
|
|
480
|
-
pathways.push({
|
|
481
|
-
memory: memoryId,
|
|
482
|
-
entities: [entityId, entity.id],
|
|
483
|
-
score: entity.confidence || 0.5
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return {
|
|
490
|
-
memories: Array.from(memories),
|
|
491
|
-
connectedEntities: relatedResult.entities,
|
|
492
|
-
pathways
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
} catch (error) {
|
|
496
|
-
if (process.env.NODE_ENV === 'development') {
|
|
497
|
-
console.error('Error finding related memories:', error);
|
|
498
|
-
}
|
|
499
|
-
return { memories: [], connectedEntities: [], pathways: [] };
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// ==================== GRAPH MANAGEMENT ====================
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Get user's knowledge graph
|
|
507
|
-
*/
|
|
508
|
-
async getUserGraph(userId: string): Promise<KnowledgeGraph | null> {
|
|
509
|
-
try {
|
|
510
|
-
// Check cache first
|
|
511
|
-
const cached = this.graphCache.get(userId);
|
|
512
|
-
if (cached) {
|
|
513
|
-
return cached.graph;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Try to get from service cache
|
|
517
|
-
const graph = this.graphService.getUserGraph(userId);
|
|
518
|
-
if (graph) {
|
|
519
|
-
this.graphCache.set(userId, { graph, lastUpdated: new Date() });
|
|
520
|
-
return graph;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return null;
|
|
524
|
-
|
|
525
|
-
} catch (error) {
|
|
526
|
-
if (process.env.NODE_ENV === 'development') {
|
|
527
|
-
console.error('Error getting user graph:', error);
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Update user's knowledge graph
|
|
535
|
-
*/
|
|
536
|
-
updateUserGraph(userId: string, graph: KnowledgeGraph): void {
|
|
537
|
-
this.graphService.setUserGraph(userId, graph);
|
|
538
|
-
this.graphCache.set(userId, { graph, lastUpdated: new Date() });
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Clear user's knowledge graph
|
|
543
|
-
*/
|
|
544
|
-
clearUserGraph(userId: string): void {
|
|
545
|
-
this.graphCache.delete(userId);
|
|
546
|
-
this.memoryMappings.delete(userId);
|
|
547
|
-
// Note: GraphService doesn't have a clear method, but we clear our caches
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Record a memory mapping (public method for tests)
|
|
552
|
-
*/
|
|
553
|
-
recordMemoryMapping(mapping: GraphMemoryMapping): void {
|
|
554
|
-
// Extract userId from first part of memoryId or use a default
|
|
555
|
-
// Assuming memoryId format doesn't include userId, store all in a global list
|
|
556
|
-
const globalKey = '__all__';
|
|
557
|
-
const mappings = this.memoryMappings.get(globalKey) || [];
|
|
558
|
-
mappings.push(mapping);
|
|
559
|
-
this.memoryMappings.set(globalKey, mappings);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Get memory mappings by memory ID (public method for tests)
|
|
564
|
-
*/
|
|
565
|
-
getMemoryMappings(memoryId: string): GraphMemoryMapping[] {
|
|
566
|
-
// Search all mappings for this memory ID
|
|
567
|
-
const allMappings: GraphMemoryMapping[] = [];
|
|
568
|
-
|
|
569
|
-
for (const mappings of this.memoryMappings.values()) {
|
|
570
|
-
allMappings.push(...mappings.filter(m => m.memoryId === memoryId));
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return allMappings;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Get statistics for a specific user's graph
|
|
578
|
-
*/
|
|
579
|
-
getGraphStatistics(userId: string): {
|
|
580
|
-
totalEntities: number;
|
|
581
|
-
totalRelationships: number;
|
|
582
|
-
sourceMemoriesCount: number;
|
|
583
|
-
entityTypeDistribution: Record<string, number>;
|
|
584
|
-
relationshipTypeDistribution: Record<string, number>;
|
|
585
|
-
averageEntityConfidence: number;
|
|
586
|
-
averageRelationshipConfidence: number;
|
|
587
|
-
} {
|
|
588
|
-
const graph = this.graphService.getUserGraph(userId);
|
|
589
|
-
|
|
590
|
-
if (!graph) {
|
|
591
|
-
return {
|
|
592
|
-
totalEntities: 0,
|
|
593
|
-
totalRelationships: 0,
|
|
594
|
-
sourceMemoriesCount: 0,
|
|
595
|
-
entityTypeDistribution: {},
|
|
596
|
-
relationshipTypeDistribution: {},
|
|
597
|
-
averageEntityConfidence: 0,
|
|
598
|
-
averageRelationshipConfidence: 0
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Entity type distribution
|
|
603
|
-
const entityTypeDistribution: Record<string, number> = {};
|
|
604
|
-
graph.entities.forEach(entity => {
|
|
605
|
-
entityTypeDistribution[entity.type] = (entityTypeDistribution[entity.type] || 0) + 1;
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// Relationship type distribution
|
|
609
|
-
const relationshipTypeDistribution: Record<string, number> = {};
|
|
610
|
-
graph.relationships.forEach(rel => {
|
|
611
|
-
const type = rel.type || rel.label;
|
|
612
|
-
relationshipTypeDistribution[type] = (relationshipTypeDistribution[type] || 0) + 1;
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
// Average confidences
|
|
616
|
-
const avgEntityConfidence = graph.entities.length > 0
|
|
617
|
-
? graph.entities.reduce((sum, e) => sum + (e.confidence || 0), 0) / graph.entities.length
|
|
618
|
-
: 0;
|
|
619
|
-
|
|
620
|
-
const avgRelationshipConfidence = graph.relationships.length > 0
|
|
621
|
-
? graph.relationships.reduce((sum, r) => sum + (r.confidence || 0), 0) / graph.relationships.length
|
|
622
|
-
: 0;
|
|
623
|
-
|
|
624
|
-
return {
|
|
625
|
-
totalEntities: graph.entities.length,
|
|
626
|
-
totalRelationships: graph.relationships.length,
|
|
627
|
-
sourceMemoriesCount: graph.metadata.sourceMemories?.length || 0,
|
|
628
|
-
entityTypeDistribution,
|
|
629
|
-
relationshipTypeDistribution,
|
|
630
|
-
averageEntityConfidence: avgEntityConfidence,
|
|
631
|
-
averageRelationshipConfidence: avgRelationshipConfidence
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* Get comprehensive statistics
|
|
637
|
-
*/
|
|
638
|
-
getKnowledgeGraphStats(): KnowledgeGraphStats {
|
|
639
|
-
const allGraphs = Array.from(this.graphCache.values()).map(cached => cached.graph);
|
|
640
|
-
|
|
641
|
-
// Aggregate statistics
|
|
642
|
-
const totalEntities = allGraphs.reduce((sum, graph) => sum + graph.entities.length, 0);
|
|
643
|
-
const totalRelationships = allGraphs.reduce((sum, graph) => sum + graph.relationships.length, 0);
|
|
644
|
-
|
|
645
|
-
// Entity type distribution
|
|
646
|
-
const entityTypeCounts = new Map<string, number>();
|
|
647
|
-
allGraphs.forEach(graph => {
|
|
648
|
-
graph.entities.forEach(entity => {
|
|
649
|
-
entityTypeCounts.set(entity.type, (entityTypeCounts.get(entity.type) || 0) + 1);
|
|
650
|
-
});
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
// Relationship type distribution
|
|
654
|
-
const relationshipTypeCounts = new Map<string, number>();
|
|
655
|
-
allGraphs.forEach(graph => {
|
|
656
|
-
graph.relationships.forEach(rel => {
|
|
657
|
-
const type = rel.type || rel.label;
|
|
658
|
-
relationshipTypeCounts.set(type, (relationshipTypeCounts.get(type) || 0) + 1);
|
|
659
|
-
});
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
// Get top types
|
|
663
|
-
const topEntityTypes = Array.from(entityTypeCounts.entries())
|
|
664
|
-
.sort(([, a], [, b]) => b - a)
|
|
665
|
-
.slice(0, 10)
|
|
666
|
-
.map(([type, count]) => ({ type, count }));
|
|
667
|
-
|
|
668
|
-
const topRelationshipTypes = Array.from(relationshipTypeCounts.entries())
|
|
669
|
-
.sort(([, a], [, b]) => b - a)
|
|
670
|
-
.slice(0, 10)
|
|
671
|
-
.map(([type, count]) => ({ type, count }));
|
|
672
|
-
|
|
673
|
-
// Calculate average connections
|
|
674
|
-
const totalConnections = allGraphs.reduce((sum, graph) => {
|
|
675
|
-
const connections = new Map<string, number>();
|
|
676
|
-
graph.relationships.forEach(rel => {
|
|
677
|
-
connections.set(rel.source, (connections.get(rel.source) || 0) + 1);
|
|
678
|
-
connections.set(rel.target, (connections.get(rel.target) || 0) + 1);
|
|
679
|
-
});
|
|
680
|
-
return sum + Array.from(connections.values()).reduce((s, c) => s + c, 0);
|
|
681
|
-
}, 0);
|
|
682
|
-
|
|
683
|
-
const averageConnections = totalEntities > 0 ? totalConnections / totalEntities : 0;
|
|
684
|
-
|
|
685
|
-
// Count memory mappings
|
|
686
|
-
const totalMappings = Array.from(this.memoryMappings.values())
|
|
687
|
-
.reduce((sum, mappings) => sum + mappings.length, 0);
|
|
688
|
-
|
|
689
|
-
return {
|
|
690
|
-
totalGraphs: allGraphs.length,
|
|
691
|
-
totalEntities,
|
|
692
|
-
totalRelationships,
|
|
693
|
-
averageConnections,
|
|
694
|
-
topEntityTypes,
|
|
695
|
-
topRelationshipTypes,
|
|
696
|
-
memoryMappings: totalMappings,
|
|
697
|
-
lastUpdate: new Date() // TODO: Track actual last update
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// ==================== PRIVATE METHODS ====================
|
|
702
|
-
|
|
703
|
-
private isMemoryProcessed(userId: string, memoryId: string): boolean {
|
|
704
|
-
const mappings = this.memoryMappings.get(userId) || [];
|
|
705
|
-
return mappings.some(mapping => mapping.memoryId === memoryId);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
private addMemoryMapping(userId: string, mapping: GraphMemoryMapping): void {
|
|
709
|
-
const mappings = this.memoryMappings.get(userId) || [];
|
|
710
|
-
mappings.push(mapping);
|
|
711
|
-
this.memoryMappings.set(userId, mappings);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
private generateRelationshipId(relationship: Relationship): string {
|
|
715
|
-
const content = `${relationship.source}_${relationship.target}_${relationship.label}`;
|
|
716
|
-
return content.toLowerCase().replace(/[^\w]/g, '_');
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
private updateAverageProcessingTime(processingTime: number): void {
|
|
720
|
-
this.stats.averageProcessingTime =
|
|
721
|
-
(this.stats.averageProcessingTime + processingTime) / this.stats.totalUpdates;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
private delay(ms: number): Promise<void> {
|
|
725
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
1
|
+
/**
|
|
2
|
+
* KnowledgeGraphManager - Integration layer for memory-graph processing
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates knowledge graph updates as memories are processed,
|
|
5
|
+
* provides intelligent graph queries, and manages graph persistence.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { GraphService, KnowledgeGraph, Entity, Relationship, GraphExtractionResult } from './GraphService';
|
|
9
|
+
import { Memory, ProcessedMemory } from '../embedding/types';
|
|
10
|
+
|
|
11
|
+
export interface GraphMemoryMapping {
|
|
12
|
+
memoryId: string;
|
|
13
|
+
entityIds: string[];
|
|
14
|
+
relationshipIds: string[];
|
|
15
|
+
extractionDate: Date;
|
|
16
|
+
confidence: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GraphUpdateResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
entitiesAdded: number;
|
|
22
|
+
relationshipsAdded: number;
|
|
23
|
+
entitiesUpdated: number;
|
|
24
|
+
relationshipsUpdated: number;
|
|
25
|
+
processingTimeMs: number;
|
|
26
|
+
extractionResult?: GraphExtractionResult;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GraphSearchQuery {
|
|
31
|
+
keywords?: string[];
|
|
32
|
+
entityTypes?: string[];
|
|
33
|
+
relationshipTypes?: string[];
|
|
34
|
+
memoryIds?: string[];
|
|
35
|
+
dateRange?: {
|
|
36
|
+
start: Date;
|
|
37
|
+
end: Date;
|
|
38
|
+
};
|
|
39
|
+
similarToMemory?: string;
|
|
40
|
+
maxResults?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface GraphSearchResult {
|
|
44
|
+
entities: Entity[];
|
|
45
|
+
relationships: Relationship[];
|
|
46
|
+
relatedMemories: string[];
|
|
47
|
+
searchPaths?: Array<{
|
|
48
|
+
score: number;
|
|
49
|
+
entities: string[];
|
|
50
|
+
relationships: string[];
|
|
51
|
+
}>;
|
|
52
|
+
totalResults: number;
|
|
53
|
+
queryTimeMs: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface KnowledgeGraphStats {
|
|
57
|
+
totalGraphs: number;
|
|
58
|
+
totalEntities: number;
|
|
59
|
+
totalRelationships: number;
|
|
60
|
+
averageConnections: number;
|
|
61
|
+
topEntityTypes: Array<{ type: string; count: number }>;
|
|
62
|
+
topRelationshipTypes: Array<{ type: string; count: number }>;
|
|
63
|
+
memoryMappings: number;
|
|
64
|
+
lastUpdate: Date;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* High-level knowledge graph manager integrating with memory processing
|
|
69
|
+
*/
|
|
70
|
+
export class KnowledgeGraphManager {
|
|
71
|
+
private graphService: GraphService;
|
|
72
|
+
private memoryMappings = new Map<string, GraphMemoryMapping[]>(); // userId -> mappings
|
|
73
|
+
private graphCache = new Map<string, { graph: KnowledgeGraph; lastUpdated: Date }>();
|
|
74
|
+
|
|
75
|
+
private stats = {
|
|
76
|
+
totalUpdates: 0,
|
|
77
|
+
successfulUpdates: 0,
|
|
78
|
+
failedUpdates: 0,
|
|
79
|
+
averageProcessingTime: 0,
|
|
80
|
+
totalEntitiesCreated: 0,
|
|
81
|
+
totalRelationshipsCreated: 0
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
constructor(graphService?: GraphService) {
|
|
85
|
+
this.graphService = graphService || new GraphService();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ==================== MEMORY INTEGRATION ====================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Process memory and update knowledge graph
|
|
92
|
+
*/
|
|
93
|
+
async processMemoryForGraph(
|
|
94
|
+
memory: ProcessedMemory,
|
|
95
|
+
userIdParam?: string,
|
|
96
|
+
options: {
|
|
97
|
+
forceReprocess?: boolean;
|
|
98
|
+
skipCache?: boolean;
|
|
99
|
+
confidenceThreshold?: number;
|
|
100
|
+
} = {}
|
|
101
|
+
): Promise<GraphUpdateResult> {
|
|
102
|
+
const startTime = Date.now();
|
|
103
|
+
this.stats.totalUpdates++;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Extract userId from memory or use provided parameter
|
|
107
|
+
const userId = userIdParam || memory.userId;
|
|
108
|
+
if (!userId) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
entitiesAdded: 0,
|
|
112
|
+
relationshipsAdded: 0,
|
|
113
|
+
entitiesUpdated: 0,
|
|
114
|
+
relationshipsUpdated: 0,
|
|
115
|
+
processingTimeMs: Date.now() - startTime,
|
|
116
|
+
error: 'No userId provided'
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check if already processed
|
|
121
|
+
if (!options.forceReprocess && this.isMemoryProcessed(userId, memory.id)) {
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
entitiesAdded: 0,
|
|
125
|
+
relationshipsAdded: 0,
|
|
126
|
+
entitiesUpdated: 0,
|
|
127
|
+
relationshipsUpdated: 0,
|
|
128
|
+
processingTimeMs: Date.now() - startTime
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Extract entities and relationships from memory content
|
|
133
|
+
const extractionResult = await this.graphService.extractEntitiesAndRelationships(
|
|
134
|
+
memory.content,
|
|
135
|
+
memory.id,
|
|
136
|
+
{ confidenceThreshold: options.confidenceThreshold }
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Check if extraction returned valid result
|
|
140
|
+
if (!extractionResult) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
entitiesAdded: 0,
|
|
144
|
+
relationshipsAdded: 0,
|
|
145
|
+
entitiesUpdated: 0,
|
|
146
|
+
relationshipsUpdated: 0,
|
|
147
|
+
processingTimeMs: Date.now() - startTime,
|
|
148
|
+
error: 'Extraction returned no result'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Skip if extraction failed or confidence too low
|
|
153
|
+
const minConfidence = options.confidenceThreshold || 0.3;
|
|
154
|
+
if ((extractionResult.confidence || 0) < minConfidence) {
|
|
155
|
+
console.log(`Skipping memory ${memory.id} due to low confidence: ${extractionResult.confidence}`);
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
entitiesAdded: 0,
|
|
159
|
+
relationshipsAdded: 0,
|
|
160
|
+
entitiesUpdated: 0,
|
|
161
|
+
relationshipsUpdated: 0,
|
|
162
|
+
processingTimeMs: Date.now() - startTime,
|
|
163
|
+
error: 'Extraction confidence below threshold'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get or create user's knowledge graph
|
|
168
|
+
let graph = await this.getUserGraph(userId);
|
|
169
|
+
if (!graph) {
|
|
170
|
+
graph = this.graphService.createGraph(userId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Count entities/relationships before update
|
|
174
|
+
const entitiesBefore = graph.entities.length;
|
|
175
|
+
const relationshipsBefore = graph.relationships.length;
|
|
176
|
+
|
|
177
|
+
// Add extracted data to graph
|
|
178
|
+
const updatedGraph = this.graphService.addToGraph(
|
|
179
|
+
graph,
|
|
180
|
+
extractionResult.entities,
|
|
181
|
+
extractionResult.relationships,
|
|
182
|
+
memory.id
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Calculate changes
|
|
186
|
+
const entitiesAdded = updatedGraph.entities.length - entitiesBefore;
|
|
187
|
+
const relationshipsAdded = updatedGraph.relationships.length - relationshipsBefore;
|
|
188
|
+
const entitiesUpdated = entitiesAdded > 0 ? 0 : extractionResult.entities.length; // Estimate
|
|
189
|
+
const relationshipsUpdated = relationshipsAdded > 0 ? 0 : extractionResult.relationships.length; // Estimate
|
|
190
|
+
|
|
191
|
+
// Update cached graph
|
|
192
|
+
this.updateUserGraph(userId, updatedGraph);
|
|
193
|
+
|
|
194
|
+
// Track memory mapping
|
|
195
|
+
this.addMemoryMapping(userId, {
|
|
196
|
+
memoryId: memory.id,
|
|
197
|
+
entityIds: extractionResult.entities.map(e => e.id),
|
|
198
|
+
relationshipIds: extractionResult.relationships.map(r =>
|
|
199
|
+
r.id || this.generateRelationshipId(r)
|
|
200
|
+
),
|
|
201
|
+
extractionDate: new Date(),
|
|
202
|
+
confidence: extractionResult.confidence
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Update statistics
|
|
206
|
+
this.stats.successfulUpdates++;
|
|
207
|
+
this.stats.totalEntitiesCreated += entitiesAdded;
|
|
208
|
+
this.stats.totalRelationshipsCreated += relationshipsAdded;
|
|
209
|
+
this.updateAverageProcessingTime(Date.now() - startTime);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
success: true,
|
|
213
|
+
entitiesAdded,
|
|
214
|
+
relationshipsAdded,
|
|
215
|
+
entitiesUpdated,
|
|
216
|
+
relationshipsUpdated,
|
|
217
|
+
processingTimeMs: Date.now() - startTime,
|
|
218
|
+
extractionResult
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (process.env.NODE_ENV === 'development') {
|
|
223
|
+
console.error('Error processing memory for graph:', error);
|
|
224
|
+
}
|
|
225
|
+
this.stats.failedUpdates++;
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
entitiesAdded: 0,
|
|
230
|
+
relationshipsAdded: 0,
|
|
231
|
+
entitiesUpdated: 0,
|
|
232
|
+
relationshipsUpdated: 0,
|
|
233
|
+
processingTimeMs: Date.now() - startTime,
|
|
234
|
+
error: (error as Error).message
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Process multiple memories for graph updates (alias for compatibility)
|
|
241
|
+
*/
|
|
242
|
+
async processBatchMemoriesForGraph(
|
|
243
|
+
userId: string,
|
|
244
|
+
memories: Memory[],
|
|
245
|
+
options: {
|
|
246
|
+
batchSize?: number;
|
|
247
|
+
delayMs?: number;
|
|
248
|
+
onProgress?: (completed: number, total: number) => void;
|
|
249
|
+
} = {}
|
|
250
|
+
): Promise<GraphUpdateResult[]> {
|
|
251
|
+
// Convert Memory to ProcessedMemory
|
|
252
|
+
const processedMemories = memories.map(m => ({
|
|
253
|
+
...m,
|
|
254
|
+
userId: m.userId || userId,
|
|
255
|
+
category: m.category || 'general',
|
|
256
|
+
createdAt: m.createdAt || (m.metadata?.createdAt as Date) || new Date()
|
|
257
|
+
})) as ProcessedMemory[];
|
|
258
|
+
|
|
259
|
+
return this.processMemoriesForGraphBatch(processedMemories, userId, options);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Process multiple memories for graph updates
|
|
264
|
+
*/
|
|
265
|
+
async processMemoriesForGraphBatch(
|
|
266
|
+
memories: ProcessedMemory[],
|
|
267
|
+
userId: string,
|
|
268
|
+
options: {
|
|
269
|
+
batchSize?: number;
|
|
270
|
+
delayMs?: number;
|
|
271
|
+
onProgress?: (completed: number, total: number) => void;
|
|
272
|
+
} = {}
|
|
273
|
+
): Promise<GraphUpdateResult[]> {
|
|
274
|
+
const batchSize = options.batchSize || 5;
|
|
275
|
+
const delayMs = options.delayMs || 1000;
|
|
276
|
+
const results: GraphUpdateResult[] = [];
|
|
277
|
+
|
|
278
|
+
for (let i = 0; i < memories.length; i += batchSize) {
|
|
279
|
+
const batch = memories.slice(i, i + batchSize);
|
|
280
|
+
|
|
281
|
+
const batchPromises = batch.map(memory =>
|
|
282
|
+
this.processMemoryForGraph(memory, userId)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const batchResults = await Promise.all(batchPromises);
|
|
286
|
+
results.push(...batchResults);
|
|
287
|
+
|
|
288
|
+
// Progress callback
|
|
289
|
+
if (options.onProgress) {
|
|
290
|
+
options.onProgress(i + batch.length, memories.length);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Delay between batches
|
|
294
|
+
if (i + batchSize < memories.length) {
|
|
295
|
+
await this.delay(delayMs);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ==================== GRAPH QUERIES ====================
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Search knowledge graph with complex queries
|
|
306
|
+
*/
|
|
307
|
+
async searchGraph(
|
|
308
|
+
userId: string,
|
|
309
|
+
query: GraphSearchQuery
|
|
310
|
+
): Promise<GraphSearchResult> {
|
|
311
|
+
const startTime = Date.now();
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const graph = await this.getUserGraph(userId);
|
|
315
|
+
if (!graph) {
|
|
316
|
+
return {
|
|
317
|
+
entities: [],
|
|
318
|
+
relationships: [],
|
|
319
|
+
relatedMemories: [],
|
|
320
|
+
totalResults: 0,
|
|
321
|
+
queryTimeMs: Date.now() - startTime
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let entities = graph.entities;
|
|
326
|
+
let relationships = graph.relationships;
|
|
327
|
+
|
|
328
|
+
// Filter by entity types
|
|
329
|
+
if (query.entityTypes && query.entityTypes.length > 0) {
|
|
330
|
+
entities = entities.filter(e => query.entityTypes!.includes(e.type));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Filter by relationship types
|
|
334
|
+
if (query.relationshipTypes && query.relationshipTypes.length > 0) {
|
|
335
|
+
relationships = relationships.filter(r =>
|
|
336
|
+
query.relationshipTypes!.includes(r.type || r.label)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Keyword search
|
|
341
|
+
if (query.keywords && query.keywords.length > 0) {
|
|
342
|
+
const keywords = query.keywords.map(k => k.toLowerCase());
|
|
343
|
+
|
|
344
|
+
entities = entities.filter(e =>
|
|
345
|
+
keywords.some(keyword =>
|
|
346
|
+
e.label.toLowerCase().includes(keyword) ||
|
|
347
|
+
JSON.stringify(e.properties || {}).toLowerCase().includes(keyword)
|
|
348
|
+
)
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
relationships = relationships.filter(r =>
|
|
352
|
+
keywords.some(keyword =>
|
|
353
|
+
r.label.toLowerCase().includes(keyword) ||
|
|
354
|
+
JSON.stringify(r.properties || {}).toLowerCase().includes(keyword)
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Filter by memory IDs
|
|
360
|
+
if (query.memoryIds && query.memoryIds.length > 0) {
|
|
361
|
+
entities = entities.filter(e =>
|
|
362
|
+
e.sourceMemoryIds &&
|
|
363
|
+
e.sourceMemoryIds.some(memId => query.memoryIds!.includes(memId))
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
relationships = relationships.filter(r =>
|
|
367
|
+
r.sourceMemoryIds &&
|
|
368
|
+
r.sourceMemoryIds.some(memId => query.memoryIds!.includes(memId))
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Date range filter
|
|
373
|
+
if (query.dateRange) {
|
|
374
|
+
const { start, end } = query.dateRange;
|
|
375
|
+
|
|
376
|
+
entities = entities.filter(e =>
|
|
377
|
+
e.createdAt && e.createdAt >= start && e.createdAt <= end
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
relationships = relationships.filter(r =>
|
|
381
|
+
r.createdAt && r.createdAt >= start && r.createdAt <= end
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Apply result limit
|
|
386
|
+
if (query.maxResults) {
|
|
387
|
+
entities = entities.slice(0, Math.floor(query.maxResults / 2));
|
|
388
|
+
relationships = relationships.slice(0, Math.floor(query.maxResults / 2));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Collect related memories
|
|
392
|
+
const relatedMemories = new Set<string>();
|
|
393
|
+
entities.forEach(e => e.sourceMemoryIds?.forEach(id => relatedMemories.add(id)));
|
|
394
|
+
relationships.forEach(r => r.sourceMemoryIds?.forEach(id => relatedMemories.add(id)));
|
|
395
|
+
|
|
396
|
+
// Find search paths if similarity query
|
|
397
|
+
let searchPaths: Array<{ score: number; entities: string[]; relationships: string[] }> = [];
|
|
398
|
+
if (query.similarToMemory) {
|
|
399
|
+
// Find entities from the reference memory
|
|
400
|
+
const referenceEntities = entities.filter(e =>
|
|
401
|
+
e.sourceMemoryIds?.includes(query.similarToMemory!)
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
if (referenceEntities.length > 0) {
|
|
405
|
+
const relatedResult = this.graphService.findRelatedEntities(
|
|
406
|
+
graph,
|
|
407
|
+
referenceEntities.map(e => e.id),
|
|
408
|
+
{ maxHops: 2, includeWeights: true }
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
searchPaths = relatedResult.paths || [];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
entities,
|
|
417
|
+
relationships,
|
|
418
|
+
relatedMemories: Array.from(relatedMemories),
|
|
419
|
+
searchPaths,
|
|
420
|
+
totalResults: entities.length + relationships.length,
|
|
421
|
+
queryTimeMs: Date.now() - startTime
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (process.env.NODE_ENV === 'development') {
|
|
426
|
+
console.error('Error searching graph:', error);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
entities: [],
|
|
431
|
+
relationships: [],
|
|
432
|
+
relatedMemories: [],
|
|
433
|
+
totalResults: 0,
|
|
434
|
+
queryTimeMs: Date.now() - startTime
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Find memories connected to a specific entity or concept
|
|
441
|
+
*/
|
|
442
|
+
async findMemoriesRelatedToEntity(
|
|
443
|
+
userId: string,
|
|
444
|
+
entityId: string,
|
|
445
|
+
options: {
|
|
446
|
+
maxHops?: number;
|
|
447
|
+
includeRelationships?: boolean;
|
|
448
|
+
} = {}
|
|
449
|
+
): Promise<{
|
|
450
|
+
memories: string[];
|
|
451
|
+
connectedEntities: Entity[];
|
|
452
|
+
pathways: Array<{ memory: string; entities: string[]; score: number }>;
|
|
453
|
+
}> {
|
|
454
|
+
try {
|
|
455
|
+
const graph = await this.getUserGraph(userId);
|
|
456
|
+
if (!graph) {
|
|
457
|
+
return { memories: [], connectedEntities: [], pathways: [] };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Find related entities through graph traversal
|
|
461
|
+
const relatedResult = this.graphService.findRelatedEntities(
|
|
462
|
+
graph,
|
|
463
|
+
[entityId],
|
|
464
|
+
{
|
|
465
|
+
maxHops: options.maxHops || 2,
|
|
466
|
+
includeWeights: true
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// Collect memories from all related entities
|
|
471
|
+
const memories = new Set<string>();
|
|
472
|
+
const pathways: Array<{ memory: string; entities: string[]; score: number }> = [];
|
|
473
|
+
|
|
474
|
+
for (const entity of relatedResult.entities) {
|
|
475
|
+
if (entity.sourceMemoryIds) {
|
|
476
|
+
for (const memoryId of entity.sourceMemoryIds) {
|
|
477
|
+
memories.add(memoryId);
|
|
478
|
+
|
|
479
|
+
// Track pathway
|
|
480
|
+
pathways.push({
|
|
481
|
+
memory: memoryId,
|
|
482
|
+
entities: [entityId, entity.id],
|
|
483
|
+
score: entity.confidence || 0.5
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
memories: Array.from(memories),
|
|
491
|
+
connectedEntities: relatedResult.entities,
|
|
492
|
+
pathways
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (process.env.NODE_ENV === 'development') {
|
|
497
|
+
console.error('Error finding related memories:', error);
|
|
498
|
+
}
|
|
499
|
+
return { memories: [], connectedEntities: [], pathways: [] };
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ==================== GRAPH MANAGEMENT ====================
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get user's knowledge graph
|
|
507
|
+
*/
|
|
508
|
+
async getUserGraph(userId: string): Promise<KnowledgeGraph | null> {
|
|
509
|
+
try {
|
|
510
|
+
// Check cache first
|
|
511
|
+
const cached = this.graphCache.get(userId);
|
|
512
|
+
if (cached) {
|
|
513
|
+
return cached.graph;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Try to get from service cache
|
|
517
|
+
const graph = this.graphService.getUserGraph(userId);
|
|
518
|
+
if (graph) {
|
|
519
|
+
this.graphCache.set(userId, { graph, lastUpdated: new Date() });
|
|
520
|
+
return graph;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return null;
|
|
524
|
+
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (process.env.NODE_ENV === 'development') {
|
|
527
|
+
console.error('Error getting user graph:', error);
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Update user's knowledge graph
|
|
535
|
+
*/
|
|
536
|
+
updateUserGraph(userId: string, graph: KnowledgeGraph): void {
|
|
537
|
+
this.graphService.setUserGraph(userId, graph);
|
|
538
|
+
this.graphCache.set(userId, { graph, lastUpdated: new Date() });
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Clear user's knowledge graph
|
|
543
|
+
*/
|
|
544
|
+
clearUserGraph(userId: string): void {
|
|
545
|
+
this.graphCache.delete(userId);
|
|
546
|
+
this.memoryMappings.delete(userId);
|
|
547
|
+
// Note: GraphService doesn't have a clear method, but we clear our caches
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Record a memory mapping (public method for tests)
|
|
552
|
+
*/
|
|
553
|
+
recordMemoryMapping(mapping: GraphMemoryMapping): void {
|
|
554
|
+
// Extract userId from first part of memoryId or use a default
|
|
555
|
+
// Assuming memoryId format doesn't include userId, store all in a global list
|
|
556
|
+
const globalKey = '__all__';
|
|
557
|
+
const mappings = this.memoryMappings.get(globalKey) || [];
|
|
558
|
+
mappings.push(mapping);
|
|
559
|
+
this.memoryMappings.set(globalKey, mappings);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Get memory mappings by memory ID (public method for tests)
|
|
564
|
+
*/
|
|
565
|
+
getMemoryMappings(memoryId: string): GraphMemoryMapping[] {
|
|
566
|
+
// Search all mappings for this memory ID
|
|
567
|
+
const allMappings: GraphMemoryMapping[] = [];
|
|
568
|
+
|
|
569
|
+
for (const mappings of this.memoryMappings.values()) {
|
|
570
|
+
allMappings.push(...mappings.filter(m => m.memoryId === memoryId));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return allMappings;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Get statistics for a specific user's graph
|
|
578
|
+
*/
|
|
579
|
+
getGraphStatistics(userId: string): {
|
|
580
|
+
totalEntities: number;
|
|
581
|
+
totalRelationships: number;
|
|
582
|
+
sourceMemoriesCount: number;
|
|
583
|
+
entityTypeDistribution: Record<string, number>;
|
|
584
|
+
relationshipTypeDistribution: Record<string, number>;
|
|
585
|
+
averageEntityConfidence: number;
|
|
586
|
+
averageRelationshipConfidence: number;
|
|
587
|
+
} {
|
|
588
|
+
const graph = this.graphService.getUserGraph(userId);
|
|
589
|
+
|
|
590
|
+
if (!graph) {
|
|
591
|
+
return {
|
|
592
|
+
totalEntities: 0,
|
|
593
|
+
totalRelationships: 0,
|
|
594
|
+
sourceMemoriesCount: 0,
|
|
595
|
+
entityTypeDistribution: {},
|
|
596
|
+
relationshipTypeDistribution: {},
|
|
597
|
+
averageEntityConfidence: 0,
|
|
598
|
+
averageRelationshipConfidence: 0
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Entity type distribution
|
|
603
|
+
const entityTypeDistribution: Record<string, number> = {};
|
|
604
|
+
graph.entities.forEach(entity => {
|
|
605
|
+
entityTypeDistribution[entity.type] = (entityTypeDistribution[entity.type] || 0) + 1;
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Relationship type distribution
|
|
609
|
+
const relationshipTypeDistribution: Record<string, number> = {};
|
|
610
|
+
graph.relationships.forEach(rel => {
|
|
611
|
+
const type = rel.type || rel.label;
|
|
612
|
+
relationshipTypeDistribution[type] = (relationshipTypeDistribution[type] || 0) + 1;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Average confidences
|
|
616
|
+
const avgEntityConfidence = graph.entities.length > 0
|
|
617
|
+
? graph.entities.reduce((sum, e) => sum + (e.confidence || 0), 0) / graph.entities.length
|
|
618
|
+
: 0;
|
|
619
|
+
|
|
620
|
+
const avgRelationshipConfidence = graph.relationships.length > 0
|
|
621
|
+
? graph.relationships.reduce((sum, r) => sum + (r.confidence || 0), 0) / graph.relationships.length
|
|
622
|
+
: 0;
|
|
623
|
+
|
|
624
|
+
return {
|
|
625
|
+
totalEntities: graph.entities.length,
|
|
626
|
+
totalRelationships: graph.relationships.length,
|
|
627
|
+
sourceMemoriesCount: graph.metadata.sourceMemories?.length || 0,
|
|
628
|
+
entityTypeDistribution,
|
|
629
|
+
relationshipTypeDistribution,
|
|
630
|
+
averageEntityConfidence: avgEntityConfidence,
|
|
631
|
+
averageRelationshipConfidence: avgRelationshipConfidence
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Get comprehensive statistics
|
|
637
|
+
*/
|
|
638
|
+
getKnowledgeGraphStats(): KnowledgeGraphStats {
|
|
639
|
+
const allGraphs = Array.from(this.graphCache.values()).map(cached => cached.graph);
|
|
640
|
+
|
|
641
|
+
// Aggregate statistics
|
|
642
|
+
const totalEntities = allGraphs.reduce((sum, graph) => sum + graph.entities.length, 0);
|
|
643
|
+
const totalRelationships = allGraphs.reduce((sum, graph) => sum + graph.relationships.length, 0);
|
|
644
|
+
|
|
645
|
+
// Entity type distribution
|
|
646
|
+
const entityTypeCounts = new Map<string, number>();
|
|
647
|
+
allGraphs.forEach(graph => {
|
|
648
|
+
graph.entities.forEach(entity => {
|
|
649
|
+
entityTypeCounts.set(entity.type, (entityTypeCounts.get(entity.type) || 0) + 1);
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Relationship type distribution
|
|
654
|
+
const relationshipTypeCounts = new Map<string, number>();
|
|
655
|
+
allGraphs.forEach(graph => {
|
|
656
|
+
graph.relationships.forEach(rel => {
|
|
657
|
+
const type = rel.type || rel.label;
|
|
658
|
+
relationshipTypeCounts.set(type, (relationshipTypeCounts.get(type) || 0) + 1);
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Get top types
|
|
663
|
+
const topEntityTypes = Array.from(entityTypeCounts.entries())
|
|
664
|
+
.sort(([, a], [, b]) => b - a)
|
|
665
|
+
.slice(0, 10)
|
|
666
|
+
.map(([type, count]) => ({ type, count }));
|
|
667
|
+
|
|
668
|
+
const topRelationshipTypes = Array.from(relationshipTypeCounts.entries())
|
|
669
|
+
.sort(([, a], [, b]) => b - a)
|
|
670
|
+
.slice(0, 10)
|
|
671
|
+
.map(([type, count]) => ({ type, count }));
|
|
672
|
+
|
|
673
|
+
// Calculate average connections
|
|
674
|
+
const totalConnections = allGraphs.reduce((sum, graph) => {
|
|
675
|
+
const connections = new Map<string, number>();
|
|
676
|
+
graph.relationships.forEach(rel => {
|
|
677
|
+
connections.set(rel.source, (connections.get(rel.source) || 0) + 1);
|
|
678
|
+
connections.set(rel.target, (connections.get(rel.target) || 0) + 1);
|
|
679
|
+
});
|
|
680
|
+
return sum + Array.from(connections.values()).reduce((s, c) => s + c, 0);
|
|
681
|
+
}, 0);
|
|
682
|
+
|
|
683
|
+
const averageConnections = totalEntities > 0 ? totalConnections / totalEntities : 0;
|
|
684
|
+
|
|
685
|
+
// Count memory mappings
|
|
686
|
+
const totalMappings = Array.from(this.memoryMappings.values())
|
|
687
|
+
.reduce((sum, mappings) => sum + mappings.length, 0);
|
|
688
|
+
|
|
689
|
+
return {
|
|
690
|
+
totalGraphs: allGraphs.length,
|
|
691
|
+
totalEntities,
|
|
692
|
+
totalRelationships,
|
|
693
|
+
averageConnections,
|
|
694
|
+
topEntityTypes,
|
|
695
|
+
topRelationshipTypes,
|
|
696
|
+
memoryMappings: totalMappings,
|
|
697
|
+
lastUpdate: new Date() // TODO: Track actual last update
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ==================== PRIVATE METHODS ====================
|
|
702
|
+
|
|
703
|
+
private isMemoryProcessed(userId: string, memoryId: string): boolean {
|
|
704
|
+
const mappings = this.memoryMappings.get(userId) || [];
|
|
705
|
+
return mappings.some(mapping => mapping.memoryId === memoryId);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
private addMemoryMapping(userId: string, mapping: GraphMemoryMapping): void {
|
|
709
|
+
const mappings = this.memoryMappings.get(userId) || [];
|
|
710
|
+
mappings.push(mapping);
|
|
711
|
+
this.memoryMappings.set(userId, mappings);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private generateRelationshipId(relationship: Relationship): string {
|
|
715
|
+
const content = `${relationship.source}_${relationship.target}_${relationship.label}`;
|
|
716
|
+
return content.toLowerCase().replace(/[^\w]/g, '_');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private updateAverageProcessingTime(processingTime: number): void {
|
|
720
|
+
this.stats.averageProcessingTime =
|
|
721
|
+
(this.stats.averageProcessingTime + processingTime) / this.stats.totalUpdates;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
private delay(ms: number): Promise<void> {
|
|
725
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
729
|
export default KnowledgeGraphManager;
|