@yugenlab/vaayu 0.1.8 → 0.1.10
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 +1 -1
- package/chunks/{chunk-7UOXFHEB.js → chunk-77725AR7.js} +416 -397
- package/chunks/{chunk-YSC77CKZ.js → chunk-AGK3A7R7.js} +2844 -3208
- package/chunks/{chunk-DOQMEQ5S.js → chunk-AS3DJFY3.js} +5 -5
- package/chunks/{chunk-NHRBVSN3.js → chunk-HIYHTWFW.js} +44 -9
- package/chunks/{chunk-IGBRBFXX.js → chunk-JGI4SDWS.js} +2 -2
- package/chunks/{chunk-D3RVJGO7.js → chunk-M7THR63C.js} +48 -74
- package/chunks/{chunk-OBYBBGHA.js → chunk-N22M7D4P.js} +118 -106
- package/chunks/{chunk-PJEYJQ2C.js → chunk-O4KV7TFP.js} +2 -2
- package/chunks/{chunk-S2HDNNC7.js → chunk-OT4G2L46.js} +552 -641
- package/chunks/chunk-TND3MU4Z.js +426 -0
- package/chunks/{chunk-LVE2EOOH.js → chunk-VJHNE47S.js} +84 -75
- package/chunks/{consolidation-indexer-CD6DS2HO.js → consolidation-indexer-VKQ6DNU3.js} +4 -4
- package/chunks/{day-consolidation-U3X6P4ZG.js → day-consolidation-BH3QU2SZ.js} +6 -2
- package/chunks/{graphrag-LAZSXLLI.js → graphrag-D7OXWAWD.js} +2 -2
- package/chunks/{hierarchical-temporal-search-ETXYYJZK.js → hierarchical-temporal-search-PVHVA3NZ.js} +2 -2
- package/chunks/{hybrid-search-TX6T3KYH.js → hybrid-search-G2NAJKJ7.js} +4 -4
- package/chunks/{periodic-consolidation-4MACZE6S.js → periodic-consolidation-LMYMNS4Q.js} +2 -2
- package/chunks/{recall-IUPQCBYP.js → recall-ZNL4DJ2L.js} +3 -3
- package/chunks/{search-HHSVHBXC.js → search-35JMSGUT.js} +3 -3
- package/chunks/{session-store-NDUDYAC7.js → session-store-3BRPGC6P.js} +2 -2
- package/chunks/{src-ZAKUL232.js → src-Y3TGMINC.js} +12 -12
- package/chunks/vasana-engine-MU25OQ23.js +30 -0
- package/gateway.js +492 -226
- package/package.json +1 -1
- package/vaayu-mark-npm.png +0 -0
- package/chunks/chunk-TEQKXGIK.js +0 -752
- package/chunks/vasana-engine-G6BPOFX7.js +0 -10
- package/vaayu-mark.png +0 -0
|
@@ -17,10 +17,6 @@ import {
|
|
|
17
17
|
getChitraguptaHome
|
|
18
18
|
} from "./chunk-UZ6OIVEC.js";
|
|
19
19
|
|
|
20
|
-
// ../chitragupta/packages/smriti/src/graphrag.ts
|
|
21
|
-
import fs from "fs";
|
|
22
|
-
import path from "path";
|
|
23
|
-
|
|
24
20
|
// ../chitragupta/packages/smriti/src/graphrag-pagerank.ts
|
|
25
21
|
var PAGERANK_DAMPING = 0.85;
|
|
26
22
|
var PAGERANK_EPSILON = 1e-4;
|
|
@@ -77,152 +73,12 @@ function computePageRank(graph) {
|
|
|
77
73
|
return ranks;
|
|
78
74
|
}
|
|
79
75
|
|
|
80
|
-
// ../chitragupta/packages/smriti/src/graphrag-pagerank-
|
|
81
|
-
function buildTopicBias(nodeIds, nodeContents, topic) {
|
|
82
|
-
const bias = /* @__PURE__ */ new Map();
|
|
83
|
-
const n = nodeIds.length;
|
|
84
|
-
if (!topic || n === 0) {
|
|
85
|
-
const uniform = 1 / Math.max(n, 1);
|
|
86
|
-
for (const id of nodeIds) bias.set(id, uniform);
|
|
87
|
-
return bias;
|
|
88
|
-
}
|
|
89
|
-
const queryTerms = tokenizeSimple(topic);
|
|
90
|
-
const queryTf = termFrequency(queryTerms);
|
|
91
|
-
let totalSim = 0;
|
|
92
|
-
const similarities = new Array(n);
|
|
93
|
-
for (let i = 0; i < n; i++) {
|
|
94
|
-
const content = nodeContents.get(nodeIds[i]) ?? "";
|
|
95
|
-
const docTerms = tokenizeSimple(content);
|
|
96
|
-
const docTf = termFrequency(docTerms);
|
|
97
|
-
const sim = tfCosineSimilarity(queryTf, docTf);
|
|
98
|
-
similarities[i] = sim;
|
|
99
|
-
totalSim += sim;
|
|
100
|
-
}
|
|
101
|
-
if (totalSim === 0) {
|
|
102
|
-
const uniform = 1 / n;
|
|
103
|
-
for (const id of nodeIds) bias.set(id, uniform);
|
|
104
|
-
return bias;
|
|
105
|
-
}
|
|
106
|
-
for (let i = 0; i < n; i++) {
|
|
107
|
-
bias.set(nodeIds[i], similarities[i] / totalSim);
|
|
108
|
-
}
|
|
109
|
-
return bias;
|
|
110
|
-
}
|
|
111
|
-
function tokenizeSimple(text) {
|
|
112
|
-
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 2);
|
|
113
|
-
}
|
|
114
|
-
function termFrequency(tokens) {
|
|
115
|
-
const tf = /* @__PURE__ */ new Map();
|
|
116
|
-
for (const t of tokens) {
|
|
117
|
-
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
118
|
-
}
|
|
119
|
-
return tf;
|
|
120
|
-
}
|
|
121
|
-
function tfCosineSimilarity(a, b) {
|
|
122
|
-
let dot = 0;
|
|
123
|
-
let normA = 0;
|
|
124
|
-
let normB = 0;
|
|
125
|
-
for (const [term, freqA] of a) {
|
|
126
|
-
normA += freqA * freqA;
|
|
127
|
-
const freqB = b.get(term);
|
|
128
|
-
if (freqB !== void 0) dot += freqA * freqB;
|
|
129
|
-
}
|
|
130
|
-
for (const freqB of b.values()) {
|
|
131
|
-
normB += freqB * freqB;
|
|
132
|
-
}
|
|
133
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
134
|
-
return denom === 0 ? 0 : dot / denom;
|
|
135
|
-
}
|
|
136
|
-
function computePersonalizedPageRank(graph, topicBias, opts) {
|
|
137
|
-
const damping = opts?.damping ?? 0.85;
|
|
138
|
-
const epsilon = opts?.epsilon ?? 1e-6;
|
|
139
|
-
const maxIter = opts?.maxIterations ?? 150;
|
|
140
|
-
const useGS = opts?.useGaussSeidel ?? true;
|
|
141
|
-
const N = graph.nodes.length;
|
|
142
|
-
if (N === 0) return /* @__PURE__ */ new Map();
|
|
143
|
-
const nodeIds = graph.nodes.map((n) => n.id);
|
|
144
|
-
const nodeIdSet = new Set(nodeIds);
|
|
145
|
-
const outDegree = /* @__PURE__ */ new Map();
|
|
146
|
-
const inLinks = /* @__PURE__ */ new Map();
|
|
147
|
-
for (const id of nodeIds) {
|
|
148
|
-
outDegree.set(id, 0);
|
|
149
|
-
inLinks.set(id, []);
|
|
150
|
-
}
|
|
151
|
-
for (const edge of graph.edges) {
|
|
152
|
-
if (nodeIdSet.has(edge.source) && nodeIdSet.has(edge.target)) {
|
|
153
|
-
outDegree.set(edge.source, (outDegree.get(edge.source) ?? 0) + 1);
|
|
154
|
-
inLinks.get(edge.target).push(edge.source);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
let bias;
|
|
158
|
-
if (typeof topicBias === "string") {
|
|
159
|
-
const nodeContents = /* @__PURE__ */ new Map();
|
|
160
|
-
for (const node of graph.nodes) {
|
|
161
|
-
nodeContents.set(node.id, node.content);
|
|
162
|
-
}
|
|
163
|
-
bias = buildTopicBias(nodeIds, nodeContents, topicBias);
|
|
164
|
-
} else if (topicBias instanceof Map) {
|
|
165
|
-
bias = topicBias;
|
|
166
|
-
} else {
|
|
167
|
-
const uniform = 1 / N;
|
|
168
|
-
bias = new Map(nodeIds.map((id) => [id, uniform]));
|
|
169
|
-
}
|
|
170
|
-
const ranks = /* @__PURE__ */ new Map();
|
|
171
|
-
for (const id of nodeIds) {
|
|
172
|
-
ranks.set(id, 1 / N);
|
|
173
|
-
}
|
|
174
|
-
const danglingNodes = [];
|
|
175
|
-
for (const id of nodeIds) {
|
|
176
|
-
if ((outDegree.get(id) ?? 0) === 0) danglingNodes.push(id);
|
|
177
|
-
}
|
|
178
|
-
for (let iter = 0; iter < maxIter; iter++) {
|
|
179
|
-
let danglingSum = 0;
|
|
180
|
-
for (const id of danglingNodes) {
|
|
181
|
-
danglingSum += ranks.get(id) ?? 0;
|
|
182
|
-
}
|
|
183
|
-
const danglingContrib = damping * danglingSum / N;
|
|
184
|
-
let maxDelta = 0;
|
|
185
|
-
if (useGS) {
|
|
186
|
-
for (const id of nodeIds) {
|
|
187
|
-
const biasVal = bias.get(id) ?? 1 / N;
|
|
188
|
-
let incomingSum = 0;
|
|
189
|
-
for (const src of inLinks.get(id) ?? []) {
|
|
190
|
-
const srcRank = ranks.get(src) ?? 0;
|
|
191
|
-
const srcOut = outDegree.get(src) ?? 1;
|
|
192
|
-
incomingSum += srcRank / srcOut;
|
|
193
|
-
}
|
|
194
|
-
const newRank = (1 - damping) * biasVal + damping * incomingSum + danglingContrib;
|
|
195
|
-
const oldRank = ranks.get(id) ?? 0;
|
|
196
|
-
const delta = Math.abs(newRank - oldRank);
|
|
197
|
-
if (delta > maxDelta) maxDelta = delta;
|
|
198
|
-
ranks.set(id, newRank);
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
const newRanks = /* @__PURE__ */ new Map();
|
|
202
|
-
for (const id of nodeIds) {
|
|
203
|
-
const biasVal = bias.get(id) ?? 1 / N;
|
|
204
|
-
let incomingSum = 0;
|
|
205
|
-
for (const src of inLinks.get(id) ?? []) {
|
|
206
|
-
const srcRank = ranks.get(src) ?? 0;
|
|
207
|
-
const srcOut = outDegree.get(src) ?? 1;
|
|
208
|
-
incomingSum += srcRank / srcOut;
|
|
209
|
-
}
|
|
210
|
-
const newRank = (1 - damping) * biasVal + damping * incomingSum + danglingContrib;
|
|
211
|
-
newRanks.set(id, newRank);
|
|
212
|
-
const delta = Math.abs(newRank - (ranks.get(id) ?? 0));
|
|
213
|
-
if (delta > maxDelta) maxDelta = delta;
|
|
214
|
-
}
|
|
215
|
-
for (const [id, rank] of newRanks) ranks.set(id, rank);
|
|
216
|
-
}
|
|
217
|
-
if (maxDelta < epsilon) break;
|
|
218
|
-
}
|
|
219
|
-
return ranks;
|
|
220
|
-
}
|
|
76
|
+
// ../chitragupta/packages/smriti/src/graphrag-pagerank-incremental.ts
|
|
221
77
|
var IncrementalPageRank = class {
|
|
222
78
|
ranks = /* @__PURE__ */ new Map();
|
|
223
79
|
outDegree = /* @__PURE__ */ new Map();
|
|
224
80
|
inLinks = /* @__PURE__ */ new Map();
|
|
225
|
-
/** Forward adjacency list: source
|
|
81
|
+
/** Forward adjacency list: source -> Set<target>. Avoids O(E) reverse lookup in getOutNeighbors(). */
|
|
226
82
|
outLinks = /* @__PURE__ */ new Map();
|
|
227
83
|
nodeSet = /* @__PURE__ */ new Set();
|
|
228
84
|
damping;
|
|
@@ -338,7 +194,7 @@ var IncrementalPageRank = class {
|
|
|
338
194
|
getRanks() {
|
|
339
195
|
return new Map(this.ranks);
|
|
340
196
|
}
|
|
341
|
-
//
|
|
197
|
+
// --- Private ---------------------------------------------------------
|
|
342
198
|
/**
|
|
343
199
|
* Propagate residuals through the graph until all are below epsilon.
|
|
344
200
|
*
|
|
@@ -384,6 +240,148 @@ var IncrementalPageRank = class {
|
|
|
384
240
|
}
|
|
385
241
|
};
|
|
386
242
|
|
|
243
|
+
// ../chitragupta/packages/smriti/src/graphrag-pagerank-personalized.ts
|
|
244
|
+
function buildTopicBias(nodeIds, nodeContents, topic) {
|
|
245
|
+
const bias = /* @__PURE__ */ new Map();
|
|
246
|
+
const n = nodeIds.length;
|
|
247
|
+
if (!topic || n === 0) {
|
|
248
|
+
const uniform = 1 / Math.max(n, 1);
|
|
249
|
+
for (const id of nodeIds) bias.set(id, uniform);
|
|
250
|
+
return bias;
|
|
251
|
+
}
|
|
252
|
+
const queryTerms = tokenizeSimple(topic);
|
|
253
|
+
const queryTf = termFrequency(queryTerms);
|
|
254
|
+
let totalSim = 0;
|
|
255
|
+
const similarities = new Array(n);
|
|
256
|
+
for (let i = 0; i < n; i++) {
|
|
257
|
+
const content = nodeContents.get(nodeIds[i]) ?? "";
|
|
258
|
+
const docTerms = tokenizeSimple(content);
|
|
259
|
+
const docTf = termFrequency(docTerms);
|
|
260
|
+
const sim = tfCosineSimilarity(queryTf, docTf);
|
|
261
|
+
similarities[i] = sim;
|
|
262
|
+
totalSim += sim;
|
|
263
|
+
}
|
|
264
|
+
if (totalSim === 0) {
|
|
265
|
+
const uniform = 1 / n;
|
|
266
|
+
for (const id of nodeIds) bias.set(id, uniform);
|
|
267
|
+
return bias;
|
|
268
|
+
}
|
|
269
|
+
for (let i = 0; i < n; i++) {
|
|
270
|
+
bias.set(nodeIds[i], similarities[i] / totalSim);
|
|
271
|
+
}
|
|
272
|
+
return bias;
|
|
273
|
+
}
|
|
274
|
+
function tokenizeSimple(text) {
|
|
275
|
+
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 2);
|
|
276
|
+
}
|
|
277
|
+
function termFrequency(tokens) {
|
|
278
|
+
const tf = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const t of tokens) {
|
|
280
|
+
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
281
|
+
}
|
|
282
|
+
return tf;
|
|
283
|
+
}
|
|
284
|
+
function tfCosineSimilarity(a, b) {
|
|
285
|
+
let dot = 0;
|
|
286
|
+
let normA = 0;
|
|
287
|
+
let normB = 0;
|
|
288
|
+
for (const [term, freqA] of a) {
|
|
289
|
+
normA += freqA * freqA;
|
|
290
|
+
const freqB = b.get(term);
|
|
291
|
+
if (freqB !== void 0) dot += freqA * freqB;
|
|
292
|
+
}
|
|
293
|
+
for (const freqB of b.values()) {
|
|
294
|
+
normB += freqB * freqB;
|
|
295
|
+
}
|
|
296
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
297
|
+
return denom === 0 ? 0 : dot / denom;
|
|
298
|
+
}
|
|
299
|
+
function computePersonalizedPageRank(graph, topicBias, opts) {
|
|
300
|
+
const damping = opts?.damping ?? 0.85;
|
|
301
|
+
const epsilon = opts?.epsilon ?? 1e-6;
|
|
302
|
+
const maxIter = opts?.maxIterations ?? 150;
|
|
303
|
+
const useGS = opts?.useGaussSeidel ?? true;
|
|
304
|
+
const N = graph.nodes.length;
|
|
305
|
+
if (N === 0) return /* @__PURE__ */ new Map();
|
|
306
|
+
const nodeIds = graph.nodes.map((n) => n.id);
|
|
307
|
+
const nodeIdSet = new Set(nodeIds);
|
|
308
|
+
const outDegree = /* @__PURE__ */ new Map();
|
|
309
|
+
const inLinks = /* @__PURE__ */ new Map();
|
|
310
|
+
for (const id of nodeIds) {
|
|
311
|
+
outDegree.set(id, 0);
|
|
312
|
+
inLinks.set(id, []);
|
|
313
|
+
}
|
|
314
|
+
for (const edge of graph.edges) {
|
|
315
|
+
if (nodeIdSet.has(edge.source) && nodeIdSet.has(edge.target)) {
|
|
316
|
+
outDegree.set(edge.source, (outDegree.get(edge.source) ?? 0) + 1);
|
|
317
|
+
inLinks.get(edge.target).push(edge.source);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
let bias;
|
|
321
|
+
if (typeof topicBias === "string") {
|
|
322
|
+
const nodeContents = /* @__PURE__ */ new Map();
|
|
323
|
+
for (const node of graph.nodes) {
|
|
324
|
+
nodeContents.set(node.id, node.content);
|
|
325
|
+
}
|
|
326
|
+
bias = buildTopicBias(nodeIds, nodeContents, topicBias);
|
|
327
|
+
} else if (topicBias instanceof Map) {
|
|
328
|
+
bias = topicBias;
|
|
329
|
+
} else {
|
|
330
|
+
const uniform = 1 / N;
|
|
331
|
+
bias = new Map(nodeIds.map((id) => [id, uniform]));
|
|
332
|
+
}
|
|
333
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
334
|
+
for (const id of nodeIds) {
|
|
335
|
+
ranks.set(id, 1 / N);
|
|
336
|
+
}
|
|
337
|
+
const danglingNodes = [];
|
|
338
|
+
for (const id of nodeIds) {
|
|
339
|
+
if ((outDegree.get(id) ?? 0) === 0) danglingNodes.push(id);
|
|
340
|
+
}
|
|
341
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
342
|
+
let danglingSum = 0;
|
|
343
|
+
for (const id of danglingNodes) {
|
|
344
|
+
danglingSum += ranks.get(id) ?? 0;
|
|
345
|
+
}
|
|
346
|
+
const danglingContrib = damping * danglingSum / N;
|
|
347
|
+
let maxDelta = 0;
|
|
348
|
+
if (useGS) {
|
|
349
|
+
for (const id of nodeIds) {
|
|
350
|
+
const biasVal = bias.get(id) ?? 1 / N;
|
|
351
|
+
let incomingSum = 0;
|
|
352
|
+
for (const src of inLinks.get(id) ?? []) {
|
|
353
|
+
const srcRank = ranks.get(src) ?? 0;
|
|
354
|
+
const srcOut = outDegree.get(src) ?? 1;
|
|
355
|
+
incomingSum += srcRank / srcOut;
|
|
356
|
+
}
|
|
357
|
+
const newRank = (1 - damping) * biasVal + damping * incomingSum + danglingContrib;
|
|
358
|
+
const oldRank = ranks.get(id) ?? 0;
|
|
359
|
+
const delta = Math.abs(newRank - oldRank);
|
|
360
|
+
if (delta > maxDelta) maxDelta = delta;
|
|
361
|
+
ranks.set(id, newRank);
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
const newRanks = /* @__PURE__ */ new Map();
|
|
365
|
+
for (const id of nodeIds) {
|
|
366
|
+
const biasVal = bias.get(id) ?? 1 / N;
|
|
367
|
+
let incomingSum = 0;
|
|
368
|
+
for (const src of inLinks.get(id) ?? []) {
|
|
369
|
+
const srcRank = ranks.get(src) ?? 0;
|
|
370
|
+
const srcOut = outDegree.get(src) ?? 1;
|
|
371
|
+
incomingSum += srcRank / srcOut;
|
|
372
|
+
}
|
|
373
|
+
const newRank = (1 - damping) * biasVal + damping * incomingSum + danglingContrib;
|
|
374
|
+
newRanks.set(id, newRank);
|
|
375
|
+
const delta = Math.abs(newRank - (ranks.get(id) ?? 0));
|
|
376
|
+
if (delta > maxDelta) maxDelta = delta;
|
|
377
|
+
}
|
|
378
|
+
for (const [id, rank] of newRanks) ranks.set(id, rank);
|
|
379
|
+
}
|
|
380
|
+
if (maxDelta < epsilon) break;
|
|
381
|
+
}
|
|
382
|
+
return ranks;
|
|
383
|
+
}
|
|
384
|
+
|
|
387
385
|
// ../chitragupta/packages/smriti/src/graphrag-extraction.ts
|
|
388
386
|
var CHUNK_TARGET_TOKENS = 350;
|
|
389
387
|
var CHUNK_MIN_TOKENS = 200;
|
|
@@ -1120,19 +1118,13 @@ function removeMemoryFromGraph(graph, scope) {
|
|
|
1120
1118
|
);
|
|
1121
1119
|
}
|
|
1122
1120
|
|
|
1123
|
-
// ../chitragupta/packages/smriti/src/
|
|
1124
|
-
var DEFAULT_LEIDEN_CONFIG = {
|
|
1125
|
-
resolution: 1,
|
|
1126
|
-
maxIterations: 10,
|
|
1127
|
-
minModularityGain: 1e-6,
|
|
1128
|
-
seed: 42,
|
|
1129
|
-
minCommunitySize: 1
|
|
1130
|
-
};
|
|
1121
|
+
// ../chitragupta/packages/smriti/src/leiden-algorithm.ts
|
|
1131
1122
|
var Xorshift32 = class {
|
|
1132
1123
|
state;
|
|
1133
1124
|
constructor(seed) {
|
|
1134
1125
|
this.state = seed | 0 || 1;
|
|
1135
1126
|
}
|
|
1127
|
+
/** Generate next pseudo-random number in [0, 1). */
|
|
1136
1128
|
next() {
|
|
1137
1129
|
let x = this.state;
|
|
1138
1130
|
x ^= x << 13;
|
|
@@ -1143,17 +1135,17 @@ var Xorshift32 = class {
|
|
|
1143
1135
|
}
|
|
1144
1136
|
};
|
|
1145
1137
|
var AdjacencyGraph = class {
|
|
1146
|
-
/** nodeIndex
|
|
1138
|
+
/** nodeIndex -> Map<neighborIndex, weight> */
|
|
1147
1139
|
adj;
|
|
1148
|
-
/** nodeIndex
|
|
1140
|
+
/** nodeIndex -> weighted degree (sum of edge weights) */
|
|
1149
1141
|
degree;
|
|
1150
1142
|
/** Total edge weight (2m). */
|
|
1151
1143
|
totalWeight;
|
|
1152
1144
|
/** Number of nodes. */
|
|
1153
1145
|
n;
|
|
1154
|
-
/** Node ID
|
|
1146
|
+
/** Node ID -> index mapping. */
|
|
1155
1147
|
idToIndex;
|
|
1156
|
-
/** Index
|
|
1148
|
+
/** Index -> node ID mapping. */
|
|
1157
1149
|
indexToId;
|
|
1158
1150
|
constructor(graph) {
|
|
1159
1151
|
const nodeIds = graph.nodes.map((n) => n.id);
|
|
@@ -1329,6 +1321,15 @@ function compactCommunities(assignment, minSize) {
|
|
|
1329
1321
|
}
|
|
1330
1322
|
return assignment.map((c) => remap.get(c) ?? 0);
|
|
1331
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
// ../chitragupta/packages/smriti/src/graphrag-leiden.ts
|
|
1326
|
+
var DEFAULT_LEIDEN_CONFIG = {
|
|
1327
|
+
resolution: 1,
|
|
1328
|
+
maxIterations: 10,
|
|
1329
|
+
minModularityGain: 1e-6,
|
|
1330
|
+
seed: 42,
|
|
1331
|
+
minCommunitySize: 1
|
|
1332
|
+
};
|
|
1332
1333
|
function leiden(graph, config) {
|
|
1333
1334
|
const cfg = { ...DEFAULT_LEIDEN_CONFIG, ...config };
|
|
1334
1335
|
if (graph.nodes.length === 0) {
|
|
@@ -1361,6 +1362,17 @@ function leiden(graph, config) {
|
|
|
1361
1362
|
communities.set(g.indexToId[i], compacted[i]);
|
|
1362
1363
|
}
|
|
1363
1364
|
const finalModularity = computeModularity(g, compacted, cfg.resolution);
|
|
1365
|
+
const communityList = buildCommunityList(g, compacted, cfg.resolution);
|
|
1366
|
+
communityList.sort((a, b) => b.members.length - a.members.length);
|
|
1367
|
+
return {
|
|
1368
|
+
communities,
|
|
1369
|
+
communityList,
|
|
1370
|
+
modularity: finalModularity,
|
|
1371
|
+
iterations,
|
|
1372
|
+
levels: 1
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function buildCommunityList(g, compacted, resolution) {
|
|
1364
1376
|
const communityNodes = /* @__PURE__ */ new Map();
|
|
1365
1377
|
for (let i = 0; i < g.n; i++) {
|
|
1366
1378
|
const c = compacted[i];
|
|
@@ -1383,11 +1395,9 @@ function leiden(graph, config) {
|
|
|
1383
1395
|
const possibleEdges = members.length * (members.length - 1) / 2;
|
|
1384
1396
|
const density = possibleEdges > 0 ? internalEdges / possibleEdges : 0;
|
|
1385
1397
|
let ac = 0;
|
|
1386
|
-
for (const mi of memberSet)
|
|
1387
|
-
ac += g.degree[mi];
|
|
1388
|
-
}
|
|
1398
|
+
for (const mi of memberSet) ac += g.degree[mi];
|
|
1389
1399
|
const twoM = g.totalWeight;
|
|
1390
|
-
const communityMod = twoM > 0 ? 2 * internalWeight / twoM -
|
|
1400
|
+
const communityMod = twoM > 0 ? 2 * internalWeight / twoM - resolution * (ac / twoM) ** 2 : 0;
|
|
1391
1401
|
communityList.push({
|
|
1392
1402
|
id,
|
|
1393
1403
|
members,
|
|
@@ -1396,14 +1406,7 @@ function leiden(graph, config) {
|
|
|
1396
1406
|
level: 0
|
|
1397
1407
|
});
|
|
1398
1408
|
}
|
|
1399
|
-
communityList
|
|
1400
|
-
return {
|
|
1401
|
-
communities,
|
|
1402
|
-
communityList,
|
|
1403
|
-
modularity: finalModularity,
|
|
1404
|
-
iterations,
|
|
1405
|
-
levels: 1
|
|
1406
|
-
};
|
|
1409
|
+
return communityList;
|
|
1407
1410
|
}
|
|
1408
1411
|
function annotateCommunities(graph, result) {
|
|
1409
1412
|
for (const node of graph.nodes) {
|
|
@@ -1412,50 +1415,318 @@ function annotateCommunities(graph, result) {
|
|
|
1412
1415
|
node.metadata.communityId = communityId;
|
|
1413
1416
|
}
|
|
1414
1417
|
}
|
|
1415
|
-
}
|
|
1416
|
-
function communitySummary(graph, communityId, maxLabels = 5) {
|
|
1417
|
-
const members = graph.nodes.filter(
|
|
1418
|
-
(n) => n.metadata.communityId === communityId
|
|
1419
|
-
);
|
|
1420
|
-
const typeCount = {};
|
|
1421
|
-
for (const node of members) {
|
|
1422
|
-
typeCount[node.type] = (typeCount[node.type] ?? 0) + 1;
|
|
1418
|
+
}
|
|
1419
|
+
function communitySummary(graph, communityId, maxLabels = 5) {
|
|
1420
|
+
const members = graph.nodes.filter(
|
|
1421
|
+
(n) => n.metadata.communityId === communityId
|
|
1422
|
+
);
|
|
1423
|
+
const typeCount = {};
|
|
1424
|
+
for (const node of members) {
|
|
1425
|
+
typeCount[node.type] = (typeCount[node.type] ?? 0) + 1;
|
|
1426
|
+
}
|
|
1427
|
+
const labels = members.filter((n) => n.type === "concept" || n.type === "session").slice(0, maxLabels).map((n) => n.label);
|
|
1428
|
+
return { communityId, labels, nodeTypes: typeCount, size: members.length };
|
|
1429
|
+
}
|
|
1430
|
+
function filterByCommunity(nodes, communityId) {
|
|
1431
|
+
return nodes.filter((n) => n.metadata.communityId === communityId);
|
|
1432
|
+
}
|
|
1433
|
+
function findBridgeNodes(graph, result, minCommunities = 2) {
|
|
1434
|
+
const touchedCommunities = /* @__PURE__ */ new Map();
|
|
1435
|
+
for (const node of graph.nodes) {
|
|
1436
|
+
const ownC = result.communities.get(node.id);
|
|
1437
|
+
if (ownC !== void 0) {
|
|
1438
|
+
touchedCommunities.set(node.id, /* @__PURE__ */ new Set([ownC]));
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
for (const edge of graph.edges) {
|
|
1442
|
+
const sc = result.communities.get(edge.source);
|
|
1443
|
+
const tc = result.communities.get(edge.target);
|
|
1444
|
+
if (sc === void 0 || tc === void 0) continue;
|
|
1445
|
+
if (sc === tc) continue;
|
|
1446
|
+
touchedCommunities.get(edge.source)?.add(tc);
|
|
1447
|
+
touchedCommunities.get(edge.target)?.add(sc);
|
|
1448
|
+
}
|
|
1449
|
+
const bridges = [];
|
|
1450
|
+
for (const node of graph.nodes) {
|
|
1451
|
+
const communities = touchedCommunities.get(node.id);
|
|
1452
|
+
if (communities && communities.size >= minCommunities) {
|
|
1453
|
+
bridges.push(node);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return bridges;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// ../chitragupta/packages/smriti/src/graphrag-persistence.ts
|
|
1460
|
+
import fs from "fs";
|
|
1461
|
+
import path from "path";
|
|
1462
|
+
function getGraphDir() {
|
|
1463
|
+
return path.join(getChitraguptaHome(), "graphrag");
|
|
1464
|
+
}
|
|
1465
|
+
function getGraphPath() {
|
|
1466
|
+
return path.join(getGraphDir(), "graph.json");
|
|
1467
|
+
}
|
|
1468
|
+
function getEmbeddingsPath() {
|
|
1469
|
+
return path.join(getGraphDir(), "embeddings.json");
|
|
1470
|
+
}
|
|
1471
|
+
function getPageRankPath() {
|
|
1472
|
+
return path.join(getGraphDir(), "pagerank.json");
|
|
1473
|
+
}
|
|
1474
|
+
function getGraphDb(initialized, markInitialized) {
|
|
1475
|
+
try {
|
|
1476
|
+
const dbm = DatabaseManager.instance();
|
|
1477
|
+
if (!initialized) {
|
|
1478
|
+
initGraphSchema(dbm);
|
|
1479
|
+
markInitialized();
|
|
1480
|
+
}
|
|
1481
|
+
return dbm.get("graph");
|
|
1482
|
+
} catch {
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
function upsertNodeToDb(db, node) {
|
|
1487
|
+
db.prepare(`
|
|
1488
|
+
INSERT OR REPLACE INTO nodes (id, type, label, content, metadata, created_at, updated_at)
|
|
1489
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1490
|
+
`).run(node.id, node.type, node.label, node.content, JSON.stringify(node.metadata), Date.now(), Date.now());
|
|
1491
|
+
}
|
|
1492
|
+
function insertEdgeToDb(db, edge) {
|
|
1493
|
+
db.prepare(`
|
|
1494
|
+
INSERT OR IGNORE INTO edges (source, target, relationship, weight, pramana, viveka,
|
|
1495
|
+
valid_from, valid_until, recorded_at, superseded_at)
|
|
1496
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1497
|
+
`).run(
|
|
1498
|
+
edge.source,
|
|
1499
|
+
edge.target,
|
|
1500
|
+
edge.relationship,
|
|
1501
|
+
edge.weight,
|
|
1502
|
+
null,
|
|
1503
|
+
null,
|
|
1504
|
+
edge.validFrom ? new Date(edge.validFrom).getTime() : Date.now(),
|
|
1505
|
+
edge.validUntil ? new Date(edge.validUntil).getTime() : null,
|
|
1506
|
+
edge.recordedAt ? new Date(edge.recordedAt).getTime() : Date.now(),
|
|
1507
|
+
edge.supersededAt ? new Date(edge.supersededAt).getTime() : null
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
function saveToSqlite(db, graph, pageRankScores, embeddingCache) {
|
|
1511
|
+
db.transaction(() => {
|
|
1512
|
+
db.prepare("DELETE FROM pagerank").run();
|
|
1513
|
+
db.prepare("DELETE FROM edges").run();
|
|
1514
|
+
db.prepare("DELETE FROM nodes").run();
|
|
1515
|
+
for (const node of graph.nodes) upsertNodeToDb(db, node);
|
|
1516
|
+
for (const edge of graph.edges) insertEdgeToDb(db, edge);
|
|
1517
|
+
for (const [nodeId, score] of pageRankScores) {
|
|
1518
|
+
db.prepare("INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)").run(nodeId, score, Date.now());
|
|
1519
|
+
}
|
|
1520
|
+
})();
|
|
1521
|
+
saveEmbeddingsJson(embeddingCache);
|
|
1522
|
+
}
|
|
1523
|
+
function loadFromSqlite(db) {
|
|
1524
|
+
const nodeCount = db.prepare("SELECT COUNT(*) as cnt FROM nodes").get().cnt;
|
|
1525
|
+
if (nodeCount === 0) return null;
|
|
1526
|
+
const nodeRows = db.prepare("SELECT * FROM nodes").all();
|
|
1527
|
+
const nodes = nodeRows.map((row) => ({
|
|
1528
|
+
id: row.id,
|
|
1529
|
+
type: row.type,
|
|
1530
|
+
label: row.label,
|
|
1531
|
+
content: row.content ?? "",
|
|
1532
|
+
metadata: (() => {
|
|
1533
|
+
try {
|
|
1534
|
+
return JSON.parse(row.metadata ?? "{}");
|
|
1535
|
+
} catch {
|
|
1536
|
+
return {};
|
|
1537
|
+
}
|
|
1538
|
+
})()
|
|
1539
|
+
}));
|
|
1540
|
+
const edgeRows = db.prepare("SELECT * FROM edges").all();
|
|
1541
|
+
const edges = edgeRows.map((row) => ({
|
|
1542
|
+
source: row.source,
|
|
1543
|
+
target: row.target,
|
|
1544
|
+
relationship: row.relationship,
|
|
1545
|
+
weight: row.weight,
|
|
1546
|
+
validFrom: row.valid_from ? new Date(row.valid_from).toISOString() : void 0,
|
|
1547
|
+
validUntil: row.valid_until ? new Date(row.valid_until).toISOString() : void 0,
|
|
1548
|
+
recordedAt: row.recorded_at ? new Date(row.recorded_at).toISOString() : void 0,
|
|
1549
|
+
supersededAt: row.superseded_at ? new Date(row.superseded_at).toISOString() : void 0
|
|
1550
|
+
}));
|
|
1551
|
+
const pageRankScores = /* @__PURE__ */ new Map();
|
|
1552
|
+
const prRows = db.prepare("SELECT * FROM pagerank").all();
|
|
1553
|
+
for (const row of prRows) {
|
|
1554
|
+
pageRankScores.set(row.node_id, row.score);
|
|
1555
|
+
}
|
|
1556
|
+
return { graph: { nodes, edges }, pageRankScores };
|
|
1557
|
+
}
|
|
1558
|
+
function migrateInMemoryToSqlite(db, graph, pageRankScores) {
|
|
1559
|
+
db.transaction(() => {
|
|
1560
|
+
for (const node of graph.nodes) upsertNodeToDb(db, node);
|
|
1561
|
+
for (const edge of graph.edges) insertEdgeToDb(db, edge);
|
|
1562
|
+
for (const [nodeId, score] of pageRankScores) {
|
|
1563
|
+
db.prepare("INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)").run(nodeId, score, Date.now());
|
|
1564
|
+
}
|
|
1565
|
+
})();
|
|
1566
|
+
}
|
|
1567
|
+
function saveToJson(graph, pageRankScores, embeddingCache) {
|
|
1568
|
+
try {
|
|
1569
|
+
const dir = getGraphDir();
|
|
1570
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1571
|
+
fs.writeFileSync(getGraphPath(), JSON.stringify(graph, null, " "), "utf-8");
|
|
1572
|
+
saveEmbeddingsJson(embeddingCache);
|
|
1573
|
+
const pageRankObj = {};
|
|
1574
|
+
for (const [key, value] of pageRankScores) pageRankObj[key] = value;
|
|
1575
|
+
fs.writeFileSync(getPageRankPath(), JSON.stringify(pageRankObj, null, " "), "utf-8");
|
|
1576
|
+
} catch {
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
function loadFromJson() {
|
|
1580
|
+
let graph = { nodes: [], edges: [] };
|
|
1581
|
+
const pageRankScores = /* @__PURE__ */ new Map();
|
|
1582
|
+
try {
|
|
1583
|
+
const graphPath = getGraphPath();
|
|
1584
|
+
if (fs.existsSync(graphPath)) {
|
|
1585
|
+
graph = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
|
|
1586
|
+
}
|
|
1587
|
+
} catch {
|
|
1588
|
+
graph = { nodes: [], edges: [] };
|
|
1589
|
+
}
|
|
1590
|
+
try {
|
|
1591
|
+
const prPath = getPageRankPath();
|
|
1592
|
+
if (fs.existsSync(prPath)) {
|
|
1593
|
+
const ranks = JSON.parse(fs.readFileSync(prPath, "utf-8"));
|
|
1594
|
+
for (const [key, value] of Object.entries(ranks)) pageRankScores.set(key, value);
|
|
1595
|
+
}
|
|
1596
|
+
} catch {
|
|
1423
1597
|
}
|
|
1424
|
-
|
|
1425
|
-
return {
|
|
1426
|
-
communityId,
|
|
1427
|
-
labels,
|
|
1428
|
-
nodeTypes: typeCount,
|
|
1429
|
-
size: members.length
|
|
1430
|
-
};
|
|
1598
|
+
return { graph, pageRankScores };
|
|
1431
1599
|
}
|
|
1432
|
-
function
|
|
1433
|
-
|
|
1600
|
+
function saveEmbeddingsJson(embeddingCache) {
|
|
1601
|
+
try {
|
|
1602
|
+
const dir = getGraphDir();
|
|
1603
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1604
|
+
const embeddings = {};
|
|
1605
|
+
for (const [key, value] of embeddingCache) embeddings[key] = value;
|
|
1606
|
+
fs.writeFileSync(getEmbeddingsPath(), JSON.stringify(embeddings, null, " "), "utf-8");
|
|
1607
|
+
} catch {
|
|
1608
|
+
}
|
|
1434
1609
|
}
|
|
1435
|
-
function
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
const
|
|
1439
|
-
if (
|
|
1440
|
-
|
|
1610
|
+
function loadEmbeddingsJson(maxCacheSize) {
|
|
1611
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1612
|
+
try {
|
|
1613
|
+
const embPath = getEmbeddingsPath();
|
|
1614
|
+
if (fs.existsSync(embPath)) {
|
|
1615
|
+
const embeddings = JSON.parse(fs.readFileSync(embPath, "utf-8"));
|
|
1616
|
+
for (const [key, value] of Object.entries(embeddings)) cache.set(key, value);
|
|
1617
|
+
while (cache.size > maxCacheSize) {
|
|
1618
|
+
const oldest = cache.keys().next().value;
|
|
1619
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
1620
|
+
}
|
|
1441
1621
|
}
|
|
1622
|
+
} catch {
|
|
1442
1623
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1624
|
+
return cache;
|
|
1625
|
+
}
|
|
1626
|
+
function lookupPramana(db, nodeId) {
|
|
1627
|
+
if (!db) return "shabda";
|
|
1628
|
+
try {
|
|
1629
|
+
const row = db.prepare(`
|
|
1630
|
+
SELECT pramana, COUNT(*) as cnt FROM edges
|
|
1631
|
+
WHERE (source = ? OR target = ?) AND pramana IS NOT NULL
|
|
1632
|
+
GROUP BY pramana ORDER BY cnt DESC LIMIT 1
|
|
1633
|
+
`).get(nodeId, nodeId);
|
|
1634
|
+
if (row?.pramana) return row.pramana;
|
|
1635
|
+
} catch {
|
|
1450
1636
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1637
|
+
return "shabda";
|
|
1638
|
+
}
|
|
1639
|
+
function lookupPramanaBatch(db, nodeIds) {
|
|
1640
|
+
const result = /* @__PURE__ */ new Map();
|
|
1641
|
+
if (nodeIds.length === 0) return result;
|
|
1642
|
+
if (!db) {
|
|
1643
|
+
for (const id of nodeIds) result.set(id, "shabda");
|
|
1644
|
+
return result;
|
|
1645
|
+
}
|
|
1646
|
+
try {
|
|
1647
|
+
const placeholders = nodeIds.map(() => "?").join(",");
|
|
1648
|
+
const rows = db.prepare(`
|
|
1649
|
+
SELECT node_id, pramana FROM (
|
|
1650
|
+
SELECT source AS node_id, pramana, COUNT(*) AS cnt FROM edges
|
|
1651
|
+
WHERE source IN (${placeholders}) AND pramana IS NOT NULL
|
|
1652
|
+
GROUP BY source, pramana
|
|
1653
|
+
UNION ALL
|
|
1654
|
+
SELECT target AS node_id, pramana, COUNT(*) AS cnt FROM edges
|
|
1655
|
+
WHERE target IN (${placeholders}) AND pramana IS NOT NULL
|
|
1656
|
+
GROUP BY target, pramana
|
|
1657
|
+
) GROUP BY node_id HAVING cnt = MAX(cnt)
|
|
1658
|
+
`).all(...nodeIds, ...nodeIds);
|
|
1659
|
+
for (const row of rows) result.set(row.node_id, row.pramana);
|
|
1660
|
+
} catch {
|
|
1661
|
+
}
|
|
1662
|
+
for (const id of nodeIds) {
|
|
1663
|
+
if (!result.has(id)) result.set(id, "shabda");
|
|
1664
|
+
}
|
|
1665
|
+
return result;
|
|
1666
|
+
}
|
|
1667
|
+
function migrateGraphJson() {
|
|
1668
|
+
const graphPath = getGraphPath();
|
|
1669
|
+
const prPath = getPageRankPath();
|
|
1670
|
+
if (!fs.existsSync(graphPath)) return { nodes: 0, edges: 0 };
|
|
1671
|
+
try {
|
|
1672
|
+
const graph = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
|
|
1673
|
+
if (!graph.nodes || graph.nodes.length === 0) return { nodes: 0, edges: 0 };
|
|
1674
|
+
const pageRankScores = /* @__PURE__ */ new Map();
|
|
1675
|
+
try {
|
|
1676
|
+
if (fs.existsSync(prPath)) {
|
|
1677
|
+
const ranks = JSON.parse(fs.readFileSync(prPath, "utf-8"));
|
|
1678
|
+
for (const [key, value] of Object.entries(ranks)) pageRankScores.set(key, value);
|
|
1679
|
+
}
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1682
|
+
const dbm = DatabaseManager.instance();
|
|
1683
|
+
initGraphSchema(dbm);
|
|
1684
|
+
const db = dbm.get("graph");
|
|
1685
|
+
db.transaction(() => {
|
|
1686
|
+
const upsertStmt = db.prepare(`
|
|
1687
|
+
INSERT OR REPLACE INTO nodes (id, type, label, content, metadata, created_at, updated_at)
|
|
1688
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1689
|
+
`);
|
|
1690
|
+
const edgeStmt = db.prepare(`
|
|
1691
|
+
INSERT OR IGNORE INTO edges (source, target, relationship, weight, pramana, viveka,
|
|
1692
|
+
valid_from, valid_until, recorded_at, superseded_at)
|
|
1693
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1694
|
+
`);
|
|
1695
|
+
const prStmt = db.prepare(
|
|
1696
|
+
"INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
|
|
1697
|
+
);
|
|
1698
|
+
const now = Date.now();
|
|
1699
|
+
for (const node of graph.nodes) {
|
|
1700
|
+
upsertStmt.run(node.id, node.type, node.label, node.content, JSON.stringify(node.metadata), now, now);
|
|
1701
|
+
}
|
|
1702
|
+
for (const edge of graph.edges) {
|
|
1703
|
+
edgeStmt.run(
|
|
1704
|
+
edge.source,
|
|
1705
|
+
edge.target,
|
|
1706
|
+
edge.relationship,
|
|
1707
|
+
edge.weight,
|
|
1708
|
+
null,
|
|
1709
|
+
null,
|
|
1710
|
+
edge.validFrom ? new Date(edge.validFrom).getTime() : now,
|
|
1711
|
+
edge.validUntil ? new Date(edge.validUntil).getTime() : null,
|
|
1712
|
+
edge.recordedAt ? new Date(edge.recordedAt).getTime() : now,
|
|
1713
|
+
edge.supersededAt ? new Date(edge.supersededAt).getTime() : null
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
for (const [nodeId, score] of pageRankScores) prStmt.run(nodeId, score, now);
|
|
1717
|
+
})();
|
|
1718
|
+
try {
|
|
1719
|
+
fs.renameSync(graphPath, graphPath + ".bak");
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1722
|
+
try {
|
|
1723
|
+
if (fs.existsSync(prPath)) fs.renameSync(prPath, prPath + ".bak");
|
|
1724
|
+
} catch {
|
|
1456
1725
|
}
|
|
1726
|
+
return { nodes: graph.nodes.length, edges: graph.edges.length };
|
|
1727
|
+
} catch {
|
|
1728
|
+
return { nodes: 0, edges: 0 };
|
|
1457
1729
|
}
|
|
1458
|
-
return bridges;
|
|
1459
1730
|
}
|
|
1460
1731
|
|
|
1461
1732
|
// ../chitragupta/packages/smriti/src/graphrag.ts
|
|
@@ -1464,18 +1735,6 @@ var DEFAULT_CONFIG = {
|
|
|
1464
1735
|
model: "nomic-embed-text",
|
|
1465
1736
|
generationModel: "llama3.2"
|
|
1466
1737
|
};
|
|
1467
|
-
function getGraphDir() {
|
|
1468
|
-
return path.join(getChitraguptaHome(), "graphrag");
|
|
1469
|
-
}
|
|
1470
|
-
function getGraphPath() {
|
|
1471
|
-
return path.join(getGraphDir(), "graph.json");
|
|
1472
|
-
}
|
|
1473
|
-
function getEmbeddingsPath() {
|
|
1474
|
-
return path.join(getGraphDir(), "embeddings.json");
|
|
1475
|
-
}
|
|
1476
|
-
function getPageRankPath() {
|
|
1477
|
-
return path.join(getGraphDir(), "pagerank.json");
|
|
1478
|
-
}
|
|
1479
1738
|
var GraphRAGEngine = class {
|
|
1480
1739
|
config;
|
|
1481
1740
|
graph;
|
|
@@ -1484,9 +1743,9 @@ var GraphRAGEngine = class {
|
|
|
1484
1743
|
maxEmbeddingCacheSize;
|
|
1485
1744
|
ollamaAvailable = null;
|
|
1486
1745
|
embeddingService;
|
|
1487
|
-
/** Push-based incremental PageRank engine
|
|
1746
|
+
/** Push-based incremental PageRank engine. */
|
|
1488
1747
|
incrementalPR = null;
|
|
1489
|
-
/** Whether
|
|
1748
|
+
/** Whether graph.db schema has been initialized in this process. */
|
|
1490
1749
|
_graphDbInitialized = false;
|
|
1491
1750
|
constructor(config) {
|
|
1492
1751
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
@@ -1497,7 +1756,6 @@ var GraphRAGEngine = class {
|
|
|
1497
1756
|
this.embeddingService = config?.embeddingService ?? new EmbeddingService();
|
|
1498
1757
|
this.loadFromDisk();
|
|
1499
1758
|
}
|
|
1500
|
-
// ─── Ollama Availability (for entity extraction) ──────────────────
|
|
1501
1759
|
async checkOllamaAvailability() {
|
|
1502
1760
|
if (this.ollamaAvailable !== null) return this.ollamaAvailable;
|
|
1503
1761
|
try {
|
|
@@ -1511,6 +1769,7 @@ var GraphRAGEngine = class {
|
|
|
1511
1769
|
}
|
|
1512
1770
|
return this.ollamaAvailable;
|
|
1513
1771
|
}
|
|
1772
|
+
/** Get (or compute and cache) an embedding vector for the given text. */
|
|
1514
1773
|
async getEmbedding(text) {
|
|
1515
1774
|
const cached = this.embeddingCache.get(text);
|
|
1516
1775
|
if (cached) return cached;
|
|
@@ -1518,17 +1777,15 @@ var GraphRAGEngine = class {
|
|
|
1518
1777
|
this.embeddingCache.set(text, vector);
|
|
1519
1778
|
if (this.embeddingCache.size > this.maxEmbeddingCacheSize) {
|
|
1520
1779
|
const oldest = this.embeddingCache.keys().next().value;
|
|
1521
|
-
if (oldest !== void 0)
|
|
1522
|
-
this.embeddingCache.delete(oldest);
|
|
1523
|
-
}
|
|
1780
|
+
if (oldest !== void 0) this.embeddingCache.delete(oldest);
|
|
1524
1781
|
}
|
|
1525
1782
|
return vector;
|
|
1526
1783
|
}
|
|
1527
|
-
|
|
1784
|
+
/** Compute cosine similarity between two vectors. */
|
|
1528
1785
|
cosineSimilarity(a, b) {
|
|
1529
1786
|
return cosineSimilarity(a, b);
|
|
1530
1787
|
}
|
|
1531
|
-
|
|
1788
|
+
/** Extract named entities from text using LLM (with keyword + NER fallback). */
|
|
1532
1789
|
async extractEntities(text) {
|
|
1533
1790
|
const isAvailable = await this.checkOllamaAvailability();
|
|
1534
1791
|
let baseEntities;
|
|
@@ -1573,46 +1830,27 @@ var GraphRAGEngine = class {
|
|
|
1573
1830
|
return baseEntities;
|
|
1574
1831
|
}
|
|
1575
1832
|
// ─── Incremental PageRank ────────────────────────────────────────
|
|
1576
|
-
/**
|
|
1577
|
-
* Initialize (or reinitialize) the incremental PageRank engine from
|
|
1578
|
-
* the current graph state. Performs one full Gauss-Seidel computation
|
|
1579
|
-
* internally, then subsequent edge changes are O(1/ε) each via
|
|
1580
|
-
* push-based residual propagation.
|
|
1581
|
-
*/
|
|
1833
|
+
/** Initialize incremental PageRank from current graph state. */
|
|
1582
1834
|
initIncrementalPR() {
|
|
1583
1835
|
this.incrementalPR = new IncrementalPageRank();
|
|
1584
1836
|
this.incrementalPR.initialize(this.graph);
|
|
1585
1837
|
this.pageRankScores = this.incrementalPR.getRanks();
|
|
1586
1838
|
}
|
|
1587
|
-
/**
|
|
1588
|
-
* Feed removed edges into the incremental PR engine so it can adjust
|
|
1589
|
-
* scores via residual propagation rather than a full recompute.
|
|
1590
|
-
*/
|
|
1839
|
+
/** Feed removed edges into incremental PR engine. */
|
|
1591
1840
|
applyEdgeRemovals(edges) {
|
|
1592
1841
|
if (!this.incrementalPR || edges.length === 0) return;
|
|
1593
|
-
for (const edge of edges)
|
|
1594
|
-
this.incrementalPR.removeEdge(edge.source, edge.target);
|
|
1595
|
-
}
|
|
1842
|
+
for (const edge of edges) this.incrementalPR.removeEdge(edge.source, edge.target);
|
|
1596
1843
|
this.pageRankScores = this.incrementalPR.getRanks();
|
|
1597
1844
|
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Feed newly added edges into the incremental PR engine.
|
|
1600
|
-
*/
|
|
1845
|
+
/** Feed newly added edges into incremental PR engine. */
|
|
1601
1846
|
applyEdgeAdditions(edges) {
|
|
1602
1847
|
if (!this.incrementalPR || edges.length === 0) return;
|
|
1603
|
-
for (const edge of edges)
|
|
1604
|
-
this.incrementalPR.addEdge(edge.source, edge.target);
|
|
1605
|
-
}
|
|
1848
|
+
for (const edge of edges) this.incrementalPR.addEdge(edge.source, edge.target);
|
|
1606
1849
|
this.pageRankScores = this.incrementalPR.getRanks();
|
|
1607
1850
|
}
|
|
1608
|
-
// ─── PageRank (delegates) ──────────────────────────────────────────
|
|
1609
1851
|
/**
|
|
1610
|
-
* Compute PageRank
|
|
1611
|
-
*
|
|
1612
|
-
* When called with an explicit graph argument, uses the full power-iteration
|
|
1613
|
-
* algorithm (useful for one-off computations on external graphs and tests).
|
|
1614
|
-
* When called without arguments, initializes the incremental PageRank engine
|
|
1615
|
-
* on the internal graph.
|
|
1852
|
+
* Compute PageRank. With an explicit graph argument uses full power-iteration;
|
|
1853
|
+
* without arguments initializes the incremental engine on the internal graph.
|
|
1616
1854
|
*/
|
|
1617
1855
|
computePageRank(graph) {
|
|
1618
1856
|
if (graph) {
|
|
@@ -1622,49 +1860,41 @@ var GraphRAGEngine = class {
|
|
|
1622
1860
|
this.initIncrementalPR();
|
|
1623
1861
|
return this.pageRankScores;
|
|
1624
1862
|
}
|
|
1863
|
+
/** Get the PageRank score for a node. Returns 0 if not computed. */
|
|
1625
1864
|
getPageRank(nodeId) {
|
|
1626
1865
|
return this.pageRankScores.get(nodeId) ?? 0;
|
|
1627
1866
|
}
|
|
1628
|
-
/**
|
|
1629
|
-
* Force a full PageRank recompute from the current graph state.
|
|
1630
|
-
* Reinitializes the incremental engine from scratch. Useful after
|
|
1631
|
-
* graph compaction or any structural change that bypasses the
|
|
1632
|
-
* normal indexSession/indexMemory paths.
|
|
1633
|
-
*/
|
|
1867
|
+
/** Force a full PageRank recompute and persist. */
|
|
1634
1868
|
recomputePageRank() {
|
|
1635
1869
|
this.initIncrementalPR();
|
|
1636
1870
|
this.saveToDisk();
|
|
1637
1871
|
}
|
|
1638
1872
|
// ─── Graph Building ───────────────────────────────────────────────
|
|
1873
|
+
/** Build a full knowledge graph from sessions and memories. */
|
|
1639
1874
|
async buildGraph(sessions, memories) {
|
|
1640
1875
|
const nodes = [];
|
|
1641
1876
|
const edges = [];
|
|
1642
1877
|
for (const session of sessions) {
|
|
1643
|
-
|
|
1644
|
-
nodes.push(sessionNode);
|
|
1878
|
+
nodes.push(await createSessionNode(this, session));
|
|
1645
1879
|
await indexSessionTurns(this, session, nodes, edges);
|
|
1646
1880
|
}
|
|
1647
|
-
for (const memory of memories)
|
|
1648
|
-
await buildMemoryNodes(this, memory, nodes, edges);
|
|
1649
|
-
}
|
|
1881
|
+
for (const memory of memories) await buildMemoryNodes(this, memory, nodes, edges);
|
|
1650
1882
|
await extractConceptsFromNodes(this, nodes, edges);
|
|
1651
1883
|
this.graph = { nodes, edges };
|
|
1652
1884
|
if (this.graph.nodes.length > 0 && this.graph.edges.length > 0) {
|
|
1653
|
-
|
|
1654
|
-
annotateCommunities(this.graph, leidenResult);
|
|
1885
|
+
annotateCommunities(this.graph, leiden(this.graph));
|
|
1655
1886
|
}
|
|
1656
1887
|
this.initIncrementalPR();
|
|
1657
1888
|
this.saveToDisk();
|
|
1658
1889
|
return this.graph;
|
|
1659
1890
|
}
|
|
1660
1891
|
// ─── Hybrid Search ───────────────────────────────────────────────
|
|
1892
|
+
/** Search the knowledge graph using hybrid scoring (cosine + PageRank + BM25). */
|
|
1661
1893
|
async search(query, graph, topK = 10) {
|
|
1662
1894
|
const searchGraph = graph ?? this.graph;
|
|
1663
1895
|
if (searchGraph.nodes.length === 0) return [];
|
|
1664
1896
|
const queryEmbedding = await this.getEmbedding(query);
|
|
1665
|
-
if (this.pageRankScores.size === 0)
|
|
1666
|
-
this.computePageRank(searchGraph);
|
|
1667
|
-
}
|
|
1897
|
+
if (this.pageRankScores.size === 0) this.computePageRank(searchGraph);
|
|
1668
1898
|
let maxPageRank = 0;
|
|
1669
1899
|
for (const node of searchGraph.nodes) {
|
|
1670
1900
|
const pr = this.pageRankScores.get(node.id) ?? 0;
|
|
@@ -1674,30 +1904,26 @@ var GraphRAGEngine = class {
|
|
|
1674
1904
|
for (const node of searchGraph.nodes) {
|
|
1675
1905
|
let cosineSim = 0;
|
|
1676
1906
|
if (node.embedding && node.embedding.length > 0) {
|
|
1677
|
-
cosineSim = cosineSimilarity(queryEmbedding, node.embedding);
|
|
1678
|
-
cosineSim = Math.max(0, cosineSim);
|
|
1907
|
+
cosineSim = Math.max(0, cosineSimilarity(queryEmbedding, node.embedding));
|
|
1679
1908
|
}
|
|
1680
|
-
const
|
|
1681
|
-
const
|
|
1909
|
+
const rawPR = this.pageRankScores.get(node.id) ?? 0;
|
|
1910
|
+
const normPR = maxPageRank > 0 ? rawPR / maxPageRank : 0;
|
|
1682
1911
|
const textScore = textMatchScore(query, node.content + " " + node.label);
|
|
1683
|
-
const finalScore = ALPHA * cosineSim + BETA *
|
|
1684
|
-
if (finalScore > 0) {
|
|
1685
|
-
scored.push({ node, score: finalScore });
|
|
1686
|
-
}
|
|
1912
|
+
const finalScore = ALPHA * cosineSim + BETA * normPR + GAMMA * textScore;
|
|
1913
|
+
if (finalScore > 0) scored.push({ node, score: finalScore });
|
|
1687
1914
|
}
|
|
1688
1915
|
scored.sort((a, b) => b.score - a.score);
|
|
1689
1916
|
return scored.slice(0, topK).map((s) => s.node);
|
|
1690
1917
|
}
|
|
1691
1918
|
// ─── Incremental Indexing ─────────────────────────────────────────
|
|
1919
|
+
/** Index a single session incrementally. */
|
|
1692
1920
|
async indexSession(session) {
|
|
1693
1921
|
const edgesBefore = [...this.graph.edges];
|
|
1694
1922
|
removeSessionFromGraph(this.graph, session.meta.id);
|
|
1695
|
-
const
|
|
1696
|
-
const edgesAfterSet = new Set(edgesAfterRemoval);
|
|
1923
|
+
const edgesAfterSet = new Set(this.graph.edges);
|
|
1697
1924
|
const removedEdges = edgesBefore.filter((e) => !edgesAfterSet.has(e));
|
|
1698
1925
|
const edgeCountBeforeAdd = this.graph.edges.length;
|
|
1699
|
-
|
|
1700
|
-
this.graph.nodes.push(sessionNode);
|
|
1926
|
+
this.graph.nodes.push(await createSessionNode(this, session));
|
|
1701
1927
|
await indexSessionTurns(this, session, this.graph.nodes, this.graph.edges);
|
|
1702
1928
|
const newEdges = this.graph.edges.slice(edgeCountBeforeAdd);
|
|
1703
1929
|
if (!this.incrementalPR) {
|
|
@@ -1708,11 +1934,11 @@ var GraphRAGEngine = class {
|
|
|
1708
1934
|
}
|
|
1709
1935
|
this.saveToDisk();
|
|
1710
1936
|
}
|
|
1937
|
+
/** Index a memory scope incrementally. */
|
|
1711
1938
|
async indexMemory(scope, content) {
|
|
1712
1939
|
const edgesBefore = [...this.graph.edges];
|
|
1713
1940
|
removeMemoryFromGraph(this.graph, scope);
|
|
1714
|
-
const
|
|
1715
|
-
const edgesAfterSet = new Set(edgesAfterRemoval);
|
|
1941
|
+
const edgesAfterSet = new Set(this.graph.edges);
|
|
1716
1942
|
const removedEdges = edgesBefore.filter((e) => !edgesAfterSet.has(e));
|
|
1717
1943
|
const edgeCountBeforeAdd = this.graph.edges.length;
|
|
1718
1944
|
await buildMemoryNodesFromContent(this, scope, content, this.graph.nodes, this.graph.edges);
|
|
@@ -1725,322 +1951,91 @@ var GraphRAGEngine = class {
|
|
|
1725
1951
|
}
|
|
1726
1952
|
this.saveToDisk();
|
|
1727
1953
|
}
|
|
1728
|
-
// ─── Pramana Lookup
|
|
1729
|
-
/**
|
|
1730
|
-
* Look up the dominant pramana type for a given node ID.
|
|
1731
|
-
*
|
|
1732
|
-
* Queries the edges table for edges involving this node (as source or target)
|
|
1733
|
-
* and returns the most common non-null pramana value. Falls back to 'shabda'
|
|
1734
|
-
* (testimony/documentation) if no pramana data is found.
|
|
1735
|
-
*
|
|
1736
|
-
* @param nodeId - The graph node ID to look up.
|
|
1737
|
-
* @returns The dominant PramanaType for this node.
|
|
1738
|
-
*/
|
|
1954
|
+
// ─── Pramana Lookup (delegates) ──────────────────────────────────
|
|
1955
|
+
/** Look up the dominant pramana type for a given node ID. */
|
|
1739
1956
|
lookupPramana(nodeId) {
|
|
1740
|
-
|
|
1741
|
-
if (!db) return "shabda";
|
|
1742
|
-
try {
|
|
1743
|
-
const row = db.prepare(`
|
|
1744
|
-
SELECT pramana, COUNT(*) as cnt
|
|
1745
|
-
FROM edges
|
|
1746
|
-
WHERE (source = ? OR target = ?) AND pramana IS NOT NULL
|
|
1747
|
-
GROUP BY pramana
|
|
1748
|
-
ORDER BY cnt DESC
|
|
1749
|
-
LIMIT 1
|
|
1750
|
-
`).get(nodeId, nodeId);
|
|
1751
|
-
if (row?.pramana) return row.pramana;
|
|
1752
|
-
} catch {
|
|
1753
|
-
}
|
|
1754
|
-
return "shabda";
|
|
1957
|
+
return lookupPramana(this.getGraphDbHandle(), nodeId);
|
|
1755
1958
|
}
|
|
1756
|
-
/**
|
|
1757
|
-
* Batch look up pramana types for multiple node IDs.
|
|
1758
|
-
*
|
|
1759
|
-
* More efficient than calling lookupPramana() individually for each node.
|
|
1760
|
-
* Returns a Map from node ID to PramanaType. Missing entries default to 'shabda'.
|
|
1761
|
-
*
|
|
1762
|
-
* @param nodeIds - Array of node IDs to look up.
|
|
1763
|
-
* @returns Map from node ID to its dominant PramanaType.
|
|
1764
|
-
*/
|
|
1959
|
+
/** Batch look up pramana types for multiple node IDs. */
|
|
1765
1960
|
lookupPramanaBatch(nodeIds) {
|
|
1766
|
-
|
|
1767
|
-
if (nodeIds.length === 0) return result;
|
|
1768
|
-
const db = this.getGraphDb();
|
|
1769
|
-
if (!db) {
|
|
1770
|
-
for (const id of nodeIds) result.set(id, "shabda");
|
|
1771
|
-
return result;
|
|
1772
|
-
}
|
|
1773
|
-
try {
|
|
1774
|
-
const placeholders = nodeIds.map(() => "?").join(",");
|
|
1775
|
-
const rows = db.prepare(`
|
|
1776
|
-
SELECT node_id, pramana FROM (
|
|
1777
|
-
SELECT source AS node_id, pramana, COUNT(*) AS cnt
|
|
1778
|
-
FROM edges
|
|
1779
|
-
WHERE source IN (${placeholders}) AND pramana IS NOT NULL
|
|
1780
|
-
GROUP BY source, pramana
|
|
1781
|
-
UNION ALL
|
|
1782
|
-
SELECT target AS node_id, pramana, COUNT(*) AS cnt
|
|
1783
|
-
FROM edges
|
|
1784
|
-
WHERE target IN (${placeholders}) AND pramana IS NOT NULL
|
|
1785
|
-
GROUP BY target, pramana
|
|
1786
|
-
)
|
|
1787
|
-
GROUP BY node_id
|
|
1788
|
-
HAVING cnt = MAX(cnt)
|
|
1789
|
-
`).all(...nodeIds, ...nodeIds);
|
|
1790
|
-
for (const row of rows) {
|
|
1791
|
-
result.set(row.node_id, row.pramana);
|
|
1792
|
-
}
|
|
1793
|
-
} catch {
|
|
1794
|
-
}
|
|
1795
|
-
for (const id of nodeIds) {
|
|
1796
|
-
if (!result.has(id)) result.set(id, "shabda");
|
|
1797
|
-
}
|
|
1798
|
-
return result;
|
|
1799
|
-
}
|
|
1800
|
-
// ─── SQLite Persistence Helpers ─────────────────────────────────
|
|
1801
|
-
/**
|
|
1802
|
-
* Get the graph database handle, initializing the schema on first access.
|
|
1803
|
-
* Returns null if the database layer is unavailable (e.g. mocked fs in tests).
|
|
1804
|
-
*/
|
|
1805
|
-
getGraphDb() {
|
|
1806
|
-
try {
|
|
1807
|
-
const dbm = DatabaseManager.instance();
|
|
1808
|
-
if (!this._graphDbInitialized) {
|
|
1809
|
-
initGraphSchema(dbm);
|
|
1810
|
-
this._graphDbInitialized = true;
|
|
1811
|
-
}
|
|
1812
|
-
return dbm.get("graph");
|
|
1813
|
-
} catch {
|
|
1814
|
-
return null;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
/** Insert or replace a node in the SQLite nodes table. */
|
|
1818
|
-
upsertNode(db, node) {
|
|
1819
|
-
db.prepare(`
|
|
1820
|
-
INSERT OR REPLACE INTO nodes (id, type, label, content, metadata, created_at, updated_at)
|
|
1821
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1822
|
-
`).run(
|
|
1823
|
-
node.id,
|
|
1824
|
-
node.type,
|
|
1825
|
-
node.label,
|
|
1826
|
-
node.content,
|
|
1827
|
-
JSON.stringify(node.metadata),
|
|
1828
|
-
Date.now(),
|
|
1829
|
-
Date.now()
|
|
1830
|
-
);
|
|
1961
|
+
return lookupPramanaBatch(this.getGraphDbHandle(), nodeIds);
|
|
1831
1962
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
`).run(
|
|
1839
|
-
edge.source,
|
|
1840
|
-
edge.target,
|
|
1841
|
-
edge.relationship,
|
|
1842
|
-
edge.weight,
|
|
1843
|
-
null,
|
|
1844
|
-
// pramana — not yet used
|
|
1845
|
-
null,
|
|
1846
|
-
// viveka — not yet used
|
|
1847
|
-
edge.validFrom ? new Date(edge.validFrom).getTime() : Date.now(),
|
|
1848
|
-
edge.validUntil ? new Date(edge.validUntil).getTime() : null,
|
|
1849
|
-
edge.recordedAt ? new Date(edge.recordedAt).getTime() : Date.now(),
|
|
1850
|
-
edge.supersededAt ? new Date(edge.supersededAt).getTime() : null
|
|
1851
|
-
);
|
|
1963
|
+
// ─── Persistence (delegates) ─────────────────────────────────────
|
|
1964
|
+
/** Get graph database handle via persistence layer. */
|
|
1965
|
+
getGraphDbHandle() {
|
|
1966
|
+
return getGraphDb(this._graphDbInitialized, () => {
|
|
1967
|
+
this._graphDbInitialized = true;
|
|
1968
|
+
});
|
|
1852
1969
|
}
|
|
1853
|
-
|
|
1970
|
+
/** Save graph state to SQLite (primary) or JSON (fallback). */
|
|
1854
1971
|
saveToDisk() {
|
|
1855
1972
|
try {
|
|
1856
|
-
const db = this.
|
|
1973
|
+
const db = this.getGraphDbHandle();
|
|
1857
1974
|
if (db) {
|
|
1858
|
-
db.
|
|
1859
|
-
db.prepare("DELETE FROM pagerank").run();
|
|
1860
|
-
db.prepare("DELETE FROM edges").run();
|
|
1861
|
-
db.prepare("DELETE FROM nodes").run();
|
|
1862
|
-
for (const node of this.graph.nodes) {
|
|
1863
|
-
this.upsertNode(db, node);
|
|
1864
|
-
}
|
|
1865
|
-
for (const edge of this.graph.edges) {
|
|
1866
|
-
this.insertEdge(db, edge);
|
|
1867
|
-
}
|
|
1868
|
-
for (const [nodeId, score] of this.pageRankScores) {
|
|
1869
|
-
db.prepare(
|
|
1870
|
-
"INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
|
|
1871
|
-
).run(nodeId, score, Date.now());
|
|
1872
|
-
}
|
|
1873
|
-
})();
|
|
1874
|
-
this.saveEmbeddingsJson();
|
|
1975
|
+
saveToSqlite(db, this.graph, this.pageRankScores, this.embeddingCache);
|
|
1875
1976
|
return;
|
|
1876
1977
|
}
|
|
1877
1978
|
} catch {
|
|
1878
1979
|
}
|
|
1879
|
-
this.
|
|
1980
|
+
saveToJson(this.graph, this.pageRankScores, this.embeddingCache);
|
|
1880
1981
|
}
|
|
1982
|
+
/** Load graph state from SQLite (primary) or JSON (fallback). */
|
|
1881
1983
|
loadFromDisk() {
|
|
1882
|
-
let
|
|
1984
|
+
let loaded = false;
|
|
1883
1985
|
try {
|
|
1884
|
-
const db = this.
|
|
1986
|
+
const db = this.getGraphDbHandle();
|
|
1885
1987
|
if (db) {
|
|
1886
|
-
const
|
|
1887
|
-
if (
|
|
1888
|
-
|
|
1889
|
-
this.
|
|
1890
|
-
id: row.id,
|
|
1891
|
-
type: row.type,
|
|
1892
|
-
label: row.label,
|
|
1893
|
-
content: row.content ?? "",
|
|
1894
|
-
metadata: (() => {
|
|
1895
|
-
try {
|
|
1896
|
-
return JSON.parse(row.metadata ?? "{}");
|
|
1897
|
-
} catch {
|
|
1898
|
-
return {};
|
|
1899
|
-
}
|
|
1900
|
-
})()
|
|
1901
|
-
// Note: embedding not stored in nodes table — it's in vectors.db (Phase 0.6)
|
|
1902
|
-
}));
|
|
1903
|
-
const edgeRows = db.prepare("SELECT * FROM edges").all();
|
|
1904
|
-
this.graph.edges = edgeRows.map((row) => ({
|
|
1905
|
-
source: row.source,
|
|
1906
|
-
target: row.target,
|
|
1907
|
-
relationship: row.relationship,
|
|
1908
|
-
weight: row.weight,
|
|
1909
|
-
validFrom: row.valid_from ? new Date(row.valid_from).toISOString() : void 0,
|
|
1910
|
-
validUntil: row.valid_until ? new Date(row.valid_until).toISOString() : void 0,
|
|
1911
|
-
recordedAt: row.recorded_at ? new Date(row.recorded_at).toISOString() : void 0,
|
|
1912
|
-
supersededAt: row.superseded_at ? new Date(row.superseded_at).toISOString() : void 0
|
|
1913
|
-
}));
|
|
1914
|
-
const prRows = db.prepare("SELECT * FROM pagerank").all();
|
|
1915
|
-
for (const row of prRows) {
|
|
1916
|
-
this.pageRankScores.set(row.node_id, row.score);
|
|
1917
|
-
}
|
|
1918
|
-
loadedFromSqlite = true;
|
|
1988
|
+
const sqliteData = loadFromSqlite(db);
|
|
1989
|
+
if (sqliteData) {
|
|
1990
|
+
this.graph = sqliteData.graph;
|
|
1991
|
+
this.pageRankScores = sqliteData.pageRankScores;
|
|
1919
1992
|
} else {
|
|
1920
|
-
|
|
1993
|
+
const jsonData = loadFromJson();
|
|
1994
|
+
this.graph = jsonData.graph;
|
|
1995
|
+
this.pageRankScores = jsonData.pageRankScores;
|
|
1921
1996
|
if (this.graph.nodes.length > 0) {
|
|
1922
1997
|
try {
|
|
1923
|
-
db.
|
|
1924
|
-
for (const node of this.graph.nodes) {
|
|
1925
|
-
this.upsertNode(db, node);
|
|
1926
|
-
}
|
|
1927
|
-
for (const edge of this.graph.edges) {
|
|
1928
|
-
this.insertEdge(db, edge);
|
|
1929
|
-
}
|
|
1930
|
-
for (const [nodeId, score] of this.pageRankScores) {
|
|
1931
|
-
db.prepare(
|
|
1932
|
-
"INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
|
|
1933
|
-
).run(nodeId, score, Date.now());
|
|
1934
|
-
}
|
|
1935
|
-
})();
|
|
1998
|
+
migrateInMemoryToSqlite(db, this.graph, this.pageRankScores);
|
|
1936
1999
|
} catch {
|
|
1937
2000
|
}
|
|
1938
2001
|
}
|
|
1939
|
-
loadedFromSqlite = true;
|
|
1940
2002
|
}
|
|
2003
|
+
loaded = true;
|
|
1941
2004
|
}
|
|
1942
2005
|
} catch {
|
|
1943
2006
|
}
|
|
1944
|
-
if (!
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
if (this.graph.edges.length > 0) {
|
|
1949
|
-
this.initIncrementalPR();
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
// ─── JSON Fallback Persistence (legacy / migration) ───────────────
|
|
1953
|
-
saveToDiskJson() {
|
|
1954
|
-
try {
|
|
1955
|
-
const dir = getGraphDir();
|
|
1956
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1957
|
-
fs.writeFileSync(getGraphPath(), JSON.stringify(this.graph, null, " "), "utf-8");
|
|
1958
|
-
this.saveEmbeddingsJson();
|
|
1959
|
-
const pageRankObj = {};
|
|
1960
|
-
for (const [key, value] of this.pageRankScores) {
|
|
1961
|
-
pageRankObj[key] = value;
|
|
1962
|
-
}
|
|
1963
|
-
fs.writeFileSync(getPageRankPath(), JSON.stringify(pageRankObj, null, " "), "utf-8");
|
|
1964
|
-
} catch {
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
saveEmbeddingsJson() {
|
|
1968
|
-
try {
|
|
1969
|
-
const dir = getGraphDir();
|
|
1970
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1971
|
-
const embeddings = {};
|
|
1972
|
-
for (const [key, value] of this.embeddingCache) {
|
|
1973
|
-
embeddings[key] = value;
|
|
1974
|
-
}
|
|
1975
|
-
fs.writeFileSync(getEmbeddingsPath(), JSON.stringify(embeddings, null, " "), "utf-8");
|
|
1976
|
-
} catch {
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
loadFromDiskJson() {
|
|
1980
|
-
try {
|
|
1981
|
-
const graphPath = getGraphPath();
|
|
1982
|
-
if (fs.existsSync(graphPath)) {
|
|
1983
|
-
const raw = fs.readFileSync(graphPath, "utf-8");
|
|
1984
|
-
this.graph = JSON.parse(raw);
|
|
1985
|
-
}
|
|
1986
|
-
} catch {
|
|
1987
|
-
this.graph = { nodes: [], edges: [] };
|
|
1988
|
-
}
|
|
1989
|
-
try {
|
|
1990
|
-
const prPath = getPageRankPath();
|
|
1991
|
-
if (fs.existsSync(prPath)) {
|
|
1992
|
-
const raw = fs.readFileSync(prPath, "utf-8");
|
|
1993
|
-
const ranks = JSON.parse(raw);
|
|
1994
|
-
for (const [key, value] of Object.entries(ranks)) {
|
|
1995
|
-
this.pageRankScores.set(key, value);
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
} catch {
|
|
1999
|
-
this.pageRankScores = /* @__PURE__ */ new Map();
|
|
2007
|
+
if (!loaded) {
|
|
2008
|
+
const jsonData = loadFromJson();
|
|
2009
|
+
this.graph = jsonData.graph;
|
|
2010
|
+
this.pageRankScores = jsonData.pageRankScores;
|
|
2000
2011
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
try {
|
|
2004
|
-
const embPath = getEmbeddingsPath();
|
|
2005
|
-
if (fs.existsSync(embPath)) {
|
|
2006
|
-
const raw = fs.readFileSync(embPath, "utf-8");
|
|
2007
|
-
const embeddings = JSON.parse(raw);
|
|
2008
|
-
for (const [key, value] of Object.entries(embeddings)) {
|
|
2009
|
-
this.embeddingCache.set(key, value);
|
|
2010
|
-
}
|
|
2011
|
-
while (this.embeddingCache.size > this.maxEmbeddingCacheSize) {
|
|
2012
|
-
const oldest = this.embeddingCache.keys().next().value;
|
|
2013
|
-
if (oldest !== void 0) {
|
|
2014
|
-
this.embeddingCache.delete(oldest);
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
} catch {
|
|
2019
|
-
this.embeddingCache = /* @__PURE__ */ new Map();
|
|
2012
|
+
for (const [k, v] of loadEmbeddingsJson(this.maxEmbeddingCacheSize)) {
|
|
2013
|
+
this.embeddingCache.set(k, v);
|
|
2020
2014
|
}
|
|
2015
|
+
if (this.graph.edges.length > 0) this.initIncrementalPR();
|
|
2021
2016
|
}
|
|
2017
|
+
/** Get the current in-memory knowledge graph. */
|
|
2022
2018
|
getGraph() {
|
|
2023
2019
|
return this.graph;
|
|
2024
2020
|
}
|
|
2021
|
+
/** Clear all graph data, caches, and persisted state. */
|
|
2025
2022
|
async clear() {
|
|
2026
2023
|
this.graph = { nodes: [], edges: [] };
|
|
2027
2024
|
this.embeddingCache = /* @__PURE__ */ new Map();
|
|
2028
2025
|
this.pageRankScores = /* @__PURE__ */ new Map();
|
|
2029
2026
|
this.incrementalPR = null;
|
|
2030
2027
|
try {
|
|
2031
|
-
const db = this.
|
|
2032
|
-
if (db)
|
|
2033
|
-
db.exec("DELETE FROM pagerank; DELETE FROM edges; DELETE FROM nodes;");
|
|
2034
|
-
}
|
|
2028
|
+
const db = this.getGraphDbHandle();
|
|
2029
|
+
if (db) db.exec("DELETE FROM pagerank; DELETE FROM edges; DELETE FROM nodes;");
|
|
2035
2030
|
} catch {
|
|
2036
2031
|
}
|
|
2037
2032
|
this.saveToDisk();
|
|
2038
2033
|
}
|
|
2034
|
+
/** Reset the Ollama availability flag to force re-check. */
|
|
2039
2035
|
resetAvailability() {
|
|
2040
2036
|
this.ollamaAvailable = null;
|
|
2041
2037
|
this.embeddingService.resetAvailability();
|
|
2042
2038
|
}
|
|
2043
|
-
// ─── Graph Neighbor Queries ───────────────────────────────────────
|
|
2044
2039
|
/**
|
|
2045
2040
|
* Get edges connected to a node, optionally filtered by direction.
|
|
2046
2041
|
* Uses the in-memory graph for fast access.
|
|
@@ -2053,94 +2048,10 @@ var GraphRAGEngine = class {
|
|
|
2053
2048
|
});
|
|
2054
2049
|
}
|
|
2055
2050
|
};
|
|
2056
|
-
function migrateGraphJson() {
|
|
2057
|
-
const graphPath = getGraphPath();
|
|
2058
|
-
const prPath = getPageRankPath();
|
|
2059
|
-
if (!fs.existsSync(graphPath)) {
|
|
2060
|
-
return { nodes: 0, edges: 0 };
|
|
2061
|
-
}
|
|
2062
|
-
try {
|
|
2063
|
-
const raw = fs.readFileSync(graphPath, "utf-8");
|
|
2064
|
-
const graph = JSON.parse(raw);
|
|
2065
|
-
if (!graph.nodes || graph.nodes.length === 0) {
|
|
2066
|
-
return { nodes: 0, edges: 0 };
|
|
2067
|
-
}
|
|
2068
|
-
const pageRankScores = /* @__PURE__ */ new Map();
|
|
2069
|
-
try {
|
|
2070
|
-
if (fs.existsSync(prPath)) {
|
|
2071
|
-
const prRaw = fs.readFileSync(prPath, "utf-8");
|
|
2072
|
-
const ranks = JSON.parse(prRaw);
|
|
2073
|
-
for (const [key, value] of Object.entries(ranks)) {
|
|
2074
|
-
pageRankScores.set(key, value);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
} catch {
|
|
2078
|
-
}
|
|
2079
|
-
const dbm = DatabaseManager.instance();
|
|
2080
|
-
initGraphSchema(dbm);
|
|
2081
|
-
const db = dbm.get("graph");
|
|
2082
|
-
db.transaction(() => {
|
|
2083
|
-
const upsertNodeStmt = db.prepare(`
|
|
2084
|
-
INSERT OR REPLACE INTO nodes (id, type, label, content, metadata, created_at, updated_at)
|
|
2085
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2086
|
-
`);
|
|
2087
|
-
const insertEdgeStmt = db.prepare(`
|
|
2088
|
-
INSERT OR IGNORE INTO edges (source, target, relationship, weight, pramana, viveka,
|
|
2089
|
-
valid_from, valid_until, recorded_at, superseded_at)
|
|
2090
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2091
|
-
`);
|
|
2092
|
-
const upsertPRStmt = db.prepare(
|
|
2093
|
-
"INSERT OR REPLACE INTO pagerank (node_id, score, updated_at) VALUES (?, ?, ?)"
|
|
2094
|
-
);
|
|
2095
|
-
const now = Date.now();
|
|
2096
|
-
for (const node of graph.nodes) {
|
|
2097
|
-
upsertNodeStmt.run(
|
|
2098
|
-
node.id,
|
|
2099
|
-
node.type,
|
|
2100
|
-
node.label,
|
|
2101
|
-
node.content,
|
|
2102
|
-
JSON.stringify(node.metadata),
|
|
2103
|
-
now,
|
|
2104
|
-
now
|
|
2105
|
-
);
|
|
2106
|
-
}
|
|
2107
|
-
for (const edge of graph.edges) {
|
|
2108
|
-
insertEdgeStmt.run(
|
|
2109
|
-
edge.source,
|
|
2110
|
-
edge.target,
|
|
2111
|
-
edge.relationship,
|
|
2112
|
-
edge.weight,
|
|
2113
|
-
null,
|
|
2114
|
-
null,
|
|
2115
|
-
edge.validFrom ? new Date(edge.validFrom).getTime() : now,
|
|
2116
|
-
edge.validUntil ? new Date(edge.validUntil).getTime() : null,
|
|
2117
|
-
edge.recordedAt ? new Date(edge.recordedAt).getTime() : now,
|
|
2118
|
-
edge.supersededAt ? new Date(edge.supersededAt).getTime() : null
|
|
2119
|
-
);
|
|
2120
|
-
}
|
|
2121
|
-
for (const [nodeId, score] of pageRankScores) {
|
|
2122
|
-
upsertPRStmt.run(nodeId, score, now);
|
|
2123
|
-
}
|
|
2124
|
-
})();
|
|
2125
|
-
try {
|
|
2126
|
-
fs.renameSync(graphPath, graphPath + ".bak");
|
|
2127
|
-
} catch {
|
|
2128
|
-
}
|
|
2129
|
-
try {
|
|
2130
|
-
if (fs.existsSync(prPath)) {
|
|
2131
|
-
fs.renameSync(prPath, prPath + ".bak");
|
|
2132
|
-
}
|
|
2133
|
-
} catch {
|
|
2134
|
-
}
|
|
2135
|
-
return { nodes: graph.nodes.length, edges: graph.edges.length };
|
|
2136
|
-
} catch {
|
|
2137
|
-
return { nodes: 0, edges: 0 };
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
2051
|
|
|
2141
2052
|
export {
|
|
2142
|
-
computePersonalizedPageRank,
|
|
2143
2053
|
IncrementalPageRank,
|
|
2054
|
+
computePersonalizedPageRank,
|
|
2144
2055
|
NERExtractor,
|
|
2145
2056
|
createEdge,
|
|
2146
2057
|
supersedEdge,
|
|
@@ -2154,7 +2065,7 @@ export {
|
|
|
2154
2065
|
communitySummary,
|
|
2155
2066
|
filterByCommunity,
|
|
2156
2067
|
findBridgeNodes,
|
|
2157
|
-
|
|
2158
|
-
|
|
2068
|
+
migrateGraphJson,
|
|
2069
|
+
GraphRAGEngine
|
|
2159
2070
|
};
|
|
2160
|
-
//# sourceMappingURL=chunk-
|
|
2071
|
+
//# sourceMappingURL=chunk-OT4G2L46.js.map
|