n8n-nodes-ollama-reranker 1.3.2 → 1.4.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 CHANGED
@@ -3,13 +3,17 @@
3
3
  [![npm version](https://img.shields.io/npm/v/n8n-nodes-ollama-reranker)](https://www.npmjs.com/package/n8n-nodes-ollama-reranker)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Ollama Reranker provider for n8n Vector Store nodes - rerank documents using local Ollama models.
6
+ **Advanced Reranker Provider for n8n** - Supporting Ollama-compatible APIs, Custom Rerank servers, and VL Classifiers.
7
+
8
+ > **⚠️ Important Note:** *Sorry folks, Ollama doesn't natively support reranker models! We're developing our own solution to bring powerful reranking capabilities to n8n. This package works with Ollama-compatible APIs that implement reranking through prompt-based scoring, custom rerank endpoints, and now Vision-Language classification servers.*
7
9
 
8
10
  ## Features
9
11
 
10
12
  - 🎯 **Integrates seamlessly** with n8n Vector Store nodes
11
- - 🚀 **Local inference** using Ollama (no API keys needed)
13
+ - 🚀 **Multiple API types**: Ollama Generate, Custom Rerank, VL Classifier
14
+ - 🤖 **Auto-detection** of server capabilities
12
15
  - 🔧 **Multiple models** supported (BGE Reranker, Qwen3 family)
16
+ - 🎨 **VL Classification** for document complexity analysis (v1.4.0+)
13
17
  - ⚡ **Concurrent processing** with configurable batch sizes
14
18
  - 🔄 **Automatic retries** with exponential backoff
15
19
  - 📊 **Flexible scoring** with threshold and topK parameters
@@ -43,6 +47,9 @@ USER node
43
47
 
44
48
  ## Prerequisites
45
49
 
50
+ Choose your server type:
51
+
52
+ ### Option 1: Ollama (Prompt-based reranking)
46
53
  1. **Ollama** must be running and accessible
47
54
  2. Pull a reranker model:
48
55
 
@@ -54,15 +61,41 @@ ollama pull bge-reranker-v2-m3
54
61
  ollama pull dengcao/Qwen3-Reranker-4B:Q5_K_M
55
62
  ```
56
63
 
64
+ ### Option 2: Custom Rerank API
65
+ Use any service implementing `/api/rerank` endpoint (like deposium-embeddings-turbov2)
66
+
67
+ ### Option 3: VL Classifier Server (NEW in v1.4.0)
68
+ Deploy a Vision-Language classifier server with:
69
+ - `/api/status` - Server health and capabilities
70
+ - `/api/classify` - Document complexity classification
71
+ - Optional `/api/rerank` - Direct reranking support
72
+
73
+ Example: `deposium_embeddings-turbov2` with ResNet18 ONNX INT8 model
74
+
57
75
  ## Usage
58
76
 
77
+ ### Basic Setup
59
78
  1. Add an **Ollama Reranker** node to your workflow
60
79
  2. Connect it to a Vector Store node (e.g., Pinecone, Qdrant, Supabase)
61
80
  3. Configure:
81
+ - **API Type**: Choose between:
82
+ - `Ollama Generate API` - Standard Ollama prompt-based
83
+ - `Custom Rerank API` - Direct reranking endpoint
84
+ - `VL Classifier + Reranker` - Vision-Language classification
85
+ - `Auto-Detect` - Automatically detect server type
62
86
  - **Model**: Select a reranker model
63
87
  - **Top K**: Number of documents to return
64
88
  - **Threshold**: Minimum relevance score (0-1)
65
- - **Ollama Host**: URL to your Ollama instance
89
+ - **Base URL**: URL to your server
90
+
91
+ ### VL Classifier Options (v1.4.0+)
92
+ When using VL Classifier:
93
+ - **Enable VL Classification**: Use complexity analysis
94
+ - **Classification Strategy**:
95
+ - `Metadata` - Add complexity as document metadata
96
+ - `Filter` - Filter by complexity before reranking
97
+ - `Both` - Combine filtering and metadata
98
+ - **Filter Complexity**: Keep LOW, HIGH, or both complexity documents
66
99
 
67
100
  ### Example Workflow
68
101
 
@@ -72,8 +105,9 @@ User Query → Vector Store (retrieve 50 docs)
72
105
  → Continue with top-ranked documents
73
106
  ```
74
107
 
75
- ## Supported Models
108
+ ## Supported Configurations
76
109
 
110
+ ### Reranker Models (Ollama/Custom API)
77
111
  | Model | Size | Speed | Accuracy | Best For |
78
112
  |-------|------|-------|----------|----------|
79
113
  | `bge-reranker-v2-m3` | ~600MB | ⚡⚡⚡ | ⭐⭐⭐⭐ | General purpose (Recommended) |
@@ -81,6 +115,12 @@ User Query → Vector Store (retrieve 50 docs)
81
115
  | `Qwen3-Reranker-4B` | ~2.5GB | ⚡⚡ | ⭐⭐⭐⭐ | Balanced performance |
82
116
  | `Qwen3-Reranker-8B` | ~5GB | ⚡ | ⭐⭐⭐⭐⭐ | Maximum accuracy |
83
117
 
118
+ ### VL Classifier Models
119
+ | Model | Size | Speed | Use Case |
120
+ |-------|------|-------|----------|
121
+ | `ResNet18-ONNX-INT8` | 11MB | ⚡⚡⚡⚡ | Document complexity classification |
122
+ | Custom VL models | Varies | Varies | Vision-Language tasks |
123
+
84
124
  ## Development
85
125
 
86
126
  ### Setup
@@ -168,10 +208,27 @@ n8n_reranker/
168
208
  └── npm-publish.yml # Automated publishing
169
209
  ```
170
210
 
211
+ ## How It Works
212
+
213
+ ### The Reranking Challenge
214
+ Ollama doesn't natively support reranker models that output relevance scores. Instead, we implement three approaches:
215
+
216
+ 1. **Prompt-based Scoring**: Use Ollama's `/api/generate` with specially formatted prompts
217
+ 2. **Custom Rerank API**: Connect to servers with dedicated `/api/rerank` endpoints
218
+ 3. **VL Classification**: Pre-process with Vision-Language models for intelligent filtering
219
+
220
+ ### API Type Detection
221
+ The node automatically detects your server type by checking:
222
+ 1. `/api/status` → VL Classifier server
223
+ 2. `/api/tags` → Ollama server
224
+ 3. `/api/rerank` → Custom rerank server
225
+ 4. Fallback → Ollama (default)
226
+
171
227
  ## Architecture
172
228
 
173
- This node follows n8n's **Sub-node Provider pattern**:
229
+ This node implements two n8n patterns:
174
230
 
231
+ ### Provider Node (OllamaReranker)
175
232
  1. **No inputs** - Provider nodes don't receive workflow data
176
233
  2. **AiReranker output** - Connects to Vector Store nodes
177
234
  3. **supplyData()** - Returns a reranker provider object
@@ -179,6 +236,11 @@ This node follows n8n's **Sub-node Provider pattern**:
179
236
  - `rerank()` - Main reranking method
180
237
  - `compressDocuments()` - LangChain compatibility
181
238
 
239
+ ### Workflow Node (OllamaRerankerWorkflow)
240
+ 1. **Main inputs/outputs** - Processes workflow items
241
+ 2. **execute()** - Transforms documents in the workflow
242
+ 3. **usableAsTool** - Can be used as AI Agent tool
243
+
182
244
  ## Contributing
183
245
 
184
246
  1. Fork the repository
@@ -201,6 +263,7 @@ MIT © Gabriel BRUMENT
201
263
 
202
264
  - [GitHub Repository](https://github.com/theseedship/n8n_reranker)
203
265
  - [npm Package](https://www.npmjs.com/package/n8n-nodes-ollama-reranker)
266
+ - [VL Classifier Integration Guide](./VL_CLASSIFIER_INTEGRATION.md)
204
267
  - [n8n Documentation](https://docs.n8n.io/)
205
268
  - [Ollama Documentation](https://ollama.ai/docs)
206
269
 
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.OllamaReranker = void 0;
4
37
  const n8n_workflow_1 = require("n8n-workflow");
@@ -182,6 +215,80 @@ class OllamaReranker {
182
215
  },
183
216
  description: 'Number of documents to process concurrently',
184
217
  },
218
+ // VL Classifier specific options
219
+ {
220
+ displayName: 'Enable VL Classification',
221
+ name: 'enableClassification',
222
+ type: 'boolean',
223
+ default: true,
224
+ description: 'Use Vision-Language classifier to analyze document complexity',
225
+ displayOptions: {
226
+ show: {
227
+ '/apiType': ['vl-classifier'],
228
+ },
229
+ },
230
+ },
231
+ {
232
+ displayName: 'Classification Strategy',
233
+ name: 'classificationStrategy',
234
+ type: 'options',
235
+ options: [
236
+ {
237
+ name: 'Add Metadata Only',
238
+ value: 'metadata',
239
+ description: 'Add complexity classification as document metadata',
240
+ },
241
+ {
242
+ name: 'Filter Documents',
243
+ value: 'filter',
244
+ description: 'Filter documents based on complexity',
245
+ },
246
+ {
247
+ name: 'Filter + Metadata',
248
+ value: 'both',
249
+ description: 'Both filter and add metadata',
250
+ },
251
+ ],
252
+ default: 'metadata',
253
+ description: 'How to use the classification results',
254
+ displayOptions: {
255
+ show: {
256
+ '/apiType': ['vl-classifier'],
257
+ 'enableClassification': [true],
258
+ },
259
+ },
260
+ },
261
+ {
262
+ displayName: 'Filter Complexity',
263
+ name: 'filterComplexity',
264
+ type: 'options',
265
+ options: [
266
+ {
267
+ name: 'Low Complexity Only',
268
+ value: 'LOW',
269
+ description: 'Keep only simple documents (good for OCR)',
270
+ },
271
+ {
272
+ name: 'High Complexity Only',
273
+ value: 'HIGH',
274
+ description: 'Keep only complex documents (good for VLM)',
275
+ },
276
+ {
277
+ name: 'Both',
278
+ value: 'both',
279
+ description: 'Keep all documents regardless of complexity',
280
+ },
281
+ ],
282
+ default: 'both',
283
+ description: 'Which complexity level to keep when filtering',
284
+ displayOptions: {
285
+ show: {
286
+ '/apiType': ['vl-classifier'],
287
+ 'enableClassification': [true],
288
+ 'classificationStrategy': ['filter', 'both'],
289
+ },
290
+ },
291
+ },
185
292
  ],
186
293
  },
187
294
  ],
@@ -194,7 +301,7 @@ class OllamaReranker {
194
301
  * that implements the standard rerank() and compressDocuments() interfaces.
195
302
  */
196
303
  async supplyData(_itemIndex) {
197
- var _a, _b, _c;
304
+ var _a, _b, _c, _d, _e, _f;
198
305
  this.logger.debug('Initializing Ollama Reranker Provider');
199
306
  const self = this;
200
307
  // Get node parameters once (provider nodes use index 0)
@@ -202,18 +309,27 @@ class OllamaReranker {
202
309
  if (!(model === null || model === void 0 ? void 0 : model.trim())) {
203
310
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Model selection is required. Please select a model from the dropdown.');
204
311
  }
205
- const apiType = this.getNodeParameter('apiType', 0, 'ollama');
312
+ // Get API type
313
+ let apiType = this.getNodeParameter('apiType', 0, 'ollama');
206
314
  const instruction = this.getNodeParameter('instruction', 0);
207
315
  const additionalOptions = this.getNodeParameter('additionalOptions', 0, {});
208
316
  const timeout = (_a = additionalOptions.timeout) !== null && _a !== void 0 ? _a : 30000;
209
317
  const batchSize = (_b = additionalOptions.batchSize) !== null && _b !== void 0 ? _b : 10;
210
318
  const includeOriginalScores = (_c = additionalOptions.includeOriginalScores) !== null && _c !== void 0 ? _c : false;
319
+ const enableClassification = (_d = additionalOptions.enableClassification) !== null && _d !== void 0 ? _d : true;
320
+ const classificationStrategy = (_e = additionalOptions.classificationStrategy) !== null && _e !== void 0 ? _e : 'metadata';
321
+ const filterComplexity = (_f = additionalOptions.filterComplexity) !== null && _f !== void 0 ? _f : 'both';
211
322
  // Get credentials (n8n's built-in ollamaApi)
212
323
  const credentials = await this.getCredentials('ollamaApi');
213
324
  if (!(credentials === null || credentials === void 0 ? void 0 : credentials.baseUrl)) {
214
325
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama Base URL not configured. Please add Ollama API credentials with a valid Base URL.');
215
326
  }
216
327
  const ollamaHost = credentials.baseUrl.replace(/\/$/, '');
328
+ // Auto-detect server type if needed
329
+ if (apiType === 'auto') {
330
+ const { detectServerType } = await Promise.resolve().then(() => __importStar(require('../shared/reranker-logic')));
331
+ apiType = await detectServerType(this, ollamaHost);
332
+ }
217
333
  /**
218
334
  * Reranker Provider Object
219
335
  *
@@ -277,7 +393,7 @@ class OllamaReranker {
277
393
  });
278
394
  self.logger.debug(`Reranking ${processedDocs.length} documents with model: ${model}`);
279
395
  try {
280
- // Rerank documents using Ollama or Custom API
396
+ // Rerank documents using Ollama, Custom API, or VL Classifier
281
397
  const rerankedDocs = await (0, reranker_logic_1.rerankDocuments)(self, {
282
398
  ollamaHost,
283
399
  model,
@@ -289,7 +405,10 @@ class OllamaReranker {
289
405
  batchSize,
290
406
  timeout,
291
407
  includeOriginalScores,
292
- apiType,
408
+ apiType: apiType,
409
+ enableClassification,
410
+ classificationStrategy,
411
+ filterComplexity,
293
412
  });
294
413
  self.logger.debug(`Reranking complete: ${rerankedDocs.length} documents returned`);
295
414
  // Log output for n8n execution tracking
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.OllamaRerankerWorkflow = void 0;
4
37
  const n8n_workflow_1 = require("n8n-workflow");
@@ -283,6 +316,80 @@ class OllamaRerankerWorkflow {
283
316
  default: 'documents',
284
317
  description: 'How to format output documents',
285
318
  },
319
+ // VL Classifier specific options
320
+ {
321
+ displayName: 'Enable VL Classification',
322
+ name: 'enableClassification',
323
+ type: 'boolean',
324
+ default: true,
325
+ description: 'Use Vision-Language classifier to analyze document complexity',
326
+ displayOptions: {
327
+ show: {
328
+ '/apiType': ['vl-classifier'],
329
+ },
330
+ },
331
+ },
332
+ {
333
+ displayName: 'Classification Strategy',
334
+ name: 'classificationStrategy',
335
+ type: 'options',
336
+ options: [
337
+ {
338
+ name: 'Add Metadata Only',
339
+ value: 'metadata',
340
+ description: 'Add complexity classification as document metadata',
341
+ },
342
+ {
343
+ name: 'Filter Documents',
344
+ value: 'filter',
345
+ description: 'Filter documents based on complexity',
346
+ },
347
+ {
348
+ name: 'Filter + Metadata',
349
+ value: 'both',
350
+ description: 'Both filter and add metadata',
351
+ },
352
+ ],
353
+ default: 'metadata',
354
+ description: 'How to use the classification results',
355
+ displayOptions: {
356
+ show: {
357
+ '/apiType': ['vl-classifier'],
358
+ 'enableClassification': [true],
359
+ },
360
+ },
361
+ },
362
+ {
363
+ displayName: 'Filter Complexity',
364
+ name: 'filterComplexity',
365
+ type: 'options',
366
+ options: [
367
+ {
368
+ name: 'Low Complexity Only',
369
+ value: 'LOW',
370
+ description: 'Keep only simple documents (good for OCR)',
371
+ },
372
+ {
373
+ name: 'High Complexity Only',
374
+ value: 'HIGH',
375
+ description: 'Keep only complex documents (good for VLM)',
376
+ },
377
+ {
378
+ name: 'Both',
379
+ value: 'both',
380
+ description: 'Keep all documents regardless of complexity',
381
+ },
382
+ ],
383
+ default: 'both',
384
+ description: 'Which complexity level to keep when filtering',
385
+ displayOptions: {
386
+ show: {
387
+ '/apiType': ['vl-classifier'],
388
+ 'enableClassification': [true],
389
+ 'classificationStrategy': ['filter', 'both'],
390
+ },
391
+ },
392
+ },
286
393
  ],
287
394
  },
288
395
  ],
@@ -295,7 +402,7 @@ class OllamaRerankerWorkflow {
295
402
  * and return processed items to output connections.
296
403
  */
297
404
  async execute() {
298
- var _a, _b, _c, _d;
405
+ var _a, _b, _c, _d, _e, _f, _g;
299
406
  const items = this.getInputData();
300
407
  const returnData = [];
301
408
  // Get credentials
@@ -309,8 +416,12 @@ class OllamaRerankerWorkflow {
309
416
  if (!(model === null || model === void 0 ? void 0 : model.trim())) {
310
417
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Model selection is required. Please select a model from the dropdown.');
311
418
  }
312
- // Get API type
313
- const apiType = this.getNodeParameter('apiType', 0, 'ollama');
419
+ // Get API type and auto-detect if needed
420
+ let apiType = this.getNodeParameter('apiType', 0, 'ollama');
421
+ if (apiType === 'auto') {
422
+ const { detectServerType } = await Promise.resolve().then(() => __importStar(require('../shared/reranker-logic')));
423
+ apiType = await detectServerType(this, ollamaHost);
424
+ }
314
425
  // Get common parameters
315
426
  const instruction = this.getNodeParameter('instruction', 0);
316
427
  const topK = this.getNodeParameter('topK', 0);
@@ -320,6 +431,9 @@ class OllamaRerankerWorkflow {
320
431
  const batchSize = (_b = additionalOptions.batchSize) !== null && _b !== void 0 ? _b : 10;
321
432
  const includeOriginalScores = (_c = additionalOptions.includeOriginalScores) !== null && _c !== void 0 ? _c : false;
322
433
  const outputFormat = (_d = additionalOptions.outputFormat) !== null && _d !== void 0 ? _d : 'documents';
434
+ const enableClassification = (_e = additionalOptions.enableClassification) !== null && _e !== void 0 ? _e : true;
435
+ const classificationStrategy = (_f = additionalOptions.classificationStrategy) !== null && _f !== void 0 ? _f : 'metadata';
436
+ const filterComplexity = (_g = additionalOptions.filterComplexity) !== null && _g !== void 0 ? _g : 'both';
323
437
  // Process each input item
324
438
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
325
439
  try {
@@ -397,7 +511,10 @@ class OllamaRerankerWorkflow {
397
511
  batchSize,
398
512
  timeout,
399
513
  includeOriginalScores,
400
- apiType,
514
+ apiType: apiType,
515
+ enableClassification,
516
+ classificationStrategy: classificationStrategy,
517
+ filterComplexity: filterComplexity,
401
518
  });
402
519
  // Format output
403
520
  let output;
@@ -11,10 +11,35 @@ export interface RerankConfig {
11
11
  batchSize: number;
12
12
  timeout: number;
13
13
  includeOriginalScores: boolean;
14
- apiType?: 'ollama' | 'custom';
14
+ apiType?: 'ollama' | 'custom' | 'vl-classifier';
15
+ enableClassification?: boolean;
16
+ classificationStrategy?: 'filter' | 'metadata' | 'both';
17
+ filterComplexity?: 'LOW' | 'HIGH' | 'both';
18
+ }
19
+ export interface VLClassificationResult {
20
+ complexity: 'LOW' | 'HIGH';
21
+ confidence?: number;
22
+ processingTime?: number;
23
+ modelUsed?: string;
24
+ }
25
+ export interface ServerStatus {
26
+ status: 'healthy' | 'degraded' | 'error';
27
+ modelsLoaded?: string[];
28
+ vramUsage?: number;
29
+ hasClassifier?: boolean;
30
+ hasReranker?: boolean;
31
+ version?: string;
15
32
  }
16
33
  /**
17
34
  * Rerank documents using Ollama reranker model or Custom Rerank API
18
35
  */
19
36
  export declare function rerankDocuments(context: RerankerContext, config: RerankConfig): Promise<any[]>;
37
+ /**
38
+ * Check server status to detect capabilities
39
+ */
40
+ export declare function checkServerStatus(context: RerankerContext, serverUrl: string, timeout?: number): Promise<ServerStatus>;
41
+ /**
42
+ * Detect server type automatically
43
+ */
44
+ export declare function detectServerType(context: RerankerContext, serverUrl: string): Promise<'ollama' | 'custom' | 'vl-classifier'>;
20
45
  export {};
@@ -1,12 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rerankDocuments = rerankDocuments;
4
+ exports.checkServerStatus = checkServerStatus;
5
+ exports.detectServerType = detectServerType;
4
6
  const n8n_workflow_1 = require("n8n-workflow");
5
7
  /**
6
8
  * Rerank documents using Ollama reranker model or Custom Rerank API
7
9
  */
8
10
  async function rerankDocuments(context, config) {
9
- const { ollamaHost, model, query, documents, instruction, topK, threshold, batchSize, timeout, includeOriginalScores, apiType = 'ollama' } = config;
11
+ const { ollamaHost, model, query, documents, instruction, topK, threshold, batchSize, timeout, includeOriginalScores, apiType = 'ollama', enableClassification = false, classificationStrategy = 'metadata', filterComplexity = 'both' } = config;
12
+ // Handle VL Classifier API with reranking
13
+ if (apiType === 'vl-classifier' && enableClassification) {
14
+ return await rerankWithVLClassifier(context, config);
15
+ }
10
16
  // Use Custom Rerank API if specified
11
17
  if (apiType === 'custom') {
12
18
  return await rerankWithCustomAPI(context, config);
@@ -326,6 +332,188 @@ function parseGenericScore(output, outputLower) {
326
332
  // Default to neutral if completely ambiguous
327
333
  return 0.5;
328
334
  }
335
+ /**
336
+ * Check server status to detect capabilities
337
+ */
338
+ async function checkServerStatus(context, serverUrl, timeout = 5000) {
339
+ try {
340
+ const response = await context.helpers.httpRequest({
341
+ method: 'GET',
342
+ url: `${serverUrl}/api/status`,
343
+ headers: {
344
+ Accept: 'application/json',
345
+ },
346
+ json: true,
347
+ timeout,
348
+ });
349
+ return {
350
+ status: response.status || 'healthy',
351
+ modelsLoaded: response.models || [],
352
+ vramUsage: response.vram_usage,
353
+ hasClassifier: response.has_classifier || false,
354
+ hasReranker: response.has_reranker || false,
355
+ version: response.version,
356
+ };
357
+ }
358
+ catch (error) {
359
+ // If /api/status doesn't exist, it's not a VL classifier server
360
+ return {
361
+ status: 'error',
362
+ hasClassifier: false,
363
+ hasReranker: false,
364
+ };
365
+ }
366
+ }
367
+ /**
368
+ * Detect server type automatically
369
+ */
370
+ async function detectServerType(context, serverUrl) {
371
+ // First check for VL classifier with /api/status
372
+ const status = await checkServerStatus(context, serverUrl);
373
+ if (status.hasClassifier) {
374
+ return 'vl-classifier';
375
+ }
376
+ // Check for Ollama with /api/tags
377
+ try {
378
+ await context.helpers.httpRequest({
379
+ method: 'GET',
380
+ url: `${serverUrl}/api/tags`,
381
+ timeout: 5000,
382
+ });
383
+ return 'ollama';
384
+ }
385
+ catch {
386
+ // Not Ollama
387
+ }
388
+ // Check for custom rerank API
389
+ try {
390
+ await context.helpers.httpRequest({
391
+ method: 'POST',
392
+ url: `${serverUrl}/api/rerank`,
393
+ headers: { 'Content-Type': 'application/json' },
394
+ body: {
395
+ model: 'test',
396
+ query: 'test',
397
+ documents: ['test'],
398
+ },
399
+ timeout: 5000,
400
+ });
401
+ return 'custom';
402
+ }
403
+ catch {
404
+ // Default to Ollama
405
+ return 'ollama';
406
+ }
407
+ }
408
+ /**
409
+ * Classify document complexity using VL Classifier API
410
+ */
411
+ async function classifyDocumentComplexity(context, serverUrl, document, timeout) {
412
+ try {
413
+ // Prepare document content for classification
414
+ const content = document.pageContent || JSON.stringify(document);
415
+ // For VL classifier, we might need to handle base64 images
416
+ // Check if document contains image data
417
+ const hasImage = document.image || document.base64Image;
418
+ let requestBody = {
419
+ text: content,
420
+ };
421
+ if (hasImage) {
422
+ requestBody.image = document.image || document.base64Image;
423
+ }
424
+ const response = await context.helpers.httpRequest({
425
+ method: 'POST',
426
+ url: `${serverUrl}/api/classify`,
427
+ headers: {
428
+ 'Content-Type': 'application/json',
429
+ Accept: 'application/json',
430
+ },
431
+ body: requestBody,
432
+ json: true,
433
+ timeout,
434
+ });
435
+ return {
436
+ complexity: response.complexity || response.classification || 'LOW',
437
+ confidence: response.confidence,
438
+ processingTime: response.processing_time,
439
+ modelUsed: response.model || 'ResNet18-ONNX',
440
+ };
441
+ }
442
+ catch (error) {
443
+ // On error, default to LOW complexity to not filter out documents
444
+ console.warn('Classification failed, defaulting to LOW complexity:', error.message);
445
+ return {
446
+ complexity: 'LOW',
447
+ confidence: 0,
448
+ };
449
+ }
450
+ }
451
+ /**
452
+ * Rerank documents using VL Classifier + Reranking
453
+ */
454
+ async function rerankWithVLClassifier(context, config) {
455
+ const { ollamaHost, model, query, documents, topK, threshold, timeout, includeOriginalScores, classificationStrategy = 'metadata', filterComplexity = 'both' } = config;
456
+ // Step 1: Classify all documents
457
+ const classificationPromises = documents.map(async (doc, index) => {
458
+ const classification = await classifyDocumentComplexity(context, ollamaHost, doc, timeout);
459
+ return { doc, index, classification };
460
+ });
461
+ const classifiedDocs = await Promise.all(classificationPromises);
462
+ // Step 2: Filter documents based on strategy
463
+ let docsToRerank = classifiedDocs;
464
+ if (classificationStrategy === 'filter' || classificationStrategy === 'both') {
465
+ if (filterComplexity !== 'both') {
466
+ docsToRerank = classifiedDocs.filter(item => item.classification.complexity === filterComplexity);
467
+ }
468
+ }
469
+ // If no documents pass the filter, return empty
470
+ if (docsToRerank.length === 0) {
471
+ return [];
472
+ }
473
+ // Step 3: Prepare documents for reranking
474
+ const rerankerDocs = docsToRerank.map(item => {
475
+ const enrichedDoc = { ...item.doc };
476
+ if (classificationStrategy === 'metadata' || classificationStrategy === 'both') {
477
+ enrichedDoc._complexityClass = item.classification.complexity;
478
+ enrichedDoc._complexityConfidence = item.classification.confidence;
479
+ }
480
+ return enrichedDoc;
481
+ });
482
+ // Step 4: Check if server has reranker capability
483
+ const status = await checkServerStatus(context, ollamaHost);
484
+ if (status.hasReranker) {
485
+ // Use the server's rerank endpoint if available
486
+ return await rerankWithCustomAPI(context, {
487
+ ...config,
488
+ documents: rerankerDocs,
489
+ });
490
+ }
491
+ else {
492
+ // Fall back to scoring-based reranking
493
+ // For VL classifier servers without reranker, we can still sort by complexity
494
+ const scoredDocs = rerankerDocs.map((doc, idx) => {
495
+ // Give higher scores to HIGH complexity documents for technical queries
496
+ const complexityScore = doc._complexityClass === 'HIGH' ? 0.8 : 0.2;
497
+ const confidenceBoost = (doc._complexityConfidence || 0) * 0.2;
498
+ return {
499
+ ...doc,
500
+ _rerankScore: complexityScore + confidenceBoost,
501
+ _originalIndex: classifiedDocs[idx].index,
502
+ };
503
+ });
504
+ // Sort and filter
505
+ return scoredDocs
506
+ .filter(doc => doc._rerankScore >= threshold)
507
+ .sort((a, b) => b._rerankScore - a._rerankScore)
508
+ .slice(0, topK)
509
+ .map(doc => {
510
+ if (!includeOriginalScores && doc._originalScore !== undefined) {
511
+ delete doc._originalScore;
512
+ }
513
+ return doc;
514
+ });
515
+ }
516
+ }
329
517
  /**
330
518
  * Parse Ollama reranker response to extract relevance score
331
519
  * Uses model-specific parsing logic for better accuracy
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "n8n-nodes-ollama-reranker",
3
- "version": "1.3.2",
4
- "description": "Ollama Reranker for n8n - Dynamic model loading + Ollama/Custom API support (Vector Store provider + workflow node)",
3
+ "version": "1.4.0",
4
+ "description": "Ollama Reranker for n8n - VL Classifier integration + Auto-detect + Dynamic model loading (Vector Store provider + workflow node)",
5
5
  "main": "index.js",
6
6
  "author": "Gabriel BRUMENT",
7
7
  "license": "MIT",