n8n-nodes-ollama-reranker 1.3.1 → 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");
@@ -51,6 +84,10 @@ class OllamaReranker {
51
84
  required: true,
52
85
  },
53
86
  ],
87
+ requestDefaults: {
88
+ ignoreHttpStatusErrors: true,
89
+ baseURL: '={{ $credentials.baseUrl.replace(new RegExp("/$"), "") }}',
90
+ },
54
91
  properties: [
55
92
  {
56
93
  displayName: 'Model',
@@ -178,6 +215,80 @@ class OllamaReranker {
178
215
  },
179
216
  description: 'Number of documents to process concurrently',
180
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
+ },
181
292
  ],
182
293
  },
183
294
  ],
@@ -190,7 +301,7 @@ class OllamaReranker {
190
301
  * that implements the standard rerank() and compressDocuments() interfaces.
191
302
  */
192
303
  async supplyData(_itemIndex) {
193
- var _a, _b, _c;
304
+ var _a, _b, _c, _d, _e, _f;
194
305
  this.logger.debug('Initializing Ollama Reranker Provider');
195
306
  const self = this;
196
307
  // Get node parameters once (provider nodes use index 0)
@@ -198,18 +309,27 @@ class OllamaReranker {
198
309
  if (!(model === null || model === void 0 ? void 0 : model.trim())) {
199
310
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Model selection is required. Please select a model from the dropdown.');
200
311
  }
201
- const apiType = this.getNodeParameter('apiType', 0, 'ollama');
312
+ // Get API type
313
+ let apiType = this.getNodeParameter('apiType', 0, 'ollama');
202
314
  const instruction = this.getNodeParameter('instruction', 0);
203
315
  const additionalOptions = this.getNodeParameter('additionalOptions', 0, {});
204
316
  const timeout = (_a = additionalOptions.timeout) !== null && _a !== void 0 ? _a : 30000;
205
317
  const batchSize = (_b = additionalOptions.batchSize) !== null && _b !== void 0 ? _b : 10;
206
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';
207
322
  // Get credentials (n8n's built-in ollamaApi)
208
323
  const credentials = await this.getCredentials('ollamaApi');
209
- if (!(credentials === null || credentials === void 0 ? void 0 : credentials.host)) {
210
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama host not configured. Please add Ollama API credentials with a valid host URL.');
324
+ if (!(credentials === null || credentials === void 0 ? void 0 : credentials.baseUrl)) {
325
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama Base URL not configured. Please add Ollama API credentials with a valid Base URL.');
326
+ }
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);
211
332
  }
212
- const ollamaHost = credentials.host.replace(/\/$/, '');
213
333
  /**
214
334
  * Reranker Provider Object
215
335
  *
@@ -273,7 +393,7 @@ class OllamaReranker {
273
393
  });
274
394
  self.logger.debug(`Reranking ${processedDocs.length} documents with model: ${model}`);
275
395
  try {
276
- // Rerank documents using Ollama or Custom API
396
+ // Rerank documents using Ollama, Custom API, or VL Classifier
277
397
  const rerankedDocs = await (0, reranker_logic_1.rerankDocuments)(self, {
278
398
  ollamaHost,
279
399
  model,
@@ -285,7 +405,10 @@ class OllamaReranker {
285
405
  batchSize,
286
406
  timeout,
287
407
  includeOriginalScores,
288
- apiType,
408
+ apiType: apiType,
409
+ enableClassification,
410
+ classificationStrategy,
411
+ filterComplexity,
289
412
  });
290
413
  self.logger.debug(`Reranking complete: ${rerankedDocs.length} documents returned`);
291
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");
@@ -52,6 +85,10 @@ class OllamaRerankerWorkflow {
52
85
  required: true,
53
86
  },
54
87
  ],
88
+ requestDefaults: {
89
+ ignoreHttpStatusErrors: true,
90
+ baseURL: '={{ $credentials.baseUrl.replace(new RegExp("/$"), "") }}',
91
+ },
55
92
  properties: [
56
93
  // Model selection with dynamic loading (using routing like Ollama Chat Model)
57
94
  {
@@ -279,6 +316,80 @@ class OllamaRerankerWorkflow {
279
316
  default: 'documents',
280
317
  description: 'How to format output documents',
281
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
+ },
282
393
  ],
283
394
  },
284
395
  ],
@@ -291,22 +402,26 @@ class OllamaRerankerWorkflow {
291
402
  * and return processed items to output connections.
292
403
  */
293
404
  async execute() {
294
- var _a, _b, _c, _d;
405
+ var _a, _b, _c, _d, _e, _f, _g;
295
406
  const items = this.getInputData();
296
407
  const returnData = [];
297
408
  // Get credentials
298
409
  const credentials = await this.getCredentials('ollamaApi');
299
- if (!(credentials === null || credentials === void 0 ? void 0 : credentials.host)) {
300
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama host not configured. Please add Ollama API credentials.');
410
+ if (!(credentials === null || credentials === void 0 ? void 0 : credentials.baseUrl)) {
411
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama Base URL not configured. Please add Ollama API credentials.');
301
412
  }
302
- const ollamaHost = credentials.host.replace(/\/$/, '');
413
+ const ollamaHost = credentials.baseUrl.replace(/\/$/, '');
303
414
  // Get model
304
415
  const model = this.getNodeParameter('model', 0);
305
416
  if (!(model === null || model === void 0 ? void 0 : model.trim())) {
306
417
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Model selection is required. Please select a model from the dropdown.');
307
418
  }
308
- // Get API type
309
- 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
+ }
310
425
  // Get common parameters
311
426
  const instruction = this.getNodeParameter('instruction', 0);
312
427
  const topK = this.getNodeParameter('topK', 0);
@@ -316,6 +431,9 @@ class OllamaRerankerWorkflow {
316
431
  const batchSize = (_b = additionalOptions.batchSize) !== null && _b !== void 0 ? _b : 10;
317
432
  const includeOriginalScores = (_c = additionalOptions.includeOriginalScores) !== null && _c !== void 0 ? _c : false;
318
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';
319
437
  // Process each input item
320
438
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
321
439
  try {
@@ -393,7 +511,10 @@ class OllamaRerankerWorkflow {
393
511
  batchSize,
394
512
  timeout,
395
513
  includeOriginalScores,
396
- apiType,
514
+ apiType: apiType,
515
+ enableClassification,
516
+ classificationStrategy: classificationStrategy,
517
+ filterComplexity: filterComplexity,
397
518
  });
398
519
  // Format output
399
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.1",
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",