cozo-memory 1.1.4 → 1.1.6
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 +161 -1159
- package/dist/adaptive-query-fusion.js +397 -0
- package/dist/dynamic-fusion.js +63 -8
- package/dist/explainable-retrieval.js +552 -0
- package/dist/hierarchical-memory.js +358 -0
- package/dist/proactive-suggestions.js +382 -0
- package/dist/temporal-conflict-resolution.js +386 -0
- package/dist/temporal-pattern-detection-backup.js +358 -0
- package/dist/temporal-pattern-detection.js +482 -0
- package/dist/test-adaptive-query-fusion.js +208 -0
- package/dist/test-explainable-retrieval.js +408 -0
- package/dist/test-hierarchical-and-patterns.js +17 -0
- package/dist/test-hierarchical-memory.js +205 -0
- package/dist/test-proactive-suggestions.js +240 -0
- package/dist/test-temporal-conflict-resolution.js +228 -0
- package/dist/test-temporal-patterns.js +317 -0
- package/package.json +1 -1
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive Query Fusion with Dynamic Weights
|
|
4
|
+
*
|
|
5
|
+
* Based on 2026 SOTA Research:
|
|
6
|
+
* - Meilisearch Adaptive RAG (2025)
|
|
7
|
+
* - FEEG Framework: Finder, Evaluator, Explainer, Generator (Samuel et al., 2026)
|
|
8
|
+
* - ORCAS-I Intent Classifier (Alexander et al., 2022)
|
|
9
|
+
* - Query Intent Classification Research (2025)
|
|
10
|
+
*
|
|
11
|
+
* Combines:
|
|
12
|
+
* 1. Heuristic keyword-based classification (fast, reliable)
|
|
13
|
+
* 2. LLM-based classification (accurate, semantic understanding)
|
|
14
|
+
* 3. Adaptive weight selection based on query type
|
|
15
|
+
* 4. Fallback mechanisms for robustness
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.AdaptiveQueryFusion = exports.QueryComplexity = exports.QueryIntent = void 0;
|
|
19
|
+
// Query Intent Types (FEEG Framework)
|
|
20
|
+
var QueryIntent;
|
|
21
|
+
(function (QueryIntent) {
|
|
22
|
+
QueryIntent["FINDER"] = "finder";
|
|
23
|
+
QueryIntent["EVALUATOR"] = "evaluator";
|
|
24
|
+
QueryIntent["EXPLAINER"] = "explainer";
|
|
25
|
+
QueryIntent["GENERATOR"] = "generator"; // Generate new content (G)
|
|
26
|
+
})(QueryIntent || (exports.QueryIntent = QueryIntent = {}));
|
|
27
|
+
// Query Complexity Levels
|
|
28
|
+
var QueryComplexity;
|
|
29
|
+
(function (QueryComplexity) {
|
|
30
|
+
QueryComplexity["SIMPLE"] = "simple";
|
|
31
|
+
QueryComplexity["MODERATE"] = "moderate";
|
|
32
|
+
QueryComplexity["COMPLEX"] = "complex";
|
|
33
|
+
QueryComplexity["EXPLORATORY"] = "exploratory"; // Open-ended, broad search
|
|
34
|
+
})(QueryComplexity || (exports.QueryComplexity = QueryComplexity = {}));
|
|
35
|
+
/**
|
|
36
|
+
* Adaptive Query Fusion Engine
|
|
37
|
+
*
|
|
38
|
+
* Classifies queries and selects optimal search weights dynamically
|
|
39
|
+
*/
|
|
40
|
+
class AdaptiveQueryFusion {
|
|
41
|
+
db;
|
|
42
|
+
embeddingService;
|
|
43
|
+
config;
|
|
44
|
+
classificationCache;
|
|
45
|
+
// Keyword patterns for heuristic classification
|
|
46
|
+
FINDER_KEYWORDS = [
|
|
47
|
+
'what', 'when', 'where', 'who', 'how many', 'specific',
|
|
48
|
+
'find', 'search', 'look for', 'get', 'retrieve',
|
|
49
|
+
'latest', 'recent', 'current', 'today', 'news'
|
|
50
|
+
];
|
|
51
|
+
EVALUATOR_KEYWORDS = [
|
|
52
|
+
'compare', 'difference', 'versus', 'vs', 'better',
|
|
53
|
+
'evaluate', 'assess', 'analyze', 'review', 'rate',
|
|
54
|
+
'pros', 'cons', 'advantages', 'disadvantages'
|
|
55
|
+
];
|
|
56
|
+
EXPLAINER_KEYWORDS = [
|
|
57
|
+
'why', 'how', 'explain', 'understand', 'about',
|
|
58
|
+
'describe', 'tell me', 'what is', 'concept',
|
|
59
|
+
'meaning', 'definition', 'work', 'process'
|
|
60
|
+
];
|
|
61
|
+
GENERATOR_KEYWORDS = [
|
|
62
|
+
'create', 'generate', 'write', 'make', 'build',
|
|
63
|
+
'suggest', 'recommend', 'example', 'template',
|
|
64
|
+
'code', 'script', 'plan', 'strategy'
|
|
65
|
+
];
|
|
66
|
+
// Predefined weight configurations for each intent
|
|
67
|
+
WEIGHT_CONFIGS = {
|
|
68
|
+
[QueryIntent.FINDER]: {
|
|
69
|
+
[QueryComplexity.SIMPLE]: {
|
|
70
|
+
vector: 0.4,
|
|
71
|
+
sparse: 0.5, // High keyword matching for factual queries
|
|
72
|
+
fts: 0.1,
|
|
73
|
+
graph: 0.0
|
|
74
|
+
},
|
|
75
|
+
[QueryComplexity.MODERATE]: {
|
|
76
|
+
vector: 0.45,
|
|
77
|
+
sparse: 0.35,
|
|
78
|
+
fts: 0.15,
|
|
79
|
+
graph: 0.05
|
|
80
|
+
},
|
|
81
|
+
[QueryComplexity.COMPLEX]: {
|
|
82
|
+
vector: 0.5,
|
|
83
|
+
sparse: 0.25,
|
|
84
|
+
fts: 0.15,
|
|
85
|
+
graph: 0.1
|
|
86
|
+
},
|
|
87
|
+
[QueryComplexity.EXPLORATORY]: {
|
|
88
|
+
vector: 0.4,
|
|
89
|
+
sparse: 0.2,
|
|
90
|
+
fts: 0.2,
|
|
91
|
+
graph: 0.2
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
[QueryIntent.EVALUATOR]: {
|
|
95
|
+
[QueryComplexity.SIMPLE]: {
|
|
96
|
+
vector: 0.5,
|
|
97
|
+
sparse: 0.3,
|
|
98
|
+
fts: 0.1,
|
|
99
|
+
graph: 0.1
|
|
100
|
+
},
|
|
101
|
+
[QueryComplexity.MODERATE]: {
|
|
102
|
+
vector: 0.5,
|
|
103
|
+
sparse: 0.2,
|
|
104
|
+
fts: 0.1,
|
|
105
|
+
graph: 0.2
|
|
106
|
+
},
|
|
107
|
+
[QueryComplexity.COMPLEX]: {
|
|
108
|
+
vector: 0.45,
|
|
109
|
+
sparse: 0.15,
|
|
110
|
+
fts: 0.1,
|
|
111
|
+
graph: 0.3
|
|
112
|
+
},
|
|
113
|
+
[QueryComplexity.EXPLORATORY]: {
|
|
114
|
+
vector: 0.4,
|
|
115
|
+
sparse: 0.1,
|
|
116
|
+
fts: 0.1,
|
|
117
|
+
graph: 0.4
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[QueryIntent.EXPLAINER]: {
|
|
121
|
+
[QueryComplexity.SIMPLE]: {
|
|
122
|
+
vector: 0.6,
|
|
123
|
+
sparse: 0.2,
|
|
124
|
+
fts: 0.1,
|
|
125
|
+
graph: 0.1
|
|
126
|
+
},
|
|
127
|
+
[QueryComplexity.MODERATE]: {
|
|
128
|
+
vector: 0.55,
|
|
129
|
+
sparse: 0.15,
|
|
130
|
+
fts: 0.1,
|
|
131
|
+
graph: 0.2
|
|
132
|
+
},
|
|
133
|
+
[QueryComplexity.COMPLEX]: {
|
|
134
|
+
vector: 0.5,
|
|
135
|
+
sparse: 0.1,
|
|
136
|
+
fts: 0.1,
|
|
137
|
+
graph: 0.3
|
|
138
|
+
},
|
|
139
|
+
[QueryComplexity.EXPLORATORY]: {
|
|
140
|
+
vector: 0.45,
|
|
141
|
+
sparse: 0.1,
|
|
142
|
+
fts: 0.1,
|
|
143
|
+
graph: 0.35
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
[QueryIntent.GENERATOR]: {
|
|
147
|
+
[QueryComplexity.SIMPLE]: {
|
|
148
|
+
vector: 0.5,
|
|
149
|
+
sparse: 0.3,
|
|
150
|
+
fts: 0.1,
|
|
151
|
+
graph: 0.1
|
|
152
|
+
},
|
|
153
|
+
[QueryComplexity.MODERATE]: {
|
|
154
|
+
vector: 0.45,
|
|
155
|
+
sparse: 0.25,
|
|
156
|
+
fts: 0.15,
|
|
157
|
+
graph: 0.15
|
|
158
|
+
},
|
|
159
|
+
[QueryComplexity.COMPLEX]: {
|
|
160
|
+
vector: 0.4,
|
|
161
|
+
sparse: 0.2,
|
|
162
|
+
fts: 0.2,
|
|
163
|
+
graph: 0.2
|
|
164
|
+
},
|
|
165
|
+
[QueryComplexity.EXPLORATORY]: {
|
|
166
|
+
vector: 0.35,
|
|
167
|
+
sparse: 0.15,
|
|
168
|
+
fts: 0.2,
|
|
169
|
+
graph: 0.3
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
constructor(db, embeddingService, config) {
|
|
174
|
+
this.db = db;
|
|
175
|
+
this.embeddingService = embeddingService;
|
|
176
|
+
this.classificationCache = new Map();
|
|
177
|
+
this.config = {
|
|
178
|
+
enableLLM: true,
|
|
179
|
+
llmModel: 'demyagent-4b-i1:Q6_K',
|
|
180
|
+
heuristicThreshold: 0.7,
|
|
181
|
+
cacheClassifications: true,
|
|
182
|
+
maxCacheSize: 1000,
|
|
183
|
+
...config
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Classify query using hybrid approach (heuristic + LLM)
|
|
188
|
+
*/
|
|
189
|
+
async classifyQuery(query) {
|
|
190
|
+
// Check cache first
|
|
191
|
+
if (this.config.cacheClassifications) {
|
|
192
|
+
const cached = this.classificationCache.get(query);
|
|
193
|
+
if (cached) {
|
|
194
|
+
console.error(`[AdaptiveQueryFusion] Cache hit for query: "${query}"`);
|
|
195
|
+
return cached;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Step 1: Heuristic classification (fast)
|
|
199
|
+
const heuristicResult = this.classifyHeuristic(query);
|
|
200
|
+
console.error(`[AdaptiveQueryFusion] Heuristic result: ${heuristicResult.intent} (confidence: ${heuristicResult.confidence.toFixed(2)})`);
|
|
201
|
+
// Step 2: If heuristic confidence is high enough, use it
|
|
202
|
+
if (heuristicResult.confidence >= this.config.heuristicThreshold) {
|
|
203
|
+
const result = {
|
|
204
|
+
...heuristicResult,
|
|
205
|
+
method: 'heuristic'
|
|
206
|
+
};
|
|
207
|
+
this.cacheClassification(query, result);
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
// Step 3: Try LLM classification if enabled and heuristic confidence is low
|
|
211
|
+
if (this.config.enableLLM) {
|
|
212
|
+
try {
|
|
213
|
+
const llmResult = await this.classifyLLM(query);
|
|
214
|
+
console.error(`[AdaptiveQueryFusion] LLM result: ${llmResult.intent} (confidence: ${llmResult.confidence.toFixed(2)})`);
|
|
215
|
+
const result = {
|
|
216
|
+
...llmResult,
|
|
217
|
+
method: 'llm'
|
|
218
|
+
};
|
|
219
|
+
this.cacheClassification(query, result);
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
console.error(`[AdaptiveQueryFusion] LLM classification failed, falling back to heuristic:`, error);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Step 4: Fallback to heuristic
|
|
227
|
+
const result = {
|
|
228
|
+
...heuristicResult,
|
|
229
|
+
method: 'hybrid'
|
|
230
|
+
};
|
|
231
|
+
this.cacheClassification(query, result);
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Heuristic-based query classification using keywords
|
|
236
|
+
*/
|
|
237
|
+
classifyHeuristic(query) {
|
|
238
|
+
const queryLower = query.toLowerCase();
|
|
239
|
+
const words = queryLower.split(/\s+/);
|
|
240
|
+
// Count keyword matches for each intent
|
|
241
|
+
const intentScores = {
|
|
242
|
+
[QueryIntent.FINDER]: this.countMatches(queryLower, this.FINDER_KEYWORDS),
|
|
243
|
+
[QueryIntent.EVALUATOR]: this.countMatches(queryLower, this.EVALUATOR_KEYWORDS),
|
|
244
|
+
[QueryIntent.EXPLAINER]: this.countMatches(queryLower, this.EXPLAINER_KEYWORDS),
|
|
245
|
+
[QueryIntent.GENERATOR]: this.countMatches(queryLower, this.GENERATOR_KEYWORDS)
|
|
246
|
+
};
|
|
247
|
+
// Find dominant intent
|
|
248
|
+
let dominantIntent = QueryIntent.FINDER;
|
|
249
|
+
let maxScore = 0;
|
|
250
|
+
for (const [intent, score] of Object.entries(intentScores)) {
|
|
251
|
+
if (score > maxScore) {
|
|
252
|
+
maxScore = score;
|
|
253
|
+
dominantIntent = intent;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Calculate confidence (0.0 - 1.0)
|
|
257
|
+
const totalScore = Object.values(intentScores).reduce((a, b) => a + b, 0);
|
|
258
|
+
const confidence = totalScore > 0 ? maxScore / totalScore : 0.5;
|
|
259
|
+
// Classify complexity
|
|
260
|
+
const complexity = this.classifyComplexity(query, words);
|
|
261
|
+
// Extract keywords
|
|
262
|
+
const keywords = words.filter(w => w.length > 3);
|
|
263
|
+
return {
|
|
264
|
+
intent: dominantIntent,
|
|
265
|
+
complexity,
|
|
266
|
+
confidence,
|
|
267
|
+
keywords,
|
|
268
|
+
reasoning: `Heuristic classification: ${dominantIntent} (${(confidence * 100).toFixed(0)}% confidence)`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* LLM-based query classification using Ollama
|
|
273
|
+
*/
|
|
274
|
+
async classifyLLM(query) {
|
|
275
|
+
try {
|
|
276
|
+
// Dynamic import to avoid hard dependency
|
|
277
|
+
const ollamaModule = await import('ollama');
|
|
278
|
+
const ollama = ollamaModule?.default ?? ollamaModule;
|
|
279
|
+
const systemPrompt = `You are a query classification expert. Classify the user's query into one of these categories:
|
|
280
|
+
- "finder": Seeking factual information (what, when, where, who, how many)
|
|
281
|
+
- "evaluator": Comparing or evaluating content (compare, difference, better)
|
|
282
|
+
- "explainer": Understanding concepts (why, how, explain)
|
|
283
|
+
- "generator": Creating or generating content (create, write, suggest)
|
|
284
|
+
|
|
285
|
+
Also classify complexity as: "simple", "moderate", "complex", or "exploratory"
|
|
286
|
+
|
|
287
|
+
Respond with JSON: {"intent": "...", "complexity": "...", "confidence": 0.0-1.0, "reasoning": "..."}`;
|
|
288
|
+
const response = await ollama.chat({
|
|
289
|
+
model: this.config.llmModel,
|
|
290
|
+
messages: [
|
|
291
|
+
{ role: 'system', content: systemPrompt },
|
|
292
|
+
{ role: 'user', content: query }
|
|
293
|
+
],
|
|
294
|
+
format: 'json'
|
|
295
|
+
});
|
|
296
|
+
let responseText = response?.message?.content?.trim?.() ?? '';
|
|
297
|
+
// Clean markdown formatting if present
|
|
298
|
+
if (responseText.startsWith('```json')) {
|
|
299
|
+
responseText = responseText.replace(/```json/g, '').replace(/```/g, '').trim();
|
|
300
|
+
}
|
|
301
|
+
else if (responseText.startsWith('```')) {
|
|
302
|
+
responseText = responseText.replace(/```/g, '').trim();
|
|
303
|
+
}
|
|
304
|
+
const parsed = JSON.parse(responseText);
|
|
305
|
+
return {
|
|
306
|
+
intent: (parsed.intent || QueryIntent.FINDER),
|
|
307
|
+
complexity: (parsed.complexity || QueryComplexity.MODERATE),
|
|
308
|
+
confidence: Math.min(1.0, Math.max(0.0, parsed.confidence || 0.8)),
|
|
309
|
+
keywords: query.split(/\s+/).filter(w => w.length > 3),
|
|
310
|
+
reasoning: parsed.reasoning || 'LLM classification'
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
console.error('[AdaptiveQueryFusion] LLM classification error:', error);
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Classify query complexity based on heuristics
|
|
320
|
+
*/
|
|
321
|
+
classifyComplexity(query, words) {
|
|
322
|
+
const queryLower = query.toLowerCase();
|
|
323
|
+
// Simple: Short, direct questions
|
|
324
|
+
if (words.length <= 4 && /^(what|when|where|who|how many)\s/.test(queryLower)) {
|
|
325
|
+
return QueryComplexity.SIMPLE;
|
|
326
|
+
}
|
|
327
|
+
// Exploratory: Open-ended, broad
|
|
328
|
+
if (/^(all|everything|explore|overview|general|summary|list)\b/.test(queryLower)) {
|
|
329
|
+
return QueryComplexity.EXPLORATORY;
|
|
330
|
+
}
|
|
331
|
+
// Complex: Multiple concepts, reasoning required
|
|
332
|
+
const complexIndicators = ['and', 'or', 'but', 'however', 'relationship', 'connection', 'impact'];
|
|
333
|
+
const complexCount = complexIndicators.filter(ind => queryLower.includes(ind)).length;
|
|
334
|
+
if (complexCount >= 2 || words.length > 15) {
|
|
335
|
+
return QueryComplexity.COMPLEX;
|
|
336
|
+
}
|
|
337
|
+
// Moderate: Default
|
|
338
|
+
return QueryComplexity.MODERATE;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Count keyword matches in query
|
|
342
|
+
*/
|
|
343
|
+
countMatches(query, keywords) {
|
|
344
|
+
return keywords.filter(keyword => query.includes(keyword)).length;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Cache classification result
|
|
348
|
+
*/
|
|
349
|
+
cacheClassification(query, classification) {
|
|
350
|
+
if (!this.config.cacheClassifications)
|
|
351
|
+
return;
|
|
352
|
+
// Simple LRU: remove oldest if cache is full
|
|
353
|
+
if (this.classificationCache.size >= this.config.maxCacheSize) {
|
|
354
|
+
const firstKey = this.classificationCache.keys().next().value;
|
|
355
|
+
if (firstKey !== undefined) {
|
|
356
|
+
this.classificationCache.delete(firstKey);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
this.classificationCache.set(query, classification);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get adaptive search weights for a query
|
|
363
|
+
*/
|
|
364
|
+
async getAdaptiveWeights(query) {
|
|
365
|
+
const classification = await this.classifyQuery(query);
|
|
366
|
+
const weights = this.WEIGHT_CONFIGS[classification.intent][classification.complexity];
|
|
367
|
+
console.error(`[AdaptiveQueryFusion] Weights for "${query}":`, {
|
|
368
|
+
intent: classification.intent,
|
|
369
|
+
complexity: classification.complexity,
|
|
370
|
+
weights
|
|
371
|
+
});
|
|
372
|
+
return weights;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get classification details for debugging
|
|
376
|
+
*/
|
|
377
|
+
async getClassificationDetails(query) {
|
|
378
|
+
return this.classifyQuery(query);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Clear cache
|
|
382
|
+
*/
|
|
383
|
+
clearCache() {
|
|
384
|
+
this.classificationCache.clear();
|
|
385
|
+
console.error('[AdaptiveQueryFusion] Cache cleared');
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get cache statistics
|
|
389
|
+
*/
|
|
390
|
+
getCacheStats() {
|
|
391
|
+
return {
|
|
392
|
+
size: this.classificationCache.size,
|
|
393
|
+
maxSize: this.config.maxCacheSize
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
exports.AdaptiveQueryFusion = AdaptiveQueryFusion;
|
package/dist/dynamic-fusion.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.DynamicFusionSearch = exports.DEFAULT_FUSION_CONFIG = void 0;
|
|
17
|
+
const adaptive_query_fusion_1 = require("./adaptive-query-fusion");
|
|
17
18
|
/**
|
|
18
19
|
* Default fusion configuration
|
|
19
20
|
*/
|
|
@@ -58,19 +59,33 @@ exports.DEFAULT_FUSION_CONFIG = {
|
|
|
58
59
|
class DynamicFusionSearch {
|
|
59
60
|
db;
|
|
60
61
|
embeddings;
|
|
62
|
+
adaptiveQueryFusion;
|
|
61
63
|
constructor(db, embeddings) {
|
|
62
64
|
this.db = db;
|
|
63
65
|
this.embeddings = embeddings;
|
|
66
|
+
this.adaptiveQueryFusion = new adaptive_query_fusion_1.AdaptiveQueryFusion(db, embeddings, {
|
|
67
|
+
enableLLM: true,
|
|
68
|
+
cacheClassifications: true,
|
|
69
|
+
maxCacheSize: 1000
|
|
70
|
+
});
|
|
64
71
|
}
|
|
65
72
|
/**
|
|
66
|
-
* Execute dynamic fusion search
|
|
73
|
+
* Execute dynamic fusion search with adaptive weights
|
|
67
74
|
*/
|
|
68
75
|
async search(query, config = {}) {
|
|
69
76
|
const startTime = Date.now();
|
|
70
|
-
//
|
|
77
|
+
// Get adaptive weights based on query classification
|
|
78
|
+
const adaptiveWeights = await this.adaptiveQueryFusion.getAdaptiveWeights(query);
|
|
79
|
+
// Merge config with defaults first, then apply adaptive weights
|
|
71
80
|
const fullConfig = this.mergeConfig(config);
|
|
72
|
-
|
|
81
|
+
// Override weights with adaptive values
|
|
82
|
+
fullConfig.vector.weight = adaptiveWeights.vector;
|
|
83
|
+
fullConfig.sparse.weight = adaptiveWeights.sparse;
|
|
84
|
+
fullConfig.fts.weight = adaptiveWeights.fts;
|
|
85
|
+
fullConfig.graph.weight = adaptiveWeights.graph;
|
|
86
|
+
console.log('[DynamicFusion] Starting search with adaptive weights:', {
|
|
73
87
|
query,
|
|
88
|
+
weights: adaptiveWeights,
|
|
74
89
|
enabledPaths: this.getEnabledPaths(fullConfig)
|
|
75
90
|
});
|
|
76
91
|
// Execute all enabled paths in parallel
|
|
@@ -82,7 +97,8 @@ class DynamicFusionSearch {
|
|
|
82
97
|
console.log('[DynamicFusion] Search completed:', {
|
|
83
98
|
totalResults: fusedResults.length,
|
|
84
99
|
pathContributions: stats.pathContributions,
|
|
85
|
-
fusionTime: stats.fusionTime
|
|
100
|
+
fusionTime: stats.fusionTime,
|
|
101
|
+
adaptiveWeights
|
|
86
102
|
});
|
|
87
103
|
return { results: fusedResults, stats };
|
|
88
104
|
}
|
|
@@ -576,10 +592,31 @@ class DynamicFusionSearch {
|
|
|
576
592
|
*/
|
|
577
593
|
mergeConfig(config) {
|
|
578
594
|
return {
|
|
579
|
-
vector: {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
595
|
+
vector: {
|
|
596
|
+
enabled: config.vector?.enabled ?? exports.DEFAULT_FUSION_CONFIG.vector.enabled,
|
|
597
|
+
weight: config.vector?.weight ?? exports.DEFAULT_FUSION_CONFIG.vector.weight,
|
|
598
|
+
topK: config.vector?.topK ?? exports.DEFAULT_FUSION_CONFIG.vector.topK,
|
|
599
|
+
efSearch: config.vector?.efSearch ?? exports.DEFAULT_FUSION_CONFIG.vector.efSearch
|
|
600
|
+
},
|
|
601
|
+
sparse: {
|
|
602
|
+
enabled: config.sparse?.enabled ?? exports.DEFAULT_FUSION_CONFIG.sparse.enabled,
|
|
603
|
+
weight: config.sparse?.weight ?? exports.DEFAULT_FUSION_CONFIG.sparse.weight,
|
|
604
|
+
topK: config.sparse?.topK ?? exports.DEFAULT_FUSION_CONFIG.sparse.topK,
|
|
605
|
+
minScore: config.sparse?.minScore ?? exports.DEFAULT_FUSION_CONFIG.sparse.minScore
|
|
606
|
+
},
|
|
607
|
+
fts: {
|
|
608
|
+
enabled: config.fts?.enabled ?? exports.DEFAULT_FUSION_CONFIG.fts.enabled,
|
|
609
|
+
weight: config.fts?.weight ?? exports.DEFAULT_FUSION_CONFIG.fts.weight,
|
|
610
|
+
topK: config.fts?.topK ?? exports.DEFAULT_FUSION_CONFIG.fts.topK,
|
|
611
|
+
fuzzy: config.fts?.fuzzy ?? exports.DEFAULT_FUSION_CONFIG.fts.fuzzy
|
|
612
|
+
},
|
|
613
|
+
graph: {
|
|
614
|
+
enabled: config.graph?.enabled ?? exports.DEFAULT_FUSION_CONFIG.graph.enabled,
|
|
615
|
+
weight: config.graph?.weight ?? exports.DEFAULT_FUSION_CONFIG.graph.weight,
|
|
616
|
+
maxDepth: config.graph?.maxDepth ?? exports.DEFAULT_FUSION_CONFIG.graph.maxDepth,
|
|
617
|
+
maxResults: config.graph?.maxResults ?? exports.DEFAULT_FUSION_CONFIG.graph.maxResults,
|
|
618
|
+
relationTypes: config.graph?.relationTypes ?? exports.DEFAULT_FUSION_CONFIG.graph.relationTypes
|
|
619
|
+
},
|
|
583
620
|
fusion: { ...exports.DEFAULT_FUSION_CONFIG.fusion, ...config.fusion }
|
|
584
621
|
};
|
|
585
622
|
}
|
|
@@ -598,5 +635,23 @@ class DynamicFusionSearch {
|
|
|
598
635
|
paths.push('graph');
|
|
599
636
|
return paths;
|
|
600
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Get query classification details (for debugging/transparency)
|
|
640
|
+
*/
|
|
641
|
+
async getQueryClassification(query) {
|
|
642
|
+
return this.adaptiveQueryFusion.getClassificationDetails(query);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Clear classification cache
|
|
646
|
+
*/
|
|
647
|
+
clearClassificationCache() {
|
|
648
|
+
this.adaptiveQueryFusion.clearCache();
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Get cache statistics
|
|
652
|
+
*/
|
|
653
|
+
getClassificationCacheStats() {
|
|
654
|
+
return this.adaptiveQueryFusion.getCacheStats();
|
|
655
|
+
}
|
|
601
656
|
}
|
|
602
657
|
exports.DynamicFusionSearch = DynamicFusionSearch;
|