Repository navigation
Expand file tree
/
Copy pathTagMemoEngine.js
More file actions
4137 lines (3796 loc) · 197 KB
/
Copy pathTagMemoEngine.js
File metadata and controls
4137 lines (3796 loc) · 197 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// TagMemoEngine.js
// 🌟 浪潮算法独立模块 (TagMemo Engine)
// 包含:浪潮增强、EPA 投影、残差金字塔分析、有序双向共现矩阵 (V8.2)、脉冲传播等核心逻辑
const path = require('path');
const crypto = require('crypto');
const EPAModule = require('./EPAModule');
const ResidualPyramid = require('./ResidualPyramid');
class TagMemoEngine {
constructor(db, tagIndex, config, ragParams, knowledgeBaseManager = null) {
this.db = db;
this.tagIndex = tagIndex;
this.config = config;
this.ragParams = ragParams;
this.knowledgeBaseManager = knowledgeBaseManager;
this.epa = null;
this.residualPyramid = null;
this.tagCooccurrenceMatrix = null;
// V9.1 单轨锚增益与数据库无关的原始残差比例。
this.tagIntrinsicResiduals = null;
this.tagRawResidualRatios = null;
this.intrinsicResidualArtifact = null;
// TagMemo V9.1: RCU 风格单轨活动资产包。
// 发布后对象及其 Map 只读;重建始终创建全新的 Map,再一次性替换此指针。
this._activeArtifactBundle = null;
this._artifactBundlesByVersion = Object.freeze({});
this._artifactBundleGeneration = 0;
// 🌟 TagMemo V7.1: 矩阵计算防抖系统
// V8.3: 阈值触发改为“唯一新增 tag”Set 累积,而不是 file_tags 关系数累加。
// 共现矩阵仍以 file_tags 组关系为真相;这里只负责判断“是否真的出现了足够多没见过的新 tag”。
this._accumulatedTagChanges = 0; // legacy 诊断字段,不再作为阈值主依据
this._accumulatedNewTagIds = new Set();
this._matrixRebuildTimer = null;
this._matrixRebuildScheduleLogged = false;
this._isMatrixRebuilding = false;
// 🌟 V8: 最近一次距离场缓存(仅保留兼容/诊断用途;搜索链路必须使用查询级 energyField,避免 await 并发污染)
this.lastEnergyField = null;
this.lastEnergyFieldProvenance = null;
// 🌟 V8.2-γ: 持久化的 Tag 对语义距离 (内存 Map: "a:b" → cosineSim)
// 边视角的语义邻近度,与 tagIntrinsicResiduals (节点视角) 正交。
this.tagPairSimilarities = new Map();
// embedding 模型签名 (含维度),跨模型自动失效
this.modelSig = this._computeModelSig();
// 是否在本进程内已经触发过冷启动 sim 预计算
this._pairSimColdStartDone = false;
this._postStartupDerivedRefreshTimer = null;
this._derivedTaskQueue = [];
this._derivedTaskRunning = false;
this._derivedTaskTimer = null;
this._derivedTaskSeq = 0;
this._shutdownRequested = false;
}
_envFlag(name, defaultValue = false) {
const raw = process.env[name];
if (raw === undefined || raw === null || raw === '') return defaultValue;
const normalized = String(raw).trim().toLowerCase();
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
}
_isEpaBackgroundRecomputeEnabled() {
return this._envFlag('KNOWLEDGEBASE_EPA_BACKGROUND_RECOMPUTE', false);
}
_isIntrinsicResidualRecomputeEnabled() {
return this._envFlag('TAGMEMO_INTRINSIC_RESIDUAL_FORCE_RECOMPUTE', false);
}
_isIntrinsicResidualThresholdRecomputeEnabled() {
return this._envFlag('TAGMEMO_IR_RECOMPUTE_ON_THRESHOLD', true);
}
_getMatrixRebuildQuietMs() {
const raw = Number(process.env.TAGMEMO_MATRIX_REBUILD_QUIET_MS);
if (!Number.isFinite(raw)) return 300000;
return Math.max(0, Math.floor(raw));
}
_hasWarmDerivedCaches() {
const epaReady = !!(this.epa && this.epa.initialized && this.epa.orthoBasis && this.epa.orthoBasis.length > 0);
// Rust 原生 Memo 重构后,生产图资产由 VexusIndex.memoRuntime 持有。
// publishNativeArtifactHandle() 会有意释放 Pairwise/IR/Matrix 的 JS Map,
// 因此不能再用这些兼容 Map 是否为空来判断原生生产资产是否就绪。
const nativeMemoReady = !!(
this._activeArtifactBundle
&& this._activeArtifactBundle.storageMode === 'rust-memo-runtime'
&& this._activeArtifactBundle.artifactSig
);
const pairwiseReady = nativeMemoReady
|| (this.tagPairSimilarities instanceof Map && this.tagPairSimilarities.size > 0);
const intrinsicReady = nativeMemoReady
|| (this.tagIntrinsicResiduals instanceof Map && this.tagIntrinsicResiduals.size > 0);
const matrixReady = nativeMemoReady
|| (this.tagCooccurrenceMatrix instanceof Map && this.tagCooccurrenceMatrix.size > 0);
return {
epaReady,
pairwiseReady,
intrinsicReady,
matrixReady,
nativeMemoReady
};
}
_shouldSkipPostStartupDerivedRefresh() {
const epaHotOff = !this._isEpaBackgroundRecomputeEnabled();
const irHotOff = !this._isIntrinsicResidualRecomputeEnabled();
const caches = this._hasWarmDerivedCaches();
const noTagChanges = this._accumulatedNewTagIds.size <= 0;
return {
skip: epaHotOff && irHotOff && noTagChanges && caches.epaReady && caches.pairwiseReady && caches.intrinsicReady && caches.matrixReady,
epaHotOff,
irHotOff,
noTagChanges,
...caches
};
}
/**
* 🌟 V8.2: 计算 embedding 模型签名(必须包含维度,
* 防止 VECTORDB_DIMENSION 切换后读到维度错位的 BLOB)
*/
_computeModelSig() {
// EmbeddingModelSig 表示“向量语义空间签名”,与实际请求渠道解耦。
// 未配置时回退到主 embedding 模型名,保持旧版本行为。
const modelName = this.config?.modelSig || this.config?.model || 'unknown-model';
const dim = this.config?.dimension || 0;
return crypto.createHash('sha256')
.update(`${modelName}:${dim}`)
.digest('hex')
.slice(0, 16);
}
_decodeVectorBlob(blob, dim, label = 'vector') {
if (blob instanceof Float32Array) {
return blob.length === dim ? blob : null;
}
if (!blob || typeof blob.length !== 'number') {
return null;
}
const expectedBytes = dim * Float32Array.BYTES_PER_ELEMENT;
if (blob.length !== expectedBytes) {
console.warn(`[TagMemoEngine] ⚠️ Invalid ${label} blob length: expected ${expectedBytes}, got ${blob.length}`);
return null;
}
if (blob.byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
return new Float32Array(blob.buffer, blob.byteOffset, dim);
}
const copied = Buffer.from(blob);
return new Float32Array(copied.buffer, copied.byteOffset, dim);
}
_queryByChunks(sqlPrefix, values, sqlSuffix = '', chunkSize = 500) {
if (!Array.isArray(values) || values.length === 0) return [];
const rows = [];
for (let i = 0; i < values.length; i += chunkSize) {
const batch = values.slice(i, i + chunkSize);
const placeholders = batch.map(() => '?').join(',');
rows.push(...this.db.prepare(`${sqlPrefix} IN (${placeholders})${sqlSuffix}`).all(...batch));
}
return rows;
}
_deepFreezeConfig(value) {
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
for (const child of Object.values(value)) this._deepFreezeConfig(child);
return Object.freeze(value);
}
_computeGraphGeneration(matrix, pairwiseView, residualMap) {
let edgeCount = 0;
let edgeMass = 0;
if (matrix instanceof Map) {
for (const edges of matrix.values()) {
if (!(edges instanceof Map)) continue;
edgeCount += edges.size;
for (const weight of edges.values()) edgeMass += Number(weight) || 0;
}
}
return [
`sources:${matrix instanceof Map ? matrix.size : 0}`,
`edges:${edgeCount}`,
`edgeMass:${edgeMass.toFixed(8)}`,
`pairs:${pairwiseView instanceof Map ? pairwiseView.size : 0}`,
`residuals:${residualMap instanceof Map ? residualMap.size : 0}`
].join('|');
}
_validateArtifactBundle(bundle) {
if (!bundle || bundle.version !== 'v9') {
throw new Error('ArtifactBundle must be the V9.1 production asset');
}
if (!bundle.artifactSig || !bundle.graphGeneration) {
throw new Error('ArtifactBundle is missing signature/generation');
}
if (!(bundle.anchorGainMap instanceof Map) || !(bundle.propagationKernel instanceof Map)) {
throw new Error('ArtifactBundle is missing anchor-gain/kernel Map');
}
if (!(bundle.rawResidualRatioMap instanceof Map)) {
throw new Error('ArtifactBundle is missing raw residual ratio Map');
}
if (!(bundle.inboundMassMap instanceof Map) || !(bundle.hubSpecificityBaseMap instanceof Map)) {
throw new Error('ArtifactBundle is missing precomputed inbound/hub-specificity Map');
}
if (!(bundle.pairwiseView instanceof Map)) {
throw new Error('ArtifactBundle is missing pairwise Map');
}
for (const [source, edges] of bundle.propagationKernel.entries()) {
if (!(edges instanceof Map)) {
throw new Error(`ArtifactBundle kernel source ${source} is not a Map`);
}
for (const [target, weight] of edges.entries()) {
if (!Number.isFinite(Number(weight)) || Number(weight) < 0) {
throw new Error(`ArtifactBundle kernel has invalid edge ${source}->${target}`);
}
}
}
return true;
}
_preparePublishedBundle(staging, generation, publishedAt) {
this._validateArtifactBundle(staging);
return Object.freeze({
...staging,
effectiveConfig: this._deepFreezeConfig(staging.effectiveConfig),
potentialFieldConfig: this._deepFreezeConfig(staging.potentialFieldConfig),
publishedAt,
generation
});
}
releaseNativeOwnedArtifactAssets(expectedArtifactSig = null) {
const active = this._activeArtifactBundle;
if (!active) return false;
if (
expectedArtifactSig
&& active.artifactSig !== expectedArtifactSig
) {
return false;
}
if (active.storageMode === 'rust-memo-runtime') return true;
const lightweight = Object.freeze({
version: active.version,
artifactSig: active.artifactSig,
graphGeneration: active.graphGeneration,
modelSig: active.modelSig,
effectiveConfig: active.effectiveConfig,
potentialFieldConfig: active.potentialFieldConfig,
residualArtifact: active.residualArtifact,
algorithmVersion: active.algorithmVersion,
generation: active.generation,
publishedAt: active.publishedAt,
storageMode: 'rust-memo-runtime'
});
this._activeArtifactBundle = lightweight;
this._artifactBundlesByVersion = Object.freeze({
generation: lightweight.generation,
publishedAt: lightweight.publishedAt,
activeVersion: 'v9',
bundles: Object.freeze({ v9: lightweight })
});
// 断开所有兼容别名,确保 V9 Map、pairwise 和 residual 可被 GC。
this.tagCooccurrenceMatrix = null;
this.tagIntrinsicResiduals = null;
this.tagRawResidualRatios = null;
this.tagPairSimilarities = new Map();
this.lastEnergyField = null;
this.lastEnergyFieldProvenance = null;
console.log(
`[TagMemoEngine] 🦀 JS graph assets released; Rust MemoRuntime owns ` +
`artifact=${lightweight.artifactSig}, generation=${lightweight.generation}.`
);
return true;
}
_assertJsGraphRuntimeAvailable(operation) {
if (this._activeArtifactBundle?.storageMode === 'rust-memo-runtime') {
const error = new Error(
`${operation} uses the retired JS graph runtime; use the asynchronous unified Rust Memo API`
);
error.code = 'TAGMEMO_JS_GRAPH_RUNTIME_RETIRED';
throw error;
}
}
publishNativeArtifactHandle(nativeResult, effectiveConfig = {}) {
if (
!nativeResult?.artifactSig
|| !nativeResult?.sourceArtifactSig
|| nativeResult.persisted !== true
) {
throw new TypeError(
'Native Memo control publication requires persisted artifact/source signatures'
);
}
const generation = ++this._artifactBundleGeneration;
const publishedAt = Date.now();
const lightweight = Object.freeze({
version: 'v9',
artifactSig: nativeResult.sourceArtifactSig,
graphGeneration: nativeResult.graphGeneration,
modelSig: this.modelSig,
effectiveConfig: this._deepFreezeConfig(
JSON.parse(JSON.stringify(effectiveConfig || {}))
),
potentialFieldConfig: this._deepFreezeConfig(
JSON.parse(JSON.stringify(
effectiveConfig?.potentialFieldRerank
|| effectiveConfig?.geodesicRerank
|| {}
))
),
residualArtifact: this.intrinsicResidualArtifact
? Object.freeze({ ...this.intrinsicResidualArtifact })
: null,
algorithmVersion: 'v9.1-rust-native',
generation,
nativeGeneration:
Number(nativeResult.generation) || null,
residentAtPublication: nativeResult.resident === true,
publishedAt,
storageMode: 'rust-memo-runtime'
});
this._activeArtifactBundle = lightweight;
this._artifactBundlesByVersion = Object.freeze({
generation,
publishedAt,
activeVersion: 'v9',
bundles: Object.freeze({ v9: lightweight })
});
// 原生构建成功后,JS 不再重新加载或保留图、pairwise、residual 资产。
this.tagCooccurrenceMatrix = null;
this.tagIntrinsicResiduals = null;
this.tagRawResidualRatios = null;
this.tagPairSimilarities = new Map();
this.lastEnergyField = null;
this.lastEnergyFieldProvenance = null;
console.log(
`[TagMemoEngine] 🦀 Native V9 control handle published: ` +
`generation=${generation}, artifact=${lightweight.artifactSig}, ` +
`runtimeArtifact=${nativeResult.artifactSig}, ` +
`resident=${lightweight.residentAtPublication}`
);
return lightweight;
}
_publishArtifactBundle(staging) {
const generation = ++this._artifactBundleGeneration;
const publishedAt = Date.now();
const bundle = this._preparePublishedBundle(staging, generation, publishedAt);
// 单次引用替换发布完整 V9.1 资产,杜绝 residual/kernel/config 分别换代。
const registry = Object.freeze({
generation,
publishedAt,
activeVersion: 'v9',
bundles: Object.freeze({ v9: bundle })
});
this._artifactBundlesByVersion = registry;
this._activeArtifactBundle = bundle;
this.tagCooccurrenceMatrix = bundle.propagationKernel;
this.tagIntrinsicResiduals = bundle.anchorGainMap;
this.tagRawResidualRatios = bundle.rawResidualRatioMap;
this.tagPairSimilarities = bundle.pairwiseView;
console.log(
`[TagMemoEngine] 📦 V9.1 production artifact published atomically: ` +
`generation=${generation}, artifact=${bundle.artifactSig}`
);
// RiverMemo 是 V9 的伴生派生资产。V9 必须先独立原子发布;
// 伴生编译/落库失败只影响 RiverMemo,不得回滚已经健康的 V9。
if (
this.knowledgeBaseManager
&& typeof this.knowledgeBaseManager
.onTagMemoArtifactPublished === 'function'
) {
try {
const companion =
this.knowledgeBaseManager.onTagMemoArtifactPublished(
bundle,
registry
);
if (companion?.artifactSig) {
this.releaseNativeOwnedArtifactAssets(bundle.artifactSig);
}
} catch (error) {
console.error(
'[TagMemoEngine] ⚠️ RiverMemo companion build failed after V9 publish; V9 remains active:',
error.message || error
);
}
}
return registry;
}
getArtifactBundleSnapshot(version = null) {
const requestedVersion = version || 'v9';
if (requestedVersion !== 'v9') return null;
return this._artifactBundlesByVersion?.bundles?.v9 || this._activeArtifactBundle;
}
resolveArtifactBundle(options = {}) {
const requestedVersion = options.version || 'v9';
if (requestedVersion !== 'v9') {
const error = new Error(
`TagMemo artifact version "${requestedVersion}" was retired; V9.1 is the only supported production version`
);
error.code = 'TAGMEMO_VERSION_RETIRED';
error.requestedVersion = requestedVersion;
throw error;
}
const bundle = this.getArtifactBundleSnapshot('v9');
if (!bundle) {
const error = new Error('V9.1 TagMemo artifact bundle is unavailable');
error.code = 'TAGMEMO_ARTIFACT_UNAVAILABLE';
throw error;
}
return {
bundle,
requestedVersion: 'v9',
effectiveVersion: 'v9',
fallbackUsed: false,
fallbackReason: null,
explicitVersion: options.version !== undefined && options.version !== null,
strictVersion: true
};
}
_buildV9PropagationKernel(factMatrix, residualMap, v9Config = {}) {
const kernel = new Map();
const wormholeEdges = new Set();
const outboundMass = Math.max(0.01, Math.min(1, Number(v9Config.outboundMass ?? 0.95)));
// V9.1 仍让虫洞在总预算内竞争;association reserve 不产生额外能量。
const associationReserveMass = Math.min(
Math.max(0, Number(v9Config.associationReserveMass ?? 0.05)),
outboundMass
);
const evidenceCompression = Math.max(0.01, Number(v9Config.evidenceCompression ?? 1));
const wormholeGain = Math.max(1, Number(v9Config.wormholeGain ?? 1.35));
const tensionThreshold = Math.max(0, Number(v9Config.tensionThreshold ?? 1));
const hubPenaltyExponent = Math.max(0, Math.min(1, Number(v9Config.hubPenaltyExponent ?? 0.3)));
const hubPenaltyFloor = Math.max(0.05, Math.min(1, Number(v9Config.hubPenaltyFloor ?? 0.55)));
const hubPenaltyCeiling = Math.max(1, Math.min(4, Number(v9Config.hubPenaltyCeiling ?? 1.8)));
const hubSmoothingRatio = Math.max(0.01, Math.min(2, Number(v9Config.hubSmoothingRatio ?? 0.1)));
// 第一遍:保留每条边的未归一化证据,并统计目标节点吸收的全图入流。
// 入流统计必须发生在行归一化之前,否则无法识别“从许多来源吸积少量质量”的通用枢纽。
const rawRows = new Map();
const targetInflows = new Map();
for (const [sourceId, edges] of factMatrix.entries()) {
if (!(edges instanceof Map) || edges.size === 0) continue;
const rawEdges = [];
for (const [targetId, compatWeight] of edges.entries()) {
const evidence = Math.log1p(Math.max(0, Number(compatWeight) || 0) * evidenceCompression);
const residual = residualMap?.get(targetId) ?? 1;
const isWormhole = evidence * residual >= tensionThreshold;
const rawConductance = evidence * (isWormhole ? wormholeGain : 1);
if (!Number.isFinite(rawConductance) || rawConductance <= 0) continue;
rawEdges.push([targetId, rawConductance, isWormhole]);
targetInflows.set(targetId, (targetInflows.get(targetId) || 0) + rawConductance);
}
if (rawEdges.length > 0) rawRows.set(sourceId, rawEdges);
}
const positiveInflows = [...targetInflows.values()]
.filter(value => Number.isFinite(value) && value > 0)
.sort((a, b) => a - b);
const medianInflow = positiveInflows.length > 0
? positiveInflows[Math.floor(positiveInflows.length / 2)]
: 1;
const smoothing = Math.max(1e-9, medianInflow * hubSmoothingRatio);
// 第二遍:按“相对中位入流”温和抑制枢纽,再统一归一化到固定行预算。
// 夹逼防止罕见节点被无限奖励,也防止真实核心概念被过度压低。
for (const [sourceId, rawEdges] of rawRows.entries()) {
const adjustedEdges = [];
let adjustedSum = 0;
let wormholeAdjustedSum = 0;
for (const [targetId, rawConductance, isWormhole] of rawEdges) {
const relativeInflow = (targetInflows.get(targetId) || 0) / (medianInflow + smoothing);
const rawPenalty = hubPenaltyExponent > 0
? Math.pow(Math.max(1e-9, relativeInflow), -hubPenaltyExponent)
: 1;
const hubPenalty = Math.max(hubPenaltyFloor, Math.min(hubPenaltyCeiling, rawPenalty));
const adjustedConductance = rawConductance * hubPenalty;
if (!Number.isFinite(adjustedConductance) || adjustedConductance <= 0) continue;
adjustedEdges.push([targetId, adjustedConductance, isWormhole]);
adjustedSum += adjustedConductance;
if (isWormhole) wormholeAdjustedSum += adjustedConductance;
}
if (adjustedSum <= 0) continue;
const normalizedEdges = new Map();
const reserveMass = wormholeAdjustedSum > 0 ? associationReserveMass : 0;
const mainMass = outboundMass - reserveMass;
for (const [targetId, adjustedConductance, isWormhole] of adjustedEdges) {
const mainConductance = mainMass * adjustedConductance / adjustedSum;
const associationConductance = isWormhole && wormholeAdjustedSum > 0
? reserveMass * adjustedConductance / wormholeAdjustedSum
: 0;
normalizedEdges.set(targetId, mainConductance + associationConductance);
if (isWormhole) wormholeEdges.add(`${sourceId}:${targetId}`);
}
kernel.set(sourceId, normalizedEdges);
}
return {
kernel,
wormholeEdges,
outboundMass,
kernelDiagnostics: Object.freeze({
algorithmVersion: 'v9.1-hub-aware',
medianInflow,
targetCount: targetInflows.size,
hubPenaltyExponent,
hubPenaltyFloor,
hubPenaltyCeiling
})
};
}
_buildInboundArtifacts(kernel) {
const inboundMassMap = new Map();
let maxInbound = 0;
if (kernel instanceof Map) {
for (const edges of kernel.values()) {
if (!(edges instanceof Map)) continue;
for (const [rawTargetId, rawConductance] of edges.entries()) {
const targetId = Number(rawTargetId);
const conductance = Math.max(0, Number(rawConductance) || 0);
if (!Number.isFinite(targetId) || conductance <= 0) continue;
const next = (inboundMassMap.get(targetId) || 0) + conductance;
inboundMassMap.set(targetId, next);
if (next > maxInbound) maxInbound = next;
}
}
}
// 不带 publicHubFloor 的基础 specificity,只依赖图资产;查询热参数可在 O(1) 读取后施加 floor。
const hubSpecificityBaseMap = new Map();
for (const [tagId, inboundMass] of inboundMassMap.entries()) {
const ratio = maxInbound > 0 ? Math.max(0, Math.min(1, inboundMass / maxInbound)) : 0;
hubSpecificityBaseMap.set(tagId, 1 - Math.sqrt(ratio));
}
return { inboundMassMap, maxInbound, hubSpecificityBaseMap };
}
_stageAndPublishV91Bundle(factMatrix) {
const pairwiseView = this.tagPairSimilarities instanceof Map
? this.tagPairSimilarities
: new Map();
const anchorGainMap = this.tagIntrinsicResiduals instanceof Map
? this.tagIntrinsicResiduals
: new Map();
const rawResidualRatioMap = this.tagRawResidualRatios instanceof Map
? this.tagRawResidualRatios
: new Map();
const kbConfig = JSON.parse(JSON.stringify(
this.ragParams?.KnowledgeBaseManager || {}
));
const v9Config = kbConfig.v9 || {};
const potentialFieldConfig = kbConfig.potentialFieldRerank
|| kbConfig.geodesicRerank
|| {};
const build = this._buildV9PropagationKernel(factMatrix, anchorGainMap, v9Config);
const inboundArtifacts = this._buildInboundArtifacts(build.kernel);
const graphGeneration = this._computeGraphGeneration(
build.kernel,
pairwiseView,
anchorGainMap
);
const effectiveConfig = JSON.parse(JSON.stringify(kbConfig));
const artifactSig = crypto.createHash('sha256')
.update(JSON.stringify({
version: 'v9',
algorithmVersion: 'v9.1',
modelSig: this.modelSig,
graphGeneration,
residualArtifact: this.intrinsicResidualArtifact?.artifactSig || 'legacy',
effectiveConfig
}))
.digest('hex')
.slice(0, 24);
return this._publishArtifactBundle({
version: 'v9',
artifactSig,
graphGeneration,
modelSig: this.modelSig,
effectiveConfig,
// 明确物理量语义;residualMap 仅保留为旧调用兼容别名。
anchorGainMap,
rawResidualRatioMap,
residualMap: anchorGainMap,
propagationKernel: build.kernel,
inboundMassMap: inboundArtifacts.inboundMassMap,
maxInbound: inboundArtifacts.maxInbound,
hubSpecificityBaseMap: inboundArtifacts.hubSpecificityBaseMap,
pairwiseView,
potentialFieldConfig: JSON.parse(JSON.stringify(potentialFieldConfig)),
residualArtifact: this.intrinsicResidualArtifact
? Object.freeze({ ...this.intrinsicResidualArtifact })
: null,
wormholeEdges: build.wormholeEdges,
outboundMass: build.outboundMass,
kernelDiagnostics: build.kernelDiagnostics,
algorithmVersion: 'v9.1'
});
}
async initialize() {
// 初始化 EPA 和残差金字塔模块
this.epa = new EPAModule(this.db, {
dimension: this.config.dimension,
vexusIndex: this.tagIndex,
nodeResidual: this.ragParams.KnowledgeBaseManager?.nodeResidualGain || 0.05,
withRustWriteLease: (owner, fn, options = {}) => this._withRustWriteLease(owner, fn, options),
deferRustRecompute: true,
});
await this.epa.initialize();
this.residualPyramid = new ResidualPyramid(this.tagIndex, this.db, {
dimension: this.config.dimension
});
// 🌟 V8.2-γ: 冷启动只做检测,不在 initialize() 内阻塞派生计算。
// 大库下 pairwise/EPA 派生写会延后到 System Ready + startup cooldown 后由后台刷新触发,
// 以避免和启动 full scan / 小巴士主写产生 WAL/checkpoint 竞态。
try {
const cnt = this.db.prepare(
'SELECT COUNT(*) as c FROM tag_pair_similarity WHERE model_sig = ?'
).get(this.modelSig)?.c || 0;
if (cnt === 0) {
console.log(`[TagMemoEngine] 🧊 V8.2 cold start: pairwise similarity cache empty for model_sig=${this.modelSig}; will refresh after startup cooldown.`);
} else {
console.log(`[TagMemoEngine] 🌡️ V8.2 warm start: ${cnt} cached pairwise similarities for model_sig=${this.modelSig}`);
}
} catch (e) {
console.warn('[TagMemoEngine] ⚠️ V8.2 cold start check failed (table may not exist yet):', e.message);
}
// 生产图资产已归属 VexusIndex.memoRuntime。启动阶段只初始化轻量
// 调度/分析门面;Pairwise、Residual、事实图和传播 Kernel 不再加载到 JS。
this.tagPairSimilarities = new Map();
this.tagIntrinsicResiduals = null;
this.tagRawResidualRatios = null;
this.tagCooccurrenceMatrix = null;
}
/**
* 更新热调控参数
*/
updateRagParams(params) {
this.ragParams = params;
if (this.epa) {
// 如果 EPA 支持动态更新参数,可以在这里调用
}
}
_propagateSpikes(initialTags, queryMatrix, queryResiduals, queryWormholeEdges, srConfig = {}) {
const MAX_SAFE_HOPS = srConfig.maxSafeHops ?? 4;
const BASE_MOMENTUM = srConfig.baseMomentum ?? 2.0;
const FIRING_THRESHOLD = srConfig.firingThreshold ?? 0.10;
const BASE_DECAY = srConfig.baseDecay ?? 0.25;
const WORMHOLE_DECAY = srConfig.wormholeDecay ?? 0.70;
const TENSION_THRESHOLD = srConfig.tensionThreshold ?? 1.0;
const MAX_NEIGHBORS_PER_NODE = srConfig.maxNeighborsPerNode ?? 20;
// V9.1 使用有前驱记忆的传播状态,精确识别 i→j→i 的立即回流。
// 状态数量设硬上限,防止边状态在高分支图中指数增长。
const returnFlowFactor = Math.max(0, Math.min(1, Number(srConfig.v91ReturnFlowFactor ?? 0.15)));
const firGamma = Math.max(0.05, Math.min(0.95, Number(srConfig.v91FirGamma ?? 0.6)));
const maxPropagationStates = Math.max(100, Math.floor(Number(srConfig.v91MaxPropagationStates ?? 2000)));
const firWeights = [];
let firWeightSum = 0;
for (let hop = 0; hop <= MAX_SAFE_HOPS; hop++) {
const weight = Math.pow(firGamma, hop);
firWeights.push(weight);
firWeightSum += weight;
}
if (firWeightSum > 0) {
for (let hop = 0; hop < firWeights.length; hop++) firWeights[hop] /= firWeightSum;
}
// key 为 prev:node,V9.1 使用前驱边状态抑制立即回流。
// provenance 记录查询场节点来自显式核心、原始种子还是第几跳涌现,
// 让读出层区分“直接事实证据”和“传播后的主题共振”。
let activeSpikes = new Map();
const accumulatedEnergy = new Map();
const fieldProvenance = new Map();
// V10 共享观测需要的不只是最终节点势,还需要查询诱导传播过程中
// 实际承载过质量的有向边。该资产严格请求级,不写入全局 Artifact。
const riverEdgeFlow = new Map();
const strongestParentByNode = new Map();
for (const tag of initialTags) {
const key = `seed:${tag.id}`;
const sourceType = tag.isCore ? 'core' : 'seed';
activeSpikes.set(key, {
nodeId: tag.id,
previousNodeId: null,
energy: tag.adjustedWeight,
momentum: BASE_MOMENTUM,
sourceType,
hop: 0
});
accumulatedEnergy.set(tag.id, tag.adjustedWeight * firWeights[0]);
fieldProvenance.set(Number(tag.id), {
sourceType,
hop: 0,
seedId: Number(tag.id)
});
}
const diagnostics = {
algorithmVersion: 'v9.1-soft-nonbacktracking-fir',
returnFlowSuppressedMass: 0,
stateTruncations: 0,
hopInFlightMass: []
};
for (let hop = 0; hop < MAX_SAFE_HOPS; hop++) {
const nextSpikes = new Map();
let propagated = false;
let inFlightMass = 0;
for (const spike of activeSpikes.values()) {
if (spike.energy < FIRING_THRESHOLD || spike.momentum < 0) continue;
const synapses = queryMatrix.get(spike.nodeId);
if (!synapses) continue;
const sortedSynapses = Array.from(synapses.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, MAX_NEIGHBORS_PER_NODE);
for (const [neighborId, coocWeight] of sortedSynapses) {
const neighborResidual = queryResiduals?.get(neighborId) ?? 1.0;
const tension = coocWeight * neighborResidual;
const isWormhole = queryWormholeEdges instanceof Set
? queryWormholeEdges.has(`${spike.nodeId}:${neighborId}`)
: tension >= TENSION_THRESHOLD;
const decayFactor = isWormhole ? WORMHOLE_DECAY : BASE_DECAY;
const momentumCost = isWormhole ? 0 : 1.0;
const isImmediateReturn = spike.previousNodeId !== null
&& neighborId === spike.previousNodeId;
const flowFactor = isImmediateReturn ? returnFlowFactor : 1;
const unpenalizedCurrent = spike.energy * coocWeight * decayFactor;
const injectedCurrent = unpenalizedCurrent * flowFactor;
if (isImmediateReturn) {
diagnostics.returnFlowSuppressedMass += unpenalizedCurrent - injectedCurrent;
}
if (injectedCurrent < 0.01) continue;
const sourceId = Number(spike.nodeId);
const targetId = Number(neighborId);
const edgeKey = `${sourceId}:${targetId}`;
const previousEdge = riverEdgeFlow.get(edgeKey);
if (previousEdge) {
previousEdge.flow += injectedCurrent;
previousEdge.maxFlow = Math.max(
previousEdge.maxFlow,
injectedCurrent
);
previousEdge.minHop = Math.min(
previousEdge.minHop,
spike.hop + 1
);
} else {
riverEdgeFlow.set(edgeKey, {
sourceId,
targetId,
flow: injectedCurrent,
maxFlow: injectedCurrent,
conductance: Math.max(0, Number(coocWeight) || 0),
minHop: spike.hop + 1,
wormhole: isWormhole,
immediateReturn: isImmediateReturn
});
}
const previousParent = strongestParentByNode.get(targetId);
if (
!previousParent
|| injectedCurrent > previousParent.flow
|| (
injectedCurrent === previousParent.flow
&& spike.hop + 1 < previousParent.hop
)
) {
strongestParentByNode.set(targetId, {
parentId: sourceId,
flow: injectedCurrent,
hop: spike.hop + 1,
wormhole: isWormhole
});
}
const nextMomentum = spike.momentum - momentumCost;
if (nextMomentum < 0 && !isWormhole) continue;
const stateKey = `${spike.nodeId}:${neighborId}`;
const existing = nextSpikes.get(stateKey);
if (existing) {
existing.energy += injectedCurrent;
existing.momentum = Math.max(existing.momentum, nextMomentum);
if (spike.hop + 1 < existing.hop) {
existing.hop = spike.hop + 1;
existing.sourceType = spike.sourceType;
}
} else {
nextSpikes.set(stateKey, {
nodeId: neighborId,
previousNodeId: spike.nodeId,
energy: injectedCurrent,
momentum: nextMomentum,
sourceType: spike.sourceType,
hop: spike.hop + 1
});
}
}
}
if (nextSpikes.size > maxPropagationStates) {
const retained = [...nextSpikes.entries()]
.sort((a, b) => b[1].energy - a[1].energy)
.slice(0, maxPropagationStates);
diagnostics.stateTruncations += nextSpikes.size - retained.length;
nextSpikes.clear();
for (const [key, value] of retained) nextSpikes.set(key, value);
}
const nodeEnergyThisHop = new Map();
for (const newSpike of nextSpikes.values()) {
nodeEnergyThisHop.set(
newSpike.nodeId,
(nodeEnergyThisHop.get(newSpike.nodeId) || 0) + newSpike.energy
);
const numericNodeId = Number(newSpike.nodeId);
const previousProvenance = fieldProvenance.get(numericNodeId);
if (!previousProvenance || newSpike.hop < previousProvenance.hop) {
fieldProvenance.set(numericNodeId, {
sourceType: 'emergent',
originType: newSpike.sourceType,
hop: newSpike.hop
});
}
inFlightMass += newSpike.energy;
}
diagnostics.hopInFlightMass.push(inFlightMass);
const fieldWeight = firWeights[hop + 1];
for (const [nodeId, energy] of nodeEnergyThisHop.entries()) {
accumulatedEnergy.set(
nodeId,
(accumulatedEnergy.get(nodeId) || 0) + energy * fieldWeight
);
if (energy > 0.01) propagated = true;
}
if (!propagated) break;
activeSpikes = nextSpikes;
}
const maximumNodeEnergy = Math.max(
0,
...accumulatedEnergy.values()
);
const maximumEdgeFlow = Math.max(
0,
...[...riverEdgeFlow.values()].map(edge => edge.flow)
);
const riverGraph = Object.freeze({
schema: 'tagmemo-query-spike-river-v1',
nodes: Object.freeze(
[...accumulatedEnergy.entries()]
.map(([rawId, rawEnergy]) => {
const id = Number(rawId);
const provenance = fieldProvenance.get(id) || {};
const parent = strongestParentByNode.get(id) || null;
return Object.freeze({
id,
energy: Math.max(0, Number(rawEnergy) || 0),
normalizedEnergy: maximumNodeEnergy > 0
? Math.max(0, Number(rawEnergy) || 0)
/ maximumNodeEnergy
: 0,
sourceType: provenance.sourceType || 'unknown',
originType: provenance.originType || null,
hop: Number.isFinite(provenance.hop)
? provenance.hop
: null,
seedId: Number.isFinite(provenance.seedId)
? provenance.seedId
: null,
strongestParent: parent
? Object.freeze({ ...parent })
: null
});
})
.sort((left, right) =>
(right.energy - left.energy) || (left.id - right.id)
)
),
edges: Object.freeze(
[...riverEdgeFlow.values()]
.map(edge => Object.freeze({
...edge,
normalizedFlow: maximumEdgeFlow > 0
? edge.flow / maximumEdgeFlow
: 0
}))
.sort((left, right) =>
(right.flow - left.flow)
|| (left.sourceId - right.sourceId)
|| (left.targetId - right.targetId)
)
),
diagnostics: Object.freeze({
seedNodes: initialTags.length,
reachedNodes: accumulatedEnergy.size,
activeEdges: riverEdgeFlow.size,
maximumNodeEnergy,
maximumEdgeFlow
})
});
return {
accumulatedEnergy,
fieldProvenance,
diagnostics,
riverGraph
};
}
/**
* 🌟 TagMemo 浪潮 + EPA + Residual Pyramid + Worldview Gating + LIF Spike Propagation (V6)
*
* 返回值中的 energyField 是查询级距离场。不要依赖 lastEnergyField 参与搜索重排:
* lastEnergyField 只是兼容/诊断缓存,在全局搜索 await 间隙会被其他并发查询覆盖。
*/
applyTagBoost(vector, baseTagBoost, coreTags = [], coreBoostFactor = 1.33, options = {}) {
this._assertJsGraphRuntimeAvailable('TagMemoEngine.applyTagBoost()');
const debug = false;
const originalFloat32 = vector instanceof Float32Array ? vector : new Float32Array(vector);
const dim = originalFloat32.length;
// 请求开始时只解析一次活动指针;后续后台发布不会改变本次查询持有的对象。
const resolution = options.artifactBundle
? {
bundle: options.artifactBundle,
requestedVersion: options.version || options.artifactBundle.version,
effectiveVersion: options.artifactBundle.version,
fallbackUsed: false,
fallbackReason: null
}
: this.resolveArtifactBundle(options);
const artifactBundle = resolution.bundle;
const queryMatrix = artifactBundle?.propagationKernel || this.tagCooccurrenceMatrix;
const queryResiduals = artifactBundle?.anchorGainMap || artifactBundle?.residualMap || this.tagIntrinsicResiduals;
const queryVersion = 'v9';
const queryWormholeEdges = artifactBundle?.wormholeEdges;
try {
// 🌟 V8: 清空旧距离场,防止跨调用数据泄露
this.lastEnergyField = null;
this.lastEnergyFieldProvenance = null;
// [1] EPA 分析 (逻辑深度与共振) - 识别"你在哪个世界"
const epaResult = this.epa.project(originalFloat32);
const resonance = this.epa.detectCrossDomainResonance(originalFloat32);
const queryWorld = epaResult.dominantAxes[0]?.label || 'Unknown';
// [2] 残差金字塔分析 (新颖度与覆盖率) - 90% 能量截断
const pyramid = this.residualPyramid.analyze(originalFloat32);
const features = pyramid.features;
// [3] 动态调整策略
// 配置与核/残差属于同一不可变资产包,禁止在请求中途读取热更新后的 ragParams。
const config = artifactBundle?.effectiveConfig || this.ragParams?.KnowledgeBaseManager || {};
const logicDepth = epaResult.logicDepth; // 0~1, 高=逻辑聚焦