@weavelogic/knowledge-graph-agent 0.3.0 → 0.4.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/README.md +290 -3
- package/dist/_virtual/index10.js +2 -2
- package/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +2 -2
- package/dist/_virtual/index8.js +2 -2
- package/dist/_virtual/index9.js +2 -2
- package/dist/audit/config.d.ts +150 -0
- package/dist/audit/config.d.ts.map +1 -0
- package/dist/audit/config.js +111 -0
- package/dist/audit/config.js.map +1 -0
- package/dist/audit/index.d.ts +38 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/services/audit-chain.d.ts +276 -0
- package/dist/audit/services/audit-chain.d.ts.map +1 -0
- package/dist/audit/services/audit-chain.js +502 -0
- package/dist/audit/services/audit-chain.js.map +1 -0
- package/dist/audit/services/index.d.ts +11 -0
- package/dist/audit/services/index.d.ts.map +1 -0
- package/dist/audit/services/syndication.d.ts +334 -0
- package/dist/audit/services/syndication.d.ts.map +1 -0
- package/dist/audit/services/syndication.js +589 -0
- package/dist/audit/services/syndication.js.map +1 -0
- package/dist/audit/types.d.ts +453 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +21 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +621 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/vector.d.ts +14 -0
- package/dist/cli/commands/vector.d.ts.map +1 -0
- package/dist/cli/commands/vector.js +429 -0
- package/dist/cli/commands/vector.js.map +1 -0
- package/dist/cli/commands/workflow.d.ts +12 -0
- package/dist/cli/commands/workflow.d.ts.map +1 -0
- package/dist/cli/commands/workflow.js +471 -0
- package/dist/cli/commands/workflow.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/database/schemas/index.d.ts +85 -0
- package/dist/database/schemas/index.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/audit/checkpoint.d.ts +58 -0
- package/dist/mcp-server/tools/audit/checkpoint.d.ts.map +1 -0
- package/dist/mcp-server/tools/audit/checkpoint.js +73 -0
- package/dist/mcp-server/tools/audit/checkpoint.js.map +1 -0
- package/dist/mcp-server/tools/audit/index.d.ts +53 -0
- package/dist/mcp-server/tools/audit/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/audit/index.js +12 -0
- package/dist/mcp-server/tools/audit/index.js.map +1 -0
- package/dist/mcp-server/tools/audit/query.d.ts +58 -0
- package/dist/mcp-server/tools/audit/query.d.ts.map +1 -0
- package/dist/mcp-server/tools/audit/query.js +125 -0
- package/dist/mcp-server/tools/audit/query.js.map +1 -0
- package/dist/mcp-server/tools/audit/sync.d.ts +58 -0
- package/dist/mcp-server/tools/audit/sync.d.ts.map +1 -0
- package/dist/mcp-server/tools/audit/sync.js +126 -0
- package/dist/mcp-server/tools/audit/sync.js.map +1 -0
- package/dist/mcp-server/tools/index.d.ts +3 -0
- package/dist/mcp-server/tools/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/registry.js +90 -0
- package/dist/mcp-server/tools/registry.js.map +1 -1
- package/dist/mcp-server/tools/vector/index.d.ts +12 -0
- package/dist/mcp-server/tools/vector/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/vector/index.js +12 -0
- package/dist/mcp-server/tools/vector/index.js.map +1 -0
- package/dist/mcp-server/tools/vector/search.d.ts +41 -0
- package/dist/mcp-server/tools/vector/search.d.ts.map +1 -0
- package/dist/mcp-server/tools/vector/search.js +224 -0
- package/dist/mcp-server/tools/vector/search.js.map +1 -0
- package/dist/mcp-server/tools/vector/trajectory.d.ts +39 -0
- package/dist/mcp-server/tools/vector/trajectory.d.ts.map +1 -0
- package/dist/mcp-server/tools/vector/trajectory.js +170 -0
- package/dist/mcp-server/tools/vector/trajectory.js.map +1 -0
- package/dist/mcp-server/tools/vector/upsert.d.ts +44 -0
- package/dist/mcp-server/tools/vector/upsert.d.ts.map +1 -0
- package/dist/mcp-server/tools/vector/upsert.js +175 -0
- package/dist/mcp-server/tools/vector/upsert.js.map +1 -0
- package/dist/mcp-server/tools/workflow/index.d.ts +29 -0
- package/dist/mcp-server/tools/workflow/index.d.ts.map +1 -0
- package/dist/mcp-server/tools/workflow/index.js +12 -0
- package/dist/mcp-server/tools/workflow/index.js.map +1 -0
- package/dist/mcp-server/tools/workflow/list.d.ts +41 -0
- package/dist/mcp-server/tools/workflow/list.d.ts.map +1 -0
- package/dist/mcp-server/tools/workflow/list.js +195 -0
- package/dist/mcp-server/tools/workflow/list.js.map +1 -0
- package/dist/mcp-server/tools/workflow/start.d.ts +40 -0
- package/dist/mcp-server/tools/workflow/start.d.ts.map +1 -0
- package/dist/mcp-server/tools/workflow/start.js +165 -0
- package/dist/mcp-server/tools/workflow/start.js.map +1 -0
- package/dist/mcp-server/tools/workflow/status.d.ts +38 -0
- package/dist/mcp-server/tools/workflow/status.d.ts.map +1 -0
- package/dist/mcp-server/tools/workflow/status.js +97 -0
- package/dist/mcp-server/tools/workflow/status.js.map +1 -0
- package/dist/node_modules/ajv/dist/compile/index.js +1 -1
- package/dist/node_modules/ajv/dist/vocabularies/applicator/index.js +1 -1
- package/dist/node_modules/ajv/dist/vocabularies/core/index.js +1 -1
- package/dist/node_modules/ajv/dist/vocabularies/format/index.js +1 -1
- package/dist/node_modules/ajv/dist/vocabularies/validation/index.js +1 -1
- package/dist/vector/config.d.ts +300 -0
- package/dist/vector/config.d.ts.map +1 -0
- package/dist/vector/config.js +124 -0
- package/dist/vector/config.js.map +1 -0
- package/dist/vector/index.d.ts +50 -0
- package/dist/vector/index.d.ts.map +1 -0
- package/dist/vector/services/index.d.ts +13 -0
- package/dist/vector/services/index.d.ts.map +1 -0
- package/dist/vector/services/trajectory-tracker.d.ts +405 -0
- package/dist/vector/services/trajectory-tracker.d.ts.map +1 -0
- package/dist/vector/services/trajectory-tracker.js +445 -0
- package/dist/vector/services/trajectory-tracker.js.map +1 -0
- package/dist/vector/services/vector-store.d.ts +339 -0
- package/dist/vector/services/vector-store.d.ts.map +1 -0
- package/dist/vector/services/vector-store.js +748 -0
- package/dist/vector/services/vector-store.js.map +1 -0
- package/dist/vector/types.d.ts +677 -0
- package/dist/vector/types.d.ts.map +1 -0
- package/dist/workflow/adapters/goap-adapter.d.ts +196 -0
- package/dist/workflow/adapters/goap-adapter.d.ts.map +1 -0
- package/dist/workflow/adapters/goap-adapter.js +706 -0
- package/dist/workflow/adapters/goap-adapter.js.map +1 -0
- package/dist/workflow/adapters/index.d.ts +10 -0
- package/dist/workflow/adapters/index.d.ts.map +1 -0
- package/dist/workflow/config.d.ts +135 -0
- package/dist/workflow/config.d.ts.map +1 -0
- package/dist/workflow/config.js +92 -0
- package/dist/workflow/config.js.map +1 -0
- package/dist/workflow/handlers/index.d.ts +9 -0
- package/dist/workflow/handlers/index.d.ts.map +1 -0
- package/dist/workflow/handlers/webhook-handlers.d.ts +397 -0
- package/dist/workflow/handlers/webhook-handlers.d.ts.map +1 -0
- package/dist/workflow/handlers/webhook-handlers.js +454 -0
- package/dist/workflow/handlers/webhook-handlers.js.map +1 -0
- package/dist/workflow/index.d.ts +42 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/services/index.d.ts +9 -0
- package/dist/workflow/services/index.d.ts.map +1 -0
- package/dist/workflow/services/workflow-service.d.ts +318 -0
- package/dist/workflow/services/workflow-service.d.ts.map +1 -0
- package/dist/workflow/services/workflow-service.js +577 -0
- package/dist/workflow/services/workflow-service.js.map +1 -0
- package/dist/workflow/types.d.ts +470 -0
- package/dist/workflow/types.d.ts.map +1 -0
- package/dist/workflow/workflows/realtime-collab.d.ts +245 -0
- package/dist/workflow/workflows/realtime-collab.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
import { createRuVectorConfig, validateRuVectorConfig } from "../config.js";
|
|
2
|
+
import { createLogger } from "../../utils/logger.js";
|
|
3
|
+
const logger = createLogger("vector-store");
|
|
4
|
+
class EnhancedVectorStore {
|
|
5
|
+
/** Store configuration */
|
|
6
|
+
config;
|
|
7
|
+
/** In-memory node storage */
|
|
8
|
+
nodes = /* @__PURE__ */ new Map();
|
|
9
|
+
/** Entry point node ID for HNSW search */
|
|
10
|
+
entryPoint = null;
|
|
11
|
+
/** Maximum level in the current index */
|
|
12
|
+
maxLevel = 0;
|
|
13
|
+
/** Level generation multiplier (1/ln(M)) */
|
|
14
|
+
levelMultiplier;
|
|
15
|
+
/** Initialization state */
|
|
16
|
+
isInitialized = false;
|
|
17
|
+
/** Event listeners */
|
|
18
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Create a new EnhancedVectorStore
|
|
21
|
+
*
|
|
22
|
+
* @param config - Optional configuration overrides
|
|
23
|
+
*/
|
|
24
|
+
constructor(config) {
|
|
25
|
+
const baseConfig = createRuVectorConfig();
|
|
26
|
+
this.config = { ...baseConfig, ...config };
|
|
27
|
+
const m = this.config.index.hnswConfig?.m || 16;
|
|
28
|
+
this.levelMultiplier = 1 / Math.log(m);
|
|
29
|
+
const validation = validateRuVectorConfig(this.config);
|
|
30
|
+
if (!validation.valid) {
|
|
31
|
+
logger.warn("Invalid configuration", { errors: validation.errors });
|
|
32
|
+
}
|
|
33
|
+
if (validation.warnings.length > 0) {
|
|
34
|
+
logger.debug("Configuration warnings", { warnings: validation.warnings });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Initialize the vector store
|
|
39
|
+
*
|
|
40
|
+
* Sets up the storage backend and prepares the index for operations.
|
|
41
|
+
* Must be called before any other operations.
|
|
42
|
+
*
|
|
43
|
+
* @throws Error if initialization fails
|
|
44
|
+
*/
|
|
45
|
+
async initialize() {
|
|
46
|
+
if (this.isInitialized) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
logger.info("Initializing vector store", {
|
|
50
|
+
backend: this.config.backend,
|
|
51
|
+
dimensions: this.config.index.dimensions,
|
|
52
|
+
indexType: this.config.index.indexType,
|
|
53
|
+
distanceMetric: this.config.index.distanceMetric
|
|
54
|
+
});
|
|
55
|
+
switch (this.config.backend) {
|
|
56
|
+
case "memory":
|
|
57
|
+
break;
|
|
58
|
+
case "postgres":
|
|
59
|
+
logger.warn("PostgreSQL backend not yet implemented, using memory");
|
|
60
|
+
break;
|
|
61
|
+
case "standalone":
|
|
62
|
+
logger.warn("Standalone backend not yet implemented, using memory");
|
|
63
|
+
break;
|
|
64
|
+
case "sqlite":
|
|
65
|
+
logger.warn("SQLite backend not yet implemented, using memory");
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
this.isInitialized = true;
|
|
69
|
+
logger.info("Vector store initialized");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Calculate distance between two vectors
|
|
73
|
+
*
|
|
74
|
+
* @param a - First vector
|
|
75
|
+
* @param b - Second vector
|
|
76
|
+
* @returns Distance value (lower = more similar for most metrics)
|
|
77
|
+
* @throws Error if vectors have different dimensions
|
|
78
|
+
*/
|
|
79
|
+
calculateDistance(a, b) {
|
|
80
|
+
if (a.length !== b.length) {
|
|
81
|
+
throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
|
|
82
|
+
}
|
|
83
|
+
switch (this.config.index.distanceMetric) {
|
|
84
|
+
case "cosine":
|
|
85
|
+
return this.cosineDistance(a, b);
|
|
86
|
+
case "euclidean":
|
|
87
|
+
return this.euclideanDistance(a, b);
|
|
88
|
+
case "dotProduct":
|
|
89
|
+
return this.dotProductDistance(a, b);
|
|
90
|
+
case "manhattan":
|
|
91
|
+
return this.manhattanDistance(a, b);
|
|
92
|
+
default:
|
|
93
|
+
return this.cosineDistance(a, b);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Calculate cosine distance (1 - cosine similarity)
|
|
98
|
+
*
|
|
99
|
+
* @param a - First vector
|
|
100
|
+
* @param b - Second vector
|
|
101
|
+
* @returns Cosine distance (0 = identical, 2 = opposite)
|
|
102
|
+
*/
|
|
103
|
+
cosineDistance(a, b) {
|
|
104
|
+
let dotProduct = 0;
|
|
105
|
+
let normA = 0;
|
|
106
|
+
let normB = 0;
|
|
107
|
+
for (let i = 0; i < a.length; i++) {
|
|
108
|
+
dotProduct += a[i] * b[i];
|
|
109
|
+
normA += a[i] * a[i];
|
|
110
|
+
normB += b[i] * b[i];
|
|
111
|
+
}
|
|
112
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
113
|
+
if (denominator === 0) {
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
const similarity = dotProduct / denominator;
|
|
117
|
+
return 1 - similarity;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Calculate Euclidean (L2) distance
|
|
121
|
+
*
|
|
122
|
+
* @param a - First vector
|
|
123
|
+
* @param b - Second vector
|
|
124
|
+
* @returns Euclidean distance
|
|
125
|
+
*/
|
|
126
|
+
euclideanDistance(a, b) {
|
|
127
|
+
let sum = 0;
|
|
128
|
+
for (let i = 0; i < a.length; i++) {
|
|
129
|
+
const diff = a[i] - b[i];
|
|
130
|
+
sum += diff * diff;
|
|
131
|
+
}
|
|
132
|
+
return Math.sqrt(sum);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Calculate negative dot product distance
|
|
136
|
+
*
|
|
137
|
+
* Higher dot product means more similar, so we negate for distance.
|
|
138
|
+
*
|
|
139
|
+
* @param a - First vector
|
|
140
|
+
* @param b - Second vector
|
|
141
|
+
* @returns Negative dot product
|
|
142
|
+
*/
|
|
143
|
+
dotProductDistance(a, b) {
|
|
144
|
+
let dotProduct = 0;
|
|
145
|
+
for (let i = 0; i < a.length; i++) {
|
|
146
|
+
dotProduct += a[i] * b[i];
|
|
147
|
+
}
|
|
148
|
+
return -dotProduct;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Calculate Manhattan (L1) distance
|
|
152
|
+
*
|
|
153
|
+
* @param a - First vector
|
|
154
|
+
* @param b - Second vector
|
|
155
|
+
* @returns Manhattan distance
|
|
156
|
+
*/
|
|
157
|
+
manhattanDistance(a, b) {
|
|
158
|
+
let sum = 0;
|
|
159
|
+
for (let i = 0; i < a.length; i++) {
|
|
160
|
+
sum += Math.abs(a[i] - b[i]);
|
|
161
|
+
}
|
|
162
|
+
return sum;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Generate random level for new node (HNSW algorithm)
|
|
166
|
+
*
|
|
167
|
+
* Uses exponential distribution to generate levels.
|
|
168
|
+
* Most nodes will be at level 0, with exponentially fewer at higher levels.
|
|
169
|
+
*
|
|
170
|
+
* @returns Generated level (0 or higher)
|
|
171
|
+
*/
|
|
172
|
+
generateLevel() {
|
|
173
|
+
const random = Math.random();
|
|
174
|
+
return Math.floor(-Math.log(random) * this.levelMultiplier);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Insert a vector into the index
|
|
178
|
+
*
|
|
179
|
+
* Implements the HNSW insertion algorithm:
|
|
180
|
+
* 1. Generate random level for the new node
|
|
181
|
+
* 2. If first node, make it the entry point
|
|
182
|
+
* 3. Otherwise, traverse from entry point to find insertion position
|
|
183
|
+
* 4. Connect to nearest neighbors at each level
|
|
184
|
+
*
|
|
185
|
+
* @param entry - Vector entry to insert
|
|
186
|
+
* @throws Error if not initialized or dimensions mismatch
|
|
187
|
+
*/
|
|
188
|
+
async insert(entry) {
|
|
189
|
+
if (!this.isInitialized) {
|
|
190
|
+
await this.initialize();
|
|
191
|
+
}
|
|
192
|
+
if (entry.vector.length !== this.config.index.dimensions) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Vector dimension mismatch: expected ${this.config.index.dimensions}, got ${entry.vector.length}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const level = this.generateLevel();
|
|
198
|
+
const node = {
|
|
199
|
+
id: entry.id,
|
|
200
|
+
vector: entry.vector,
|
|
201
|
+
neighbors: /* @__PURE__ */ new Map(),
|
|
202
|
+
metadata: entry.metadata || {},
|
|
203
|
+
level,
|
|
204
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
205
|
+
};
|
|
206
|
+
for (let l = 0; l <= level; l++) {
|
|
207
|
+
node.neighbors.set(l, []);
|
|
208
|
+
}
|
|
209
|
+
if (!this.entryPoint) {
|
|
210
|
+
this.entryPoint = entry.id;
|
|
211
|
+
this.maxLevel = level;
|
|
212
|
+
this.nodes.set(entry.id, node);
|
|
213
|
+
this.emitEvent({ type: "insert", id: entry.id, timestamp: /* @__PURE__ */ new Date() });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let currentId = this.entryPoint;
|
|
217
|
+
const m = this.config.index.hnswConfig?.m || 16;
|
|
218
|
+
const efConstruction = this.config.index.hnswConfig?.efConstruction || 200;
|
|
219
|
+
for (let l = this.maxLevel; l > level; l--) {
|
|
220
|
+
currentId = this.greedySearch(entry.vector, currentId, l);
|
|
221
|
+
}
|
|
222
|
+
for (let l = Math.min(level, this.maxLevel); l >= 0; l--) {
|
|
223
|
+
const neighbors = this.searchLayer(entry.vector, currentId, efConstruction, l);
|
|
224
|
+
const selected = neighbors.slice(0, m);
|
|
225
|
+
node.neighbors.set(l, selected.map((n) => n.id));
|
|
226
|
+
for (const neighbor of selected) {
|
|
227
|
+
const neighborNode = this.nodes.get(neighbor.id);
|
|
228
|
+
if (neighborNode) {
|
|
229
|
+
const neighborList = neighborNode.neighbors.get(l) || [];
|
|
230
|
+
neighborList.push(entry.id);
|
|
231
|
+
const maxConnections = l === 0 ? m * 2 : m;
|
|
232
|
+
if (neighborList.length > maxConnections) {
|
|
233
|
+
const pruned = this.pruneNeighbors(neighborNode.vector, neighborList, m);
|
|
234
|
+
neighborNode.neighbors.set(l, pruned);
|
|
235
|
+
} else {
|
|
236
|
+
neighborNode.neighbors.set(l, neighborList);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (neighbors.length > 0) {
|
|
241
|
+
currentId = neighbors[0].id;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (level > this.maxLevel) {
|
|
245
|
+
this.entryPoint = entry.id;
|
|
246
|
+
this.maxLevel = level;
|
|
247
|
+
}
|
|
248
|
+
this.nodes.set(entry.id, node);
|
|
249
|
+
this.emitEvent({ type: "insert", id: entry.id, timestamp: /* @__PURE__ */ new Date() });
|
|
250
|
+
logger.debug("Inserted vector", { id: entry.id, level });
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Greedy search to find nearest neighbor at a level
|
|
254
|
+
*
|
|
255
|
+
* Traverses the graph at the specified level, always moving
|
|
256
|
+
* toward the nearest neighbor until no improvement is found.
|
|
257
|
+
*
|
|
258
|
+
* @param query - Query vector
|
|
259
|
+
* @param startId - Starting node ID
|
|
260
|
+
* @param level - Level to search at
|
|
261
|
+
* @returns ID of nearest node found
|
|
262
|
+
*/
|
|
263
|
+
greedySearch(query, startId, level) {
|
|
264
|
+
let currentId = startId;
|
|
265
|
+
const currentNode = this.nodes.get(currentId);
|
|
266
|
+
if (!currentNode) {
|
|
267
|
+
return startId;
|
|
268
|
+
}
|
|
269
|
+
let currentDist = this.calculateDistance(query, currentNode.vector);
|
|
270
|
+
let improved = true;
|
|
271
|
+
while (improved) {
|
|
272
|
+
improved = false;
|
|
273
|
+
const node = this.nodes.get(currentId);
|
|
274
|
+
if (!node) break;
|
|
275
|
+
const neighbors = node.neighbors.get(level) || [];
|
|
276
|
+
for (const neighborId of neighbors) {
|
|
277
|
+
const neighborNode = this.nodes.get(neighborId);
|
|
278
|
+
if (!neighborNode) continue;
|
|
279
|
+
const dist = this.calculateDistance(query, neighborNode.vector);
|
|
280
|
+
if (dist < currentDist) {
|
|
281
|
+
currentId = neighborId;
|
|
282
|
+
currentDist = dist;
|
|
283
|
+
improved = true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return currentId;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Search a layer for nearest neighbors
|
|
291
|
+
*
|
|
292
|
+
* Implements beam search at a specific level using a priority queue.
|
|
293
|
+
*
|
|
294
|
+
* @param query - Query vector
|
|
295
|
+
* @param startId - Starting node ID
|
|
296
|
+
* @param ef - Size of dynamic candidate list
|
|
297
|
+
* @param level - Level to search at
|
|
298
|
+
* @returns Array of nearest neighbors with distances
|
|
299
|
+
*/
|
|
300
|
+
searchLayer(query, startId, ef, level) {
|
|
301
|
+
const visited = /* @__PURE__ */ new Set();
|
|
302
|
+
const candidates = [];
|
|
303
|
+
const results = [];
|
|
304
|
+
const startNode = this.nodes.get(startId);
|
|
305
|
+
if (!startNode) return [];
|
|
306
|
+
const startDist = this.calculateDistance(query, startNode.vector);
|
|
307
|
+
candidates.push({ id: startId, distance: startDist });
|
|
308
|
+
results.push({ id: startId, distance: startDist });
|
|
309
|
+
visited.add(startId);
|
|
310
|
+
while (candidates.length > 0) {
|
|
311
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
312
|
+
const current = candidates.shift();
|
|
313
|
+
results.sort((a, b) => a.distance - b.distance);
|
|
314
|
+
const furthestResult = results[results.length - 1];
|
|
315
|
+
if (current.distance > furthestResult.distance && results.length >= ef) {
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
const node = this.nodes.get(current.id);
|
|
319
|
+
if (!node) continue;
|
|
320
|
+
const neighbors = node.neighbors.get(level) || [];
|
|
321
|
+
for (const neighborId of neighbors) {
|
|
322
|
+
if (visited.has(neighborId)) continue;
|
|
323
|
+
visited.add(neighborId);
|
|
324
|
+
const neighborNode = this.nodes.get(neighborId);
|
|
325
|
+
if (!neighborNode) continue;
|
|
326
|
+
const dist = this.calculateDistance(query, neighborNode.vector);
|
|
327
|
+
if (results.length < ef || dist < furthestResult.distance) {
|
|
328
|
+
candidates.push({ id: neighborId, distance: dist });
|
|
329
|
+
results.push({ id: neighborId, distance: dist });
|
|
330
|
+
results.sort((a, b) => a.distance - b.distance);
|
|
331
|
+
if (results.length > ef) {
|
|
332
|
+
results.pop();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return results;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Prune neighbors to keep only the best M
|
|
341
|
+
*
|
|
342
|
+
* Uses a simple distance-based pruning strategy.
|
|
343
|
+
*
|
|
344
|
+
* @param nodeVector - Vector of the node being pruned
|
|
345
|
+
* @param neighborIds - Current neighbor IDs
|
|
346
|
+
* @param m - Maximum neighbors to keep
|
|
347
|
+
* @returns Pruned list of neighbor IDs
|
|
348
|
+
*/
|
|
349
|
+
pruneNeighbors(nodeVector, neighborIds, m) {
|
|
350
|
+
const distances = neighborIds.map((id) => {
|
|
351
|
+
const node = this.nodes.get(id);
|
|
352
|
+
if (!node) return { id, distance: Infinity };
|
|
353
|
+
return { id, distance: this.calculateDistance(nodeVector, node.vector) };
|
|
354
|
+
});
|
|
355
|
+
distances.sort((a, b) => a.distance - b.distance);
|
|
356
|
+
return distances.slice(0, m).map((d) => d.id);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Search for similar vectors
|
|
360
|
+
*
|
|
361
|
+
* Implements HNSW search algorithm:
|
|
362
|
+
* 1. Start from entry point at top level
|
|
363
|
+
* 2. Greedy search down to level 1
|
|
364
|
+
* 3. Beam search at level 0 with efSearch candidates
|
|
365
|
+
* 4. Return top-k results
|
|
366
|
+
*
|
|
367
|
+
* @param query - Search query with vector and options
|
|
368
|
+
* @returns Array of search results sorted by similarity
|
|
369
|
+
*/
|
|
370
|
+
async search(query) {
|
|
371
|
+
const startTime = Date.now();
|
|
372
|
+
if (!this.isInitialized) {
|
|
373
|
+
await this.initialize();
|
|
374
|
+
}
|
|
375
|
+
if (!this.entryPoint) {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const k = query.k || 10;
|
|
379
|
+
const efSearch = this.config.index.hnswConfig?.efSearch || 100;
|
|
380
|
+
let currentId = this.entryPoint;
|
|
381
|
+
for (let l = this.maxLevel; l > 0; l--) {
|
|
382
|
+
currentId = this.greedySearch(query.vector, currentId, l);
|
|
383
|
+
}
|
|
384
|
+
const candidates = this.searchLayer(query.vector, currentId, Math.max(efSearch, k), 0);
|
|
385
|
+
const results = candidates.slice(0, k * 2).map((c) => {
|
|
386
|
+
const node = this.nodes.get(c.id);
|
|
387
|
+
const score = this.distanceToScore(c.distance);
|
|
388
|
+
return {
|
|
389
|
+
id: c.id,
|
|
390
|
+
score,
|
|
391
|
+
metadata: node.metadata,
|
|
392
|
+
distance: c.distance
|
|
393
|
+
};
|
|
394
|
+
}).filter((r) => {
|
|
395
|
+
if (query.minScore !== void 0 && r.score < query.minScore) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
if (query.filter) {
|
|
399
|
+
for (const [key, value] of Object.entries(query.filter)) {
|
|
400
|
+
if (r.metadata[key] !== value) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}).slice(0, k);
|
|
407
|
+
const durationMs = Date.now() - startTime;
|
|
408
|
+
this.emitEvent({
|
|
409
|
+
type: "search",
|
|
410
|
+
queryId: crypto.randomUUID?.() || `search-${Date.now()}`,
|
|
411
|
+
resultCount: results.length,
|
|
412
|
+
durationMs,
|
|
413
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
414
|
+
});
|
|
415
|
+
return results;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Convert distance to similarity score
|
|
419
|
+
*
|
|
420
|
+
* @param distance - Distance value
|
|
421
|
+
* @returns Similarity score between 0 and 1
|
|
422
|
+
*/
|
|
423
|
+
distanceToScore(distance) {
|
|
424
|
+
switch (this.config.index.distanceMetric) {
|
|
425
|
+
case "cosine":
|
|
426
|
+
return Math.max(0, Math.min(1, 1 - distance));
|
|
427
|
+
case "euclidean":
|
|
428
|
+
return Math.exp(-distance);
|
|
429
|
+
case "dotProduct":
|
|
430
|
+
return 1 / (1 + Math.exp(distance));
|
|
431
|
+
case "manhattan":
|
|
432
|
+
return Math.exp(-distance / 10);
|
|
433
|
+
default:
|
|
434
|
+
return Math.max(0, Math.min(1, 1 - distance));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Hybrid search combining vectors and graph queries
|
|
439
|
+
*
|
|
440
|
+
* Performs vector similarity search and optionally enriches
|
|
441
|
+
* results with data from graph queries (Cypher).
|
|
442
|
+
*
|
|
443
|
+
* @param query - Hybrid search query
|
|
444
|
+
* @returns Array of hybrid search results
|
|
445
|
+
*/
|
|
446
|
+
async hybridSearch(query) {
|
|
447
|
+
if (!this.isInitialized) {
|
|
448
|
+
await this.initialize();
|
|
449
|
+
}
|
|
450
|
+
const limit = query.limit || 10;
|
|
451
|
+
const vectorResults = await this.search({
|
|
452
|
+
vector: query.embedding,
|
|
453
|
+
k: limit * 2,
|
|
454
|
+
// Get more candidates for merging
|
|
455
|
+
filter: query.filters,
|
|
456
|
+
minScore: query.minScore
|
|
457
|
+
});
|
|
458
|
+
if (!query.cypher) {
|
|
459
|
+
return vectorResults.map((r) => ({
|
|
460
|
+
...r,
|
|
461
|
+
source: "vector"
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
logger.debug("Hybrid search with Cypher query", {
|
|
465
|
+
cypher: query.cypher,
|
|
466
|
+
vectorResultCount: vectorResults.length
|
|
467
|
+
});
|
|
468
|
+
const hybridResults = vectorResults.slice(0, limit).map((r) => ({
|
|
469
|
+
...r,
|
|
470
|
+
source: "merged",
|
|
471
|
+
graphData: {
|
|
472
|
+
cypherQuery: query.cypher,
|
|
473
|
+
cypherParams: query.cypherParams,
|
|
474
|
+
note: "Graph query execution pending integration"
|
|
475
|
+
}
|
|
476
|
+
}));
|
|
477
|
+
return hybridResults;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Batch insert vectors
|
|
481
|
+
*
|
|
482
|
+
* Efficiently inserts multiple vectors in a single operation.
|
|
483
|
+
* Supports progress callbacks and duplicate handling.
|
|
484
|
+
*
|
|
485
|
+
* @param operation - Batch insert operation configuration
|
|
486
|
+
* @returns Result with counts of inserted, skipped, and errors
|
|
487
|
+
*/
|
|
488
|
+
async batchInsert(operation) {
|
|
489
|
+
const startTime = Date.now();
|
|
490
|
+
const result = {
|
|
491
|
+
inserted: 0,
|
|
492
|
+
skipped: 0,
|
|
493
|
+
errors: []
|
|
494
|
+
};
|
|
495
|
+
const total = operation.entries.length;
|
|
496
|
+
for (let i = 0; i < operation.entries.length; i++) {
|
|
497
|
+
const entry = operation.entries[i];
|
|
498
|
+
try {
|
|
499
|
+
if (this.nodes.has(entry.id)) {
|
|
500
|
+
if (operation.skipDuplicates) {
|
|
501
|
+
result.skipped++;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
throw new Error(`Duplicate ID: ${entry.id}`);
|
|
505
|
+
}
|
|
506
|
+
await this.insert(entry);
|
|
507
|
+
result.inserted++;
|
|
508
|
+
if (operation.onProgress) {
|
|
509
|
+
operation.onProgress(result.inserted + result.skipped, total);
|
|
510
|
+
}
|
|
511
|
+
} catch (error) {
|
|
512
|
+
result.errors.push({
|
|
513
|
+
id: entry.id,
|
|
514
|
+
error: error instanceof Error ? error.message : String(error)
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
result.durationMs = Date.now() - startTime;
|
|
519
|
+
logger.info("Batch insert completed", {
|
|
520
|
+
inserted: result.inserted,
|
|
521
|
+
skipped: result.skipped,
|
|
522
|
+
errors: result.errors.length,
|
|
523
|
+
durationMs: result.durationMs
|
|
524
|
+
});
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Get a vector by ID
|
|
529
|
+
*
|
|
530
|
+
* @param id - Vector ID to retrieve
|
|
531
|
+
* @returns Vector entry or null if not found
|
|
532
|
+
*/
|
|
533
|
+
async get(id) {
|
|
534
|
+
const node = this.nodes.get(id);
|
|
535
|
+
if (!node) return null;
|
|
536
|
+
return {
|
|
537
|
+
id: node.id,
|
|
538
|
+
vector: node.vector,
|
|
539
|
+
metadata: node.metadata,
|
|
540
|
+
createdAt: node.createdAt
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Delete a vector by ID
|
|
545
|
+
*
|
|
546
|
+
* Removes the vector from the index and updates neighbor connections.
|
|
547
|
+
*
|
|
548
|
+
* @param id - Vector ID to delete
|
|
549
|
+
* @returns True if deleted, false if not found
|
|
550
|
+
*/
|
|
551
|
+
async delete(id) {
|
|
552
|
+
const node = this.nodes.get(id);
|
|
553
|
+
if (!node) return false;
|
|
554
|
+
for (const [level, neighbors] of node.neighbors) {
|
|
555
|
+
for (const neighborId of neighbors) {
|
|
556
|
+
const neighborNode = this.nodes.get(neighborId);
|
|
557
|
+
if (neighborNode) {
|
|
558
|
+
const neighborList = neighborNode.neighbors.get(level) || [];
|
|
559
|
+
const idx = neighborList.indexOf(id);
|
|
560
|
+
if (idx !== -1) {
|
|
561
|
+
neighborList.splice(idx, 1);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (this.entryPoint === id) {
|
|
567
|
+
const nodeIterator = this.nodes.keys();
|
|
568
|
+
let nextEntry = nodeIterator.next();
|
|
569
|
+
while (!nextEntry.done && nextEntry.value === id) {
|
|
570
|
+
nextEntry = nodeIterator.next();
|
|
571
|
+
}
|
|
572
|
+
this.entryPoint = nextEntry.done ? null : nextEntry.value;
|
|
573
|
+
if (this.entryPoint) {
|
|
574
|
+
const newEntry = this.nodes.get(this.entryPoint);
|
|
575
|
+
this.maxLevel = newEntry?.level || 0;
|
|
576
|
+
} else {
|
|
577
|
+
this.maxLevel = 0;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
this.nodes.delete(id);
|
|
581
|
+
this.emitEvent({ type: "delete", id, timestamp: /* @__PURE__ */ new Date() });
|
|
582
|
+
logger.debug("Deleted vector", { id });
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Get index statistics
|
|
587
|
+
*
|
|
588
|
+
* @returns Current statistics about the vector index
|
|
589
|
+
*/
|
|
590
|
+
getStats() {
|
|
591
|
+
return {
|
|
592
|
+
totalVectors: this.nodes.size,
|
|
593
|
+
dimensions: this.config.index.dimensions,
|
|
594
|
+
indexType: this.config.index.indexType,
|
|
595
|
+
memoryUsage: this.estimateMemoryUsage(),
|
|
596
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
597
|
+
indexStats: {
|
|
598
|
+
levels: this.maxLevel + 1,
|
|
599
|
+
entryPoint: this.entryPoint || void 0,
|
|
600
|
+
avgConnections: this.calculateAverageConnections()
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Estimate memory usage in bytes
|
|
606
|
+
*
|
|
607
|
+
* Provides rough estimate of memory consumption.
|
|
608
|
+
*
|
|
609
|
+
* @returns Estimated memory usage in bytes
|
|
610
|
+
*/
|
|
611
|
+
estimateMemoryUsage() {
|
|
612
|
+
const vectorSize = this.config.index.dimensions * 4;
|
|
613
|
+
const metadataSize = 100;
|
|
614
|
+
const neighborsSize = 50 * 8;
|
|
615
|
+
const nodeOverhead = 64;
|
|
616
|
+
return this.nodes.size * (vectorSize + metadataSize + neighborsSize + nodeOverhead);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Calculate average number of connections per node
|
|
620
|
+
*
|
|
621
|
+
* @returns Average connection count
|
|
622
|
+
*/
|
|
623
|
+
calculateAverageConnections() {
|
|
624
|
+
if (this.nodes.size === 0) return 0;
|
|
625
|
+
let totalConnections = 0;
|
|
626
|
+
for (const node of this.nodes.values()) {
|
|
627
|
+
for (const neighbors of node.neighbors.values()) {
|
|
628
|
+
totalConnections += neighbors.length;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return totalConnections / this.nodes.size;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Clear all vectors
|
|
635
|
+
*
|
|
636
|
+
* Removes all vectors from the index and resets state.
|
|
637
|
+
*/
|
|
638
|
+
async clear() {
|
|
639
|
+
this.nodes.clear();
|
|
640
|
+
this.entryPoint = null;
|
|
641
|
+
this.maxLevel = 0;
|
|
642
|
+
logger.info("Vector store cleared");
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Get configuration
|
|
646
|
+
*
|
|
647
|
+
* @returns Copy of current configuration
|
|
648
|
+
*/
|
|
649
|
+
getConfig() {
|
|
650
|
+
return { ...this.config };
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Check if store is initialized
|
|
654
|
+
*
|
|
655
|
+
* @returns True if initialized
|
|
656
|
+
*/
|
|
657
|
+
isReady() {
|
|
658
|
+
return this.isInitialized;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get total vector count
|
|
662
|
+
*
|
|
663
|
+
* @returns Number of vectors in the store
|
|
664
|
+
*/
|
|
665
|
+
size() {
|
|
666
|
+
return this.nodes.size;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Check if a vector exists
|
|
670
|
+
*
|
|
671
|
+
* @param id - Vector ID to check
|
|
672
|
+
* @returns True if exists
|
|
673
|
+
*/
|
|
674
|
+
has(id) {
|
|
675
|
+
return this.nodes.has(id);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Get all vector IDs
|
|
679
|
+
*
|
|
680
|
+
* @returns Array of all vector IDs
|
|
681
|
+
*/
|
|
682
|
+
getAllIds() {
|
|
683
|
+
return Array.from(this.nodes.keys());
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Add event listener
|
|
687
|
+
*
|
|
688
|
+
* @param event - Event type to listen for
|
|
689
|
+
* @param listener - Callback function
|
|
690
|
+
*/
|
|
691
|
+
on(event, listener) {
|
|
692
|
+
if (!this.eventListeners.has(event)) {
|
|
693
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
694
|
+
}
|
|
695
|
+
this.eventListeners.get(event).add(listener);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Remove event listener
|
|
699
|
+
*
|
|
700
|
+
* @param event - Event type
|
|
701
|
+
* @param listener - Callback function to remove
|
|
702
|
+
*/
|
|
703
|
+
off(event, listener) {
|
|
704
|
+
const listeners = this.eventListeners.get(event);
|
|
705
|
+
if (listeners) {
|
|
706
|
+
listeners.delete(listener);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Emit an event to all registered listeners
|
|
711
|
+
*
|
|
712
|
+
* @param event - Event to emit
|
|
713
|
+
*/
|
|
714
|
+
emitEvent(event) {
|
|
715
|
+
const listeners = this.eventListeners.get(event.type);
|
|
716
|
+
if (listeners) {
|
|
717
|
+
for (const listener of listeners) {
|
|
718
|
+
try {
|
|
719
|
+
listener(event);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
logger.error("Event listener error", error instanceof Error ? error : void 0, {
|
|
722
|
+
eventType: event.type
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const wildcardListeners = this.eventListeners.get("*");
|
|
728
|
+
if (wildcardListeners) {
|
|
729
|
+
for (const listener of wildcardListeners) {
|
|
730
|
+
try {
|
|
731
|
+
listener(event);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
logger.error("Wildcard listener error", error instanceof Error ? error : void 0, {
|
|
734
|
+
eventType: event.type
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function createVectorStore(config) {
|
|
742
|
+
return new EnhancedVectorStore(config);
|
|
743
|
+
}
|
|
744
|
+
export {
|
|
745
|
+
EnhancedVectorStore,
|
|
746
|
+
createVectorStore
|
|
747
|
+
};
|
|
748
|
+
//# sourceMappingURL=vector-store.js.map
|