n8n-nodes-ollama-reranker 1.3.2 → 1.4.1

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");
@@ -112,6 +145,11 @@ class OllamaReranker {
112
145
  value: 'custom',
113
146
  description: 'Custom /api/rerank endpoint (for deposium-embeddings-turbov2, etc.)',
114
147
  },
148
+ {
149
+ name: 'VL Classifier API',
150
+ value: 'vl-classifier',
151
+ description: 'Document complexity classification with VL models (vl-classifier, lfm25-vl)',
152
+ },
115
153
  ],
116
154
  default: 'ollama',
117
155
  description: 'Which API endpoint to use for reranking',
@@ -182,6 +220,80 @@ class OllamaReranker {
182
220
  },
183
221
  description: 'Number of documents to process concurrently',
184
222
  },
223
+ // VL Classifier specific options
224
+ {
225
+ displayName: 'Enable VL Classification',
226
+ name: 'enableClassification',
227
+ type: 'boolean',
228
+ default: true,
229
+ description: 'Use Vision-Language classifier to analyze document complexity',
230
+ displayOptions: {
231
+ show: {
232
+ '/apiType': ['vl-classifier'],
233
+ },
234
+ },
235
+ },
236
+ {
237
+ displayName: 'Classification Strategy',
238
+ name: 'classificationStrategy',
239
+ type: 'options',
240
+ options: [
241
+ {
242
+ name: 'Add Metadata Only',
243
+ value: 'metadata',
244
+ description: 'Add complexity classification as document metadata',
245
+ },
246
+ {
247
+ name: 'Filter Documents',
248
+ value: 'filter',
249
+ description: 'Filter documents based on complexity',
250
+ },
251
+ {
252
+ name: 'Filter + Metadata',
253
+ value: 'both',
254
+ description: 'Both filter and add metadata',
255
+ },
256
+ ],
257
+ default: 'metadata',
258
+ description: 'How to use the classification results',
259
+ displayOptions: {
260
+ show: {
261
+ '/apiType': ['vl-classifier'],
262
+ 'enableClassification': [true],
263
+ },
264
+ },
265
+ },
266
+ {
267
+ displayName: 'Filter Complexity',
268
+ name: 'filterComplexity',
269
+ type: 'options',
270
+ options: [
271
+ {
272
+ name: 'Low Complexity Only',
273
+ value: 'LOW',
274
+ description: 'Keep only simple documents (good for OCR)',
275
+ },
276
+ {
277
+ name: 'High Complexity Only',
278
+ value: 'HIGH',
279
+ description: 'Keep only complex documents (good for VLM)',
280
+ },
281
+ {
282
+ name: 'Both',
283
+ value: 'both',
284
+ description: 'Keep all documents regardless of complexity',
285
+ },
286
+ ],
287
+ default: 'both',
288
+ description: 'Which complexity level to keep when filtering',
289
+ displayOptions: {
290
+ show: {
291
+ '/apiType': ['vl-classifier'],
292
+ 'enableClassification': [true],
293
+ 'classificationStrategy': ['filter', 'both'],
294
+ },
295
+ },
296
+ },
185
297
  ],
186
298
  },
187
299
  ],
