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 +68 -5
- package/dist/nodes/OllamaReranker/OllamaReranker.node.js +130 -7
- package/dist/nodes/OllamaRerankerWorkflow/OllamaRerankerWorkflow.node.js +128 -7
- package/dist/nodes/shared/reranker-logic.d.ts +26 -1
- package/dist/nodes/shared/reranker-logic.js +189 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/n8n-nodes-ollama-reranker)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
- 🚀 **
|
|
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
|
-
- **
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
210
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama
|
|
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
|
|
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.
|
|
300
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Ollama
|
|
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.
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "Ollama Reranker for n8n -
|
|
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",
|