@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.
Files changed (101) hide show
  1. package/README.md +92 -40
  2. package/dist/chunker/semantic-chunker.d.ts +0 -1
  3. package/dist/chunker/semantic-chunker.d.ts.map +1 -1
  4. package/dist/chunker/semantic-chunker.js +1 -1
  5. package/dist/chunker/semantic-chunker.js.map +1 -1
  6. package/dist/embedder/index.d.ts +5 -0
  7. package/dist/embedder/index.d.ts.map +1 -1
  8. package/dist/embedder/index.js +40 -5
  9. package/dist/embedder/index.js.map +1 -1
  10. package/dist/errors/index.d.ts +1 -1
  11. package/dist/errors/index.d.ts.map +1 -1
  12. package/dist/flywheel/feedback.d.ts +1 -1
  13. package/dist/flywheel/feedback.d.ts.map +1 -1
  14. package/dist/flywheel/feedback.js +1 -1
  15. package/dist/flywheel/feedback.js.map +1 -1
  16. package/dist/hyde/index.d.ts +47 -0
  17. package/dist/hyde/index.d.ts.map +1 -0
  18. package/dist/hyde/index.js +203 -0
  19. package/dist/hyde/index.js.map +1 -0
  20. package/dist/parser/pdf-filter.d.ts +3 -5
  21. package/dist/parser/pdf-filter.d.ts.map +1 -1
  22. package/dist/parser/pdf-filter.js +1 -1
  23. package/dist/parser/pdf-filter.js.map +1 -1
  24. package/dist/query/parser.d.ts +2 -6
  25. package/dist/query/parser.d.ts.map +1 -1
  26. package/dist/query/parser.js +14 -22
  27. package/dist/query/parser.js.map +1 -1
  28. package/dist/reranker/index.d.ts +76 -0
  29. package/dist/reranker/index.d.ts.map +1 -0
  30. package/dist/reranker/index.js +199 -0
  31. package/dist/reranker/index.js.map +1 -0
  32. package/dist/server/index.d.ts +25 -0
  33. package/dist/server/index.d.ts.map +1 -1
  34. package/dist/server/index.js +140 -48
  35. package/dist/server/index.js.map +1 -1
  36. package/dist/server/raw-data-utils.d.ts +0 -40
  37. package/dist/server/raw-data-utils.d.ts.map +1 -1
  38. package/dist/server/raw-data-utils.js +9 -8
  39. package/dist/server/raw-data-utils.js.map +1 -1
  40. package/dist/server/remote-transport.d.ts +2 -1
  41. package/dist/server/remote-transport.d.ts.map +1 -1
  42. package/dist/server/remote-transport.js +26 -6
  43. package/dist/server/remote-transport.js.map +1 -1
  44. package/dist/server/schemas.d.ts +26 -129
  45. package/dist/server/schemas.d.ts.map +1 -1
  46. package/dist/server/schemas.js +9 -9
  47. package/dist/server/schemas.js.map +1 -1
  48. package/dist/utils/config-parsers.d.ts +14 -0
  49. package/dist/utils/config-parsers.d.ts.map +1 -1
  50. package/dist/utils/config-parsers.js +26 -0
  51. package/dist/utils/config-parsers.js.map +1 -1
  52. package/dist/utils/config.d.ts +23 -0
  53. package/dist/utils/config.d.ts.map +1 -1
  54. package/dist/utils/config.js +39 -1
  55. package/dist/utils/config.js.map +1 -1
  56. package/dist/utils/file-utils.d.ts.map +1 -1
  57. package/dist/utils/file-utils.js +17 -1
  58. package/dist/utils/file-utils.js.map +1 -1
  59. package/dist/vectordb/index.d.ts +45 -16
  60. package/dist/vectordb/index.d.ts.map +1 -1
  61. package/dist/vectordb/index.js +363 -170
  62. package/dist/vectordb/index.js.map +1 -1
  63. package/dist/web/api-routes.d.ts.map +1 -1
  64. package/dist/web/api-routes.js +23 -10
  65. package/dist/web/api-routes.js.map +1 -1
  66. package/dist/web/database-manager.d.ts.map +1 -1
  67. package/dist/web/database-manager.js +32 -25
  68. package/dist/web/database-manager.js.map +1 -1
  69. package/dist/web/http-server.d.ts +0 -5
  70. package/dist/web/http-server.d.ts.map +1 -1
  71. package/dist/web/http-server.js +3 -7
  72. package/dist/web/http-server.js.map +1 -1
  73. package/dist/web/middleware/async-handler.d.ts +2 -1
  74. package/dist/web/middleware/async-handler.d.ts.map +1 -1
  75. package/dist/web/middleware/rate-limit.d.ts +2 -1
  76. package/dist/web/middleware/rate-limit.d.ts.map +1 -1
  77. package/dist/web/middleware/request-logger.d.ts +1 -1
  78. package/dist/web/middleware/request-logger.d.ts.map +1 -1
  79. package/package.json +8 -7
  80. package/skills/rag-vault/SKILL.md +3 -3
  81. package/skills/rag-vault/references/html-ingestion.md +1 -1
  82. package/web-ui/dist/assets/{CollectionsPage-BDmEfv3V.js → CollectionsPage-wbfgYFTw.js} +1 -1
  83. package/web-ui/dist/assets/{FilesPage-pG9HmpgQ.js → FilesPage-D6TlldaR.js} +1 -1
  84. package/web-ui/dist/assets/ReaderPage-Sgy0vMZ6.js +28 -0
  85. package/web-ui/dist/assets/{ReaderSettingsContext-CkSjqsRh.js → ReaderSettingsContext-DsvLXuaf.js} +1 -1
  86. package/web-ui/dist/assets/{SearchPage-DAltjnLL.js → SearchPage-mPKXZEyq.js} +1 -1
  87. package/web-ui/dist/assets/{SettingsPage-C6J5BITP.js → SettingsPage-DXeWwfvd.js} +1 -1
  88. package/web-ui/dist/assets/{StatusPage-powRGmW3.js → StatusPage-AirpfsGF.js} +1 -1
  89. package/web-ui/dist/assets/{UploadPage-eyfSjL4u.js → UploadPage-Cob25kDa.js} +5 -5
  90. package/web-ui/dist/assets/index-BZMzEssr.js +6 -0
  91. package/web-ui/dist/assets/index-DovQIIL4.css +1 -0
  92. package/web-ui/dist/assets/motion-DdHBXDWx.js +9 -0
  93. package/web-ui/dist/assets/query-DbAD_nLW.js +1 -0
  94. package/web-ui/dist/assets/vendor-DNJ-hWNb.js +10 -0
  95. package/web-ui/dist/index.html +4 -4
  96. package/web-ui/dist/assets/ReaderPage-CwMN03NU.js +0 -28
  97. package/web-ui/dist/assets/index-BpwaiuGL.css +0 -1
  98. package/web-ui/dist/assets/index-D068MV_o.js +0 -6
  99. package/web-ui/dist/assets/motion-CKwJwI3J.js +0 -9
  100. package/web-ui/dist/assets/query-DPt-uCb6.js +0 -1
  101. 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":"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"}
