@robthepcguy/rag-vault 1.7.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -40
- package/dist/chunker/semantic-chunker.d.ts +0 -1
- package/dist/chunker/semantic-chunker.d.ts.map +1 -1
- package/dist/chunker/semantic-chunker.js +1 -1
- package/dist/chunker/semantic-chunker.js.map +1 -1
- package/dist/embedder/index.d.ts +5 -0
- package/dist/embedder/index.d.ts.map +1 -1
- package/dist/embedder/index.js +40 -5
- package/dist/embedder/index.js.map +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/flywheel/feedback.d.ts +1 -1
- package/dist/flywheel/feedback.d.ts.map +1 -1
- package/dist/flywheel/feedback.js +1 -1
- package/dist/flywheel/feedback.js.map +1 -1
- package/dist/hyde/index.d.ts +47 -0
- package/dist/hyde/index.d.ts.map +1 -0
- package/dist/hyde/index.js +203 -0
- package/dist/hyde/index.js.map +1 -0
- package/dist/parser/pdf-filter.d.ts +3 -5
- package/dist/parser/pdf-filter.d.ts.map +1 -1
- package/dist/parser/pdf-filter.js +1 -1
- package/dist/parser/pdf-filter.js.map +1 -1
- package/dist/query/parser.d.ts +2 -6
- package/dist/query/parser.d.ts.map +1 -1
- package/dist/query/parser.js +14 -22
- package/dist/query/parser.js.map +1 -1
- package/dist/reranker/index.d.ts +76 -0
- package/dist/reranker/index.d.ts.map +1 -0
- package/dist/reranker/index.js +199 -0
- package/dist/reranker/index.js.map +1 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +140 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/raw-data-utils.d.ts +0 -40
- package/dist/server/raw-data-utils.d.ts.map +1 -1
- package/dist/server/raw-data-utils.js +9 -8
- package/dist/server/raw-data-utils.js.map +1 -1
- package/dist/server/remote-transport.d.ts +2 -1
- package/dist/server/remote-transport.d.ts.map +1 -1
- package/dist/server/remote-transport.js +26 -6
- package/dist/server/remote-transport.js.map +1 -1
- package/dist/server/schemas.d.ts +26 -129
- package/dist/server/schemas.d.ts.map +1 -1
- package/dist/server/schemas.js +9 -9
- package/dist/server/schemas.js.map +1 -1
- package/dist/utils/config-parsers.d.ts +14 -0
- package/dist/utils/config-parsers.d.ts.map +1 -1
- package/dist/utils/config-parsers.js +26 -0
- package/dist/utils/config-parsers.js.map +1 -1
- package/dist/utils/config.d.ts +23 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +39 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +17 -1
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/vectordb/index.d.ts +45 -16
- package/dist/vectordb/index.d.ts.map +1 -1
- package/dist/vectordb/index.js +363 -170
- package/dist/vectordb/index.js.map +1 -1
- package/dist/web/api-routes.d.ts.map +1 -1
- package/dist/web/api-routes.js +23 -10
- package/dist/web/api-routes.js.map +1 -1
- package/dist/web/database-manager.d.ts.map +1 -1
- package/dist/web/database-manager.js +32 -25
- package/dist/web/database-manager.js.map +1 -1
- package/dist/web/http-server.d.ts +0 -5
- package/dist/web/http-server.d.ts.map +1 -1
- package/dist/web/http-server.js +3 -7
- package/dist/web/http-server.js.map +1 -1
- package/dist/web/middleware/async-handler.d.ts +2 -1
- package/dist/web/middleware/async-handler.d.ts.map +1 -1
- package/dist/web/middleware/rate-limit.d.ts +2 -1
- package/dist/web/middleware/rate-limit.d.ts.map +1 -1
- package/dist/web/middleware/request-logger.d.ts +1 -1
- package/dist/web/middleware/request-logger.d.ts.map +1 -1
- package/package.json +8 -7
- package/skills/rag-vault/SKILL.md +3 -3
- package/skills/rag-vault/references/html-ingestion.md +1 -1
- package/web-ui/dist/assets/{CollectionsPage-BDmEfv3V.js → CollectionsPage-wbfgYFTw.js} +1 -1
- package/web-ui/dist/assets/{FilesPage-pG9HmpgQ.js → FilesPage-D6TlldaR.js} +1 -1
- package/web-ui/dist/assets/ReaderPage-Sgy0vMZ6.js +28 -0
- package/web-ui/dist/assets/{ReaderSettingsContext-CkSjqsRh.js → ReaderSettingsContext-DsvLXuaf.js} +1 -1
- package/web-ui/dist/assets/{SearchPage-DAltjnLL.js → SearchPage-mPKXZEyq.js} +1 -1
- package/web-ui/dist/assets/{SettingsPage-C6J5BITP.js → SettingsPage-DXeWwfvd.js} +1 -1
- package/web-ui/dist/assets/{StatusPage-powRGmW3.js → StatusPage-AirpfsGF.js} +1 -1
- package/web-ui/dist/assets/{UploadPage-eyfSjL4u.js → UploadPage-Cob25kDa.js} +5 -5
- package/web-ui/dist/assets/index-BZMzEssr.js +6 -0
- package/web-ui/dist/assets/index-DovQIIL4.css +1 -0
- package/web-ui/dist/assets/motion-DdHBXDWx.js +9 -0
- package/web-ui/dist/assets/query-DbAD_nLW.js +1 -0
- package/web-ui/dist/assets/vendor-DNJ-hWNb.js +10 -0
- package/web-ui/dist/index.html +4 -4
- package/web-ui/dist/assets/ReaderPage-CwMN03NU.js +0 -28
- package/web-ui/dist/assets/index-BpwaiuGL.css +0 -1
- package/web-ui/dist/assets/index-D068MV_o.js +0 -6
- package/web-ui/dist/assets/motion-CKwJwI3J.js +0 -9
- package/web-ui/dist/assets/query-DPt-uCb6.js +0 -1
- package/web-ui/dist/assets/vendor-C2QPsZ3S.js +0 -10
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Cross-encoder reranker implementation with Transformers.js
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pipeline } from '@huggingface/transformers';
|
|
5
|
+
const SUPPORTED_DEVICES = [
|
|
6
|
+
'auto',
|
|
7
|
+
'gpu',
|
|
8
|
+
'cpu',
|
|
9
|
+
'wasm',
|
|
10
|
+
'webgpu',
|
|
11
|
+
'cuda',
|
|
12
|
+
'dml',
|
|
13
|
+
'webnn',
|
|
14
|
+
'webnn-npu',
|
|
15
|
+
'webnn-gpu',
|
|
16
|
+
'webnn-cpu',
|
|
17
|
+
];
|
|
18
|
+
const SUPPORTED_DEVICE_SET = new Set(SUPPORTED_DEVICES);
|
|
19
|
+
/** Default init timeout: 10 minutes */
|
|
20
|
+
const DEFAULT_INIT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
21
|
+
function getInitTimeoutMs(configValue) {
|
|
22
|
+
if (configValue !== undefined && configValue > 0)
|
|
23
|
+
return configValue;
|
|
24
|
+
const envValue = process.env['RERANKER_INIT_TIMEOUT_MS'];
|
|
25
|
+
if (envValue) {
|
|
26
|
+
const parsed = Number.parseInt(envValue, 10);
|
|
27
|
+
if (!Number.isNaN(parsed) && parsed > 0)
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
return DEFAULT_INIT_TIMEOUT_MS;
|
|
31
|
+
}
|
|
32
|
+
function withTimeout(promise, ms, label) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`)), ms);
|
|
35
|
+
promise.then((value) => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
resolve(value);
|
|
38
|
+
}, (error) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
reject(error);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// ============================================
|
|
45
|
+
// Reranker Class
|
|
46
|
+
// ============================================
|
|
47
|
+
/**
|
|
48
|
+
* Cross-encoder reranker using Transformers.js
|
|
49
|
+
*
|
|
50
|
+
* Scores (query, passage) pairs for relevance using a cross-encoder model.
|
|
51
|
+
* Unlike bi-encoders, cross-encoders jointly encode both texts, producing
|
|
52
|
+
* more accurate relevance judgments at the cost of speed.
|
|
53
|
+
*
|
|
54
|
+
* Default model: Xenova/ms-marco-MiniLM-L-6-v2 (~23MB ONNX)
|
|
55
|
+
*/
|
|
56
|
+
export class Reranker {
|
|
57
|
+
// Using unknown to avoid TS2590 (union type too complex with @types/jsdom)
|
|
58
|
+
model = null;
|
|
59
|
+
initPromise = null;
|
|
60
|
+
config;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.config = config;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the model name/path
|
|
66
|
+
*/
|
|
67
|
+
getModelName() {
|
|
68
|
+
return this.config.modelPath;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the device to use for inference
|
|
72
|
+
*/
|
|
73
|
+
resolveDevice() {
|
|
74
|
+
// Check config first
|
|
75
|
+
if (this.config.device && SUPPORTED_DEVICE_SET.has(this.config.device)) {
|
|
76
|
+
return this.config.device;
|
|
77
|
+
}
|
|
78
|
+
// Check environment variable
|
|
79
|
+
const envDevice = process.env['RAG_RERANKER_DEVICE'] || process.env['RAG_EMBEDDING_DEVICE'];
|
|
80
|
+
if (envDevice && SUPPORTED_DEVICE_SET.has(envDevice.toLowerCase())) {
|
|
81
|
+
return envDevice.toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
return 'auto';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a recovery cache directory for corrupted model caches
|
|
87
|
+
*/
|
|
88
|
+
getRecoveryCacheDir() {
|
|
89
|
+
return path.join(this.config.cacheDir, '.recovery-reranker');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if an error is recoverable by using a fresh cache
|
|
93
|
+
*/
|
|
94
|
+
isRecoverableCacheError(error) {
|
|
95
|
+
if (!(error instanceof Error))
|
|
96
|
+
return false;
|
|
97
|
+
const msg = error.message.toLowerCase();
|
|
98
|
+
return msg.includes('protobuf') || msg.includes('parsing failed') || msg.includes('corrupt');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Initialize Transformers.js cross-encoder model
|
|
102
|
+
*/
|
|
103
|
+
async initialize() {
|
|
104
|
+
if (this.model)
|
|
105
|
+
return;
|
|
106
|
+
try {
|
|
107
|
+
const device = this.resolveDevice();
|
|
108
|
+
const timeoutMs = getInitTimeoutMs(this.config.initTimeoutMs);
|
|
109
|
+
console.error(`Reranker: Using device preference "${device}"`);
|
|
110
|
+
console.error(`Reranker: Loading model "${this.config.modelPath}" (timeout: ${Math.round(timeoutMs / 1000)}s)...`);
|
|
111
|
+
this.model = await withTimeout(pipeline('text-classification', this.config.modelPath, {
|
|
112
|
+
device,
|
|
113
|
+
cache_dir: this.config.cacheDir,
|
|
114
|
+
}), timeoutMs, 'Reranker model initialization');
|
|
115
|
+
console.error('Reranker: Model loaded successfully');
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (this.isRecoverableCacheError(error)) {
|
|
119
|
+
const recoveryCacheDir = this.getRecoveryCacheDir();
|
|
120
|
+
console.error(`Reranker: Detected corrupted cache. Retrying with isolated cache: "${recoveryCacheDir}"`);
|
|
121
|
+
try {
|
|
122
|
+
await mkdir(recoveryCacheDir, { recursive: true });
|
|
123
|
+
const device = this.resolveDevice();
|
|
124
|
+
this.model = await withTimeout(pipeline('text-classification', this.config.modelPath, {
|
|
125
|
+
device,
|
|
126
|
+
cache_dir: recoveryCacheDir,
|
|
127
|
+
}), getInitTimeoutMs(this.config.initTimeoutMs), 'Reranker model initialization (recovery)');
|
|
128
|
+
console.error('Reranker: Model loaded successfully via recovery cache');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
catch (recoveryError) {
|
|
132
|
+
throw new Error(`Failed to initialize Reranker after cache recovery: ${recoveryError.message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Failed to initialize Reranker: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Ensure model is initialized (lazy initialization)
|
|
140
|
+
*/
|
|
141
|
+
async ensureInitialized() {
|
|
142
|
+
if (this.model)
|
|
143
|
+
return;
|
|
144
|
+
if (this.initPromise) {
|
|
145
|
+
await this.initPromise;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.error('Reranker: First use detected. Initializing model (downloading ~23MB, may take a moment)...');
|
|
149
|
+
const initWork = this.initialize().catch((error) => {
|
|
150
|
+
this.initPromise = null;
|
|
151
|
+
throw new Error(`Failed to initialize reranker on first use: ${error.message}\n\nPossible causes:\n • Network connectivity issues during model download\n • Insufficient disk space\n • Corrupted model cache\n\nRecommended actions:\n 1. Check your internet connection and try again\n 2. Delete cache: ${this.config.cacheDir}\n 3. Retry your query\n`);
|
|
152
|
+
});
|
|
153
|
+
this.initPromise = initWork;
|
|
154
|
+
await this.initPromise;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Rerank passages by relevance to a query using cross-encoder scoring.
|
|
158
|
+
*
|
|
159
|
+
* @param query - The search query
|
|
160
|
+
* @param passages - Array of passage texts to score
|
|
161
|
+
* @returns Array of {index, score} sorted by score descending (most relevant first)
|
|
162
|
+
*/
|
|
163
|
+
async rerank(query, passages) {
|
|
164
|
+
if (passages.length === 0)
|
|
165
|
+
return [];
|
|
166
|
+
await this.ensureInitialized();
|
|
167
|
+
try {
|
|
168
|
+
// Runtime check: model must be a callable pipeline
|
|
169
|
+
if (typeof this.model !== 'function') {
|
|
170
|
+
throw new Error('Reranker model is not initialized or not callable');
|
|
171
|
+
}
|
|
172
|
+
// Cross-encoder expects pairs of (text_a, text_b)
|
|
173
|
+
// For ms-marco models, we use the text_pair approach
|
|
174
|
+
const modelCall = this.model;
|
|
175
|
+
// Prepare inputs: query repeated for each passage
|
|
176
|
+
const queries = passages.map(() => query);
|
|
177
|
+
const rawOutputs = await modelCall(queries, { text_pair: passages });
|
|
178
|
+
// Validate output shape at runtime
|
|
179
|
+
if (!Array.isArray(rawOutputs)) {
|
|
180
|
+
throw new Error(`Reranker returned non-array output: ${typeof rawOutputs}`);
|
|
181
|
+
}
|
|
182
|
+
// Map results back with original indices
|
|
183
|
+
const results = rawOutputs.map((output, index) => {
|
|
184
|
+
const outputObj = output;
|
|
185
|
+
const rawScore = outputObj && typeof outputObj === 'object' ? Number(outputObj['score']) : 0;
|
|
186
|
+
return { index, score: Number.isFinite(rawScore) ? rawScore : 0 };
|
|
187
|
+
});
|
|
188
|
+
// Sort by score descending (most relevant first)
|
|
189
|
+
results.sort((a, b) => b.score - a.score);
|
|
190
|
+
return results;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.error(`Reranker: Scoring failed: ${error.message}`);
|
|
194
|
+
// Return original order on failure (graceful degradation)
|
|
195
|
+
return passages.map((_, index) => ({ index, score: 0 }));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reranker/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAoCpD,MAAM,iBAAiB,GAAG;IACxB,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,QAAQ;IACR,MAAM;IACN,KAAK;IACL,OAAO;IACP,WAAW;IACX,WAAW;IACX,WAAW;CACH,CAAA;AAGV,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAS,iBAAiB,CAAC,CAAA;AAE/D,uCAAuC;AACvC,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAE9C,SAAS,gBAAgB,CAAC,WAAoB;IAC5C,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,GAAG,CAAC;QAAE,OAAO,WAAW,CAAA;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;IACxD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;YAAE,OAAO,MAAM,CAAA;IACxD,CAAC;IACD,OAAO,uBAAuB,CAAA;AAChC,CAAC;AAED,SAAS,WAAW,CAAI,OAAmB,EAAE,EAAU,EAAE,KAAa;IACpE,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAC7E,EAAE,CACH,CAAA;QACD,OAAO,CAAC,IAAI,CACV,CAAC,KAAK,EAAE,EAAE;YACR,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,OAAO,CAAC,KAAK,CAAC,CAAA;QAChB,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACR,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,CAAC,KAAK,CAAC,CAAA;QACf,CAAC,CACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,+CAA+C;AAC/C,iBAAiB;AACjB,+CAA+C;AAE/C;;;;;;;;GAQG;AACH,MAAM,OAAO,QAAQ;IACnB,2EAA2E;IACnE,KAAK,GAAY,IAAI,CAAA;IACrB,WAAW,GAAyB,IAAI,CAAA;IAC/B,MAAM,CAAgB;IAEvC,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA;IAC9B,CAAC;IAED;;OAEG;IACK,aAAa;QACnB,qBAAqB;QACrB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACvE,OAAO,IAAI,CAAC,MAAM,CAAC,MAAoB,CAAA;QACzC,CAAC;QAED,6BAA6B;QAC7B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;QAC3F,IAAI,SAAS,IAAI,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACnE,OAAO,SAAS,CAAC,WAAW,EAAgB,CAAA;QAC9C,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAA;IAC9D,CAAC;IAED;;OAEG;IACK,uBAAuB,CAAC,KAAc;QAC5C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;YAAE,OAAO,KAAK,CAAA;QAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;QACvC,OAAO,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAC9F,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,KAAK;YAAE,OAAM;QAEtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAA;YACnC,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;YAE7D,OAAO,CAAC,KAAK,CAAC,sCAAsC,MAAM,GAAG,CAAC,CAAA;YAC9D,OAAO,CAAC,KAAK,CACX,4BAA4B,IAAI,CAAC,MAAM,CAAC,SAAS,eAAe,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CACpG,CAAA;YAED,IAAI,CAAC,KAAK,GAAG,MAAM,WAAW,CAC5B,QAAQ,CAAC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;gBACrD,MAAM;gBACN,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;aAChC,CAAC,EACF,SAAS,EACT,+BAA+B,CAChC,CAAA;YACD,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAA;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxC,MAAM,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAA;gBACnD,OAAO,CAAC,KAAK,CACX,sEAAsE,gBAAgB,GAAG,CAC1F,CAAA;gBAED,IAAI,CAAC;oBACH,MAAM,KAAK,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;oBAClD,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAA;oBACnC,IAAI,CAAC,KAAK,GAAG,MAAM,WAAW,CAC5B,QAAQ,CAAC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;wBACrD,MAAM;wBACN,SAAS,EAAE,gBAAgB;qBAC5B,CAAC,EACF,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAC3C,0CAA0C,CAC3C,CAAA;oBACD,OAAO,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAA;oBACvE,OAAM;gBACR,CAAC;gBAAC,OAAO,aAAa,EAAE,CAAC;oBACvB,MAAM,IAAI,KAAK,CACb,uDAAwD,aAAuB,CAAC,OAAO,EAAE,CAC1F,CAAA;gBACH,CAAC;YACH,CAAC;YAED,MAAM,IAAI,KAAK,CAAC,kCAAmC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;QAC/E,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,iBAAiB;QAC7B,IAAI,IAAI,CAAC,KAAK;YAAE,OAAM;QAEtB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,CAAC,WAAW,CAAA;YACtB,OAAM;QACR,CAAC;QAED,OAAO,CAAC,KAAK,CACX,4FAA4F,CAC7F,CAAA;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACjD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;YACvB,MAAM,IAAI,KAAK,CACb,+CAAgD,KAAe,CAAC,OAAO,sOAAsO,IAAI,CAAC,MAAM,CAAC,QAAQ,2BAA2B,CAC7V,CAAA;QACH,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAA;QAE3B,MAAM,IAAI,CAAC,WAAW,CAAA;IACxB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,QAAkB;QAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAA;QAEpC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAE9B,IAAI,CAAC;YACH,mDAAmD;YACnD,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACrC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;YACtE,CAAC;YAED,kDAAkD;YAClD,qDAAqD;YACrD,MAAM,SAAS,GAAG,IAAI,CAAC,KAGF,CAAA;YAErB,kDAAkD;YAClD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAA;YAEzC,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAA;YAEpE,mCAAmC;YACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,uCAAuC,OAAO,UAAU,EAAE,CAAC,CAAA;YAC7E,CAAC;YAED,yCAAyC;YACzC,MAAM,OAAO,GAAqB,UAAU,CAAC,GAAG,CAAC,CAAC,MAAe,EAAE,KAAa,EAAE,EAAE;gBAClF,MAAM,SAAS,GAAG,MAAwC,CAAA;gBAC1D,MAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC5F,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YACnE,CAAC,CAAC,CAAA;YAEF,iDAAiD;YACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA;YAEzC,OAAO,OAAO,CAAA;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,6BAA8B,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;YACtE,0DAA0D;YAC1D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAC1D,CAAC;IACH,CAAC;CACF"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -21,6 +21,28 @@ export interface RAGServerConfig {
|
|
|
21
21
|
grouping?: GroupingMode;
|
|
22
22
|
/** Hybrid search weight for BM25 (0.0 = vector only, 1.0 = BM25 only, default 0.6) */
|
|
23
23
|
hybridWeight?: number;
|
|
24
|
+
/** Enable cross-encoder reranking */
|
|
25
|
+
rerankerEnabled?: boolean;
|
|
26
|
+
/** Cross-encoder model name (default: Xenova/ms-marco-MiniLM-L-6-v2) */
|
|
27
|
+
rerankerModel?: string;
|
|
28
|
+
/** Reranker candidate multiplier (default: 2) */
|
|
29
|
+
rerankerCandidateMultiplier?: number;
|
|
30
|
+
/** Enable HyDE query expansion */
|
|
31
|
+
hydeEnabled?: boolean;
|
|
32
|
+
/** HyDE backend: 'rule-based' or 'api' */
|
|
33
|
+
hydeBackend?: string;
|
|
34
|
+
/** Number of HyDE expansions (default: 2) */
|
|
35
|
+
hydeExpansions?: number;
|
|
36
|
+
/** API key for HyDE API backend (only used when hydeBackend='api') */
|
|
37
|
+
hydeApiKey?: string;
|
|
38
|
+
/** API base URL for HyDE API backend */
|
|
39
|
+
hydeApiBaseUrl?: string;
|
|
40
|
+
/** API model for HyDE API backend */
|
|
41
|
+
hydeApiModel?: string;
|
|
42
|
+
/** Search mode: 'rrf' or 'boost' */
|
|
43
|
+
searchMode?: string;
|
|
44
|
+
/** RRF K constant (smoothing factor, default: 60) */
|
|
45
|
+
rrfK?: number;
|
|
24
46
|
}
|
|
25
47
|
/**
|
|
26
48
|
* RAG server compliant with MCP Protocol
|
|
@@ -35,6 +57,9 @@ export declare class RAGServer {
|
|
|
35
57
|
private readonly server;
|
|
36
58
|
private readonly vectorStore;
|
|
37
59
|
private readonly embedder;
|
|
60
|
+
private readonly reranker;
|
|
61
|
+
private readonly rerankerCandidateMultiplier;
|
|
62
|
+
private readonly hydeExpander;
|
|
38
63
|
private readonly chunker;
|
|
39
64
|
private readonly parser;
|
|
40
65
|
private readonly dbPath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { SemanticChunker } from '../chunker/index.js';
|
|
8
8
|
import { Embedder } from '../embedder/index.js';
|
|
9
|
+
import { HyDEExpander } from '../hyde/index.js';
|
|
10
|
+
import { Reranker } from '../reranker/index.js';
|
|
9
11
|
import { getErrorMessage } from '../errors/index.js';
|
|
10
12
|
import { explainChunkSimilarity } from '../explainability/keywords.js';
|
|
11
13
|
import { getFeedbackStore } from '../flywheel/feedback.js';
|
|
@@ -16,6 +18,28 @@ import { VectorStore } from '../vectordb/index.js';
|
|
|
16
18
|
import { extractSourceFromPath, generateRawDataPath, isManagedRawDataPath, saveRawData, } from './raw-data-utils.js';
|
|
17
19
|
import { DeleteFileSchema, } from './schemas.js';
|
|
18
20
|
// ============================================
|
|
21
|
+
// Constants
|
|
22
|
+
// ============================================
|
|
23
|
+
/** Timeout for VectorStore.initialize() (default: 10 seconds) */
|
|
24
|
+
const VECTORSTORE_INIT_TIMEOUT_MS = Number.parseInt(process.env['VECTORSTORE_INIT_TIMEOUT_MS'] || '10000', 10);
|
|
25
|
+
/** Timeout for MCP transport connect (default: 10 seconds) */
|
|
26
|
+
const MCP_CONNECT_TIMEOUT_MS = Number.parseInt(process.env['MCP_CONNECT_TIMEOUT_MS'] || '10000', 10);
|
|
27
|
+
/**
|
|
28
|
+
* Wrap a promise with a timeout
|
|
29
|
+
*/
|
|
30
|
+
function withTimeout(promise, ms, label) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`)), ms);
|
|
33
|
+
promise.then((value) => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
resolve(value);
|
|
36
|
+
}, (error) => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
reject(error);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// ============================================
|
|
19
43
|
// RAGServer Class
|
|
20
44
|
// ============================================
|
|
21
45
|
/**
|
|
@@ -31,6 +55,9 @@ export class RAGServer {
|
|
|
31
55
|
server;
|
|
32
56
|
vectorStore;
|
|
33
57
|
embedder;
|
|
58
|
+
reranker;
|
|
59
|
+
rerankerCandidateMultiplier;
|
|
60
|
+
hydeExpander;
|
|
34
61
|
chunker;
|
|
35
62
|
parser;
|
|
36
63
|
dbPath;
|
|
@@ -55,12 +82,53 @@ export class RAGServer {
|
|
|
55
82
|
if (config.hybridWeight !== undefined) {
|
|
56
83
|
vectorStoreConfig.hybridWeight = config.hybridWeight;
|
|
57
84
|
}
|
|
85
|
+
if (config.searchMode !== undefined) {
|
|
86
|
+
vectorStoreConfig.searchMode = config.searchMode;
|
|
87
|
+
}
|
|
88
|
+
if (config.rrfK !== undefined) {
|
|
89
|
+
vectorStoreConfig.rrfK = config.rrfK;
|
|
90
|
+
}
|
|
58
91
|
this.vectorStore = new VectorStore(vectorStoreConfig);
|
|
59
92
|
this.embedder = new Embedder({
|
|
60
93
|
modelPath: config.modelName,
|
|
61
94
|
batchSize: 16,
|
|
62
95
|
cacheDir: config.cacheDir,
|
|
63
96
|
});
|
|
97
|
+
// Cross-encoder reranker (opt-in)
|
|
98
|
+
if (config.rerankerEnabled) {
|
|
99
|
+
this.reranker = new Reranker({
|
|
100
|
+
modelPath: config.rerankerModel || 'Xenova/ms-marco-MiniLM-L-6-v2',
|
|
101
|
+
cacheDir: config.cacheDir,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.reranker = null;
|
|
106
|
+
}
|
|
107
|
+
this.rerankerCandidateMultiplier = config.rerankerCandidateMultiplier ?? 2;
|
|
108
|
+
// HyDE query expansion (opt-in)
|
|
109
|
+
if (config.hydeEnabled) {
|
|
110
|
+
const hydeConfig = {
|
|
111
|
+
enabled: true,
|
|
112
|
+
backend: (config.hydeBackend === 'api' ? 'api' : 'rule-based'),
|
|
113
|
+
numExpansions: config.hydeExpansions ?? 2,
|
|
114
|
+
};
|
|
115
|
+
if (config.hydeApiKey)
|
|
116
|
+
hydeConfig.apiKey = config.hydeApiKey;
|
|
117
|
+
if (config.hydeApiBaseUrl)
|
|
118
|
+
hydeConfig.apiBaseUrl = config.hydeApiBaseUrl;
|
|
119
|
+
if (config.hydeApiModel)
|
|
120
|
+
hydeConfig.apiModel = config.hydeApiModel;
|
|
121
|
+
this.hydeExpander = new HyDEExpander(hydeConfig);
|
|
122
|
+
if (config.hydeBackend === 'api' && config.hydeApiKey) {
|
|
123
|
+
console.error('WARNING: HyDE API backend is enabled. Queries will be sent to an external LLM endpoint ' +
|
|
124
|
+
`(${config.hydeApiBaseUrl || 'https://api.anthropic.com'}). ` +
|
|
125
|
+
'This breaks the "Zero cloud" privacy guarantee. ' +
|
|
126
|
+
'Set RAG_HYDE_BACKEND=rule-based to use local-only query expansion.');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.hydeExpander = null;
|
|
131
|
+
}
|
|
64
132
|
this.chunker = new SemanticChunker();
|
|
65
133
|
this.parser = new DocumentParser({
|
|
66
134
|
baseDir: config.baseDir,
|
|
@@ -83,13 +151,13 @@ export class RAGServer {
|
|
|
83
151
|
*/
|
|
84
152
|
setupHandlers(target = this.server) {
|
|
85
153
|
// query_documents tool
|
|
86
|
-
target.tool('query_documents', `Search
|
|
154
|
+
target.tool('query_documents', `Search your documents using both meaning and exact keyword matching. You can also use advanced syntax:
|
|
87
155
|
- "exact phrase" → Match phrase exactly
|
|
88
156
|
- field:value → Filter by custom metadata (e.g., domain:legal, author:john)
|
|
89
157
|
- term1 AND term2 → Both terms required (default)
|
|
90
158
|
- term1 OR term2 → Either term matches
|
|
91
159
|
- -term → Exclude results containing term
|
|
92
|
-
Results include score (0 =
|
|
160
|
+
Results include a score (0 = best match, higher = less relevant). Set explain=true to see why each result matched.`, {
|
|
93
161
|
query: z.string(),
|
|
94
162
|
limit: z.number().optional(),
|
|
95
163
|
explain: z.boolean().optional(),
|
|
@@ -100,9 +168,9 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
100
168
|
};
|
|
101
169
|
});
|
|
102
170
|
// ingest_file tool
|
|
103
|
-
target.tool('ingest_file', '
|
|
171
|
+
target.tool('ingest_file', 'Add a document (PDF, DOCX, TXT, MD, JSON, JSONL) to your knowledge base so you can search it. Use the full file path. If you ingest the same file again, it replaces the old version. You can tag it with metadata like author, domain, or tags.', {
|
|
104
172
|
filePath: z.string(),
|
|
105
|
-
metadata: z.record(z.string()).optional(),
|
|
173
|
+
metadata: z.record(z.string(), z.string()).optional(),
|
|
106
174
|
}, async (args) => {
|
|
107
175
|
const result = await this.executeIngestFile(args);
|
|
108
176
|
return {
|
|
@@ -110,12 +178,12 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
110
178
|
};
|
|
111
179
|
});
|
|
112
180
|
// ingest_data tool
|
|
113
|
-
target.tool('ingest_data', '
|
|
181
|
+
target.tool('ingest_data', 'Add text content directly instead of from a file. Good for: fetched web pages (format: html), copied text (format: text), or markdown strings (format: markdown). The source identifier lets you update the content later by re-ingesting with the same source. You can add custom metadata too. For files on disk, use ingest_file instead.', {
|
|
114
182
|
content: z.string(),
|
|
115
183
|
metadata: z.object({
|
|
116
184
|
source: z.string(),
|
|
117
185
|
format: z.enum(['text', 'html', 'markdown']),
|
|
118
|
-
custom: z.record(z.string()).optional(),
|
|
186
|
+
custom: z.record(z.string(), z.string()).optional(),
|
|
119
187
|
}),
|
|
120
188
|
}, async (args) => {
|
|
121
189
|
const result = await this.executeIngestData(args);
|
|
@@ -124,7 +192,7 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
124
192
|
};
|
|
125
193
|
});
|
|
126
194
|
// delete_file tool (uses .refine(), so handle validation in handler)
|
|
127
|
-
target.tool('delete_file', '
|
|
195
|
+
target.tool('delete_file', 'Remove a document from your knowledge base. Use filePath for files you added with ingest_file, or source for content you added with ingest_data. You need to provide one or the other.', {
|
|
128
196
|
filePath: z.string().optional(),
|
|
129
197
|
source: z.string().optional(),
|
|
130
198
|
}, async (args) => {
|
|
@@ -139,21 +207,21 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
139
207
|
};
|
|
140
208
|
});
|
|
141
209
|
// list_files tool
|
|
142
|
-
target.tool('list_files', '
|
|
210
|
+
target.tool('list_files', 'Show all documents in your knowledge base, with file paths and how many chunks each one has.', {}, async () => {
|
|
143
211
|
const files = await this.executeListFiles();
|
|
144
212
|
return {
|
|
145
213
|
content: [{ type: 'text', text: JSON.stringify(files, null, 2) }],
|
|
146
214
|
};
|
|
147
215
|
});
|
|
148
216
|
// status tool
|
|
149
|
-
target.tool('status', '
|
|
217
|
+
target.tool('status', 'Check how many documents and chunks you have, the database size, and current settings.', {}, async () => {
|
|
150
218
|
const status = await this.executeStatus();
|
|
151
219
|
return {
|
|
152
220
|
content: [{ type: 'text', text: JSON.stringify(status, null, 2) }],
|
|
153
221
|
};
|
|
154
222
|
});
|
|
155
223
|
// feedback_pin tool
|
|
156
|
-
target.tool('feedback_pin', '
|
|
224
|
+
target.tool('feedback_pin', 'Mark a search result as relevant for a query. Pinned results get boosted in future searches. Use this when a result was helpful.', {
|
|
157
225
|
sourceQuery: z.string().describe('The query that returned this result'),
|
|
158
226
|
targetFilePath: z.string().describe('File path of the result to pin'),
|
|
159
227
|
targetChunkIndex: z.number().describe('Chunk index of the result to pin'),
|
|
@@ -181,7 +249,7 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
181
249
|
}
|
|
182
250
|
});
|
|
183
251
|
// feedback_dismiss tool
|
|
184
|
-
target.tool('feedback_dismiss',
|
|
252
|
+
target.tool('feedback_dismiss', "Mark a search result as irrelevant for a query. Dismissed results get pushed down in future searches. Use this when a result wasn't helpful.", {
|
|
185
253
|
sourceQuery: z.string().describe('The query that returned this result'),
|
|
186
254
|
targetFilePath: z.string().describe('File path of the result to dismiss'),
|
|
187
255
|
targetChunkIndex: z.number().describe('Chunk index of the result to dismiss'),
|
|
@@ -209,7 +277,7 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
209
277
|
}
|
|
210
278
|
});
|
|
211
279
|
// feedback_stats tool
|
|
212
|
-
target.tool('feedback_stats',
|
|
280
|
+
target.tool('feedback_stats', "See your feedback stats: total events, how many results you've pinned, and how many you've dismissed.", {}, async () => {
|
|
213
281
|
try {
|
|
214
282
|
const stats = this.executeFeedbackStats();
|
|
215
283
|
return {
|
|
@@ -233,7 +301,7 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
233
301
|
* Initialization
|
|
234
302
|
*/
|
|
235
303
|
async initialize() {
|
|
236
|
-
await this.vectorStore.initialize();
|
|
304
|
+
await withTimeout(this.vectorStore.initialize(), VECTORSTORE_INIT_TIMEOUT_MS, 'VectorStore initialization');
|
|
237
305
|
console.error('RAGServer initialized');
|
|
238
306
|
}
|
|
239
307
|
/**
|
|
@@ -282,12 +350,55 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
282
350
|
const semanticQuery = toSemanticQuery(parsed);
|
|
283
351
|
// Generate query embedding from semantic terms
|
|
284
352
|
const queryVector = await this.embedder.embed(semanticQuery || args.query);
|
|
353
|
+
// HyDE: expand query into hypothetical documents and embed them
|
|
354
|
+
let additionalVectors;
|
|
355
|
+
if (this.hydeExpander) {
|
|
356
|
+
try {
|
|
357
|
+
const expandedQueries = await this.hydeExpander.expandQuery(args.query);
|
|
358
|
+
// Skip first item (original query, already embedded above)
|
|
359
|
+
const expansions = expandedQueries.slice(1);
|
|
360
|
+
if (expansions.length > 0) {
|
|
361
|
+
additionalVectors = [];
|
|
362
|
+
for (const expansion of expansions) {
|
|
363
|
+
const expansionVector = await this.embedder.embed(expansion.text);
|
|
364
|
+
additionalVectors.push({ vector: expansionVector, weight: expansion.weight });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
catch (hydeError) {
|
|
369
|
+
console.error(`HyDE expansion failed, using original query only: ${hydeError.message}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
285
372
|
// Request extra results to account for post-filtering (capped at 20)
|
|
286
373
|
const userLimit = args.limit || 10;
|
|
287
374
|
const hasFilters = parsed.excludeTerms.length > 0 || parsed.filters.length > 0;
|
|
288
375
|
const requestLimit = hasFilters ? Math.min(userLimit * 2, 20) : userLimit;
|
|
289
|
-
//
|
|
290
|
-
|
|
376
|
+
// Expand request limit for reranking if enabled
|
|
377
|
+
const rerankerActive = this.reranker !== null;
|
|
378
|
+
const rerankerLimit = rerankerActive
|
|
379
|
+
? Math.min(requestLimit * this.rerankerCandidateMultiplier, 20)
|
|
380
|
+
: requestLimit;
|
|
381
|
+
// Hybrid search (vector + BM25 keyword matching, with RRF fusion if enabled)
|
|
382
|
+
let searchResults = await this.vectorStore.search(queryVector, args.query, rerankerLimit, additionalVectors);
|
|
383
|
+
// Cross-encoder reranking: re-score top candidates for better relevance
|
|
384
|
+
if (rerankerActive && this.reranker && searchResults.length > 0) {
|
|
385
|
+
try {
|
|
386
|
+
const reranked = await this.reranker.rerank(args.query, searchResults.map((r) => r.text));
|
|
387
|
+
// Reorder results by cross-encoder score, convert to pseudo-distance
|
|
388
|
+
const reorderedResults = reranked.map(({ index, score }) => ({
|
|
389
|
+
...searchResults[index],
|
|
390
|
+
// Convert cross-encoder score to distance-like metric (lower = better)
|
|
391
|
+
// Cross-encoder scores can be negative, so we use: 1 / (1 + exp(score))
|
|
392
|
+
// This produces values in (0, 1) where lower = more relevant
|
|
393
|
+
score: 1 / (1 + Math.exp(score)),
|
|
394
|
+
}));
|
|
395
|
+
searchResults = reorderedResults.slice(0, requestLimit);
|
|
396
|
+
}
|
|
397
|
+
catch (rerankerError) {
|
|
398
|
+
console.error(`Reranker failed, using original ordering: ${rerankerError.message}`);
|
|
399
|
+
searchResults = searchResults.slice(0, requestLimit);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
291
402
|
// Apply flywheel reranking based on user feedback
|
|
292
403
|
const feedbackStore = getFeedbackStore();
|
|
293
404
|
const sourceRef = {
|
|
@@ -384,25 +495,6 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
384
495
|
const chunks = await this.chunker.chunkText(text, this.embedder);
|
|
385
496
|
// Generate embeddings for final chunks
|
|
386
497
|
const embeddings = await this.embedder.embedBatch(chunks.map((chunk) => chunk.text));
|
|
387
|
-
// Note: Full backup with vectors is not implemented because search results don't include vectors.
|
|
388
|
-
// If rollback is needed, re-ingestion from the original file is required.
|
|
389
|
-
// Track if this is a re-ingestion for logging purposes.
|
|
390
|
-
let isReingestion = false;
|
|
391
|
-
try {
|
|
392
|
-
const existingFiles = await this.vectorStore.listFiles();
|
|
393
|
-
const existingFile = existingFiles.find((file) => file.filePath === args.filePath);
|
|
394
|
-
if (existingFile && existingFile.chunkCount > 0) {
|
|
395
|
-
isReingestion = true;
|
|
396
|
-
console.error(`Re-ingesting existing file: ${args.filePath} (${existingFile.chunkCount} existing chunks)`);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
catch (error) {
|
|
400
|
-
// File check failure is warning only (for new files)
|
|
401
|
-
console.warn('Failed to check existing file (new file?):', error);
|
|
402
|
-
}
|
|
403
|
-
// Delete existing data
|
|
404
|
-
await this.vectorStore.deleteChunks(args.filePath);
|
|
405
|
-
console.error(`Deleted existing chunks for: ${args.filePath}`);
|
|
406
498
|
// Validate embeddings and chunks match
|
|
407
499
|
if (embeddings.length !== chunks.length) {
|
|
408
500
|
throw new Error(`Embedding count (${embeddings.length}) doesn't match chunk count (${chunks.length})`);
|
|
@@ -429,20 +521,20 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
429
521
|
timestamp,
|
|
430
522
|
};
|
|
431
523
|
});
|
|
432
|
-
// Insert
|
|
524
|
+
// Insert-then-delete strategy: avoids data-loss window during re-ingestion.
|
|
525
|
+
// Insert new vectors first, then delete old ones. On crash between the two,
|
|
526
|
+
// the worst case is temporary duplicates rather than permanent data loss.
|
|
527
|
+
await this.vectorStore.insertChunks(vectorChunks);
|
|
528
|
+
console.error(`Inserted ${vectorChunks.length} chunks for: ${args.filePath}`);
|
|
529
|
+
// Delete old chunks that don't belong to this ingestion batch
|
|
433
530
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
if (isReingestion) {
|
|
442
|
-
console.error('Ingestion failed during re-ingestion. Previous data was deleted. ' +
|
|
443
|
-
'Please re-ingest from the original file to restore.', insertError);
|
|
444
|
-
}
|
|
445
|
-
throw insertError;
|
|
531
|
+
const newIds = new Set(vectorChunks.map((c) => c.id));
|
|
532
|
+
await this.vectorStore.deleteChunksExcluding(args.filePath, newIds);
|
|
533
|
+
console.error(`Cleaned up old chunks for: ${args.filePath}`);
|
|
534
|
+
}
|
|
535
|
+
catch (deleteError) {
|
|
536
|
+
// Non-fatal: duplicates are better than data loss
|
|
537
|
+
console.warn(`Failed to clean up old chunks for ${args.filePath}. Duplicates may exist until next re-ingestion.`, deleteError);
|
|
446
538
|
}
|
|
447
539
|
return {
|
|
448
540
|
filePath: args.filePath,
|
|
@@ -822,7 +914,7 @@ Results include score (0 = most relevant, higher = less relevant). Set explain=t
|
|
|
822
914
|
*/
|
|
823
915
|
async run() {
|
|
824
916
|
const transport = new StdioServerTransport();
|
|
825
|
-
await this.server.connect(transport);
|
|
917
|
+
await withTimeout(this.server.connect(transport), MCP_CONNECT_TIMEOUT_MS, 'MCP transport connect');
|
|
826
918
|
console.error('RAGServer running on stdio transport');
|
|
827
919
|
}
|
|
828
920
|
}
|