mcvay-mind 1.0.7 → 1.0.8
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/lib/metrics.js +15 -1
- package/lib/search.js +57 -3
- package/package.json +1 -1
package/lib/metrics.js
CHANGED
|
@@ -37,6 +37,7 @@ const DEFAULT_STATE = {
|
|
|
37
37
|
unified_graph_traversal: { count: 0, total: 0, min: null, max: null, avg: 0 },
|
|
38
38
|
keyword: { count: 0, total: 0, min: null, max: null, avg: 0 },
|
|
39
39
|
semantic: { count: 0, total: 0, min: null, max: null, avg: 0 },
|
|
40
|
+
fast_semantic: { count: 0, total: 0, min: null, max: null, avg: 0 },
|
|
40
41
|
graph: { count: 0, total: 0, min: null, max: null, avg: 0 },
|
|
41
42
|
format: { count: 0, total: 0, min: null, max: null, avg: 0 },
|
|
42
43
|
},
|
|
@@ -101,6 +102,7 @@ function mergeState(rawState) {
|
|
|
101
102
|
unified_graph_traversal: mergeLatency(latency.unified_graph_traversal),
|
|
102
103
|
keyword: mergeLatency(latency.keyword),
|
|
103
104
|
semantic: mergeLatency(latency.semantic),
|
|
105
|
+
fast_semantic: mergeLatency(latency.fast_semantic),
|
|
104
106
|
graph: mergeLatency(latency.graph),
|
|
105
107
|
format: mergeLatency(latency.format),
|
|
106
108
|
},
|
|
@@ -162,7 +164,16 @@ function recordSearch({
|
|
|
162
164
|
} else {
|
|
163
165
|
state.search.card_mode += 1;
|
|
164
166
|
}
|
|
165
|
-
|
|
167
|
+
let modeKey = `${mode || 'hybrid'}`.trim().toLowerCase() || 'hybrid';
|
|
168
|
+
if (
|
|
169
|
+
modeKey === 'fast:keyword'
|
|
170
|
+
&& latencyBreakdown
|
|
171
|
+
&& latencyBreakdown.fastSemanticMs !== null
|
|
172
|
+
&& latencyBreakdown.fastSemanticMs !== undefined
|
|
173
|
+
&& Number.isFinite(latencyBreakdown.fastSemanticMs)
|
|
174
|
+
) {
|
|
175
|
+
modeKey = 'fast:hybrid';
|
|
176
|
+
}
|
|
166
177
|
state.search.by_engine_mode[modeKey] = toNumber(state.search.by_engine_mode[modeKey], 0) + 1;
|
|
167
178
|
updateLatency(state.latency_ms.search, durationMs);
|
|
168
179
|
if (latencyBreakdown && typeof latencyBreakdown === 'object') {
|
|
@@ -172,6 +183,9 @@ function recordSearch({
|
|
|
172
183
|
if (Number.isFinite(latencyBreakdown.semanticMs)) {
|
|
173
184
|
updateLatency(state.latency_ms.semantic, latencyBreakdown.semanticMs);
|
|
174
185
|
}
|
|
186
|
+
if (Number.isFinite(latencyBreakdown.fastSemanticMs)) {
|
|
187
|
+
updateLatency(state.latency_ms.fast_semantic, latencyBreakdown.fastSemanticMs);
|
|
188
|
+
}
|
|
175
189
|
if (Number.isFinite(latencyBreakdown.graphMs)) {
|
|
176
190
|
updateLatency(state.latency_ms.graph, latencyBreakdown.graphMs);
|
|
177
191
|
}
|
package/lib/search.js
CHANGED
|
@@ -39,6 +39,9 @@ const TYPE_PRIORITY = {
|
|
|
39
39
|
const SEARCH_CACHE_MAX_ENTRIES = 200;
|
|
40
40
|
const SEARCH_CACHE_TTL_MS = 60 * 1000;
|
|
41
41
|
const EXPAND_INTENT_TERMS = new Set(['why', 'how', 'details', 'detail', 'context', 'full', 'explain', 'history']);
|
|
42
|
+
const FAST_SEMANTIC_MAX_TOP_N = 10;
|
|
43
|
+
const FAST_LATENCY_BUDGET_MS = 500;
|
|
44
|
+
const FAST_SEMANTIC_LATENCY_GUARD_MS = 400;
|
|
42
45
|
const queryCache = new Map();
|
|
43
46
|
|
|
44
47
|
function normalizeQuery(query = '') {
|
|
@@ -141,6 +144,14 @@ function shouldRunSemanticForBalanced(keywordResults = [], options = {}) {
|
|
|
141
144
|
return topNorm < 0.62 || sparse;
|
|
142
145
|
}
|
|
143
146
|
|
|
147
|
+
function shouldRunSemanticForFast(keywordResults = []) {
|
|
148
|
+
if (!keywordResults.length) return true;
|
|
149
|
+
const top = keywordResults[0];
|
|
150
|
+
const topNorm = normalizeKeywordScore(top && top.score);
|
|
151
|
+
const sparse = keywordResults.length < 3;
|
|
152
|
+
return topNorm < 0.5 || sparse;
|
|
153
|
+
}
|
|
154
|
+
|
|
144
155
|
// ============================================================================
|
|
145
156
|
// Keyword Extraction
|
|
146
157
|
// ============================================================================
|
|
@@ -203,6 +214,14 @@ function computeHybridScore(memory, options, keywords, query, cwd, linkCounts =
|
|
|
203
214
|
return (keywordNorm * keywordWeight) + (semanticNorm * semanticWeight) + (metadataNorm * 0.1);
|
|
204
215
|
}
|
|
205
216
|
|
|
217
|
+
function computeSimpleFusionScore(memory, options = {}) {
|
|
218
|
+
const keywordWeight = Number.isFinite(options.keywordWeight) ? options.keywordWeight : 0.5;
|
|
219
|
+
const semanticWeight = Number.isFinite(options.semanticWeight) ? options.semanticWeight : 0.5;
|
|
220
|
+
const keywordNorm = normalizeKeywordScore(memory.keywordScore || memory.score || 0);
|
|
221
|
+
const semanticNorm = normalizeSemanticScore(memory.semanticScore || 0);
|
|
222
|
+
return (keywordWeight * keywordNorm) + (semanticWeight * semanticNorm);
|
|
223
|
+
}
|
|
224
|
+
|
|
206
225
|
function normalizeBm25ForDisplay(score) {
|
|
207
226
|
if (!Number.isFinite(score)) return 0;
|
|
208
227
|
return 1 / (1 + Math.max(0, score));
|
|
@@ -417,6 +436,7 @@ function fuseResults(keywordResults, semanticResults, options, cwd = process.cwd
|
|
|
417
436
|
const keywords = extractKeywords(query);
|
|
418
437
|
const mode = options.mode || 'hybrid';
|
|
419
438
|
const limit = options.limit || 10;
|
|
439
|
+
const simpleFusion = !!options.simpleFusion;
|
|
420
440
|
|
|
421
441
|
if (mode === 'keyword') {
|
|
422
442
|
return keywordResults.slice(0, limit);
|
|
@@ -460,7 +480,9 @@ function fuseResults(keywordResults, semanticResults, options, cwd = process.cwd
|
|
|
460
480
|
}
|
|
461
481
|
|
|
462
482
|
const fused = Array.from(bySlug.values()).map(m => {
|
|
463
|
-
const hybrid =
|
|
483
|
+
const hybrid = simpleFusion
|
|
484
|
+
? computeSimpleFusionScore(m, options)
|
|
485
|
+
: computeHybridScore(m, options, keywords, query, cwd, linkCounts);
|
|
464
486
|
return {
|
|
465
487
|
...m,
|
|
466
488
|
score: Math.round(hybrid * 150),
|
|
@@ -578,12 +600,14 @@ async function searchMemories(options, cwd = process.cwd()) {
|
|
|
578
600
|
const phaseMs = {
|
|
579
601
|
keywordMs: 0,
|
|
580
602
|
semanticMs: 0,
|
|
603
|
+
fastSemanticMs: null,
|
|
581
604
|
graphMs: 0,
|
|
582
605
|
formatMs: 0,
|
|
583
606
|
};
|
|
584
607
|
let cacheHit = false;
|
|
585
608
|
let cacheMiss = false;
|
|
586
609
|
let errored = false;
|
|
610
|
+
let fastSemanticTriggered = false;
|
|
587
611
|
const cacheKey = buildSearchCacheKey({
|
|
588
612
|
...options,
|
|
589
613
|
query,
|
|
@@ -615,7 +639,37 @@ async function searchMemories(options, cwd = process.cwd()) {
|
|
|
615
639
|
phaseMs.keywordMs = Date.now() - keywordStart;
|
|
616
640
|
|
|
617
641
|
if (preset === 'fast' || !query || mode === 'keyword') {
|
|
618
|
-
|
|
642
|
+
const fastGateOpen = preset === 'fast'
|
|
643
|
+
&& query
|
|
644
|
+
&& shouldRunSemanticForFast(keywordResults)
|
|
645
|
+
&& phaseMs.keywordMs < FAST_SEMANTIC_LATENCY_GUARD_MS
|
|
646
|
+
&& (Date.now() - startMs) < FAST_LATENCY_BUDGET_MS;
|
|
647
|
+
if (fastGateOpen) {
|
|
648
|
+
fastSemanticTriggered = true;
|
|
649
|
+
const semanticStart = Date.now();
|
|
650
|
+
const fastRun = await runSemanticSearch({
|
|
651
|
+
...options,
|
|
652
|
+
limit: Math.min(
|
|
653
|
+
FAST_SEMANTIC_MAX_TOP_N,
|
|
654
|
+
Number.isFinite(options.annTopN) ? options.annTopN : FAST_SEMANTIC_MAX_TOP_N,
|
|
655
|
+
),
|
|
656
|
+
semanticBackend: 'ann',
|
|
657
|
+
}, cwd);
|
|
658
|
+
phaseMs.fastSemanticMs = Date.now() - semanticStart;
|
|
659
|
+
phaseMs.semanticMs += phaseMs.fastSemanticMs;
|
|
660
|
+
const canFuse = fastRun.available && fastRun.results.length > 0;
|
|
661
|
+
ranked = canFuse
|
|
662
|
+
? fuseResults(
|
|
663
|
+
keywordResults,
|
|
664
|
+
fastRun.results,
|
|
665
|
+
{ ...options, simpleFusion: true, mode: 'hybrid' },
|
|
666
|
+
cwd,
|
|
667
|
+
linkCounts,
|
|
668
|
+
)
|
|
669
|
+
: keywordResults;
|
|
670
|
+
} else {
|
|
671
|
+
ranked = keywordResults;
|
|
672
|
+
}
|
|
619
673
|
} else if (preset === 'balanced') {
|
|
620
674
|
const shouldSemantic = shouldRunSemanticForBalanced(keywordResults, options);
|
|
621
675
|
if (shouldSemantic) {
|
|
@@ -686,7 +740,7 @@ async function searchMemories(options, cwd = process.cwd()) {
|
|
|
686
740
|
|
|
687
741
|
metrics.recordSearch({
|
|
688
742
|
resultMode: resultModeForMetrics,
|
|
689
|
-
mode: `${preset}:${mode}`,
|
|
743
|
+
mode: (preset === 'fast' && fastSemanticTriggered) ? 'fast:hybrid' : `${preset}:${mode}`,
|
|
690
744
|
durationMs: Date.now() - startMs,
|
|
691
745
|
errored,
|
|
692
746
|
latencyBreakdown: phaseMs,
|