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,390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MultiHopVectorPivot = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Multi-Hop Reasoning with Vector Pivots (v2.0 - Logic-Aware)
|
|
6
|
+
*
|
|
7
|
+
* Pattern: Use Vector Search as "springboard" for Graph Traversal with Logic-Aware Reasoning
|
|
8
|
+
*
|
|
9
|
+
* Research:
|
|
10
|
+
* - HopRAG (ACL 2025): Logic-aware RAG with pseudo-queries as edges
|
|
11
|
+
* - Retrieval Pivot Attacks (arXiv:2602.08668): Security & boundary enforcement
|
|
12
|
+
* - Neo4j GraphRAG: Multi-hop reasoning patterns
|
|
13
|
+
*
|
|
14
|
+
* Pipeline (Retrieve-Reason-Prune):
|
|
15
|
+
* 1. Vector Search: Find semantic pivot points using HNSW
|
|
16
|
+
* 2. Reasoning-Augmented Traversal: BFS with LLM-guided hops via pseudo-queries
|
|
17
|
+
* 3. Helpfulness Scoring: Combines textual similarity + logical importance
|
|
18
|
+
* 4. Pruning: Filter by confidence threshold and pivot depth
|
|
19
|
+
* 5. Aggregation: Deduplicate and rank entities
|
|
20
|
+
*/
|
|
21
|
+
class MultiHopVectorPivot {
|
|
22
|
+
db;
|
|
23
|
+
embeddingService;
|
|
24
|
+
maxBranchingFactor = 5;
|
|
25
|
+
maxNodesExplored = 100;
|
|
26
|
+
confidenceThreshold = 0.5;
|
|
27
|
+
maxPivotDepth = 3; // Security: limit graph expansion depth
|
|
28
|
+
constructor(db, embeddingService, maxBranchingFactor, maxNodesExplored, confidenceThreshold, maxPivotDepth) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
this.embeddingService = embeddingService;
|
|
31
|
+
if (maxBranchingFactor)
|
|
32
|
+
this.maxBranchingFactor = maxBranchingFactor;
|
|
33
|
+
if (maxNodesExplored)
|
|
34
|
+
this.maxNodesExplored = maxNodesExplored;
|
|
35
|
+
if (confidenceThreshold)
|
|
36
|
+
this.confidenceThreshold = confidenceThreshold;
|
|
37
|
+
if (maxPivotDepth)
|
|
38
|
+
this.maxPivotDepth = maxPivotDepth;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Main entry point: Multi-Hop Reasoning with Vector Pivots
|
|
42
|
+
*
|
|
43
|
+
* Retrieve-Reason-Prune Pipeline:
|
|
44
|
+
* 1. Retrieve: Find vector pivots via semantic search
|
|
45
|
+
* 2. Reason: Traverse graph with logic-aware hops
|
|
46
|
+
* 3. Prune: Filter by helpfulness and confidence
|
|
47
|
+
*/
|
|
48
|
+
async multiHopVectorPivot(query, maxHops = 3, limit = 10) {
|
|
49
|
+
try {
|
|
50
|
+
console.error(`[MultiHop] Starting multi-hop reasoning for query: "${query}"`);
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
// Step 1: RETRIEVE - Find vector pivots
|
|
53
|
+
const pivots = await this.findVectorPivots(query, this.maxBranchingFactor);
|
|
54
|
+
console.error(`[MultiHop] Found ${pivots.length} vector pivots`);
|
|
55
|
+
if (pivots.length === 0) {
|
|
56
|
+
return {
|
|
57
|
+
query,
|
|
58
|
+
pivots: [],
|
|
59
|
+
paths: [],
|
|
60
|
+
aggregated_results: [],
|
|
61
|
+
total_hops: 0,
|
|
62
|
+
execution_time_ms: Date.now() - startTime,
|
|
63
|
+
status: "no_pivots_found"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Step 2: REASON - Traverse graph from each pivot with logic-aware hops
|
|
67
|
+
const allPaths = [];
|
|
68
|
+
const nodesExplored = new Set();
|
|
69
|
+
for (const pivot of pivots) {
|
|
70
|
+
if (nodesExplored.size >= this.maxNodesExplored)
|
|
71
|
+
break;
|
|
72
|
+
const paths = await this.reasoningAugmentedTraversal(pivot, query, maxHops, nodesExplored);
|
|
73
|
+
allPaths.push(...paths);
|
|
74
|
+
}
|
|
75
|
+
console.error(`[MultiHop] Explored ${nodesExplored.size} nodes across ${allPaths.length} paths`);
|
|
76
|
+
// Step 3: PRUNE - Filter by helpfulness and confidence
|
|
77
|
+
const prunedPaths = this.prunePathsByHelpfulness(allPaths, query);
|
|
78
|
+
console.error(`[MultiHop] Pruned to ${prunedPaths.length} high-quality paths`);
|
|
79
|
+
// Step 4: AGGREGATE - Deduplicate and rank
|
|
80
|
+
const aggregated = this.aggregatePathResults(prunedPaths);
|
|
81
|
+
const topResults = aggregated.slice(0, limit);
|
|
82
|
+
const executionTime = Date.now() - startTime;
|
|
83
|
+
console.error(`[MultiHop] Completed in ${executionTime}ms`);
|
|
84
|
+
return {
|
|
85
|
+
query,
|
|
86
|
+
pivots: pivots.map(p => ({ id: p.id, name: p.name, similarity: p.similarity })),
|
|
87
|
+
paths: prunedPaths.slice(0, 5), // Top 5 paths for context
|
|
88
|
+
aggregated_results: topResults,
|
|
89
|
+
total_hops: Math.max(...allPaths.map(p => p.path.length)),
|
|
90
|
+
execution_time_ms: executionTime,
|
|
91
|
+
status: "success"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error("[MultiHop] Error:", error.message);
|
|
96
|
+
return {
|
|
97
|
+
query,
|
|
98
|
+
pivots: [],
|
|
99
|
+
paths: [],
|
|
100
|
+
aggregated_results: [],
|
|
101
|
+
total_hops: 0,
|
|
102
|
+
execution_time_ms: 0,
|
|
103
|
+
status: "error",
|
|
104
|
+
error: error.message
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Step 1: RETRIEVE - Find vector pivots via semantic search
|
|
110
|
+
*/
|
|
111
|
+
async findVectorPivots(query, topK) {
|
|
112
|
+
try {
|
|
113
|
+
const queryEmbedding = await this.embeddingService.embed(query);
|
|
114
|
+
const datalog = `
|
|
115
|
+
?[id, name, type, similarity] :=
|
|
116
|
+
~entity:semantic{id | query: vec($query_vector), k: $topk, ef: $ef_search, bind_distance: dist},
|
|
117
|
+
*entity{id, name, type, @ "NOW"},
|
|
118
|
+
similarity = 1.0 - dist
|
|
119
|
+
:sort -similarity
|
|
120
|
+
:limit $topk
|
|
121
|
+
`;
|
|
122
|
+
const result = await this.db.run(datalog, {
|
|
123
|
+
query_vector: queryEmbedding,
|
|
124
|
+
topk: topK,
|
|
125
|
+
ef_search: 100
|
|
126
|
+
});
|
|
127
|
+
return result.rows.map((r) => ({
|
|
128
|
+
id: r[0],
|
|
129
|
+
name: r[1],
|
|
130
|
+
type: r[2],
|
|
131
|
+
similarity: r[3]
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error("[MultiHop] Error finding pivots:", error.message);
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Step 2: REASON - Reasoning-Augmented Graph Traversal
|
|
141
|
+
*
|
|
142
|
+
* Uses BFS with logic-aware hops guided by pseudo-queries and relationship context
|
|
143
|
+
*/
|
|
144
|
+
async reasoningAugmentedTraversal(pivot, query, maxHops, nodesExplored) {
|
|
145
|
+
const paths = [];
|
|
146
|
+
const queue = [
|
|
147
|
+
{
|
|
148
|
+
id: pivot.id,
|
|
149
|
+
depth: 0,
|
|
150
|
+
path: [{ id: pivot.id, name: pivot.name, type: pivot.type, confidence: 1.0 }],
|
|
151
|
+
pathScore: pivot.similarity,
|
|
152
|
+
visitedInPath: new Set([pivot.id])
|
|
153
|
+
}
|
|
154
|
+
];
|
|
155
|
+
const queryEmbedding = await this.embeddingService.embed(query);
|
|
156
|
+
while (queue.length > 0 && nodesExplored.size < this.maxNodesExplored) {
|
|
157
|
+
const current = queue.shift();
|
|
158
|
+
// Pivot Depth Security: Enforce max depth to prevent uncontrolled expansion
|
|
159
|
+
if (current.depth >= maxHops || current.depth >= this.maxPivotDepth) {
|
|
160
|
+
paths.push({
|
|
161
|
+
path: current.path,
|
|
162
|
+
score: current.pathScore,
|
|
163
|
+
depth: current.depth,
|
|
164
|
+
confidence: this.calculatePathConfidence(current.path)
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Get neighbors with relationship context (logic-aware)
|
|
169
|
+
const neighbors = await this.getNeighborsWithContext(current.id, query, queryEmbedding, current.visitedInPath);
|
|
170
|
+
for (const neighbor of neighbors.slice(0, this.maxBranchingFactor)) {
|
|
171
|
+
if (nodesExplored.has(neighbor.id))
|
|
172
|
+
continue;
|
|
173
|
+
nodesExplored.add(neighbor.id);
|
|
174
|
+
// Confidence decay: 0.9^depth for recency weighting
|
|
175
|
+
const depthPenalty = Math.pow(0.9, current.depth + 1);
|
|
176
|
+
const newConfidence = neighbor.confidence * depthPenalty;
|
|
177
|
+
// Pruning: Skip if confidence drops below threshold
|
|
178
|
+
if (newConfidence < this.confidenceThreshold) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const newPath = [
|
|
182
|
+
...current.path,
|
|
183
|
+
{
|
|
184
|
+
id: neighbor.id,
|
|
185
|
+
name: neighbor.name,
|
|
186
|
+
type: neighbor.type,
|
|
187
|
+
confidence: newConfidence,
|
|
188
|
+
relationshipType: neighbor.relationshipType,
|
|
189
|
+
relationshipStrength: neighbor.relationshipStrength
|
|
190
|
+
}
|
|
191
|
+
];
|
|
192
|
+
const newPathScore = current.pathScore * newConfidence;
|
|
193
|
+
const newVisited = new Set(current.visitedInPath);
|
|
194
|
+
newVisited.add(neighbor.id);
|
|
195
|
+
// Add to queue for further exploration
|
|
196
|
+
queue.push({
|
|
197
|
+
id: neighbor.id,
|
|
198
|
+
depth: current.depth + 1,
|
|
199
|
+
path: newPath,
|
|
200
|
+
pathScore: newPathScore,
|
|
201
|
+
visitedInPath: newVisited
|
|
202
|
+
});
|
|
203
|
+
// Also record as a complete path
|
|
204
|
+
paths.push({
|
|
205
|
+
path: newPath,
|
|
206
|
+
score: newPathScore,
|
|
207
|
+
depth: current.depth + 1,
|
|
208
|
+
confidence: newConfidence
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return paths;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get neighbors with relationship context (logic-aware edges)
|
|
216
|
+
*
|
|
217
|
+
* Considers:
|
|
218
|
+
* - Semantic similarity to query
|
|
219
|
+
* - Relationship type and strength
|
|
220
|
+
* - Entity type compatibility
|
|
221
|
+
*/
|
|
222
|
+
async getNeighborsWithContext(entityId, query, queryEmbedding, visitedInPath) {
|
|
223
|
+
try {
|
|
224
|
+
const datalog = `
|
|
225
|
+
rank_val[id, r] := *entity_rank{entity_id: id, pagerank: r}
|
|
226
|
+
rank_val[id, r] := *entity{id, @ "NOW"}, not *entity_rank{entity_id: id}, r = 0.0
|
|
227
|
+
|
|
228
|
+
outgoing[to_id, rel_type, strength] :=
|
|
229
|
+
*relationship{from_id: $entity_id, to_id, relation_type: rel_type, strength, @ "NOW"}
|
|
230
|
+
|
|
231
|
+
incoming[from_id, rel_type, strength] :=
|
|
232
|
+
*relationship{from_id, to_id: $entity_id, relation_type: rel_type, strength, @ "NOW"}
|
|
233
|
+
|
|
234
|
+
neighbors[id, name, type, rel_type, strength, pr] :=
|
|
235
|
+
outgoing[id, rel_type, strength],
|
|
236
|
+
*entity{id, name, type, @ "NOW"},
|
|
237
|
+
rank_val[id, pr]
|
|
238
|
+
|
|
239
|
+
neighbors[id, name, type, rel_type, strength, pr] :=
|
|
240
|
+
incoming[id, rel_type, strength],
|
|
241
|
+
*entity{id, name, type, @ "NOW"},
|
|
242
|
+
rank_val[id, pr]
|
|
243
|
+
|
|
244
|
+
?[id, name, type, rel_type, strength, pr] := neighbors[id, name, type, rel_type, strength, pr]
|
|
245
|
+
:sort -strength
|
|
246
|
+
:limit $limit
|
|
247
|
+
`;
|
|
248
|
+
const result = await this.db.run(datalog, {
|
|
249
|
+
entity_id: entityId,
|
|
250
|
+
limit: this.maxBranchingFactor * 2
|
|
251
|
+
});
|
|
252
|
+
const neighbors = [];
|
|
253
|
+
for (const row of result.rows) {
|
|
254
|
+
const neighborId = row[0];
|
|
255
|
+
const name = row[1];
|
|
256
|
+
const type = row[2];
|
|
257
|
+
const relType = row[3];
|
|
258
|
+
const strength = row[4];
|
|
259
|
+
const pagerank = row[5];
|
|
260
|
+
// Calculate semantic similarity to query
|
|
261
|
+
try {
|
|
262
|
+
const entityRes = await this.db.run('?[embedding] := *entity{id: $id, embedding, @ "NOW"}', { id: neighborId });
|
|
263
|
+
if (entityRes.rows.length > 0) {
|
|
264
|
+
const embedding = entityRes.rows[0][0];
|
|
265
|
+
const cosineSim = this.cosineSimilarity(queryEmbedding, embedding);
|
|
266
|
+
// Confidence combines: semantic similarity (0.4) + relationship strength (0.3) + pagerank (0.3)
|
|
267
|
+
const confidence = 0.4 * cosineSim + 0.3 * strength + 0.3 * Math.min(pagerank, 1.0);
|
|
268
|
+
neighbors.push({
|
|
269
|
+
id: neighborId,
|
|
270
|
+
name,
|
|
271
|
+
type,
|
|
272
|
+
relationshipType: relType,
|
|
273
|
+
relationshipStrength: strength,
|
|
274
|
+
confidence,
|
|
275
|
+
pagerank
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
// Fallback: use relationship strength + pagerank only
|
|
281
|
+
const confidence = 0.5 * strength + 0.5 * Math.min(pagerank, 1.0);
|
|
282
|
+
neighbors.push({
|
|
283
|
+
id: neighborId,
|
|
284
|
+
name,
|
|
285
|
+
type,
|
|
286
|
+
relationshipType: relType,
|
|
287
|
+
relationshipStrength: strength,
|
|
288
|
+
confidence,
|
|
289
|
+
pagerank
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return neighbors.sort((a, b) => b.confidence - a.confidence);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error("[MultiHop] Error getting neighbors:", error.message);
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Step 3: PRUNE - Filter paths by Helpfulness Score
|
|
302
|
+
*
|
|
303
|
+
* Helpfulness = textual_similarity (0.6) + logical_importance (0.4)
|
|
304
|
+
* Logical importance = path_length_penalty + confidence + diversity
|
|
305
|
+
*/
|
|
306
|
+
prunePathsByHelpfulness(paths, query) {
|
|
307
|
+
return paths
|
|
308
|
+
.map(path => {
|
|
309
|
+
// Textual similarity: average confidence of path nodes
|
|
310
|
+
const textualSim = path.confidence;
|
|
311
|
+
// Logical importance: combination of factors
|
|
312
|
+
const pathLength = path.path.length;
|
|
313
|
+
const lengthPenalty = 1.0 / (1.0 + 0.1 * pathLength); // Prefer shorter paths
|
|
314
|
+
const logicalImportance = lengthPenalty * path.confidence;
|
|
315
|
+
// Helpfulness score
|
|
316
|
+
const helpfulness = 0.6 * textualSim + 0.4 * logicalImportance;
|
|
317
|
+
return {
|
|
318
|
+
...path,
|
|
319
|
+
helpfulness: helpfulness
|
|
320
|
+
};
|
|
321
|
+
})
|
|
322
|
+
.filter(p => p.helpfulness >= this.confidenceThreshold)
|
|
323
|
+
.sort((a, b) => (b.helpfulness || 0) - (a.helpfulness || 0));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Step 4: AGGREGATE - Deduplicate and rank entities
|
|
327
|
+
*/
|
|
328
|
+
aggregatePathResults(paths) {
|
|
329
|
+
const entityMap = new Map();
|
|
330
|
+
for (const path of paths) {
|
|
331
|
+
for (const node of path.path) {
|
|
332
|
+
const existing = entityMap.get(node.id);
|
|
333
|
+
if (existing) {
|
|
334
|
+
existing.occurrences++;
|
|
335
|
+
existing.max_score = Math.max(existing.max_score, path.score);
|
|
336
|
+
existing.avg_score = (existing.avg_score * (existing.occurrences - 1) + path.score) / existing.occurrences;
|
|
337
|
+
existing.min_depth = Math.min(existing.min_depth, path.depth);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
entityMap.set(node.id, {
|
|
341
|
+
id: node.id,
|
|
342
|
+
name: node.name,
|
|
343
|
+
type: node.type,
|
|
344
|
+
occurrences: 1,
|
|
345
|
+
max_score: path.score,
|
|
346
|
+
avg_score: path.score,
|
|
347
|
+
min_depth: path.depth,
|
|
348
|
+
confidence: node.confidence
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return Array.from(entityMap.values())
|
|
354
|
+
.sort((a, b) => {
|
|
355
|
+
// Sort by: occurrences (frequency) > avg_score > min_depth
|
|
356
|
+
if (b.occurrences !== a.occurrences)
|
|
357
|
+
return b.occurrences - a.occurrences;
|
|
358
|
+
if (b.avg_score !== a.avg_score)
|
|
359
|
+
return b.avg_score - a.avg_score;
|
|
360
|
+
return a.min_depth - b.min_depth;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Calculate path confidence: average confidence of all nodes in path
|
|
365
|
+
*/
|
|
366
|
+
calculatePathConfidence(path) {
|
|
367
|
+
if (path.length === 0)
|
|
368
|
+
return 0;
|
|
369
|
+
const sum = path.reduce((acc, node) => acc + node.confidence, 0);
|
|
370
|
+
return sum / path.length;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Cosine similarity between two vectors
|
|
374
|
+
*/
|
|
375
|
+
cosineSimilarity(a, b) {
|
|
376
|
+
if (a.length !== b.length)
|
|
377
|
+
return 0;
|
|
378
|
+
let dotProduct = 0;
|
|
379
|
+
let normA = 0;
|
|
380
|
+
let normB = 0;
|
|
381
|
+
for (let i = 0; i < a.length; i++) {
|
|
382
|
+
dotProduct += a[i] * b[i];
|
|
383
|
+
normA += a[i] * a[i];
|
|
384
|
+
normB += b[i] * b[i];
|
|
385
|
+
}
|
|
386
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
387
|
+
return denominator === 0 ? 0 : dotProduct / denominator;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
exports.MultiHopVectorPivot = MultiHopVectorPivot;
|