@@ -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":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -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 ingested documents using hybrid semantic + keyword search. Advanced syntax supported:
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 = most relevant, higher = less relevant). Set explain=true to see why each result matched.`, {
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', 'Ingest a document file (PDF, DOCX, TXT, MD, JSON, JSONL) into the vector database for semantic search. File path must be an absolute path. Supports re-ingestion to update existing documents. Optional metadata can include author, domain, tags, etc.', {
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', 'Ingest content as a string, not from a file. Use for: fetched web pages (format: html), copied text (format: text), or markdown strings (format: markdown). The source identifier enables re-ingestion to update existing content. Optional custom metadata can include author, domain, tags, etc. For files on disk, use ingest_file instead.', {
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', 'Delete a previously ingested file or data from the vector database. Use filePath for files ingested via ingest_file, or source for data ingested via ingest_data. Either filePath or source must be provided.', {
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', 'List all ingested files in the vector database. Returns file paths and chunk counts for each document.', {}, async () => {
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', 'Get system status including total documents, total chunks, database size, and configuration information.', {}, async () => {
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', 'Pin a search result as relevant for a query. Pinned results will be boosted in future searches. Use when a result was particularly helpful.', {
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', 'Dismiss a search result as irrelevant for a query. Dismissed results will be penalized in future searches. Use when a result was unhelpful.', {
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', 'Get feedback statistics including total events, pinned pairs, and dismissed pairs.', {}, async () => {
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
- // Hybrid search (vector + BM25 keyword matching)
290
- let searchResults = await this.vectorStore.search(queryVector, args.query, requestLimit);
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 vectors (transaction processing)
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
- await this.vectorStore.insertChunks(vectorChunks);
435
- console.error(`Inserted ${vectorChunks.length} chunks for: ${args.filePath}`);
436
- }
437
- catch (insertError) {
438
- // Note: Full rollback is not possible without stored vectors.
439
- // If this was a re-ingestion and it failed, the old data has been deleted.
440
- // User should re-ingest from the original file.
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
  }