first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,99 @@
import { CohereRerank } from '@langchain/cohere';
import {
NodeConnectionTypes,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
import { logWrapper } from '@n8n/ai-utilities';
export class RerankerCohere implements INodeType {
description: INodeTypeDescription = {
displayName: 'Reranker Cohere',
name: 'rerankerCohere',
icon: { light: 'file:cohere.svg', dark: 'file:cohere.dark.svg' },
group: ['transform'],
version: 1,
description:
'Use Cohere Reranker to reorder documents after retrieval from a vector store by relevance to the given query.',
defaults: {
name: 'Reranker Cohere',
},
requestDefaults: {
ignoreHttpStatusErrors: true,
baseURL: '={{ $credentials.host }}',
},
credentials: [
{
name: 'cohereApi',
required: true,
},
],
codex: {
categories: ['AI'],
subcategories: {
AI: ['Rerankers'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.rerankercohere/',
},
],
},
},
inputs: [],
outputs: [NodeConnectionTypes.AiReranker],
outputNames: ['Reranker'],
properties: [
{
displayName: 'Model',
name: 'modelName',
type: 'options',
description:
'The model that should be used to rerank the documents. <a href="https://docs.cohere.com/docs/models">Learn more</a>.',
default: 'rerank-v3.5',
options: [
{
name: 'rerank-v3.5',
value: 'rerank-v3.5',
},
{
name: 'rerank-english-v3.0',
value: 'rerank-english-v3.0',
},
{
name: 'rerank-multilingual-v3.0',
value: 'rerank-multilingual-v3.0',
},
],
},
{
displayName: 'Top N',
name: 'topN',
type: 'number',
description: 'The maximum number of documents to return after reranking',
default: 3,
},
],
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
this.logger.debug('Supply data for reranking Cohere');
const modelName = this.getNodeParameter('modelName', itemIndex, 'rerank-v3.5') as string;
const topN = this.getNodeParameter('topN', itemIndex, 3) as number;
const credentials = await this.getCredentials<{ apiKey: string }>('cohereApi');
const reranker = new CohereRerank({
apiKey: credentials.apiKey,
model: modelName,
topN,
});
return {
response: logWrapper(reranker, this),
};
}
}
@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.96 23.84C14.0267 23.84 16.16 23.7867 19.1467 22.56C22.6133 21.12 29.44 18.56 34.4 15.8933C37.8667 14.0267 39.36 11.5733 39.36 8.26667C39.36 3.73333 35.68 0 31.0933 0H11.8933C5.33333 0 0 5.33333 0 11.8933C0 18.4533 5.01333 23.84 12.96 23.84Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2134 31.9999C16.2134 28.7999 18.1334 25.8666 21.12 24.6399L27.1467 22.1333C33.28 19.6266 40 24.1066 40 30.7199C40 35.8399 35.84 39.9999 30.72 39.9999H24.16C19.7867 39.9999 16.2134 36.4266 16.2134 31.9999Z" fill="white"/>
<path d="M6.88 25.3867C3.09333 25.3867 0 28.4801 0 32.2667V33.1734C0 36.9067 3.09333 40.0001 6.88 40.0001C10.6667 40.0001 13.76 36.9067 13.76 33.1201V32.2134C13.7067 28.4801 10.6667 25.3867 6.88 25.3867Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 907 B

@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.96 23.84C14.0267 23.84 16.16 23.7867 19.1467 22.56C22.6133 21.12 29.44 18.56 34.4 15.8933C37.8667 14.0267 39.36 11.5733 39.36 8.26667C39.36 3.73333 35.68 0 31.0933 0H11.8933C5.33333 0 0 5.33333 0 11.8933C0 18.4533 5.01333 23.84 12.96 23.84Z" fill="#39594D"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2134 31.9999C16.2134 28.7999 18.1334 25.8666 21.12 24.6399L27.1467 22.1333C33.28 19.6266 40 24.1066 40 30.7199C40 35.8399 35.84 39.9999 30.72 39.9999H24.16C19.7867 39.9999 16.2134 36.4266 16.2134 31.9999Z" fill="#D18EE2"/>
<path d="M6.88 25.3867C3.09333 25.3867 0 28.4801 0 32.2667V33.1734C0 36.9067 3.09333 40.0001 6.88 40.0001C10.6667 40.0001 13.76 36.9067 13.76 33.1201V32.2134C13.7067 28.4801 10.6667 25.3867 6.88 25.3867Z" fill="#FF7759"/>
</svg>

After

Width:  |  Height:  |  Size: 913 B

