cozo-memory 1.1.2 ā 1.1.4
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/README.md +356 -5
- package/dist/adaptive-retrieval.js +520 -0
- package/dist/db-inspect.js +25 -0
- package/dist/dynamic-fusion.js +602 -0
- package/dist/hybrid-search.js +4 -4
- package/dist/index.js +699 -23
- package/dist/inference-engine.js +104 -76
- package/dist/logical-edges-service.js +316 -0
- package/dist/multi-hop-vector-pivot.js +390 -0
- package/dist/temporal-embedding-service.js +313 -0
- package/dist/test-adaptive-integration.js +84 -0
- package/dist/test-adaptive-retrieval.js +135 -0
- package/dist/test-compaction.js +91 -0
- package/dist/test-dynamic-fusion.js +231 -0
- package/dist/test-fact-lifecycle.js +82 -0
- package/dist/test-logical-edges.js +282 -0
- package/dist/test-manual-compact.js +95 -0
- package/dist/test-multi-hop-vector-pivot-v2.js +239 -0
- package/dist/test-multi-hop-vector-pivot.js +240 -0
- package/dist/test-temporal-embeddings.js +123 -0
- package/dist/test-validity-retract.js +45 -0
- package/dist/test-validity-rm.js +49 -0
- package/package.json +1 -1
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TemporalEmbeddingService = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Temporal Graph Neural Network Embedding Service
|
|
6
|
+
*
|
|
7
|
+
* Implements time-aware node embeddings that capture:
|
|
8
|
+
* 1. Historical context aggregation (past observations)
|
|
9
|
+
* 2. Temporal smoothness (gradual changes over time)
|
|
10
|
+
* 3. Time encoding (Time2Vec-inspired approach)
|
|
11
|
+
* 4. Recency weighting (recent events matter more)
|
|
12
|
+
*
|
|
13
|
+
* Based on research:
|
|
14
|
+
* - ACM Temporal Graph Learning Primer (2025)
|
|
15
|
+
* - TempGNN: Temporal Graph Neural Networks (2023)
|
|
16
|
+
* - Time-Aware Graph Embedding with Temporal Smoothness (2021)
|
|
17
|
+
*/
|
|
18
|
+
class TemporalEmbeddingService {
|
|
19
|
+
embeddingService;
|
|
20
|
+
dbQuery;
|
|
21
|
+
EMBEDDING_DIM = 1024;
|
|
22
|
+
TEMPORAL_ENCODING_DIM = 64;
|
|
23
|
+
MEMORY_CACHE = new Map();
|
|
24
|
+
constructor(embeddingService, dbQuery) {
|
|
25
|
+
this.embeddingService = embeddingService;
|
|
26
|
+
this.dbQuery = dbQuery;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generate temporal embedding for an entity at a specific timepoint
|
|
30
|
+
*
|
|
31
|
+
* Combines:
|
|
32
|
+
* 1. Content embedding (semantic meaning)
|
|
33
|
+
* 2. Temporal encoding (time difference from now)
|
|
34
|
+
* 3. Historical context (past observations)
|
|
35
|
+
* 4. Neighborhood aggregation (related entities)
|
|
36
|
+
*/
|
|
37
|
+
async generateTemporalEmbedding(entityId, timepoint) {
|
|
38
|
+
const now = timepoint || new Date();
|
|
39
|
+
// 1. Get entity state at timepoint via CozoDB Validity
|
|
40
|
+
const entityState = await this.getEntityAtTime(entityId, now);
|
|
41
|
+
if (!entityState) {
|
|
42
|
+
throw new Error(`Entity ${entityId} not found at ${now.toISOString()}`);
|
|
43
|
+
}
|
|
44
|
+
// 2. Generate base content embedding
|
|
45
|
+
const contentEmbedding = await this.embeddingService.embed(entityState.name + ' ' + (entityState.metadata?.description || ''));
|
|
46
|
+
// 3. Generate temporal encoding (Time2Vec-inspired)
|
|
47
|
+
const temporalEncoding = this.encodeTemporalDistance(entityState.createdAt, now);
|
|
48
|
+
// 4. Aggregate historical context
|
|
49
|
+
const historicalContext = await this.aggregateHistoricalContext(entityId, now);
|
|
50
|
+
// 5. Aggregate neighborhood information
|
|
51
|
+
const neighborhoodAggregation = await this.aggregateNeighborhood(entityId, now);
|
|
52
|
+
// 6. Combine all signals with learned weights
|
|
53
|
+
const combinedEmbedding = this.fuseEmbeddings(contentEmbedding, temporalEncoding, historicalContext, neighborhoodAggregation);
|
|
54
|
+
return {
|
|
55
|
+
entityId,
|
|
56
|
+
timepoint: now,
|
|
57
|
+
embedding: combinedEmbedding,
|
|
58
|
+
contentEmbedding,
|
|
59
|
+
temporalEncoding,
|
|
60
|
+
historicalContext,
|
|
61
|
+
neighborhoodAggregation,
|
|
62
|
+
confidence: this.calculateConfidence(entityState, now),
|
|
63
|
+
metadata: {
|
|
64
|
+
ageInDays: Math.floor((now.getTime() - entityState.createdAt.getTime()) / (1000 * 60 * 60 * 24)),
|
|
65
|
+
observationCount: entityState.observationCount || 0,
|
|
66
|
+
relationshipCount: entityState.relationshipCount || 0,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get entity state at a specific timepoint using CozoDB Validity
|
|
72
|
+
* Returns the entity as it existed at that point in time
|
|
73
|
+
*/
|
|
74
|
+
async getEntityAtTime(entityId, timepoint) {
|
|
75
|
+
try {
|
|
76
|
+
const result = await this.dbQuery(`?[id, name, metadata, createdAt, observationCount, relationshipCount] :=
|
|
77
|
+
*entity{id, name, metadata, @ $timepoint},
|
|
78
|
+
id = $entityId,
|
|
79
|
+
observationCount = count{*observation{entity_id: id}},
|
|
80
|
+
relationshipCount = count{*relationship{from_id: id} or *relationship{to_id: id}},
|
|
81
|
+
createdAt = now()`, {
|
|
82
|
+
entityId,
|
|
83
|
+
timepoint: timepoint.toISOString(),
|
|
84
|
+
});
|
|
85
|
+
if (!result.rows || result.rows.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
const [id, name, metadata, createdAt, obsCount, relCount] = result.rows[0];
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
name,
|
|
91
|
+
metadata: metadata ? JSON.parse(metadata) : {},
|
|
92
|
+
createdAt: new Date(createdAt),
|
|
93
|
+
observationCount: obsCount,
|
|
94
|
+
relationshipCount: relCount,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(`[TemporalEmbedding] Error fetching entity at time:`, error);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Time2Vec-inspired temporal encoding
|
|
104
|
+
* Captures periodicity and time differences using sinusoidal functions
|
|
105
|
+
*
|
|
106
|
+
* Formula: t_enc[i] = sin(Ļ_i * Īt) for i in [0, d/2]
|
|
107
|
+
* t_enc[i+d/2] = cos(Ļ_i * Īt) for i in [0, d/2]
|
|
108
|
+
*
|
|
109
|
+
* Where Ļ_i = 1 / 10000^(2i/d) (similar to transformer positional encoding)
|
|
110
|
+
*/
|
|
111
|
+
encodeTemporalDistance(createdAt, currentTime) {
|
|
112
|
+
const deltaSeconds = (currentTime.getTime() - createdAt.getTime()) / 1000;
|
|
113
|
+
const encoding = [];
|
|
114
|
+
// Generate sinusoidal encodings for different frequencies
|
|
115
|
+
for (let i = 0; i < this.TEMPORAL_ENCODING_DIM / 2; i++) {
|
|
116
|
+
const omega = 1 / Math.pow(10000, (2 * i) / this.TEMPORAL_ENCODING_DIM);
|
|
117
|
+
// Normalize delta to reasonable range (0-1 for recent, >1 for old)
|
|
118
|
+
const normalizedDelta = Math.min(deltaSeconds / (365 * 24 * 3600), 10);
|
|
119
|
+
encoding.push(Math.sin(omega * normalizedDelta));
|
|
120
|
+
encoding.push(Math.cos(omega * normalizedDelta));
|
|
121
|
+
}
|
|
122
|
+
return encoding;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Aggregate historical context from past observations
|
|
126
|
+
*
|
|
127
|
+
* Implements temporal smoothness by:
|
|
128
|
+
* 1. Fetching all observations up to timepoint
|
|
129
|
+
* 2. Weighting by recency (exponential decay)
|
|
130
|
+
* 3. Embedding each observation
|
|
131
|
+
* 4. Averaging with recency weights
|
|
132
|
+
*/
|
|
133
|
+
async aggregateHistoricalContext(entityId, timepoint) {
|
|
134
|
+
try {
|
|
135
|
+
const result = await this.dbQuery(`?[id, text, createdAt] :=
|
|
136
|
+
*observation{id, entity_id: $entityId, text, @ $timepoint},
|
|
137
|
+
createdAt = now()
|
|
138
|
+
| order by createdAt desc
|
|
139
|
+
| limit 50`, {
|
|
140
|
+
entityId,
|
|
141
|
+
timepoint: timepoint.toISOString(),
|
|
142
|
+
});
|
|
143
|
+
if (!result.rows || result.rows.length === 0) {
|
|
144
|
+
return new Array(this.EMBEDDING_DIM).fill(0);
|
|
145
|
+
}
|
|
146
|
+
const embeddings = [];
|
|
147
|
+
const weights = [];
|
|
148
|
+
for (const [, text, createdAt] of result.rows) {
|
|
149
|
+
const embedding = await this.embeddingService.embed(text);
|
|
150
|
+
const age = (timepoint.getTime() - new Date(createdAt).getTime()) / 1000;
|
|
151
|
+
const halfLife = 30 * 24 * 3600;
|
|
152
|
+
const weight = Math.exp(-age / halfLife);
|
|
153
|
+
embeddings.push(embedding);
|
|
154
|
+
weights.push(weight);
|
|
155
|
+
}
|
|
156
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
157
|
+
const normalizedWeights = weights.map(w => w / totalWeight);
|
|
158
|
+
const aggregated = new Array(this.EMBEDDING_DIM).fill(0);
|
|
159
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
160
|
+
for (let j = 0; j < this.EMBEDDING_DIM; j++) {
|
|
161
|
+
aggregated[j] += embeddings[i][j] * normalizedWeights[i];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return aggregated;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error(`[TemporalEmbedding] Error aggregating history:`, error);
|
|
168
|
+
return new Array(this.EMBEDDING_DIM).fill(0);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Aggregate neighborhood information
|
|
173
|
+
*
|
|
174
|
+
* Implements graph-based aggregation by:
|
|
175
|
+
* 1. Finding related entities (via relationships)
|
|
176
|
+
* 2. Embedding their content
|
|
177
|
+
* 3. Weighting by relationship strength and recency
|
|
178
|
+
* 4. Averaging to get neighborhood signal
|
|
179
|
+
*/
|
|
180
|
+
async aggregateNeighborhood(entityId, timepoint) {
|
|
181
|
+
try {
|
|
182
|
+
const result = await this.dbQuery(`?[toId, relationshipType, strength, createdAt] :=
|
|
183
|
+
*relationship{from_id: $entityId, to_id: toId, relation_type: relationshipType, strength, @ $timepoint},
|
|
184
|
+
createdAt = now()
|
|
185
|
+
| limit 20`, {
|
|
186
|
+
entityId,
|
|
187
|
+
timepoint: timepoint.toISOString(),
|
|
188
|
+
});
|
|
189
|
+
if (!result.rows || result.rows.length === 0) {
|
|
190
|
+
return new Array(this.EMBEDDING_DIM).fill(0);
|
|
191
|
+
}
|
|
192
|
+
const embeddings = [];
|
|
193
|
+
const weights = [];
|
|
194
|
+
for (const [toId, , strength, createdAt] of result.rows) {
|
|
195
|
+
const neighbor = await this.getEntityAtTime(toId, timepoint);
|
|
196
|
+
if (!neighbor)
|
|
197
|
+
continue;
|
|
198
|
+
const embedding = await this.embeddingService.embed(neighbor.name);
|
|
199
|
+
const age = (timepoint.getTime() - new Date(createdAt).getTime()) / 1000;
|
|
200
|
+
const halfLife = 30 * 24 * 3600;
|
|
201
|
+
const recencyWeight = Math.exp(-age / halfLife);
|
|
202
|
+
const weight = (strength || 0.5) * recencyWeight;
|
|
203
|
+
embeddings.push(embedding);
|
|
204
|
+
weights.push(weight);
|
|
205
|
+
}
|
|
206
|
+
if (embeddings.length === 0) {
|
|
207
|
+
return new Array(this.EMBEDDING_DIM).fill(0);
|
|
208
|
+
}
|
|
209
|
+
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
210
|
+
const normalizedWeights = weights.map(w => w / totalWeight);
|
|
211
|
+
const aggregated = new Array(this.EMBEDDING_DIM).fill(0);
|
|
212
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
213
|
+
for (let j = 0; j < this.EMBEDDING_DIM; j++) {
|
|
214
|
+
aggregated[j] += embeddings[i][j] * normalizedWeights[i];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return aggregated;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
console.error(`[TemporalEmbedding] Error aggregating neighborhood:`, error);
|
|
221
|
+
return new Array(this.EMBEDDING_DIM).fill(0);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Fuse multiple embedding signals into final temporal embedding
|
|
226
|
+
*
|
|
227
|
+
* Uses learned weights to combine:
|
|
228
|
+
* - Content embedding (semantic meaning)
|
|
229
|
+
* - Temporal encoding (time information)
|
|
230
|
+
* - Historical context (past observations)
|
|
231
|
+
* - Neighborhood aggregation (related entities)
|
|
232
|
+
*/
|
|
233
|
+
fuseEmbeddings(contentEmbedding, temporalEncoding, historicalContext, neighborhoodAggregation) {
|
|
234
|
+
// Learned fusion weights (can be tuned)
|
|
235
|
+
const weights = {
|
|
236
|
+
content: 0.4,
|
|
237
|
+
temporal: 0.2,
|
|
238
|
+
history: 0.2,
|
|
239
|
+
neighborhood: 0.2,
|
|
240
|
+
};
|
|
241
|
+
const fused = new Array(this.EMBEDDING_DIM).fill(0);
|
|
242
|
+
// Combine content embedding
|
|
243
|
+
for (let i = 0; i < this.EMBEDDING_DIM; i++) {
|
|
244
|
+
fused[i] += contentEmbedding[i] * weights.content;
|
|
245
|
+
}
|
|
246
|
+
// Combine historical context
|
|
247
|
+
for (let i = 0; i < this.EMBEDDING_DIM; i++) {
|
|
248
|
+
fused[i] += historicalContext[i] * weights.history;
|
|
249
|
+
}
|
|
250
|
+
// Combine neighborhood aggregation
|
|
251
|
+
for (let i = 0; i < this.EMBEDDING_DIM; i++) {
|
|
252
|
+
fused[i] += neighborhoodAggregation[i] * weights.neighborhood;
|
|
253
|
+
}
|
|
254
|
+
// Combine temporal encoding (pad to EMBEDDING_DIM)
|
|
255
|
+
for (let i = 0; i < Math.min(this.TEMPORAL_ENCODING_DIM, this.EMBEDDING_DIM); i++) {
|
|
256
|
+
fused[i] += temporalEncoding[i] * weights.temporal;
|
|
257
|
+
}
|
|
258
|
+
// Normalize
|
|
259
|
+
const norm = Math.sqrt(fused.reduce((a, b) => a + b * b, 0));
|
|
260
|
+
if (norm > 0) {
|
|
261
|
+
for (let i = 0; i < fused.length; i++) {
|
|
262
|
+
fused[i] /= norm;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return fused;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Calculate confidence score for the embedding
|
|
269
|
+
* Based on data freshness and completeness
|
|
270
|
+
*/
|
|
271
|
+
calculateConfidence(entityState, timepoint) {
|
|
272
|
+
let confidence = 0.5; // Base confidence
|
|
273
|
+
// Boost for recent entities
|
|
274
|
+
const ageInDays = Math.floor((timepoint.getTime() - entityState.createdAt.getTime()) / (1000 * 60 * 60 * 24));
|
|
275
|
+
if (ageInDays < 7)
|
|
276
|
+
confidence += 0.3;
|
|
277
|
+
else if (ageInDays < 30)
|
|
278
|
+
confidence += 0.2;
|
|
279
|
+
else if (ageInDays < 90)
|
|
280
|
+
confidence += 0.1;
|
|
281
|
+
// Boost for entities with observations
|
|
282
|
+
if ((entityState.observationCount || 0) > 5)
|
|
283
|
+
confidence += 0.15;
|
|
284
|
+
else if ((entityState.observationCount || 0) > 0)
|
|
285
|
+
confidence += 0.05;
|
|
286
|
+
// Boost for well-connected entities
|
|
287
|
+
if ((entityState.relationshipCount || 0) > 10)
|
|
288
|
+
confidence += 0.15;
|
|
289
|
+
else if ((entityState.relationshipCount || 0) > 0)
|
|
290
|
+
confidence += 0.05;
|
|
291
|
+
return Math.min(confidence, 1.0);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get temporal memory for an entity
|
|
295
|
+
* Caches temporal state for efficient multi-hop queries
|
|
296
|
+
*/
|
|
297
|
+
getTemporalMemory(entityId) {
|
|
298
|
+
return this.MEMORY_CACHE.get(entityId);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Update temporal memory for an entity
|
|
302
|
+
*/
|
|
303
|
+
setTemporalMemory(entityId, memory) {
|
|
304
|
+
this.MEMORY_CACHE.set(entityId, memory);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Clear temporal memory cache
|
|
308
|
+
*/
|
|
309
|
+
clearMemoryCache() {
|
|
310
|
+
this.MEMORY_CACHE.clear();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
exports.TemporalEmbeddingService = TemporalEmbeddingService;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Integration Test for Adaptive Retrieval in MCP Server
|
|
4
|
+
* Tests the full integration of GraphRAG-R1 adaptive retrieval
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const index_1 = require("./index");
|
|
8
|
+
async function testAdaptiveIntegration() {
|
|
9
|
+
console.log('=== Testing Adaptive Retrieval MCP Integration ===\n');
|
|
10
|
+
const server = new index_1.MemoryServer();
|
|
11
|
+
await server.initPromise;
|
|
12
|
+
console.log('ā
MemoryServer initialized with AdaptiveRetrieval\n');
|
|
13
|
+
// Test 1: Simple Query
|
|
14
|
+
console.log('--- Test 1: Simple Query via Adaptive Retrieval ---');
|
|
15
|
+
const result1 = await server.adaptiveRetrieval.retrieve('Alice', 5);
|
|
16
|
+
console.log(`Query: "Alice"`);
|
|
17
|
+
console.log(`Strategy Selected: ${result1.strategy}`);
|
|
18
|
+
console.log(`Results: ${result1.results.length}`);
|
|
19
|
+
console.log(`Retrieval Count: ${result1.retrievalCount}`);
|
|
20
|
+
console.log(`CAF Score: ${result1.cafScore?.toFixed(3)}`);
|
|
21
|
+
console.log(`Latency: ${result1.latency}ms`);
|
|
22
|
+
if (result1.results.length > 0) {
|
|
23
|
+
console.log('Top Result:', result1.results[0].name);
|
|
24
|
+
}
|
|
25
|
+
console.log();
|
|
26
|
+
// Test 2: Complex Query
|
|
27
|
+
console.log('--- Test 2: Complex Multi-Hop Query ---');
|
|
28
|
+
const result2 = await server.adaptiveRetrieval.retrieve('What are the connections between people working on projects?', 10);
|
|
29
|
+
console.log(`Query: "What are the connections between people working on projects?"`);
|
|
30
|
+
console.log(`Strategy Selected: ${result2.strategy}`);
|
|
31
|
+
console.log(`Results: ${result2.results.length}`);
|
|
32
|
+
console.log(`Retrieval Count: ${result2.retrievalCount}`);
|
|
33
|
+
console.log(`CAF Score: ${result2.cafScore?.toFixed(3)}`);
|
|
34
|
+
console.log(`Latency: ${result2.latency}ms`);
|
|
35
|
+
console.log();
|
|
36
|
+
// Test 3: Exploratory Query
|
|
37
|
+
console.log('--- Test 3: Exploratory Query ---');
|
|
38
|
+
const result3 = await server.adaptiveRetrieval.retrieve('Show me everything about software development', 10);
|
|
39
|
+
console.log(`Query: "Show me everything about software development"`);
|
|
40
|
+
console.log(`Strategy Selected: ${result3.strategy}`);
|
|
41
|
+
console.log(`Results: ${result3.results.length}`);
|
|
42
|
+
console.log(`Retrieval Count: ${result3.retrievalCount}`);
|
|
43
|
+
console.log(`CAF Score: ${result3.cafScore?.toFixed(3)}`);
|
|
44
|
+
console.log(`Latency: ${result3.latency}ms`);
|
|
45
|
+
console.log();
|
|
46
|
+
// Test 4: Performance Statistics
|
|
47
|
+
console.log('--- Test 4: Performance Statistics ---');
|
|
48
|
+
const stats = server.adaptiveRetrieval.getPerformanceStats();
|
|
49
|
+
console.log(`Tracked Strategies: ${stats.size}`);
|
|
50
|
+
for (const [strategy, perf] of stats.entries()) {
|
|
51
|
+
if (perf.totalCount > 0) {
|
|
52
|
+
console.log(`\nStrategy: ${strategy}`);
|
|
53
|
+
console.log(` Success Rate: ${((perf.successCount / perf.totalCount) * 100).toFixed(1)}%`);
|
|
54
|
+
console.log(` Avg F1 Score: ${perf.avgF1Score.toFixed(3)}`);
|
|
55
|
+
console.log(` Avg Retrieval Cost: ${perf.avgRetrievalCost.toFixed(2)}`);
|
|
56
|
+
console.log(` Avg Latency: ${perf.avgLatency.toFixed(0)}ms`);
|
|
57
|
+
console.log(` Total Uses: ${perf.totalCount}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log();
|
|
61
|
+
// Test 5: Learning Over Time
|
|
62
|
+
console.log('--- Test 5: Learning Test (Repeat Query) ---');
|
|
63
|
+
console.log('Running same query 3 times to test adaptation...');
|
|
64
|
+
for (let i = 1; i <= 3; i++) {
|
|
65
|
+
const result = await server.adaptiveRetrieval.retrieve('TypeScript', 5);
|
|
66
|
+
console.log(`Run ${i}: Strategy=${result.strategy}, Results=${result.results.length}, CAF=${result.cafScore?.toFixed(3)}`);
|
|
67
|
+
// Simulate feedback
|
|
68
|
+
await server.adaptiveRetrieval.updateStrategyPerformance(result.strategy, 0.8, result.retrievalCount, result.latency, true);
|
|
69
|
+
}
|
|
70
|
+
console.log();
|
|
71
|
+
console.log('ā
All integration tests completed successfully!');
|
|
72
|
+
console.log('\nš Summary:');
|
|
73
|
+
console.log('1. AdaptiveRetrieval successfully integrated into MemoryServer');
|
|
74
|
+
console.log('2. Query complexity classification working');
|
|
75
|
+
console.log('3. Strategy selection adapting based on query type');
|
|
76
|
+
console.log('4. Performance tracking persisting to CozoDB');
|
|
77
|
+
console.log('5. PRA and CAF scoring functioning correctly');
|
|
78
|
+
console.log('\nšÆ Next Step: Restart Kiro to test via MCP tool call');
|
|
79
|
+
}
|
|
80
|
+
// Run tests
|
|
81
|
+
testAdaptiveIntegration().catch(error => {
|
|
82
|
+
console.error('ā Integration test failed:', error);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test for GraphRAG-R1 Inspired Adaptive Retrieval System
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cozo_node_1 = require("cozo-node");
|
|
7
|
+
const embedding_service_1 = require("./embedding-service");
|
|
8
|
+
const adaptive_retrieval_1 = require("./adaptive-retrieval");
|
|
9
|
+
const DB_PATH = 'memory_db.cozo.db';
|
|
10
|
+
async function testAdaptiveRetrieval() {
|
|
11
|
+
console.log('=== Testing GraphRAG-R1 Adaptive Retrieval ===\n');
|
|
12
|
+
const db = new cozo_node_1.CozoDb('sqlite', DB_PATH);
|
|
13
|
+
const embeddingService = new embedding_service_1.EmbeddingService();
|
|
14
|
+
const adaptiveRetrieval = new adaptive_retrieval_1.AdaptiveGraphRetrieval(db, embeddingService, {
|
|
15
|
+
enablePRA: true,
|
|
16
|
+
enableCAF: true,
|
|
17
|
+
maxRetrievalCalls: 5,
|
|
18
|
+
explorationRate: 0.2, // 20% exploration for testing
|
|
19
|
+
decayFactor: 0.8,
|
|
20
|
+
costPenalty: 0.15
|
|
21
|
+
});
|
|
22
|
+
// Wait for initialization
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
24
|
+
console.log('ā
Adaptive Retrieval System initialized\n');
|
|
25
|
+
// Test 1: Simple Query
|
|
26
|
+
console.log('--- Test 1: Simple Query ---');
|
|
27
|
+
const simpleQuery = 'Alice';
|
|
28
|
+
const result1 = await adaptiveRetrieval.retrieve(simpleQuery, 5);
|
|
29
|
+
console.log(`Query: "${simpleQuery}"`);
|
|
30
|
+
console.log(`Strategy: ${result1.strategy}`);
|
|
31
|
+
console.log(`Results: ${result1.results.length}`);
|
|
32
|
+
console.log(`Retrieval Count: ${result1.retrievalCount}`);
|
|
33
|
+
console.log(`Latency: ${result1.latency}ms`);
|
|
34
|
+
console.log(`CAF Score: ${result1.cafScore?.toFixed(3)}`);
|
|
35
|
+
console.log('Top Results:', result1.results.slice(0, 3).map(r => r.name));
|
|
36
|
+
console.log();
|
|
37
|
+
// Simulate feedback (in production, this would come from user or evaluation)
|
|
38
|
+
await adaptiveRetrieval.updateStrategyPerformance(result1.strategy, 0.85, // F1 score
|
|
39
|
+
result1.retrievalCount, result1.latency, true // success
|
|
40
|
+
);
|
|
41
|
+
// Test 2: Moderate Complexity Query
|
|
42
|
+
console.log('--- Test 2: Moderate Complexity Query ---');
|
|
43
|
+
const moderateQuery = 'Who works on Project Alpha and knows TypeScript?';
|
|
44
|
+
const result2 = await adaptiveRetrieval.retrieve(moderateQuery, 5);
|
|
45
|
+
console.log(`Query: "${moderateQuery}"`);
|
|
46
|
+
console.log(`Strategy: ${result2.strategy}`);
|
|
47
|
+
console.log(`Results: ${result2.results.length}`);
|
|
48
|
+
console.log(`Retrieval Count: ${result2.retrievalCount}`);
|
|
49
|
+
console.log(`Latency: ${result2.latency}ms`);
|
|
50
|
+
console.log(`CAF Score: ${result2.cafScore?.toFixed(3)}`);
|
|
51
|
+
console.log('Top Results:', result2.results.slice(0, 3).map(r => r.name));
|
|
52
|
+
console.log();
|
|
53
|
+
await adaptiveRetrieval.updateStrategyPerformance(result2.strategy, 0.72, result2.retrievalCount, result2.latency, true);
|
|
54
|
+
// Test 3: Complex Multi-Hop Query
|
|
55
|
+
console.log('--- Test 3: Complex Multi-Hop Query ---');
|
|
56
|
+
const complexQuery = 'What are the connections between Alice and Bob through their projects?';
|
|
57
|
+
const result3 = await adaptiveRetrieval.retrieve(complexQuery, 5);
|
|
58
|
+
console.log(`Query: "${complexQuery}"`);
|
|
59
|
+
console.log(`Strategy: ${result3.strategy}`);
|
|
60
|
+
console.log(`Results: ${result3.results.length}`);
|
|
61
|
+
console.log(`Retrieval Count: ${result3.retrievalCount}`);
|
|
62
|
+
console.log(`Latency: ${result3.latency}ms`);
|
|
63
|
+
console.log(`CAF Score: ${result3.cafScore?.toFixed(3)}`);
|
|
64
|
+
console.log('Top Results:', result3.results.slice(0, 3).map(r => r.name));
|
|
65
|
+
console.log();
|
|
66
|
+
await adaptiveRetrieval.updateStrategyPerformance(result3.strategy, 0.68, result3.retrievalCount, result3.latency, true);
|
|
67
|
+
// Test 4: Exploratory Query
|
|
68
|
+
console.log('--- Test 4: Exploratory Query ---');
|
|
69
|
+
const exploratoryQuery = 'Show me everything related to software development';
|
|
70
|
+
const result4 = await adaptiveRetrieval.retrieve(exploratoryQuery, 10);
|
|
71
|
+
console.log(`Query: "${exploratoryQuery}"`);
|
|
72
|
+
console.log(`Strategy: ${result4.strategy}`);
|
|
73
|
+
console.log(`Results: ${result4.results.length}`);
|
|
74
|
+
console.log(`Retrieval Count: ${result4.retrievalCount}`);
|
|
75
|
+
console.log(`Latency: ${result4.latency}ms`);
|
|
76
|
+
console.log(`CAF Score: ${result4.cafScore?.toFixed(3)}`);
|
|
77
|
+
console.log('Top Results:', result4.results.slice(0, 3).map(r => r.name));
|
|
78
|
+
console.log();
|
|
79
|
+
await adaptiveRetrieval.updateStrategyPerformance(result4.strategy, 0.55, result4.retrievalCount, result4.latency, false // Lower success for exploratory
|
|
80
|
+
);
|
|
81
|
+
// Test 5: Repeat Simple Query (should use learned strategy)
|
|
82
|
+
console.log('--- Test 5: Repeat Simple Query (Learning Test) ---');
|
|
83
|
+
const result5 = await adaptiveRetrieval.retrieve(simpleQuery, 5);
|
|
84
|
+
console.log(`Query: "${simpleQuery}"`);
|
|
85
|
+
console.log(`Strategy: ${result5.strategy}`);
|
|
86
|
+
console.log(`Results: ${result5.results.length}`);
|
|
87
|
+
console.log(`Retrieval Count: ${result5.retrievalCount}`);
|
|
88
|
+
console.log(`Latency: ${result5.latency}ms`);
|
|
89
|
+
console.log(`CAF Score: ${result5.cafScore?.toFixed(3)}`);
|
|
90
|
+
console.log();
|
|
91
|
+
// Display Performance Statistics
|
|
92
|
+
console.log('=== Performance Statistics ===\n');
|
|
93
|
+
const stats = adaptiveRetrieval.getPerformanceStats();
|
|
94
|
+
for (const [strategy, perf] of stats.entries()) {
|
|
95
|
+
if (perf.totalCount > 0) {
|
|
96
|
+
console.log(`Strategy: ${strategy}`);
|
|
97
|
+
console.log(` Success Rate: ${((perf.successCount / perf.totalCount) * 100).toFixed(1)}%`);
|
|
98
|
+
console.log(` Avg F1 Score: ${perf.avgF1Score.toFixed(3)}`);
|
|
99
|
+
console.log(` Avg Retrieval Cost: ${perf.avgRetrievalCost.toFixed(2)}`);
|
|
100
|
+
console.log(` Avg Latency: ${perf.avgLatency.toFixed(0)}ms`);
|
|
101
|
+
console.log(` Total Uses: ${perf.totalCount}`);
|
|
102
|
+
console.log();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Test PRA Reward Calculation
|
|
106
|
+
console.log('=== PRA Reward Analysis ===');
|
|
107
|
+
console.log('Retrieval Count | PRA Reward');
|
|
108
|
+
console.log('----------------|------------');
|
|
109
|
+
for (let i = 1; i <= 10; i++) {
|
|
110
|
+
const reward = Math.pow(0.8, i - 1);
|
|
111
|
+
console.log(`${i.toString().padStart(15)} | ${reward.toFixed(4)}`);
|
|
112
|
+
}
|
|
113
|
+
console.log();
|
|
114
|
+
// Test CAF Score Calculation
|
|
115
|
+
console.log('=== CAF Score Analysis ===');
|
|
116
|
+
console.log('F1=0.9, varying retrieval counts:');
|
|
117
|
+
console.log('Retrieval Count | CAF Score');
|
|
118
|
+
console.log('----------------|----------');
|
|
119
|
+
for (let i = 1; i <= 10; i++) {
|
|
120
|
+
const cafScore = 0.9 * Math.exp(-0.15 * i);
|
|
121
|
+
console.log(`${i.toString().padStart(15)} | ${cafScore.toFixed(4)}`);
|
|
122
|
+
}
|
|
123
|
+
console.log();
|
|
124
|
+
console.log('ā
All tests completed successfully!');
|
|
125
|
+
console.log('\nš Key Insights:');
|
|
126
|
+
console.log('1. System adapts strategy based on query complexity');
|
|
127
|
+
console.log('2. PRA encourages essential retrievals, penalizes excessive ones');
|
|
128
|
+
console.log('3. CAF balances answer quality with computational cost');
|
|
129
|
+
console.log('4. Performance tracking enables continuous improvement');
|
|
130
|
+
}
|
|
131
|
+
// Run tests
|
|
132
|
+
testAdaptiveRetrieval().catch(error => {
|
|
133
|
+
console.error('ā Test failed:', error);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
async function testCompaction() {
|
|
6
|
+
console.log("Starting Context Compaction Tests...");
|
|
7
|
+
const server = new index_1.MemoryServer();
|
|
8
|
+
await server.initPromise;
|
|
9
|
+
try {
|
|
10
|
+
// 1. Test Session Compaction
|
|
11
|
+
console.log("\n--- Testing Session Compaction ---");
|
|
12
|
+
const session = await server.startSession({ name: "Compaction Test Session" });
|
|
13
|
+
const sessionId = session.id;
|
|
14
|
+
console.log("Adding observations to session...");
|
|
15
|
+
await server.addObservation({
|
|
16
|
+
entity_name: "CompactionBot",
|
|
17
|
+
text: "Users prefers dark mode for all interfaces.",
|
|
18
|
+
session_id: sessionId
|
|
19
|
+
});
|
|
20
|
+
await server.addObservation({
|
|
21
|
+
entity_name: "CompactionBot",
|
|
22
|
+
text: "User is a senior software engineer specialized in TypeScript.",
|
|
23
|
+
session_id: sessionId
|
|
24
|
+
});
|
|
25
|
+
await server.addObservation({
|
|
26
|
+
entity_name: "CompactionBot",
|
|
27
|
+
text: "User likes concise documentation with many examples.",
|
|
28
|
+
session_id: sessionId
|
|
29
|
+
});
|
|
30
|
+
console.log("Stopping session (should trigger compaction)...");
|
|
31
|
+
const stopResult = await server.stopSession({ id: sessionId });
|
|
32
|
+
console.log("Stop Result:", JSON.stringify(stopResult, null, 2));
|
|
33
|
+
// Check if summary exists in global_user_profile
|
|
34
|
+
const profileObs = await server.db.run('?[text] := *observation{entity_id: "global_user_profile", text, metadata, @ "NOW"}, regex_matches(text, ".*Session Summary.*")');
|
|
35
|
+
console.log(`Found ${profileObs.rows.length} session summaries in profile.`);
|
|
36
|
+
if (profileObs.rows.length > 0) {
|
|
37
|
+
console.log("Latest Summary:", profileObs.rows[0][0]);
|
|
38
|
+
}
|
|
39
|
+
// 2. Test Entity Compaction (Threshold-based)
|
|
40
|
+
console.log("\n--- Testing Entity Compaction ---");
|
|
41
|
+
const entityName = `HeavyEntity_${(0, uuid_1.v4)().substring(0, 8)}`;
|
|
42
|
+
const createRes = await server.createEntity({ name: entityName, type: "Test" });
|
|
43
|
+
const entityId = createRes.id;
|
|
44
|
+
console.log(`Adding 25 observations to ${entityName} (Threshold is 20)...`);
|
|
45
|
+
for (let i = 1; i <= 25; i++) {
|
|
46
|
+
process.stdout.write(`.`);
|
|
47
|
+
await server.addObservation({
|
|
48
|
+
entity_id: entityId,
|
|
49
|
+
text: `Fact number ${i}: This is a piece of information about the heavy entity that needs to be compacted eventually.`,
|
|
50
|
+
deduplicate: false
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
console.log("\nDone adding observations.");
|
|
54
|
+
// The last few should have triggered compaction in background
|
|
55
|
+
console.log("Waiting for background compaction (30s for Ollama)...");
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, 30000));
|
|
57
|
+
// Check observation count
|
|
58
|
+
const countRes = await server.db.run('?[count(oid)] := *observation{entity_id: $eid, id: oid, @ "NOW"}', { eid: entityId });
|
|
59
|
+
const finalCount = Number(countRes.rows[0][0]);
|
|
60
|
+
console.log(`Final observation count for ${entityName}: ${finalCount}`);
|
|
61
|
+
// Check for ExecutiveSummary
|
|
62
|
+
// Check for ExecutiveSummary (Ollama might use bold, lowercase, or slightly different labels)
|
|
63
|
+
const summaryRes = await server.db.run('?[text] := *observation{entity_id: $eid, text, @ "NOW"}, regex_matches(text, "(?i).*(Executive\\\\s*Summary|Zusammenfassung|ExecutiveSummary).*")', { eid: entityId });
|
|
64
|
+
console.log(`Found ${summaryRes.rows.length} ExecutiveSummaries.`);
|
|
65
|
+
if (summaryRes.rows.length > 0) {
|
|
66
|
+
console.log("Executive Summary Content Preview:", summaryRes.rows[0][0].substring(0, 100) + "...");
|
|
67
|
+
}
|
|
68
|
+
else if (finalCount > 0) {
|
|
69
|
+
// Debug: Print what we actually have
|
|
70
|
+
const allObs = await server.db.run('?[text] := *observation{entity_id: $eid, text, @ "NOW"}', { eid: entityId });
|
|
71
|
+
console.log("Actual observations found for entity:");
|
|
72
|
+
allObs.rows.forEach((row, i) => {
|
|
73
|
+
console.log(`[${i}] ${row[0].substring(0, 200)}...`);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// 3. Test Manual Compaction via direct call (bypass MCP wrapper for test)
|
|
77
|
+
console.log("\n--- Testing Manual Compaction ---");
|
|
78
|
+
const manageResult = await server.compactEntity({
|
|
79
|
+
entity_id: entityId,
|
|
80
|
+
threshold: 2 // force compaction on remaining context
|
|
81
|
+
});
|
|
82
|
+
console.log("Manual Compact Result:", JSON.stringify(manageResult, null, 2));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error("Test failed:", error);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
testCompaction();
|