@@ -194,7 +306,7 @@ class OllamaReranker {
194
306
  * that implements the standard rerank() and compressDocuments() interfaces.
195
307
  */
196
308
  async supplyData(_itemIndex) {
197
- var _a, _b, _c;
309
+ var _a, _b, _c, _d, _e, _f;
198
310
  this.logger.debug('Initializing Ollama Reranker Provider');
199
311
  const self = this;
200
312
  // Get node parameters once (provider nodes use index 0)
@@ -202,18 +314,27 @@ class OllamaReranker {
202
314
  if (!(model === null || model === void 0 ? void 0 : model.trim())) {
203
315
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Model selection is required. Please select a model from the dropdown.');
204
316
  }
205
- const apiType = this.getNodeParameter('apiType', 0, 'ollama');
317
+ // Get API type
318
+ let apiType = this.getNodeParameter('apiType', 0, 'ollama');
206
319
  const instruction = this.getNodeParameter('instruction', 0);
207
320
  const additionalOptions = this.getNodeParameter('additionalOptions', 0, {});
208
321
  const timeout = (_a = additionalOptions.timeout) !== null && _a !== void 0 ? _a : 30000;
209
322
  const batchSize = (_b = additionalOptions.batchSize) !== null && _b !== void 0 ? _b : 10;
210
323
  const includeOriginalScores = (_c = additionalOptions.includeOriginalScores) !== null && _c !== void 0 ? _c : false;
324
+ const enableClassification = (_d = additionalOptions.enableClassification) !== null && _d !== void 0 ? _d : true;
325
+ const classificationStrategy = (_e = additionalOptions.classificationStrategy) !== null && _e !== void 0 ? _e : 'metadata';
326
+ const filterComplexity = (_f = additionalOptions.filterComplexity) !== null && _f !== void 0 ? _f : 'both';
211
327
  // Get credentials (n8n's built-in ollamaApi)
212
328
  const credentials = await this.getCredentials('ollamaApi');
213
329
  if (!(credentials === null || credentials === void 0 ? void 0 : credentials.baseUrl)) {
214
330
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama Base URL not configured. Please add Ollama API credentials with a valid Base URL.');
215
331
  }
216
332
  const ollamaHost = credentials.baseUrl.replace(/\/$/, '');
333
+ // Auto-detect server type if needed
334
+ if (apiType === 'auto') {
335
+ const { detectServerType } = await Promise.resolve().then(() => __importStar(require('../shared/reranker-logic')));
336
+ apiType = await detectServerType(this, ollamaHost);
337
+ }
217
338
  /**
218
339
  * Reranker Provider Object
219
340
  *
@@ -277,7 +398,7 @@ class OllamaReranker {
277
398
  });
278
399
  self.logger.debug(`Reranking ${processedDocs.length} documents with model: ${model}`);
279
400
  try {
280
- // Rerank documents using Ollama or Custom API
401
+ // Rerank documents using Ollama, Custom API, or VL Classifier
281
402
  const rerankedDocs = await (0, reranker_logic_1.rerankDocuments)(self, {
282
403
  ollamaHost,
283
404
  model,
@@ -289,7 +410,10 @@ class OllamaReranker {
289
410
  batchSize,
290
411
  timeout,
291
412
  includeOriginalScores,
292
- apiType,
413
+ apiType: apiType,
414
+ enableClassification,
415
+ classificationStrategy,
416
+ filterComplexity,
293
417
  });
294
418
  self.logger.debug(`Reranking complete: ${rerankedDocs.length} documents returned`);
295
419
  // 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");
@@ -115,6 +148,11 @@ class OllamaRerankerWorkflow {
115
148
  value: 'custom',
116
149
  description: 'Custom /api/rerank endpoint (for deposium-embeddings-turbov2, etc.)',
117
150
  },
151
+ {
152
+ name: 'VL Classifier API',
153
+ value: 'vl-classifier',
154
+ description: 'Document complexity classification with VL models (vl-classifier, lfm25-vl)',
155
+ },
118
156
  ],
119
157
  default: 'ollama',
120
158
  description: 'Which API endpoint to use for reranking',
@@ -283,6 +321,80 @@ class OllamaRerankerWorkflow {
283
321
  default: 'documents',
284
322
  description: 'How to format output documents',
285
323
  },
324
+ // VL Classifier specific options
325
+ {
326
+ displayName: 'Enable VL Classification',
327
+ name: 'enableClassification',
328
+ type: 'boolean',
329
+ default: true,
330
+ description: 'Use Vision-Language classifier to analyze document complexity',
331
+ displayOptions: {
332
+ show: {
333
+ '/apiType': ['vl-classifier'],
334
+ },
335
+ },
336
+ },
337
+ {
338
+ displayName: 'Classification Strategy',
339
+ name: 'classificationStrategy',
340
+ type: 'options',
341
+ options: [
342
+ {
343
+ name: 'Add Metadata Only',
344
+ value: 'metadata',
345
+ description: 'Add complexity classification as document metadata',
346
+ },
347
+ {
348
+ name: 'Filter Documents',
349
+ value: 'filter',
350
+ description: 'Filter documents based on complexity',
351
+ },
352
+ {
353
+ name: 'Filter + Metadata',
354
+ value: 'both',
355
+ description: 'Both filter and add metadata',
356
+ },
357
+ ],
358
+ default: 'metadata',
359
+ description: 'How to use the classification results',
360
+ displayOptions: {
361
+ show: {
362
+ '/apiType': ['vl-classifier'],
363
+ 'enableClassification': [true],
364
+ },
365
+ },
366
+ },
367
+ {
368
+ displayName: 'Filter Complexity',
369
+ name: 'filterComplexity',
370
+ type: 'options',
371
+ options: [
372
+ {
373
+ name: 'Low Complexity Only',
374
+ value: 'LOW',
375
+ description: 'Keep only simple documents (good for OCR)',
376
+ },
377
+ {
378
+ name: 'High Complexity Only',
379
+ value: 'HIGH',
380
+ description: 'Keep only complex documents (good for VLM)',
381
+ },
382
+ {
383
+ name: 'Both',
384
+ value: 'both',
385
+ description: 'Keep all documents regardless of complexity',
386
+ },
387
+ ],
388
+ default: 'both',
389
+ description: 'Which complexity level to keep when filtering',
390
+ displayOptions: {
391
+ show: {
392
+ '/apiType': ['vl-classifier'],
393
+ 'enableClassification': [true],
394
+ 'classificationStrategy': ['filter', 'both'],
395
+ },
396
+ },
397
+ },
286
398
  ],
287
399
  },
288
400
  ],
@@ -295,7 +407,7 @@ class OllamaRerankerWorkflow {
295
407
  * and return processed items to output connections.
296
408
  */
297
409
  async execute() {
298
- var _a, _b, _c, _d;
410
+ var _a, _b, _c, _d, _e, _f, _g;
299
411
  const items = this.getInputData();
300
412
  const returnData = [];
301
413
  // Get credentials
@@ -309,8 +421,12 @@ class OllamaRerankerWorkflow {
309
421
  if (!(model === null || model === void 0 ? void 0 : model.trim())) {
310
422
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Model selection is required. Please select a model from the dropdown.');
311
423
  }
312
- // Get API type
313
- const apiType = this.getNodeParameter('apiType', 0, 'ollama');
424
+ // Get API type and auto-detect if needed
425
+ let apiType = this.getNodeParameter('apiType', 0, 'ollama');
426
+ if (apiType === 'auto') {
427
+ const { detectServerType } = await Promise.resolve().then(() => __importStar(require('../shared/reranker-logic')));
428
+ apiType = await detectServerType(this, ollamaHost);
429
+ }
314
430
  // Get common parameters
315
431
  const instruction = this.getNodeParameter('instruction', 0);
316
432
  const topK = this.getNodeParameter('topK', 0);
@@ -320,6 +436,9 @@ class OllamaRerankerWorkflow {
320
436
  const batchSize = (_b = additionalOptions.batchSize) !== null && _b !== void 0 ? _b : 10;
321
437
  const includeOriginalScores = (_c = additionalOptions.includeOriginalScores) !== null && _c !== void 0 ? _c : false;
322
438
  const outputFormat = (_d = additionalOptions.outputFormat) !== null && _d !== void 0 ? _d : 'documents';
439
+ const enableClassification = (_e = additionalOptions.enableClassification) !== null && _e !== void 0 ? _e : true;
440
+ const classificationStrategy = (_f = additionalOptions.classificationStrategy) !== null && _f !== void 0 ? _f : 'metadata';
441
+ const filterComplexity = (_g = additionalOptions.filterComplexity) !== null && _g !== void 0 ? _g : 'both';
323
442
  // Process each input item
324
443
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
325
444
  try {
@@ -397,7 +516,10 @@ class OllamaRerankerWorkflow {
397
516
  batchSize,
398
517
  timeout,
399
518
  includeOriginalScores,
400
- apiType,
519
+ apiType: apiType,
520
+ enableClassification,
521
+ classificationStrategy: classificationStrategy,
522
+ filterComplexity: filterComplexity,
401
523
  });
402
524
  // Format output
403
525
  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,189 @@ 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, model) {
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
+ model: model || 'vl-classifier',
421
+ };
422
+ if (hasImage) {
423
+ requestBody.image = document.image || document.base64Image;
424
+ }
425
+ const response = await context.helpers.httpRequest({
426
+ method: 'POST',
427
+ url: `${serverUrl}/api/classify`,
428
+ headers: {
429
+ 'Content-Type': 'application/json',
430
+ Accept: 'application/json',
431
+ },
432
+ body: requestBody,
433
+ json: true,
434
+ timeout,
435
+ });
436
+ return {
437
+ complexity: response.complexity || response.classification || 'LOW',
438
+ confidence: response.confidence,
439
+ processingTime: response.processing_time,
440
+ modelUsed: response.model || 'ResNet18-ONNX',
441
+ };
442
+ }
443
+ catch (error) {
444
+ // On error, default to LOW complexity to not filter out documents
445
+ console.warn('Classification failed, defaulting to LOW complexity:', error.message);
446
+ return {
447
+ complexity: 'LOW',
448
+ confidence: 0,
449
+ };
450
+ }
451
+ }
452
+ /**
453
+ * Rerank documents using VL Classifier + Reranking
454
+ */
455
+ async function rerankWithVLClassifier(context, config) {
456
+ const { ollamaHost, model, query, documents, topK, threshold, timeout, includeOriginalScores, classificationStrategy = 'metadata', filterComplexity = 'both' } = config;
457
+ // Step 1: Classify all documents
458
+ const classificationPromises = documents.map(async (doc, index) => {
459
+ const classification = await classifyDocumentComplexity(context, ollamaHost, doc, timeout, model);
460
+ return { doc, index, classification };
461
+ });
462
+ const classifiedDocs = await Promise.all(classificationPromises);
463
+ // Step 2: Filter documents based on strategy
464
+ let docsToRerank = classifiedDocs;
465
+ if (classificationStrategy === 'filter' || classificationStrategy === 'both') {
466
+ if (filterComplexity !== 'both') {
467
+ docsToRerank = classifiedDocs.filter(item => item.classification.complexity === filterComplexity);
468
+ }
469
+ }
470
+ // If no documents pass the filter, return empty
471
+ if (docsToRerank.length === 0) {
472
+ return [];
473
+ }
474
+ // Step 3: Prepare documents for reranking
475
+ const rerankerDocs = docsToRerank.map(item => {
476
+ const enrichedDoc = { ...item.doc };
477
+ if (classificationStrategy === 'metadata' || classificationStrategy === 'both') {
478
+ enrichedDoc._complexityClass = item.classification.complexity;
479
+ enrichedDoc._complexityConfidence = item.classification.confidence;
480
+ }
481
+ return enrichedDoc;
482
+ });
483
+ // Step 4: Check if server has reranker capability
484
+ const status = await checkServerStatus(context, ollamaHost);
485
+ if (status.hasReranker) {
486
+ // Use the server's rerank endpoint if available
487
+ return await rerankWithCustomAPI(context, {
488
+ ...config,
489
+ documents: rerankerDocs,
490
+ });
491
+ }
492
+ else {
493
+ // Fall back to scoring-based reranking
494
+ // For VL classifier servers without reranker, we can still sort by complexity
495
+ const scoredDocs = rerankerDocs.map((doc, idx) => {
496
+ // Give higher scores to HIGH complexity documents for technical queries
497
+ const complexityScore = doc._complexityClass === 'HIGH' ? 0.8 : 0.2;
498
+ const confidenceBoost = (doc._complexityConfidence || 0) * 0.2;
499
+ return {
500
+ ...doc,
501
+ _rerankScore: complexityScore + confidenceBoost,
502
+ _originalIndex: classifiedDocs[idx].index,
503
+ };
504
+ });
505
+ // Sort and filter
506
+ return scoredDocs
507
+ .filter(doc => doc._rerankScore >= threshold)
508
+ .sort((a, b) => b._rerankScore - a._rerankScore)
509
+ .slice(0, topK)
510
+ .map(doc => {
511
+ if (!includeOriginalScores && doc._originalScore !== undefined) {
512
+ delete doc._originalScore;
513
+ }
514
+ return doc;
515
+ });
516
+ }
517
+ }
329
518
  /**
330
519
  * Parse Ollama reranker response to extract relevance score
331
520
  * 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.1",
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",