@@ -0,0 +1,212 @@
import { CohereRerank } from '@langchain/cohere';
import { mock } from 'jest-mock-extended';
import type { ISupplyDataFunctions } from 'n8n-workflow';
import { logWrapper } from '@n8n/ai-utilities';
import { RerankerCohere } from '../RerankerCohere.node';
// Mock the CohereRerank class
jest.mock('@langchain/cohere', () => ({
CohereRerank: jest.fn(),
}));
// Mock the logWrapper utility
jest.mock('@n8n/ai-utilities', () => ({
logWrapper: jest.fn().mockImplementation((obj) => ({ logWrapped: obj })),
}));
describe('RerankerCohere', () => {
let rerankerCohere: RerankerCohere;
let mockSupplyDataFunctions: ISupplyDataFunctions;
let mockCohereRerank: jest.Mocked<CohereRerank>;
beforeEach(() => {
rerankerCohere = new RerankerCohere();
// Reset the mock
jest.clearAllMocks();
// Create a mock CohereRerank instance
mockCohereRerank = {
compressDocuments: jest.fn(),
} as unknown as jest.Mocked<CohereRerank>;
// Make the CohereRerank constructor return our mock instance
(CohereRerank as jest.MockedClass<typeof CohereRerank>).mockImplementation(
() => mockCohereRerank,
);
// Create mock supply data functions
mockSupplyDataFunctions = mock<ISupplyDataFunctions>({
logger: {
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
},
});
// Mock specific methods with proper jest functions
mockSupplyDataFunctions.getNodeParameter = jest.fn();
mockSupplyDataFunctions.getCredentials = jest.fn();
});
it('should create CohereRerank with default model and return wrapped instance', async () => {
// Setup mocks
const mockCredentials = { apiKey: 'test-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-v3.5') // modelName
.mockReturnValueOnce(3); // topN (default)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute
const result = await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0);
expect(mockSupplyDataFunctions.getNodeParameter).toHaveBeenCalledWith(
'modelName',
0,
'rerank-v3.5',
);
expect(mockSupplyDataFunctions.getNodeParameter).toHaveBeenCalledWith('topN', 0, 3);
expect(mockSupplyDataFunctions.getCredentials).toHaveBeenCalledWith('cohereApi');
expect(CohereRerank).toHaveBeenCalledWith({
apiKey: 'test-api-key',
model: 'rerank-v3.5',
topN: 3,
});
expect(logWrapper).toHaveBeenCalledWith(mockCohereRerank, mockSupplyDataFunctions);
expect(result.response).toEqual({ logWrapped: mockCohereRerank });
});
it('should create CohereRerank with custom model', async () => {
// Setup mocks
const mockCredentials = { apiKey: 'custom-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-multilingual-v3.0') // modelName
.mockReturnValueOnce(3); // topN (default)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute
await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0);
// Verify
expect(CohereRerank).toHaveBeenCalledWith({
apiKey: 'custom-api-key',
model: 'rerank-multilingual-v3.0',
topN: 3,
});
});
it('should handle different item indices', async () => {
// Setup mocks
const mockCredentials = { apiKey: 'test-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-english-v3.0') // modelName
.mockReturnValueOnce(3); // topN (default)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute with different item index
await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 2);
// Verify the correct item index is passed
expect(mockSupplyDataFunctions.getNodeParameter).toHaveBeenCalledWith(
'modelName',
2,
'rerank-v3.5',
);
expect(mockSupplyDataFunctions.getNodeParameter).toHaveBeenCalledWith('topN', 2, 3);
});
it('should throw error when credentials are missing', async () => {
// Setup mocks
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-v3.5') // modelName
.mockReturnValueOnce(3); // topN (default)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockRejectedValue(
new Error('Missing credentials'),
);
// Execute and verify error
await expect(rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0)).rejects.toThrow(
'Missing credentials',
);
});
it('should use fallback model when parameter is not provided', async () => {
// Setup mocks - getNodeParameter returns the fallback value
const mockCredentials = { apiKey: 'test-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-v3.5') // modelName (fallback value)
.mockReturnValueOnce(3); // topN (fallback value)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute
await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0);
// Verify fallback is used
expect(CohereRerank).toHaveBeenCalledWith({
apiKey: 'test-api-key',
model: 'rerank-v3.5',
topN: 3,
});
});
it('should create CohereRerank with custom topN value', async () => {
// Setup mocks
const mockCredentials = { apiKey: 'test-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-v3.5') // modelName
.mockReturnValueOnce(10); // topN (custom value)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute
await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0);
// Verify custom topN is used
expect(CohereRerank).toHaveBeenCalledWith({
apiKey: 'test-api-key',
model: 'rerank-v3.5',
topN: 10,
});
expect(mockSupplyDataFunctions.getNodeParameter).toHaveBeenCalledWith('topN', 0, 3);
});
it('should create CohereRerank with topN value of 1', async () => {
// Setup mocks
const mockCredentials = { apiKey: 'test-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-english-v3.0') // modelName
.mockReturnValueOnce(1); // topN (edge case value)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute
await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0);
// Verify edge case topN is used
expect(CohereRerank).toHaveBeenCalledWith({
apiKey: 'test-api-key',
model: 'rerank-english-v3.0',
topN: 1,
});
});
it('should create CohereRerank with large topN value', async () => {
// Setup mocks
const mockCredentials = { apiKey: 'test-api-key' };
(mockSupplyDataFunctions.getNodeParameter as jest.Mock)
.mockReturnValueOnce('rerank-multilingual-v3.0') // modelName
.mockReturnValueOnce(100); // topN (large value)
(mockSupplyDataFunctions.getCredentials as jest.Mock).mockResolvedValue(mockCredentials);
// Execute
await rerankerCohere.supplyData.call(mockSupplyDataFunctions, 0);
// Verify large topN is used
expect(CohereRerank).toHaveBeenCalledWith({
apiKey: 'test-api-key',
model: 'rerank-multilingual-v3.0',
topN: 100,
});
});
});